Skip to content

Commit db0f836

Browse files
committed
Fix inaccuracy of in_days() method.
Fixes #153
1 parent bfc7313 commit db0f836

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Change Log
22

3+
## [Unreleased]
4+
5+
### Fixed
6+
7+
- Fixed inaccuracy of `in_days()` method on DST transitions.
8+
9+
310
## [1.3.0] - 2017-09-25
411

512
### Added
@@ -23,7 +30,7 @@
2330
### Fixed
2431

2532
- Fixed normalization of microseconds in durations.
26-
- Fixed microseconds not being included in `average()`. (Thanks to [ericfrederich](https://github.com/ericfrederich))
33+
- Fixed microseconds not being included in `average()`. (Thanks to [ericfrederich](https://github.com/ericfrederich))
2734

2835

2936
## [1.2.4] - 2017-06-20
@@ -296,7 +303,7 @@
296303

297304
### Changed
298305

299-
- Makes `.offset_hours` return a float.
306+
- Makes `.offset_hours` return a float.
300307

301308
### Fixed
302309

pendulum/helpers.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pendulum
44

55
from math import copysign
6-
from datetime import timedelta
6+
from datetime import datetime, timedelta
77

88
try:
99
from ._extensions._helpers import local_time, parse_iso8601 as _parse_iso8601
@@ -136,13 +136,25 @@ def precise_diff(d1, d2):
136136
'hours': 0,
137137
'minutes': 0,
138138
'seconds': 0,
139-
'microseconds': 0
139+
'microseconds': 0,
140+
'total': {
141+
'days': 0
142+
}
140143
}
141144
sign = 1
142145

143146
if d1 == d2:
144147
return diff
145148

149+
tzinfo1 = d1.tzinfo if isinstance(d1, datetime) else None
150+
tzinfo2 = d2.tzinfo if isinstance(d2, datetime) else None
151+
152+
if (tzinfo1 is None and tzinfo2 is not None
153+
or tzinfo2 is None and tzinfo1 is not None):
154+
raise ValueError(
155+
'Comparison between naive and aware datetimes is not supported'
156+
)
157+
146158
if d1 > d2:
147159
d1, d2 = d2, d1
148160
sign = -1
@@ -154,8 +166,44 @@ def precise_diff(d1, d2):
154166
min_diff = 0
155167
sec_diff = 0
156168
mic_diff = 0
169+
total_days = (
170+
_day_number(d2.year, d2.month, d2.day)
171+
- _day_number(d1.year, d1.month, d1.day)
172+
)
173+
in_same_tz = False
174+
tz1 = None
175+
tz2 = None
176+
177+
# Trying to figure out the timezone names
178+
# If we can't find them, we assume different timezones
179+
if tzinfo1 and tzinfo2:
180+
if hasattr(tzinfo1, 'name'):
181+
# Pendulum timezone
182+
tz1 = tzinfo1.name
183+
elif hasattr(tzinfo1, 'zone'):
184+
# pytz timezone
185+
tz1 = tzinfo1.zone
186+
187+
if hasattr(tzinfo2, 'name'):
188+
tz2 = tzinfo2.name
189+
elif hasattr(tzinfo2, 'zone'):
190+
tz2 = tzinfo2.zone
191+
192+
in_same_tz = tz1 == tz2 and tz1 is not None
157193

158194
if hasattr(d2, 'hour'):
195+
# If we are not in the same timezone
196+
# we need to adjust
197+
if not in_same_tz:
198+
offset1 = d1.utcoffset()
199+
offset2 = d2.utcoffset()
200+
201+
if offset1:
202+
d1 = d1 - offset1
203+
204+
if offset2:
205+
d2 = d2 - offset2
206+
159207
hour_diff = d2.hour - d1.hour
160208
min_diff = d2.minute - d1.minute
161209
sec_diff = d2.second - d1.second
@@ -221,6 +269,7 @@ def precise_diff(d1, d2):
221269
diff['days'] = sign * d_diff
222270
diff['months'] = sign * m_diff
223271
diff['years'] = sign * y_diff
272+
diff['total']['days'] = sign * total_days
224273

225274
return diff
226275

@@ -243,5 +292,18 @@ def days_in_year(year):
243292

244293
return DAYS_PER_N_YEAR
245294

295+
246296
def _sign(x):
247297
return int(copysign(1, x))
298+
299+
300+
def _day_number(year, month, day):
301+
month = (month + 9) % 12
302+
year = year - month // 10
303+
304+
return (
305+
365 * year
306+
+ year // 4 - year // 100 + year // 400
307+
+ (month * 306 + 5) // 10
308+
+ (day - 1 )
309+
)

pendulum/period.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ def in_months(self):
131131
"""
132132
return self.years * MONTHS_PER_YEAR + self.months
133133

134+
def in_weeks(self):
135+
days = self.in_days()
136+
sign = 1
137+
138+
if days < 0:
139+
sign = -1
140+
141+
return sign * (abs(days) // 7)
142+
143+
def in_days(self):
144+
return self._delta['total']['days']
145+
134146
def in_weekdays(self):
135147
start, end = self.start.start_of('day'), self.end.start_of('day')
136148
if not self._absolute and self.invert:

tests/period_tests/test_construct.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ def test_dst_transition(self):
8585
assert period.hours == 0
8686
assert period.remaining_seconds == 0
8787

88+
assert period.in_days() == 6
89+
assert period.in_hours() == 5 * 24 + 23
90+
8891
def test_timedelta_behavior(self):
8992
dt1 = Pendulum(2000, 11, 20, 1)
9093
dt2 = Pendulum(2000, 11, 25, 2)

0 commit comments

Comments
 (0)