Skip to content

Commit

Permalink
Fixes accuracy of microseconds for some timezones.
Browse files Browse the repository at this point in the history
Fixes #74
  • Loading branch information
sdispater committed Nov 14, 2016
1 parent 1228127 commit 184b94a
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- Exception when loading specific timezones has been fixed.
- `end_of('day')` now properly sets microseconds to `999999`.
- Accuracy of `Period` instances properties has been improved.
- Accuracy for microseconds when initializing a Pendulum instance in some timezones has been fixed.


## [0.6.5] - 2016-10-31
Expand Down
10 changes: 3 additions & 7 deletions pendulum/_extensions/_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ PyObject* local_time(PyObject *self, PyObject *args) {
double unix_time;
int32_t utc_offset;
int32_t year;
int64_t microsecond;
int32_t microsecond;
int64_t seconds;
int32_t leap_year;
int64_t sec_per_100years;
Expand All @@ -105,18 +105,14 @@ PyObject* local_time(PyObject *self, PyObject *args) {
int32_t minute;
int32_t second;

if (!PyArg_ParseTuple(args, "di", &unix_time, &utc_offset)) {
if (!PyArg_ParseTuple(args, "dii", &unix_time, &utc_offset, &microsecond)) {
PyErr_SetString(
PyExc_ValueError, "Invalid parameters"
);
return NULL;
}

year = EPOCH_YEAR;
microsecond = (int64_t) (unix_time * 1000000) % 1000000;
if (microsecond < 0) {
microsecond += 1000000;
}
seconds = (int64_t) unix_time;

// Shift to a base year that is 400-year aligned.
Expand Down Expand Up @@ -179,7 +175,7 @@ PyObject* local_time(PyObject *self, PyObject *args) {
month -= 1;
}

// Handle hours, minutes, seconds and microseconds
// Handle hours, minutes and seconds
hour = seconds / SECS_PER_HOUR;
seconds %= SECS_PER_HOUR;
minute = seconds / SECS_PER_MIN;
Expand Down
6 changes: 3 additions & 3 deletions pendulum/_extensions/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@
)


def local_time(unix_time, utc_offset):
def local_time(unix_time, utc_offset, microseconds):
"""
Returns a UNIX time as a broken down time
for a particular transition type.
:type unix_time: int
:type utc_offset: int
:type microseconds: int
:rtype: tuple
"""
year = EPOCH_YEAR
microsecond = int(round(unix_time % 1, 6) * 1e6)
seconds = int(unix_time)

# Shift to a base year that is 400-year aligned.
Expand Down Expand Up @@ -89,5 +89,5 @@ def local_time(unix_time, utc_offset):

return (
year, month, day,
hour, minute, second, microsecond
hour, minute, second, microseconds
)
6 changes: 4 additions & 2 deletions pendulum/tz/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,13 @@ def _load(cls, fp):
# we retrieved
pre_time = datetime(
*local_time(transition_times[i],
pre_transition_type.utc_offset)
pre_transition_type.utc_offset,
0)
)
time = datetime(
*local_time(transition_times[i],
transition_type.utc_offset)
transition_type.utc_offset,
0)
)

# We build the tzinfo information as tuples
Expand Down
22 changes: 9 additions & 13 deletions pendulum/tz/timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime, tzinfo
from bisect import bisect_right

from ..constants import SECONDS_PER_DAY
from .loader import Loader
from .timezone_info import TimezoneInfo, UTC
from ..helpers import local_time as _local_time
Expand Down Expand Up @@ -220,7 +221,7 @@ def _normalize(self, dt, dst_rule=POST_TRANSITION):
(unix_time,
tzinfo_index) = self._get_previous_transition_time(tr, dt)

return self._to_local_time(unix_time, tzinfo_index)
return self._to_local_time(unix_time, dt.microsecond, tzinfo_index)

def _convert(self, dt):
"""
Expand All @@ -238,26 +239,21 @@ def _convert(self, dt):

return dt.astimezone(self)

def _to_local_time(self, unix_time, tzinfo_index):
def _to_local_time(self, unix_time, microseconds, tzinfo_index):
tzinfo = self._tzinfos[tzinfo_index]

local_time = _local_time(
unix_time,
tzinfo.offset
tzinfo.offset,
microseconds
)

return local_time + (tzinfo,)

def _get_timestamp(self, dt):
if hasattr(dt, 'float_timestamp'):
return dt.float_timestamp
def _get_diff(self, dt1, dt2):
diff = dt2 - dt1

t = (dt - datetime(1970, 1, 1, tzinfo=UTC)).total_seconds()

if dt.microsecond > 0 and t < 0:
t -= 1

return t
return diff.days * SECONDS_PER_DAY + diff.seconds

def _find_transition_index(self, dt, prop='_time'):
lo, hi = 0, len(self._transitions)
Expand All @@ -282,7 +278,7 @@ def _find_transition_index(self, dt, prop='_time'):
return lo

def _get_previous_transition_time(self, tr, dt):
diff = (dt - tr.pre_time).total_seconds()
diff = self._get_diff(tr.pre_time, dt)
if -1 < diff < 0 and tr.unix_time < 0:
diff -= 1

Expand Down
17 changes: 15 additions & 2 deletions tests/helpers_tests/test_local_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@
class LocalTimeTest(AbstractTestCase):

def test_local_time_positive_integer(self):
d = Pendulum(2016, 8, 7, 12, 34, 56)
d = Pendulum(2016, 8, 7, 12, 34, 56, 123456)

t = local_time(d.timestamp, 0)
t = local_time(d.int_timestamp, 0, d.microsecond)
self.assertEqual(d.year, t[0])
self.assertEqual(d.month, t[1])
self.assertEqual(d.day, t[2])
self.assertEqual(d.hour, t[3])
self.assertEqual(d.minute, t[4])
self.assertEqual(d.second, t[5])
self.assertEqual(d.microsecond, t[6])

def test_local_time_negative_integer(self):
d = Pendulum(1951, 8, 7, 12, 34, 56, 123456)

t = local_time(d.int_timestamp, 0, d.microsecond)
self.assertEqual(d.year, t[0])
self.assertEqual(d.month, t[1])
self.assertEqual(d.day, t[2])
self.assertEqual(d.hour, t[3])
self.assertEqual(d.minute, t[4])
self.assertEqual(d.second, t[5])
self.assertEqual(d.microsecond, t[6])

0 comments on commit 184b94a

Please sign in to comment.