Skip to content

Commit 1ea3f2e

Browse files
committed
Changes the internal structure of the Pendulum class to make it more efficient
1 parent 2637a8e commit 1ea3f2e

File tree

4 files changed

+166
-79
lines changed

4 files changed

+166
-79
lines changed

pendulum/_extensions/tz/cbreakdown.pyx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# -*- coding: utf-8 -*-
22

33
from cpython cimport bool
4-
from datetime import datetime
54

65

76
cdef long EPOCH_YEAR = 1970

pendulum/helpers.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from .constants import SECONDS_PER_HOUR, SECONDS_PER_MINUTE
4+
5+
6+
def day_ordinal(y, m, d):
7+
"""
8+
Returns the number of days before/after 1970-01-01.
9+
10+
:rtype: integer
11+
"""
12+
if m <= 2:
13+
y -= 1
14+
15+
era = (y if y >= 0 else y - 399) // 400
16+
yoe = y - era * 400
17+
doy = (153 * (m + (-3 if m > 2 else 9)) + 2) // 5 + d - 1
18+
doe = yoe * 365 + yoe // 4 - yoe // 100 + doy
19+
20+
return era * 146097 + doe - 719468
21+
22+
23+
def timestamp(year, month, day, hour, minute, second, microsecond, tzinfo):
24+
days_in_seconds = day_ordinal(year, month, day) * 86400
25+
26+
return (
27+
days_in_seconds
28+
+ hour * SECONDS_PER_HOUR
29+
+ minute * SECONDS_PER_MINUTE
30+
+ second
31+
+ microsecond / 1e6
32+
- tzinfo.offset
33+
)

pendulum/pendulum.py

Lines changed: 72 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
from __future__ import division
44

5-
import time as _time
65
import math
76
import calendar
87
import datetime
9-
import warnings
108
import locale as _locale
119

