Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fastpaths for Timestamp properties #18539

Merged
merged 3 commits into from
Nov 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions asv_bench/benchmarks/timestamp.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .pandas_vb_common import *
from pandas import to_timedelta, Timestamp
import pytz
import datetime
Expand All @@ -7,61 +6,64 @@
class TimestampProperties(object):
goal_time = 0.2

params = [None, pytz.timezone('Europe/Amsterdam')]
param_names = ['tz']
params = [(None, None),
(pytz.timezone('Europe/Amsterdam'), None),
(None, 'B'),
(pytz.timezone('Europe/Amsterdam'), 'B')]
param_names = ['tz', 'freq']

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

def time_tz(self, tz):
def time_tz(self, tz, freq):
self.ts.tz

def time_offset(self, tz):
def time_offset(self, tz, freq):
self.ts.offset

def time_dayofweek(self, tz):
def time_dayofweek(self, tz, freq):
self.ts.dayofweek

def time_weekday_name(self, tz):
def time_weekday_name(self, tz, freq):
self.ts.weekday_name

def time_dayofyear(self, tz):
def time_dayofyear(self, tz, freq):
self.ts.dayofyear

def time_week(self, tz):
def time_week(self, tz, freq):
self.ts.week

def time_quarter(self, tz):
def time_quarter(self, tz, freq):
self.ts.quarter

def time_days_in_month(self, tz):
def time_days_in_month(self, tz, freq):
self.ts.days_in_month

def time_freqstr(self, tz):
def time_freqstr(self, tz, freq):
self.ts.freqstr

def time_is_month_start(self, tz):
def time_is_month_start(self, tz, freq):
self.ts.is_month_start

def time_is_month_end(self, tz):
def time_is_month_end(self, tz, freq):
self.ts.is_month_end

def time_is_quarter_start(self, tz):
def time_is_quarter_start(self, tz, freq):
self.ts.is_quarter_start

def time_is_quarter_end(self, tz):
def time_is_quarter_end(self, tz, freq):
self.ts.is_quarter_end

def time_is_year_start(self, tz):
def time_is_year_start(self, tz, freq):
self.ts.is_quarter_end

def time_is_year_end(self, tz):
def time_is_year_end(self, tz, freq):
self.ts.is_quarter_end

def time_is_leap_year(self, tz):
def time_is_leap_year(self, tz, freq):
self.ts.is_quarter_end

def time_microsecond(self, tz):
def time_microsecond(self, tz, freq):
self.ts.microsecond


Expand Down
24 changes: 22 additions & 2 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,12 @@ cdef class _Timestamp(datetime):
out = get_date_field(np.array([val], dtype=np.int64), field)
return int(out[0])

cpdef _get_start_end_field(self, field):
cpdef bint _get_start_end_field(self, str field):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so why is this in _Timestamp again (as opposed to Timestamp); this is why its cpdef, why not just cdef?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why its in _Timestamp instead of Timestamp (though my understanding is putting it in _Timestamp is slightly more performant and smaller memory footprint); it was that way before I got here.

It is cpdef and not cdef because if it were cdef then calling it from Timestamp would be an AttributeError. That's why #18446 moved a bunch of properties up to _Timestamp after making it cdef.

cdef:
int64_t val
dict kwds
ndarray out
int month_kw

freq = self.freq
if freq:
Expand Down Expand Up @@ -713,7 +715,7 @@ class Timestamp(_Timestamp):

@property
def quarter(self):
return self._get_field('q')
return ((self.month - 1) // 3) + 1

@property
def days_in_month(self):
Expand All @@ -727,26 +729,44 @@ class Timestamp(_Timestamp):

@property
def is_month_start(self):
if self.freq is None:
# fast-path for non-business frequencies
return self.day == 1
return self._get_start_end_field('is_month_start')

@property
def is_month_end(self):
if self.freq is None:
# fast-path for non-business frequencies
return self.day == self.days_in_month
return self._get_start_end_field('is_month_end')

@property
def is_quarter_start(self):
if self.freq is None:
# fast-path for non-business frequencies
return self.day == 1 and self.month % 3 == 1
return self._get_start_end_field('is_quarter_start')

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

@property
def is_year_start(self):
if self.freq is None:
# fast-path for non-business frequencies
return self.day == self.month == 1
return self._get_start_end_field('is_year_start')

@property
def is_year_end(self):
if self.freq is None:
# fast-path for non-business frequencies
return self.month == 12 and self.day == 31
return self._get_start_end_field('is_year_end')

@property
Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/scalar/test_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,28 @@ def test_overflow_offset(self):
stamp - offset


class TestTimestampProperties(object):

def test_properties_business(self):
ts = Timestamp('2017-10-01', freq='B')
control = Timestamp('2017-10-01')
assert ts.dayofweek == 6
assert not ts.is_month_start # not a weekday
assert not ts.is_quarter_start # not a weekday
# Control case: non-business is month/qtr start
assert control.is_month_start
assert control.is_quarter_start

ts = Timestamp('2017-09-30', freq='B')
control = Timestamp('2017-09-30')
assert ts.dayofweek == 5
assert not ts.is_month_end # not a weekday
assert not ts.is_quarter_end # not a weekday
# Control case: non-business is month/qtr start
assert control.is_month_end
assert control.is_quarter_end


class TestTimestamp(object):

def test_constructor(self):
Expand Down