FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add support for setting DST rule in create() · python-pendulum/pendulum@5be0094 · GitHub

Commit 5be0094

Browse files
committed
Add support for setting DST rule in create()
1 parent f8df08a commit 5be0094

4 files changed

Lines changed: 198 additions & 110 deletions

File tree

‎docs/_docs/timezones.rst‎

Lines changed: 65 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
Timezones
22
=========
33

4-
54
Timezones are an important part of every datetime library and ``pendulum``
65
tries to provide an easy and accurate system to handle them properly.
76

@@ -21,47 +20,43 @@ given timezone to properly handle any transition that might have occurred.
2120
2221
import pendulum
2322
24-
pendulum.create(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris')
23+
pendulum.create(2013, 3, 31, 2, 30, tz='Europe/Paris')
2524
# 2:30 for the 31th of March 2013 does not exist
2625
# so pendulum will return the actual time which is 3:30+02:00
2726
'2013-03-31T03:30:00+02:00'
2827
29-
pendulum.create(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris')
28+
pendulum.create(2013, 10, 27, 2, 30, tz='Europe/Paris')
3029
# Here, 2:30 exists twice in the day so pendulum will
3130
# assume that the transition already occurred
3231
'2013-10-27T02:30:00+01:00'
3332
33+
You can, however, control the normalization behavior:
3434

35-
.. note::
36-
37-
You can control the normalization behavior:
38-
39-
.. code-block:: python
40-
41-
import pendulum
42-
43-
pendulum.set_transition_rule(pendulum.PRE_TRANSITION)
44-
45-
pendulum.create(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris')
46-
'2013-03-31T01:30:00+01:00'
47-
pendulum.create(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris')
48-
'2013-10-27T02:30:00+02:00'
35+
.. code-block:: python
4936
50-
pendulum.set_transition_rule(pendulum.TRANSITION_ERROR)
37+
import pendulum
5138
52-
pendulum.create(2013, 3, 31, 2, 30, 0, 0, 'Europe/Paris')
53-
# NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist
54-
pendulum.create(2013, 10, 27, 2, 30, 0, 0, 'Europe/Paris')
55-
# AmbiguousTime: The datetime 2013-10-27 02:30:00 is ambiguous.
39+
pendulum.create(2013, 3, 31, 2, 30, 0, 0, tz='Europe/Paris',
40+
dst_rule=pendulum.PRE_TRANSITION)
41+
'2013-03-31T01:30:00+01:00'
42+
pendulum.create(2013, 10, 27, 2, 30, 0, 0, tz='Europe/Paris',
43+
dst_rule=pendulum.PRE_TRANSITION)
44+
'2013-10-27T02:30:00+02:00'
5645
57-
Note that it only affects instances at creation time. Shifting time around
58-
transition times still behaves the same.
46+
pendulum.create(2013, 3, 31, 2, 30, 0, 0, tz='Europe/Paris'
47+
dst_rule=pendulum.TRANSITION_ERROR)
48+
# NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist
49+
pendulum.create(2013, 10, 27, 2, 30, 0, 0, tz='Europe/Paris',
50+
dst_rule=pendulum.TRANSITION_ERROR)
51+
# AmbiguousTime: The datetime 2013-10-27 02:30:00 is ambiguous.
5952
53+
Note that it only affects instances at creation time. Shifting time around
54+
transition times still behaves the same.
6055

6156
Shifting time to transition
6257
---------------------------
6358

64-
So, what happens when you add time to a ``Pendulum`` instance and stumble upon
59+
So, what happens when you add time to a ``DateTime`` instance and stumble upon
6560
a transition time?
6661
Well ``pendulum``, provided with the context of the previous instance, will
6762
adopt the proper behavior and apply the transition accordingly.
@@ -77,11 +72,8 @@ adopt the proper behavior and apply the transition accordingly.
7772
dt.subtract(microseconds=1)
7873
'2013-03-31T01:59:59.999998+01:00'
7974
80-
dt = pendulum.create(2013, 10, 27, 1, 59, 59, 999999, tz='Europe/Paris')
81-
dt = dt.add(hours=1)
82-
# We can't just do
83-
# pendulum.create(2013, 10, 27, 2, 59, 59, 999999, 'Europe/Paris')
84-
# because of the default normalization
75+
dt = pendulum.create(2013, 10, 27, 2, 59, 59, 999999, tz='Europe/Paris'
76+
dst_rule=pendulum.PRE_TRANSITION)
8577
'2013-10-27T02:59:59.999999+02:00'
8678
dt = dt.add(microseconds=1)
8779
'2013-10-27T02:00:00+01:00'
@@ -114,95 +106,75 @@ Like said in the introduction, you can use the timezone library
114106
directly with standard ``datetime`` objects but with limitations, especially
115107
when adding and subtracting time around transition times.
116108

117-
.. warning::
109+
The value of the ``fold`` attribute will be used
110+
by default to determine the transition rule.
118111

119-
The value of the ``fold`` attribute will be used by default
120-
to determine the transition rule.
112+
.. code-block:: python
121113
122-
.. code-block:: python
114+
from datetime import datetime
115+
from pendulum import timezone
123116
124-
from datetime import datetime
125-
from pendulum import timezone
117+
paris = timezone('Europe/Paris')
118+
dt = datetime(2013, 3, 31, 2, 30)
119+
# By default, fold is set to 0
120+
dt = paris.convert(dt)
121+
dt.isoformat()
122+
'2013-03-31T01:30:00+01:00'
126123
127-
paris = timezone('Europe/Paris')
128-
dt = datetime(2013, 3, 31, 2, 30)
129-
# By default, fold is set to 0
130-
dt = paris.convert(dt)
131-
dt.isoformat()
132-
'2013-03-31T01:30:00+01:00'
124+
dt = datetime(2013, 3, 31, 2, 30, fold=1)
125+
dt = paris.convert(dt)
126+
dt.isoformat()
127+
'2013-03-31T03:30:00+02:00'
133128
134-
dt = datetime(2013, 3, 31, 2, 30, fold=1)
135-
dt = paris.convert(dt)
136-
dt.isoformat()
137-
'2013-03-31T03:30:00+02:00'
129+
Instead of relying on the `fold` attribute, you can use the `dst_rule`
130+
keyword argument, this is especially useful if you want to raise errors
131+
on non-existing and ambiguous times.
138132

139-
You can override this behavior by explicitely passing the
140-
transition rule to ``convert()``.
133+
.. code-block:: python
134+
135+
import pendulum
136+
137+
dt = datetime(2013, 3, 31, 2, 30)
138+
# By default, fold is set to 0
139+
dt = paris.convert(dt, dst_rule=pendulum.PRE_TRANSITION)
140+
dt.isoformat()
141+
'2013-03-31T01:30:00+01:00'
141142
142-
.. code-block:: python
143+
dt = paris.convert(dt, dst_rule=pendulum.POST_TRANSITION)
144+
dt.isoformat()
145+
'2013-03-31T03:30:00+02:00'
143146
144-
paris = timezone('Europe/Paris')
145-
dt = datetime(2013, 3, 31, 2, 30)
146-
# By default, fold is set to 0
147-
dt = paris.convert(dt, dst_rule=paris.POST_TRANSITION)
148-
dt.isoformat()
149-
'2013-03-31T03:30:00+02:00'
147+
paris.convert(dt, dst_rule=pendulum.TRANSITION_ERROR)
148+
# NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist
150149
150+
This works as expected. However, whenever we add or subtract a `timedelta`
151+
object, things get tricky.
151152

152153
.. code-block:: python
153154
154155
from datetime import datetime, timedelta
155156
from pendulum import timezone
156157
157-
paris = timezone('Europe/Paris')
158-
dt = datetime(2013, 3, 31, 2, 30)
159-
dt = paris.convert(dt)
160-
dt.isoformat()
161-
'2013-03-31T03:30:00+02:00'
162-
# Normalization works as expected
163-
164-
new_york = timezone('America/New_York')
165-
new_york.convert(dt).isoformat()
166-
'2013-03-30T21:30:00-04:00'
167-
# Timezone switching works as expected
168-
169158
dt = datetime(2013, 3, 31, 1, 59, 59, 999999)
170159
dt = paris.convert(dt)
171160
dt.isoformat()
172161
'2013-03-31T01:59:59.999999+01:00'
173162
dt = dt + timedelta(microseconds=1)
174163
dt.isoformat()
175164
'2013-03-31T02:00:00+01:00'
176-
# This does not work as expected.
177-
# This is a limitation of datetime objects
178-
# that can't switch around transition times.
179-
# However, you can use convert()
180-
# to retrieve the proper datetime.
181-
dt = tz.convert(dt)
182-
dt.isoformat()
183-
'2013-03-31T03:00:00+02:00'
184-
185-
186-
.. note::
187-
188-
You can control the normalization behavior:
189-
190-
.. code-block:: python
191165
192-
from datetime import datetime
193-
from pendulum import timezone
166+
This is not what we expect, it should be ``2013-03-31T03:00:00+02:00``.
167+
This is actually easy to retrieve the proper datetime by using ``convert()``
168+
again.
194169

195-
tz = timezone('Europe/Paris')
196-
197-
dt = datetime(2013, 3, 31, 2, 30)
198-
dt = tz.convert(dt, dst_rule=tz.PRE_TRANSITION)
199-
dt.isoformat()
200-
'2013-03-31T01:30:00+01:00'
201-
tz.convert(dt, dst_rule=tz.TRANSITION_ERROR)
202-
# NonExistingTime: The datetime 2013-03-31 02:30:00 does not exist.
170+
.. code-block:: python
203171
172+
dt = tz.convert(dt)
173+
dt.isoformat()
174+
'2013-03-31T03:00:00+02:00'
204175
205-
You can also get a normalized ``datetime`` object from a ``Timezone`` by using the ``datetime()`` method:
176+
You can also get a normalized ``datetime`` object
177+
from a ``Timezone`` by using the ``datetime()`` method:
206178

207179
.. code-block:: python
208180

‎pendulum/datetime.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def yesterday(cls, tz=None):
222222
@classmethod
223223
def create(cls, year=None, month=None, day=None,
224224
hour=0, minute=0, second=0, microsecond=0,
225-
tz=UTC):
225+
tz=UTC, *, dst_rule=Timezone.POST_TRANSITION):
226226
"""
227227
Create a new DateTime instance from a specific date and time.
228228
@@ -237,6 +237,7 @@ def create(cls, year=None, month=None, day=None,
237237
:type second: int
238238
:type microsecond: int
239239
:type tz: tzinfo or str or int or None
240+
:type dst_rule: str
240241
241242
:rtype: DateTime
242243
"""
@@ -262,11 +263,10 @@ def create(cls, year=None, month=None, day=None,
262263

263264
dt = datetime.datetime(
264265
year, month, day,
265-
hour, minute, second, microsecond,
266-
fold=1
266+
hour, minute, second, microsecond
267267
)
268268
if tz is not None:
269-
dt = tz.convert(dt)
269+
dt = tz.convert(dt, dst_rule=dst_rule)
270270

271271
return cls(
272272
dt.year, dt.month, dt.day,

‎pendulum/tz/timezone.py‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def load(cls, name):
9797

9898
return cls._cache[name]
9999

100-
def convert(self, dt, *, with_errors=False):
100+
def convert(self, dt, *, dst_rule=None):
101101
"""
102102
Converts or normalizes a datetime.
103103
@@ -108,7 +108,7 @@ def convert(self, dt, *, with_errors=False):
108108
"""
109109
if dt.tzinfo is None:
110110
# we assume local time
111-
converted = self._normalize(dt, with_errors=with_errors)
111+
converted = self._normalize(dt, dst_rule=dst_rule)
112112
else:
113113
converted = self._convert(dt)
114114

@@ -152,7 +152,7 @@ def datetime(self, year, month, day,
152152

153153
return self.convert(dt)
154154

155-
def _normalize(self, dt, *, with_errors=False):
155+
def _normalize(self, dt, *, dst_rule=None):
156156
# if tzinfo is set, something wrong happened
157157
if dt.tzinfo is not None:
158158
raise ValueError(
@@ -162,13 +162,12 @@ def _normalize(self, dt, *, with_errors=False):
162162

163163
# fold attribute (Python 3.6)?
164164
# We use it to determine the DST rule if none has been specified.
165-
fold = None
166-
dst_rule = self.PRE_TRANSITION
167-
if dt.fold == 1:
168-
dst_rule = self.POST_TRANSITION
169-
170-
if with_errors:
171-
dst_rule = self.TRANSITION_ERROR
165+
fold = 0
166+
if dst_rule is None:
167+
dst_rule = self.PRE_TRANSITION
168+
if dt.fold == 1:
169+
dst_rule = self.POST_TRANSITION
170+
fold = 1
172171

173172
if not self._transitions:
174173
# Use the default offset
@@ -503,7 +502,7 @@ def load(cls, name):
503502
def offset(self):
504503
return self._offset
505504

506-
def _normalize(self, dt, *, with_errors=False):
505+
def _normalize(self, dt, *, dst_rule=None):
507506
return dt.replace(tzinfo=self._tzinfo)
508507

509508
def utcoffset(self, dt):

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL