Skip to content

Commit c8c9e6d

Browse files
committed
Adds experimental support for Python 3.6 and the fold attribute.
Fixes #83
1 parent 8d78a5a commit c8c9e6d

File tree

11 files changed

+320
-28
lines changed

11 files changed

+320
-28
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ matrix:
2222
env: PENDULUM_EXTENSIONS=1
2323
- python: 3.5
2424
env: PENDULUM_EXTENSIONS=0
25+
- python: 3.6-dev
26+
env: PENDULUM_EXTENSIONS=1
27+
- python: 3.6-dev
28+
env: PENDULUM_EXTENSIONS=0
2529
- python: pypy
2630

2731
before_install:

docs/_docs/timezones.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,38 @@ given timezone to properly handle any transition that might have occurred.
5757
Note that it only affects instances at creation time. Shifting time around
5858
transition times still behaves the same.
5959

60+
.. note::
61+
62+
As of version **0.7.0**, and to be consistent with the standard library (Python 3.6+),
63+
the ``Pendulum`` class accepts a ``fold`` keyword argument which will be used, when set explicitely,
64+
to determine the rule to apply on ambiguous or non-existing times.
65+
Be aware that when it is not set explicitely, the previous behavior remains,
66+
i.e. the configured transition rule or the default one will be used.
67+
68+
.. code-block:: python
69+
70+
from pendulum import Pendulum
71+
72+
dt = Pendulum(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris')
73+
dt.isoformat()
74+
'2013-03-31T03:30:00+02:00'
75+
76+
dt = Pendulum(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris', fold=0)
77+
dt.isoformat()
78+
'2013-03-31T02:30:00+01:00'
79+
80+
dt = Pendulum(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris', fold=1)
81+
dt.isoformat()
82+
'2013-03-31T02:30:00+01:00'
83+
84+
dt = Pendulum(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris', fold=0)
85+
dt.isoformat()
86+
'2013-10-27T02:30:00+02:00'
87+
88+
dt = Pendulum(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris', fold=1)
89+
dt.isoformat()
90+
'2013-10-27T02:30:00+01:00'
91+
6092
Shifting time to transition
6193
---------------------------
6294

@@ -113,6 +145,41 @@ Like said in the introduction, you can use the timezone library
113145
directly with standard ``datetime`` objects but with limitations, especially
114146
when adding and subtracting time around transition times.
115147

148+
.. warning::
149+
150+
By default in **Python 3.6+**, the value of the ``fold`` attribute will be used
151+
to determine the transition rule. So the behavior will be slightly different
152+
compared to previous versions.
153+
154+
.. code-block:: python
155+
156+
from datetime import datetime
157+
from pendulum import timezone
158+
159+
paris = timezone('Europe/Paris')
160+
dt = datetime(2013, 3, 31, 2, 30)
161+
# By default, fold is set to 0
162+
dt = paris.convert(dt)
163+
dt.isoformat()
164+
'2013-03-31T02:30:00+01:00'
165+
166+
dt = datetime(2013, 3, 31, 2, 30, fold=1)
167+
dt = paris.convert(dt)
168+
dt.isoformat()
169+
'2013-03-31T03:30:00+02:00'
170+
171+
You can override this behavior by explicitely passing the
172+
transition rule to ``convert()``.
173+
174+
.. code-block:: python
175+
176+
paris = timezone('Europe/Paris')
177+
dt = datetime(2013, 3, 31, 2, 30)
178+
# By default, fold is set to 0
179+
dt = paris.convert(dt, dst_rule=paris.POST_TRANSITION)
180+
dt.isoformat()
181+
'2013-03-31T03:30:00+02:00'
182+
116183
117184
.. code-block:: python
118185

pendulum/_compat.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
PY2 = sys.version_info[0] == 2
66
PY3K = sys.version_info[0] >= 3
77
PY33 = sys.version_info >= (3, 3)
8+
PY36 = sys.version_info >= (3, 6)
89

910

1011
if PY2:

pendulum/pendulum.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def _timezone(cls, zone):
110110

111111
def __new__(cls, year, month, day,
112112
hour=0, minute=0, second=0, microsecond=0,
113-
tzinfo=None):
113+
tzinfo=None, fold=None):
114114
"""
115115
Constructor.
116116
@@ -125,7 +125,7 @@ def __new__(cls, year, month, day,
125125

126126
def __init__(self, year, month, day,
127127
hour=0, minute=0, second=0, microsecond=0,
128-
tzinfo=UTC):
128+
tzinfo=UTC, fold=None):
129129
# If a TimezoneInfo is passed we do not convert
130130
if isinstance(tzinfo, TimezoneInfo):
131131
self._tz = tzinfo.tz
@@ -139,6 +139,15 @@ def __init__(self, year, month, day,
139139
self._microsecond = microsecond
140140
self._tzinfo = tzinfo
141141

142+
if fold is None:
143+
# Converting rule to fold value
144+
if self._TRANSITION_RULE == Timezone.POST_TRANSITION:
145+
fold = 1
146+
else:
147+
fold = 0
148+
149+
self._fold = fold
150+
142151
dt = datetime.datetime(
143152
year, month, day,
144153
hour, minute, second, microsecond,
@@ -147,10 +156,24 @@ def __init__(self, year, month, day,
147156
else:
148157
self._tz = self._safe_create_datetime_zone(tzinfo)
149158

159+
# Support for explicit fold attribute
160+
if fold is None:
161+
transition_rule = self._TRANSITION_RULE
162+
163+
# Converting rule to fold value
164+
if self._TRANSITION_RULE == Timezone.POST_TRANSITION:
165+
fold = 1
166+
else:
167+
fold = 0
168+
elif fold == 1:
169+
transition_rule = Timezone.POST_TRANSITION
170+
else:
171+
transition_rule = Timezone.PRE_TRANSITION
172+
150173
dt = self._tz.convert(datetime.datetime(
151174
year, month, day,
152175
hour, minute, second, microsecond
153-
), dst_rule=self._TRANSITION_RULE)
176+
), dst_rule=transition_rule)
154177

155178
self._year = dt.year
156179
self._month = dt.month
@@ -160,6 +183,7 @@ def __init__(self, year, month, day,
160183
self._second = dt.second
161184
self._microsecond = dt.microsecond
162185
self._tzinfo = dt.tzinfo
186+
self._fold = getattr(dt, 'fold', fold)
163187

164188
self._timestamp = None
165189
self._int_timestamp = None
@@ -337,7 +361,7 @@ def create(cls, year=None, month=None, day=None,
337361
now = cls.get_test_now().in_tz(tz)
338362
else:
339363
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
340-
now = tz.convert(now, dst_rule=cls._TRANSITION_RULE)
364+
now = tz.convert(now)
341365

342366
if year is None:
343367
year = now.year
@@ -510,6 +534,10 @@ def microsecond(self):
510534
def tzinfo(self):
511535
return self._tzinfo
512536

537+
@property
538+
def fold(self):
539+
return self._fold
540+
513541
@property
514542
def timestamp(self):
515543
if self._timestamp is None:
@@ -1108,7 +1136,10 @@ def add(self, years=0, months=0, weeks=0, days=0,
11081136
if any([years, months, weeks, days]):
11091137
# If we specified any of years, months, weeks or days
11101138
# we will not apply the transition (if any)
1111-
dt = self._tz.convert(dt.replace(tzinfo=None))
1139+
dt = self._tz.convert(
1140+
dt.replace(tzinfo=None),
1141+
dst_rule=Timezone.POST_TRANSITION
1142+
)
11121143
else:
11131144
# Else, we need to apply the transition properly (if any)
11141145
dt = self._tz.convert(dt)
@@ -1956,7 +1987,7 @@ def __hash__(self):
19561987
def __getnewargs__(self):
19571988
return(self, )
19581989

1959-
def _getstate(self):
1990+
def _getstate(self, protocol=3):
19601991
tz = self.timezone_name
19611992

19621993
# Fix for fixed timezones not being properly unpickled
@@ -1966,11 +1997,15 @@ def _getstate(self):
19661997
return (
19671998
self.year, self.month, self.day,
19681999
self.hour, self.minute, self.second, self.microsecond,
1969-
tz
2000+
tz,
2001+
self.fold
19702002
)
19712003

19722004
def __reduce__(self):
1973-
return self.__class__, self._getstate()
2005+
return self.__reduce_ex__(2)
2006+
2007+
def __reduce_ex__(self, protocol):
2008+
return self.__class__, self._getstate(protocol)
19742009

19752010
Pendulum.min = Pendulum.instance(datetime.datetime.min.replace(tzinfo=UTC))
19762011
Pendulum.max = Pendulum.instance(datetime.datetime.max.replace(tzinfo=UTC))

pendulum/period.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ def __repr__(self):
284284
self._start, self._end
285285
)
286286

287-
def _getstate(self):
287+
def _getstate(self, protocol=3):
288288
start, end = self.start, self.end
289289

290290
if self._invert and self._absolute:
@@ -295,4 +295,7 @@ def _getstate(self):
295295
)
296296

297297
def __reduce__(self):
298-
return self.__class__, self._getstate()
298+
return self.__reduce_ex__(2)
299+
300+
def __reduce_ex__(self, protocol):
301+
return self.__class__, self._getstate(protocol)

pendulum/time.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class Time(TranslatableMixin, FormattableMixing, TestableMixin, time):
1414
Represents a time instance as hour, minute, second, microsecond.
1515
"""
1616

17-
def __init__(self, hour, minute=0, second=0, microsecond=0, tzinfo=None):
17+
def __init__(self, hour, minute=0, second=0, microsecond=0,
18+
tzinfo=None, fold=0):
1819
"""
1920
Constructor.
2021
@@ -39,6 +40,7 @@ def __init__(self, hour, minute=0, second=0, microsecond=0, tzinfo=None):
3940
self._microsecond = microsecond
4041
self._tzinfo = tzinfo
4142
self._time = time(hour, minute, second, microsecond, tzinfo)
43+
self._fold = fold
4244

4345
@classmethod
4446
def instance(cls, t, copy=True):
@@ -107,6 +109,10 @@ def microsecond(self):
107109
def tzinfo(self):
108110
return self._tzinfo
109111

112+
@property
113+
def fold(self):
114+
return self._fold
115+
110116
# Comparisons
111117

112118
def between(self, dt1, dt2, equal=True):
@@ -503,7 +509,7 @@ def tzname(self):
503509

504510
return self._time.tzname()
505511

506-
def _getstate(self):
512+
def _getstate(self, protocol=3):
507513
tz = self.tzinfo
508514

509515
return (
@@ -512,7 +518,10 @@ def _getstate(self):
512518
)
513519

514520
def __reduce__(self):
515-
return self.__class__, self._getstate()
521+
return self.__reduce_ex__(2)
522+
523+
def __reduce_ex__(self, protocol):
524+
return self.__class__, self._getstate(protocol)
516525

517526
Time.min = Time(0, 0, 0)
518527
Time.max = Time(23, 59, 59, 999999)

0 commit comments

Comments
 (0)