Skip to content

Commit 32f562d

Browse files
jbrockmendeljreback
authored andcommitted
Fastpaths for Timestamp properties (#18539)
1 parent 2a0e54b commit 32f562d

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

asv_bench/benchmarks/timestamp.py

+24-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from .pandas_vb_common import *
21
from pandas import to_timedelta, Timestamp
32
import pytz
43
import datetime
@@ -7,61 +6,64 @@
76
class TimestampProperties(object):
87
goal_time = 0.2
98

10-
params = [None, pytz.timezone('Europe/Amsterdam')]
11-
param_names = ['tz']
9+
params = [(None, None),
10+
(pytz.timezone('Europe/Amsterdam'), None),
11+
(None, 'B'),
12+
(pytz.timezone('Europe/Amsterdam'), 'B')]
13+
param_names = ['tz', 'freq']
1214

13-
def setup(self, tz):
14-
self.ts = Timestamp('2017-08-25 08:16:14', tzinfo=tz)
15+
def setup(self, tz, freq):
16+
self.ts = Timestamp('2017-08-25 08:16:14', tzinfo=tz, freq=freq)
1517

16-
def time_tz(self, tz):
18+
def time_tz(self, tz, freq):
1719
self.ts.tz
1820

19-
def time_offset(self, tz):
21+
def time_offset(self, tz, freq):
2022
self.ts.offset
2123

22-
def time_dayofweek(self, tz):
24+
def time_dayofweek(self, tz, freq):
2325
self.ts.dayofweek
2426

25-
def time_weekday_name(self, tz):
27+
def time_weekday_name(self, tz, freq):
2628
self.ts.weekday_name
2729

28-
def time_dayofyear(self, tz):
30+
def time_dayofyear(self, tz, freq):
2931
self.ts.dayofyear
3032

31-
def time_week(self, tz):
33+
def time_week(self, tz, freq):
3234
self.ts.week
3335

34-
def time_quarter(self, tz):
36+
def time_quarter(self, tz, freq):
3537
self.ts.quarter
3638

37-
def time_days_in_month(self, tz):
39+
def time_days_in_month(self, tz, freq):
3840
self.ts.days_in_month
3941

40-
def time_freqstr(self, tz):
42+
def time_freqstr(self, tz, freq):
4143
self.ts.freqstr
4244

43-
def time_is_month_start(self, tz):
45+
def time_is_month_start(self, tz, freq):
4446
self.ts.is_month_start
4547

46-
def time_is_month_end(self, tz):
48+
def time_is_month_end(self, tz, freq):
4749
self.ts.is_month_end
4850

49-
def time_is_quarter_start(self, tz):
51+
def time_is_quarter_start(self, tz, freq):
5052
self.ts.is_quarter_start
5153

52-
def time_is_quarter_end(self, tz):
54+
def time_is_quarter_end(self, tz, freq):
5355
self.ts.is_quarter_end
5456

55-
def time_is_year_start(self, tz):
57+
def time_is_year_start(self, tz, freq):
5658
self.ts.is_quarter_end
5759

58-
def time_is_year_end(self, tz):
60+
def time_is_year_end(self, tz, freq):
5961
self.ts.is_quarter_end
6062

61-
def time_is_leap_year(self, tz):
63+
def time_is_leap_year(self, tz, freq):
6264
self.ts.is_quarter_end
6365

64-
def time_microsecond(self, tz):
66+
def time_microsecond(self, tz, freq):
6567
self.ts.microsecond
6668

6769

pandas/_libs/tslibs/timestamps.pyx

+22-2
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,12 @@ cdef class _Timestamp(datetime):
304304
out = get_date_field(np.array([val], dtype=np.int64), field)
305305
return int(out[0])
306306

307-
cpdef _get_start_end_field(self, field):
307+
cpdef bint _get_start_end_field(self, str field):
308308
cdef:
309309
int64_t val
310310
dict kwds
311+
ndarray out
312+
int month_kw
311313

312314
freq = self.freq
313315
if freq:
@@ -713,7 +715,7 @@ class Timestamp(_Timestamp):
713715

714716
@property
715717
def quarter(self):
716-
return self._get_field('q')
718+
return ((self.month - 1) // 3) + 1
717719

718720
@property
719721
def days_in_month(self):
@@ -727,26 +729,44 @@ class Timestamp(_Timestamp):
727729

728730
@property
729731
def is_month_start(self):
732+
if self.freq is None:
733+
# fast-path for non-business frequencies
734+
return self.day == 1
730735
return self._get_start_end_field('is_month_start')
731736

732737
@property
733738
def is_month_end(self):
739+
if self.freq is None:
740+
# fast-path for non-business frequencies
741+
return self.day == self.days_in_month
734742
return self._get_start_end_field('is_month_end')
735743

736744
@property
737745
def is_quarter_start(self):
746+
if self.freq is None:
747+
# fast-path for non-business frequencies
748+
return self.day == 1 and self.month % 3 == 1
738749
return self._get_start_end_field('is_quarter_start')
739750

740751
@property
741752
def is_quarter_end(self):
753+
if self.freq is None:
754+
# fast-path for non-business frequencies
755+
return (self.month % 3) == 0 and self.day == self.days_in_month
742756
return self._get_start_end_field('is_quarter_end')
743757

744758
@property
745759
def is_year_start(self):
760+
if self.freq is None:
761+
# fast-path for non-business frequencies
762+
return self.day == self.month == 1
746763
return self._get_start_end_field('is_year_start')
747764

748765
@property
749766
def is_year_end(self):
767+
if self.freq is None:
768+
# fast-path for non-business frequencies
769+
return self.month == 12 and self.day == 31
750770
return self._get_start_end_field('is_year_end')
751771

752772
@property

pandas/tests/scalar/test_timestamp.py

+22
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,28 @@ def test_overflow_offset(self):
4747
stamp - offset
4848

4949

50+
class TestTimestampProperties(object):
51+
52+
def test_properties_business(self):
53+
ts = Timestamp('2017-10-01', freq='B')
54+
control = Timestamp('2017-10-01')
55+
assert ts.dayofweek == 6
56+
assert not ts.is_month_start # not a weekday
57+
assert not ts.is_quarter_start # not a weekday
58+
# Control case: non-business is month/qtr start
59+
assert control.is_month_start
60+
assert control.is_quarter_start
61+
62+
ts = Timestamp('2017-09-30', freq='B')
63+
control = Timestamp('2017-09-30')
64+
assert ts.dayofweek == 5
65+
assert not ts.is_month_end # not a weekday
66+
assert not ts.is_quarter_end # not a weekday
67+
# Control case: non-business is month/qtr start
68+
assert control.is_month_end
69+
assert control.is_quarter_end
70+
71+
5072
class TestTimestamp(object):
5173

5274
def test_constructor(self):

0 commit comments

Comments
 (0)