Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Orange/preprocess/discretize.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from Orange.data.sql.table import SqlTable
from Orange.statistics import distribution, contingency, util as ut
from Orange.statistics.basic_stats import BasicStats
from Orange.util import Reprable
from Orange.util import Reprable, utc_from_timestamp
from .transformation import Transformation
from . import _discretize

Expand Down Expand Up @@ -359,7 +359,8 @@ def time_binnings(data, *, min_bins=2, max_bins=50, min_unique=5, add_unique=0):
(number_of_seconds_since_epoch, label).
"""
mn, mx, unique = _min_max_unique(data)
mn, mx = time.gmtime(mn), time.gmtime(mx)
mn = utc_from_timestamp(mn).timetuple()
mx = utc_from_timestamp(mx).timetuple()
bins = []
if len(unique) <= max(min_unique, add_unique):
bins.append(_unique_time_bins(unique))
Expand Down Expand Up @@ -464,7 +465,7 @@ def _simplified_labels(labels):


def _unique_time_bins(unique):
times = [time.gmtime(x) for x in unique]
times = [utc_from_timestamp(x).timetuple() for x in unique]
fmt = f'%y %b %d'
fmt += " %H:%M" * (len({t[2:] for t in times}) > 1)
fmt += ":%S" * bool(np.all(unique % 60 == 0))
Expand Down
6 changes: 6 additions & 0 deletions Orange/preprocess/tests/test_discretize.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,12 @@ def test_no_values(self):
dates = np.array([np.nan, np.nan])
self.assertRaises(ValueError, time_binnings, dates)

def test_before_epoch(self):
hour = 24 * 60 * 60
dates = [-hour, 0, hour,]
bins = time_binnings(dates)
self.assertEqual(list(bins[0].thresholds), [-hour, 0, hour, 2 * hour])


class TestBinDefinition(unittest.TestCase):
def test_labels(self):
Expand Down
9 changes: 9 additions & 0 deletions Orange/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import os
import inspect
import datetime
from contextlib import contextmanager

import pkg_resources
Expand Down Expand Up @@ -522,6 +523,14 @@ def dummy_callback(*_, **__):
return 1


def utc_from_timestamp(timestamp) -> datetime.datetime:
"""
Return the UTC datetime corresponding to the POSIX timestamp.
"""
return datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + \
datetime.timedelta(seconds=float(timestamp))


# For best result, keep this at the bottom
__all__ = export_globals(globals(), __name__)

Expand Down
5 changes: 3 additions & 2 deletions Orange/widgets/data/owfeaturestatistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import Orange.statistics.util as ut
from Orange.data import Table, StringVariable, DiscreteVariable, \
ContinuousVariable, TimeVariable, Domain, Variable
from Orange.util import utc_from_timestamp
from Orange.widgets import widget, gui
from Orange.widgets.data.utils.histogram import Histogram
from Orange.widgets.settings import Setting, ContextSetting, \
Expand Down Expand Up @@ -69,8 +70,8 @@ def format_time_diff(start, end, round_up_after=2):
str

"""
start = datetime.datetime.fromtimestamp(start)
end = datetime.datetime.fromtimestamp(end)
start = utc_from_timestamp(start)
end = utc_from_timestamp(end)
diff = abs(end - start) # type: datetime.timedelta

# Get the different resolutions
Expand Down
12 changes: 11 additions & 1 deletion Orange/widgets/data/tests/test_owfeaturestatistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,12 @@ def _to_timestamps(years):
TimeVariable('time_same', have_date=True, have_time=True),
np.array(_to_timestamps([2004] * 5), dtype=float),
)
time_negative = VarDataPair(
TimeVariable('time_negative', have_date=True, have_time=True),
np.array([0, -1, 24 * 60 * 60], dtype=float),
)
time = [
time_full, time_missing, time_all_missing, time_same
time_full, time_missing, time_all_missing, time_same, time_negative
]

# String variable variations
Expand Down Expand Up @@ -249,6 +253,12 @@ def test_on_data_with_continuous_values_all_the_same(self, prepare_table):
self.send_signal(self.widget.Inputs.data, prepare_table(data))
self.run_through_variables()

@table_dense_sparse
def test_on_data_with_negative_timestamps(self, prepare_table):
data = make_table([time_negative])
self.send_signal(self.widget.Inputs.data, prepare_table(data))
self.run_through_variables()

def test_switching_to_dataset_with_no_target_var(self):
"""Switching from data set with target variable to a data set with
no target variable should not result in crash."""
Expand Down
11 changes: 4 additions & 7 deletions Orange/widgets/visualize/owscatterplotgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from xml.sax.saxutils import escape
from math import log10, floor, ceil
from datetime import datetime, timezone
from time import gmtime

import numpy as np
from AnyQt.QtCore import Qt, QRectF, QSize, QTimer, pyqtSignal as Signal, \
Expand All @@ -20,6 +19,7 @@
from pyqtgraph.graphicsItems.TextItem import TextItem

from Orange.preprocess.discretize import _time_binnings
from Orange.util import utc_from_timestamp
from Orange.widgets import gui
from Orange.widgets.settings import Setting
from Orange.widgets.utils import classdensity, colorpalettes
Expand Down Expand Up @@ -311,8 +311,8 @@ def tickValues(self, minVal, maxVal, size):
datetime.min.replace(tzinfo=timezone.utc).timestamp() + 1)
maxVal = min(maxVal,
datetime.max.replace(tzinfo=timezone.utc).timestamp() - 1)
mn, mx = gmtime(minVal), gmtime(maxVal)

mn = utc_from_timestamp(minVal).timetuple()
mx = utc_from_timestamp(maxVal).timetuple()
try:
bins = _time_binnings(mn, mx, 6, 30)[-1]
except (IndexError, ValueError):
Expand Down Expand Up @@ -350,10 +350,7 @@ def tickStrings(self, values, scale, spacing):
else:
fmt = '%S.%f'

# if timezone is not set, then local timezone is used
# which cause exceptions for edge cases
return [datetime.fromtimestamp(x, tz=timezone.utc).strftime(fmt)
for x in values]
return [utc_from_timestamp(x).strftime(fmt) for x in values]


class ScatterBaseParameterSetter(CommonParameterSetter):
Expand Down