1210
from contextlib import contextmanager
@@ -19,7 +17,8 @@
1917
from .tz import Timezone, UTC, FixedTimezone, local_timezone
2018
from .tz.timezone_info import TimezoneInfo
2119
from .formatting import FORMATTERS
22-
from ._compat import PY33, basestring
20+
from .helpers import timestamp
21+
from ._compat import basestring
2322
from .constants import (
2423
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
2524
THURSDAY, FRIDAY, SATURDAY,
@@ -156,25 +155,44 @@ def __new__(cls, year, month, day,
156155
def __init__(self, year, month, day,
157156
hour=0, minute=0, second=0, microsecond=0,
158157
tzinfo=UTC):
159-
self.__float_timestamp = None
160-
161-
# If a TimezoneInfo is passed we do not convert:
158+
# If a TimezoneInfo is passed we do not convert
162159
if isinstance(tzinfo, TimezoneInfo):
163160
self._tz = tzinfo.tz
164161

165-
self._datetime = datetime.datetime(
162+
self._year = year
163+
self._month = month
164+
self._day = day
165+
self._hour = hour
166+
self._minute = minute
167+
self._second = second
168+
self._microsecond = microsecond
169+
self._tzinfo = tzinfo
170+
171+
dt = datetime.datetime(
166172
year, month, day,
167173
hour, minute, second, microsecond,
168-
tzinfo=tzinfo
174+
tzinfo
169175
)
170176
else:
171177
self._tz = self._safe_create_datetime_zone(tzinfo)
172178

173-
self._datetime = self._tz.convert(datetime.datetime(
179+
dt = self._tz.convert(datetime.datetime(
174180
year, month, day,
175181
hour, minute, second, microsecond
176182
), dst_rule=self._TRANSITION_RULE)
177183

184+
self._year = dt.year
185+
self._month = dt.month
186+
self._day = dt.day
187+
self._hour = dt.hour
188+
self._minute = dt.minute
189+
self._second = dt.second
190+
self._microsecond = dt.microsecond
191+
self._tzinfo = dt.tzinfo
192+
193+
self._timestamp = None
194+
self._datetime = dt
195+
178196
@classmethod
179197
def instance(cls, dt, tz=UTC):
180198
"""
@@ -482,12 +500,15 @@ def microsecond_(self, microsecond):
482500
def _setter(self, **kwargs):
483501
kwargs['tzinfo'] = None
484502

485-
return self.instance(self._datetime.replace(**kwargs), self._tz)
503+
return self._tz.convert(self.replace(**kwargs))
486504

487505
def timezone_(self, tz):
488506
tz = self._safe_create_datetime_zone(tz)
489507

490-
return self.instance(self._datetime.replace(tzinfo=None), tz)
508+
dt = self.copy()
509+
dt._tz = tz
510+
511+
return dt
491512

492513
def tz_(self, tz):
493514
return self.timezone_(tz)
@@ -497,35 +518,35 @@ def timestamp_(self, timestamp, tz=UTC):
497518

498519
@property
499520
def year(self):
500-
return self._datetime.year
521+
return self._year
501522

502523
@property
503524
def month(self):
504-
return self._datetime.month
525+
return self._month
505526

506527
@property
507528
def day(self):
508-
return self._datetime.day
529+
return self._day
509530

510531
@property
511532
def hour(self):
512-
return self._datetime.hour
533+
return self._hour
513534

514535
@property
515536
def minute(self):
516-
return self._datetime.minute
537+
return self._minute
517538

518539
@property
519540
def second(self):
520-
return self._datetime.second
541+
return self._second
521542

522543
@property
523544
def microsecond(self):
524-
return self._datetime.microsecond
545+
return self._microsecond
525546

526547
@property
527548
def tzinfo(self):
528-
return self._datetime.tzinfo
549+
return self._tzinfo
529550

530551
@property
531552
def day_of_week(self):
@@ -545,39 +566,30 @@ def days_in_month(self):
545566

546567
@property
547568
def timestamp(self):
548-
return int(self.float_timestamp - self._datetime.microsecond / 1e6)
569+
return int(self.float_timestamp // 1)
549570

550571
@property
551572
def float_timestamp(self):
552-
if self.__float_timestamp is not None:
553-
return self.__float_timestamp
554-
555-
# If Python > 3.3 we use the native function
556-
# else we emulate it
557-
if PY33:
558-
self.__float_timestamp = self._datetime.timestamp()
559-
elif self._datetime.tzinfo is None:
560-
self.__float_timestamp = _time.mktime(
561-
(self.year, self.month, self.day,
562-
self.hour, self.minute, self.second,
563-
-1, -1, -1)) + self.microsecond / 1e6
564-
565-
else:
566-
self.__float_timestamp = (self._datetime - self._EPOCH).total_seconds()
573+
if self._timestamp is None:
574+
self._timestamp = timestamp(
575+
self._year, self._month, self._day,
576+
self._hour, self._minute, self._second, self._microsecond,
577+
self._tzinfo
578+
)
567579

568-
return self.__float_timestamp
580+
return self._timestamp
569581

570582
@property
571583
def week_of_month(self):
572-
return math.ceil(self.day / DAYS_PER_WEEK)
584+
return math.ceil(self._day / DAYS_PER_WEEK)
573585

574586
@property
575587
def age(self):
576588
return self.diff().in_years()
577589

578590
@property
579591
def quarter(self):
580-
return int(math.ceil(self.month / 3))
592+
return int(math.ceil(self._month / 3))
581593

582594
@property
583595
def offset(self):
@@ -599,7 +611,7 @@ def utc(self):
599611

600612
@property
601613
def is_dst(self):
602-
return self._datetime.tzinfo.is_dst
614+
return self.tzinfo.is_dst
603615

604616
@property
605617
def timezone(self):
@@ -617,7 +629,7 @@ def get_timezone(self):
617629
return self._tz
618630

619631
def get_offset(self):
620-
return int(self._datetime.utcoffset().total_seconds())
632+
return int(self._tzinfo.offset)
621633

622634
def with_date(self, year, month, day):
623635
"""
@@ -634,12 +646,11 @@ def with_date(self, year, month, day):
634646
635647
:rtype: Pendulum
636648
"""
637-
dt = self._datetime.replace(
638-
year=int(year), month=int(month), day=int(day),
639-
tzinfo=None
649+
dt = self.replace(
650+
year=int(year), month=int(month), day=int(day)
640651
)
641652

642-
return self.instance(dt, self._tz)
653+
return self._tz.convert(dt)
643654

644655
def with_time(self, hour, minute, second, microsecond=0):
645656
"""
@@ -940,7 +951,7 @@ def get_formatter(cls):
940951

941952
def __str__(self):
942953
if self._to_string_format is None:
943-
return self._datetime.isoformat()
954+
return self.isoformat()
944955

945956
return self.format(self._to_string_format, formatter='classic')
946957

@@ -2174,25 +2185,16 @@ def timetuple(self):
21742185
def utctimetuple(self):
21752186
return self._datetime.utctimetuple()
21762187

2177-
def date(self):
2178-
return self._datetime.date()
2179-
2180-
def time(self):
2181-
return self._datetime.time()
2182-
2183-
def timetz(self):
2184-
return self._datetime.timetz()
2185-
21862188
def replace(self, year=None, month=None, day=None, hour=None,
21872189
minute=None, second=None, microsecond=None, tzinfo=True):
2188-
year = year if year is not None else self._datetime.year
2189-
month = month if month is not None else self._datetime.month
2190-
day = day if day is not None else self._datetime.day
2191-
hour = hour if hour is not None else self._datetime.hour
2192-
minute = minute if minute is not None else self._datetime.minute
2193-
second = second if second is not None else self._datetime.second
2194-
microsecond = microsecond if microsecond is not None else self._datetime.microsecond
2195-
tzinfo = tzinfo if tzinfo is not True else self._datetime.tzinfo
2190+
year = year if year is not None else self._year
2191+
month = month if month is not None else self._month
2192+
day = day if day is not None else self._day
2193+
hour = hour if hour is not None else self._hour
2194+
minute = minute if minute is not None else self._minute
2195+
second = second if second is not None else self._second
2196+
microsecond = microsecond if microsecond is not None else self._microsecond
2197+
tzinfo = tzinfo if tzinfo is not True else self._tzinfo
21962198

21972199
return self.instance(
21982200
self._datetime.replace(year=year, month=month, day=day,
@@ -2203,33 +2205,18 @@ def replace(self, year=None, month=None, day=None, hour=None,
22032205
def astimezone(self, tz=None):
22042206
return self.instance(self._datetime.astimezone(tz))
22052207

2206-
def ctime(self):
2207-
return self._datetime.ctime()
2208-
22092208
def isoformat(self, sep='T'):
22102209
return self._datetime.isoformat(sep)
22112210

22122211
def utcoffset(self):
2213-
return self._datetime.utcoffset()
2212+
return self._tzinfo.utcoffset(self)
22142213

22152214
def tzname(self):
22162215
return self._datetime.tzname()
22172216

22182217
def dst(self):
22192218
return self._datetime.dst()
22202219

2221-
def toordinal(self):
2222-
return self._datetime.toordinal()
2223-
2224-
def weekday(self):
2225-
return self._datetime.weekday()
2226-
2227-
def isoweekday(self):
2228-
return self._datetime.isoweekday()
2229-
2230-
def isocalendar(self):
2231-
return self._datetime.isocalendar()
2232-
22332220
def __format__(self, format_spec):
22342221
if len(format_spec) > 0:
22352222
return self.strftime(format_spec)
@@ -2250,11 +2237,18 @@ def _getstate(self):
22502237
)
22512238

22522239
def __setstate__(self, year, month, day, hour, minute, second, microsecond, tz):
2240+
self._year = year
2241+
self._month = year
2242+
self._day = year
2243+
self._hour = year
2244+
self._minute = year
2245+
self._second = year
22532246
self._datetime = tz.convert(datetime.datetime(
22542247
year, month, day,
22552248
hour, minute, second, microsecond
22562249
))
22572250
self._tz = tz
2251+
self._tzinfo = self._datetime.tzinfo
22582252

22592253
def __reduce__(self):
22602254
return self.__class__, self._getstate()

0 commit comments

Comments
 (0)