diff --git a/asv_bench/benchmarks/timestamp.py b/asv_bench/benchmarks/timestamp.py index fc5e6dc8c06d6..9d7d6d2998a8b 100644 --- a/asv_bench/benchmarks/timestamp.py +++ b/asv_bench/benchmarks/timestamp.py @@ -1,4 +1,3 @@ -from .pandas_vb_common import * from pandas import to_timedelta, Timestamp import pytz import datetime @@ -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 diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 8fdded0bcb07a..cf0c0e2c01d60 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -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): cdef: int64_t val dict kwds + ndarray out + int month_kw freq = self.freq if freq: @@ -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): @@ -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 diff --git a/pandas/tests/scalar/test_timestamp.py b/pandas/tests/scalar/test_timestamp.py index 545ed7f1ebbf3..992f211229441 100644 --- a/pandas/tests/scalar/test_timestamp.py +++ b/pandas/tests/scalar/test_timestamp.py @@ -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):