Skip to content

Commit b8dadbd

Browse files
committed
Consolidate EPOCH resolution.
This fixes #138.
1 parent 9036c88 commit b8dadbd

File tree

2 files changed

+91
-8
lines changed

2 files changed

+91
-8
lines changed

src/croniter/croniter.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ def is_32bit():
7373
OrderedDict = dict # py26 degraded mode, expanders order will not be immutable
7474

7575

76-
EPOCH = datetime.datetime.fromtimestamp(0)
76+
try:
77+
# py3 recent
78+
UTC_DT = datetime.timezone.utc
79+
except AttributeError:
80+
UTC_DT = pytz.utc
81+
EPOCH = datetime.datetime.fromtimestamp(0, UTC_DT)
7782

7883
# fmt: off
7984
M_ALPHAS = {
@@ -128,11 +133,6 @@ def is_32bit():
128133
VALID_LEN_EXPRESSION = set(a for a in CRON_FIELDS if isinstance(a, int))
129134
TIMESTAMP_TO_DT_CACHE = {}
130135
EXPRESSIONS = {}
131-
try:
132-
# py3 recent
133-
UTC_DT = datetime.timezone.utc
134-
except AttributeError:
135-
UTC_DT = pytz.utc
136136
MARKER = object()
137137

138138

@@ -328,7 +328,7 @@ def datetime_to_timestamp(d):
328328

329329
def timestamp_to_datetime(self, timestamp, tzinfo=MARKER):
330330
"""
331-
Converts a UNIX timestamp `timestamp` into a `datetime` object.
331+
Converts a UNIX `timestamp` into a `datetime` object.
332332
"""
333333
if tzinfo is MARKER: # allow to give tzinfo=None even if self.tzinfo is set
334334
tzinfo = self.tzinfo
@@ -342,7 +342,7 @@ def timestamp_to_datetime(self, timestamp, tzinfo=MARKER):
342342
if OVERFLOW32B_MODE:
343343
# degraded mode to workaround Y2038
344344
# see https://github.com/python/cpython/issues/101069
345-
result = EPOCH + datetime.timedelta(seconds=timestamp)
345+
result = EPOCH.replace(tzinfo=None) + datetime.timedelta(seconds=timestamp)
346346
else:
347347
result = datetime.datetime.fromtimestamp(timestamp, tz=tzutc()).replace(tzinfo=None)
348348
if tzinfo:
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python
2+
"""
3+
All related DST croniter tests are isolated here.
4+
"""
5+
# -*- coding: utf-8 -*-
6+
7+
try:
8+
import unittest2 as unittest
9+
except ImportError:
10+
import unittest
11+
12+
import os
13+
import time
14+
from collections import OrderedDict
15+
from datetime import datetime, timedelta
16+
17+
from croniter import (
18+
HOUR_FIELD,
19+
CroniterBadCronError,
20+
CroniterBadDateError,
21+
CroniterBadTypeRangeError,
22+
CroniterError,
23+
cron_m,
24+
croniter,
25+
croniter_range,
26+
)
27+
from croniter.tests import base
28+
29+
ORIG_OVERFLOW32B_MODE = cron_m.OVERFLOW32B_MODE
30+
31+
32+
class CroniterDST138Test(base.TestCase):
33+
"""
34+
See https://github.com/kiorky/croniter/issues/138.
35+
"""
36+
37+
_tz = "UTC"
38+
39+
def setUp(self):
40+
self._time = os.environ.setdefault("TZ", "")
41+
self.base = datetime(2024, 1, 25, 4, 46)
42+
self.iter = croniter("*/5 * * * *", self.base)
43+
self.results = [
44+
datetime(2024, 1, 25, 4, 50),
45+
datetime(2024, 1, 25, 4, 55),
46+
datetime(2024, 1, 25, 5, 0),
47+
]
48+
self.tzname, self.timezone = time.tzname, time.timezone
49+
50+
def tearDown(self):
51+
cron_m.OVERFLOW32B_MODE = ORIG_OVERFLOW32B_MODE
52+
if not self._time:
53+
del os.environ["TZ"]
54+
else:
55+
os.environ["TZ"] = self._time
56+
time.tzset()
57+
58+
def test_issue_138_dt_to_ts_32b(self):
59+
"""
60+
test local tz, forcing 32b mode.
61+
"""
62+
self._test(m32b=True)
63+
64+
def test_issue_138_dt_to_ts_n(self):
65+
"""
66+
test local tz, forcing non 32b mode.
67+
"""
68+
self._test(m32b=False)
69+
70+
def _test(self, tz="UTC", m32b=True):
71+
cron_m.OVERFLOW32B_MODE = m32b
72+
os.environ["TZ"] = tz
73+
time.tzset()
74+
res = [self.iter.get_next(datetime) for i in range(3)]
75+
self.assertEqual(res, self.results)
76+
77+
78+
class CroniterDST138TestLocal(CroniterDST138Test):
79+
_tz = "UTC-8"
80+
81+
82+
if __name__ == "__main__":
83+
unittest.main()

0 commit comments

Comments
 (0)