Skip to content

Commit 2eb568a

Browse files
jbrockmendeljreback
authored andcommitted
Bitesize offsets (#17318)
1 parent b555613 commit 2eb568a

File tree

3 files changed

+58
-76
lines changed

3 files changed

+58
-76
lines changed

asv_bench/benchmarks/timeseries.py

+1-1
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -56,7 +56,7 @@ def setup(self):
56
self.no_freq = self.rng7[:50000].append(self.rng7[50002:])
56
self.no_freq = self.rng7[:50000].append(self.rng7[50002:])
57
self.d_freq = self.rng7[:50000].append(self.rng7[50000:])
57
self.d_freq = self.rng7[:50000].append(self.rng7[50000:])
58

58

59-
self.rng8 = date_range(start='1/1/1700', freq='B', periods=100000)
59+
self.rng8 = date_range(start='1/1/1700', freq='B', periods=75000)
60
self.b_freq = self.rng8[:50000].append(self.rng8[50000:])
60
self.b_freq = self.rng8[:50000].append(self.rng8[50000:])
61

61

62
def time_add_timedelta(self):
62
def time_add_timedelta(self):

pandas/tseries/frequencies.py

+1
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: utf-8 -*-
1
from datetime import timedelta
2
from datetime import timedelta
2
from pandas.compat import long, zip
3
from pandas.compat import long, zip
3
from pandas import compat
4
from pandas import compat

pandas/tseries/offsets.py

+56-75
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: utf-8 -*-
1
from datetime import date, datetime, timedelta
2
from datetime import date, datetime, timedelta
2
from pandas.compat import range
3
from pandas.compat import range
3
from pandas import compat
4
from pandas import compat
@@ -323,37 +324,42 @@ def _params(self):
323

324

324
def __repr__(self):
325
def __repr__(self):
325
className = getattr(self, '_outputName', type(self).__name__)
326
className = getattr(self, '_outputName', type(self).__name__)
327+
328+
if abs(self.n) != 1:
329+
plural = 's'
330+
else:
331+
plural = ''
332+
333+
n_str = ""
334+
if self.n != 1:
335+
n_str = "%s * " % self.n
336+
337+
out = '<%s' % n_str + className + plural + self._repr_attrs() + '>'
338+
return out
339+
340+
# TODO: Combine this with BusinessMixin version by defining a whitelisted
341+
# set of attributes on each object rather than the existing behavior of
342+
# iterating over internal ``__dict__``
343+
def _repr_attrs(self):
326
exclude = set(['n', 'inc', 'normalize'])
344
exclude = set(['n', 'inc', 'normalize'])
327
attrs = []
345
attrs = []
328
for attr in sorted(self.__dict__):
346
for attr in sorted(self.__dict__):
329-
if ((attr == 'kwds' and len(self.kwds) == 0) or
347+
if attr.startswith('_'):
330-
attr.startswith('_')):
331
continue
348
continue
332-
elif attr == 'kwds':
349+
elif attr == 'kwds': # TODO: get rid of this
333
kwds_new = {}
350
kwds_new = {}
334
for key in self.kwds:
351
for key in self.kwds:
335
if not hasattr(self, key):
352
if not hasattr(self, key):
336
kwds_new[key] = self.kwds[key]
353
kwds_new[key] = self.kwds[key]
337
if len(kwds_new) > 0:
354
if len(kwds_new) > 0:
338-
attrs.append('='.join((attr, repr(kwds_new))))
355+
attrs.append('kwds=%s' % (kwds_new))
339-
else:
356+
elif attr not in exclude:
340-
if attr not in exclude:
357+
value = getattr(self, attr)
341-
attrs.append('='.join((attr, repr(getattr(self, attr)))))
358+
attrs.append('%s=%s' % (attr, value))
342-
343-
plural = ''
344-
if abs(self.n) != 1:
345-
plural = 's'
346-
347-
n_str = ''
348-
if self.n != 1:
349-
n_str = '{n} * '.format(n=self.n)
350

359

351-
attrs_str = ''
360+
out = ''
352
if attrs:
361
if attrs:
353-
attrs_str = ': ' + ', '.join(attrs)
362+
out += ': ' + ', '.join(attrs)
354-
355-
repr_content = ''.join([n_str, className, plural, attrs_str])
356-
out = '<{content}>'.format(content=repr_content)
357
return out
363
return out
358

364

359
@property
365
@property
@@ -507,8 +513,18 @@ def freqstr(self):
507
else:
513
else:
508
fstr = code
514
fstr = code
509

515

516+
try:
517+
if self._offset:
518+
fstr += self._offset_str()
519+
except AttributeError:
520+
# TODO: standardize `_offset` vs `offset` naming convention
521+
pass
522+
510
return fstr
523
return fstr
511

524

525+
def _offset_str(self):
526+
return ''
527+
512
@property
528
@property
513
def nanos(self):
529
def nanos(self):
514
raise ValueError("{name} is a non-fixed frequency".format(name=self))
530
raise ValueError("{name} is a non-fixed frequency".format(name=self))
@@ -527,23 +543,11 @@ def _from_name(cls, suffix=None):
527
class BusinessMixin(object):
543
class BusinessMixin(object):
528
""" mixin to business types to provide related functions """
544
""" mixin to business types to provide related functions """
529

545

530-
# TODO: Combine this with DateOffset by defining a whitelisted set of
546+
@property
531-
# attributes on each object rather than the existing behavior of iterating
547+
def offset(self):
532-
# over internal ``__dict__``
548+
"""Alias for self._offset"""
533-
def __repr__(self):
549+
# Alias for backward compat
534-
className = getattr(self, '_outputName', self.__class__.__name__)
550+
return self._offset
535-
536-
plural = ''
537-
if abs(self.n) != 1:
538-
plural = 's'
539-
540-
n_str = ''
541-
if self.n != 1:
542-
n_str = '{n} * '.format(n=self.n)
543-
544-
repr_content = ''.join([n_str, className, plural, self._repr_attrs()])
545-
out = '<{content}>'.format(content=repr_content)
546-
return out
547

551

548
def _repr_attrs(self):
552
def _repr_attrs(self):
549
if self.offset:
553
if self.offset:
@@ -572,6 +576,11 @@ def __getstate__(self):
572

576

573
def __setstate__(self, state):
577
def __setstate__(self, state):
574
"""Reconstruct an instance from a pickled state"""
578
"""Reconstruct an instance from a pickled state"""
579+
if 'offset' in state:
580+
# Older versions have offset attribute instead of _offset
581+
if '_offset' in state: # pragma: no cover
582+
raise ValueError('Unexpected key `_offset`')
583+
state['_offset'] = state.pop('offset')
575
self.__dict__ = state
584
self.__dict__ = state
576
if 'weekmask' in state and 'holidays' in state:
585
if 'weekmask' in state and 'holidays' in state:
577
calendar, holidays = _get_calendar(weekmask=self.weekmask,
586
calendar, holidays = _get_calendar(weekmask=self.weekmask,
@@ -593,24 +602,7 @@ def __init__(self, n=1, normalize=False, **kwds):
593
self.n = int(n)
602
self.n = int(n)
594
self.normalize = normalize
603
self.normalize = normalize
595
self.kwds = kwds
604
self.kwds = kwds
596-
self.offset = kwds.get('offset', timedelta(0))
605+
self._offset = kwds.get('offset', timedelta(0))
597-
598-
@property
599-
def freqstr(self):
600-
try:
601-
code = self.rule_code
602-
except NotImplementedError:
603-
return repr(self)
604-
605-
if self.n != 1:
606-
fstr = '{n}{code}'.format(n=self.n, code=code)
607-
else:
608-
fstr = code
609-
610-
if self.offset:
611-
fstr += self._offset_str()
612-
613-
return fstr
614

606

615
def _offset_str(self):
607
def _offset_str(self):
616
def get_str(td):
608
def get_str(td):
@@ -643,9 +635,6 @@ def get_str(td):
643
else:
635
else:
644
return '+' + repr(self.offset)
636
return '+' + repr(self.offset)
645

637

646-
def isAnchored(self):
647-
return (self.n == 1)
648-
649
@apply_wraps
638
@apply_wraps
650
def apply(self, other):
639
def apply(self, other):
651
if isinstance(other, datetime):
640
if isinstance(other, datetime):
@@ -709,7 +698,7 @@ def __init__(self, **kwds):
709
kwds['start'] = self._validate_time(kwds.get('start', '09:00'))
698
kwds['start'] = self._validate_time(kwds.get('start', '09:00'))
710
kwds['end'] = self._validate_time(kwds.get('end', '17:00'))
699
kwds['end'] = self._validate_time(kwds.get('end', '17:00'))
711
self.kwds = kwds
700
self.kwds = kwds
712-
self.offset = kwds.get('offset', timedelta(0))
701+
self._offset = kwds.get('offset', timedelta(0))
713
self.start = kwds.get('start', '09:00')
702
self.start = kwds.get('start', '09:00')
714
self.end = kwds.get('end', '17:00')
703
self.end = kwds.get('end', '17:00')
715

704

@@ -776,7 +765,7 @@ def _get_business_hours_by_sec(self):
776
Return business hours in a day by seconds.
765
Return business hours in a day by seconds.
777
"""
766
"""
778
if self._get_daytime_flag():
767
if self._get_daytime_flag():
779-
# create dummy datetime to calcurate businesshours in a day
768+
# create dummy datetime to calculate businesshours in a day
780
dtstart = datetime(2014, 4, 1, self.start.hour, self.start.minute)
769
dtstart = datetime(2014, 4, 1, self.start.hour, self.start.minute)
781
until = datetime(2014, 4, 1, self.end.hour, self.end.minute)
770
until = datetime(2014, 4, 1, self.end.hour, self.end.minute)
782
return (until - dtstart).total_seconds()
771
return (until - dtstart).total_seconds()
@@ -811,7 +800,7 @@ def rollforward(self, dt):
811

800

812
@apply_wraps
801
@apply_wraps
813
def apply(self, other):
802
def apply(self, other):
814-
# calcurate here because offset is not immutable
803+
# calculate here because offset is not immutable
815
daytime = self._get_daytime_flag()
804
daytime = self._get_daytime_flag()
816
businesshours = self._get_business_hours_by_sec()
805
businesshours = self._get_business_hours_by_sec()
817
bhdelta = timedelta(seconds=businesshours)
806
bhdelta = timedelta(seconds=businesshours)
@@ -860,7 +849,7 @@ def apply(self, other):
860
if n >= 0:
849
if n >= 0:
861
bday_edge = self._prev_opening_time(other)
850
bday_edge = self._prev_opening_time(other)
862
bday_edge = bday_edge + bhdelta
851
bday_edge = bday_edge + bhdelta
863-
# calcurate remainder
852+
# calculate remainder
864
bday_remain = result - bday_edge
853
bday_remain = result - bday_edge
865
result = self._next_opening_time(other)
854
result = self._next_opening_time(other)
866
result += bday_remain
855
result += bday_remain
@@ -898,7 +887,7 @@ def onOffset(self, dt):
898

887

899
def _onOffset(self, dt, businesshours):
888
def _onOffset(self, dt, businesshours):
900
"""
889
"""
901-
Slight speedups using calcurated values
890+
Slight speedups using calculated values
902
"""
891
"""
903
# if self.normalize and not _is_normalized(dt):
892
# if self.normalize and not _is_normalized(dt):
904
# return False
893
# return False
@@ -975,7 +964,8 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
975
self.n = int(n)
964
self.n = int(n)
976
self.normalize = normalize
965
self.normalize = normalize
977
self.kwds = kwds
966
self.kwds = kwds
978-
self.offset = kwds.get('offset', timedelta(0))
967+
self._offset = kwds.get('offset', timedelta(0))
968+
979
calendar, holidays = _get_calendar(weekmask=weekmask,
969
calendar, holidays = _get_calendar(weekmask=weekmask,
980
holidays=holidays,
970
holidays=holidays,
981
calendar=calendar)
971
calendar=calendar)
@@ -1337,9 +1327,6 @@ def _apply_index_days(self, i, roll):
1337
class BusinessMonthEnd(MonthOffset):
1327
class BusinessMonthEnd(MonthOffset):
1338
"""DateOffset increments between business EOM dates"""
1328
"""DateOffset increments between business EOM dates"""
1339

1329

1340-
def isAnchored(self):
1341-
return (self.n == 1)
1342-
1343
@apply_wraps
1330
@apply_wraps
1344
def apply(self, other):
1331
def apply(self, other):
1345
n = self.n
1332
n = self.n
@@ -1425,7 +1412,7 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
1425
self.n = int(n)
1412
self.n = int(n)
1426
self.normalize = normalize
1413
self.normalize = normalize
1427
self.kwds = kwds
1414
self.kwds = kwds
1428-
self.offset = kwds.get('offset', timedelta(0))
1415+
self._offset = kwds.get('offset', timedelta(0))
1429

1416

1430
calendar, holidays = _get_calendar(weekmask=weekmask,
1417
calendar, holidays = _get_calendar(weekmask=weekmask,
1431
holidays=holidays,
1418
holidays=holidays,
@@ -1495,7 +1482,7 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
1495
self.n = int(n)
1482
self.n = int(n)
1496
self.normalize = normalize
1483
self.normalize = normalize
1497
self.kwds = kwds
1484
self.kwds = kwds
1498-
self.offset = kwds.get('offset', timedelta(0))
1485+
self._offset = kwds.get('offset', timedelta(0))
1499

1486

1500
# _get_calendar does validation and possible transformation
1487
# _get_calendar does validation and possible transformation
1501
# of calendar and holidays.
1488
# of calendar and holidays.
@@ -1966,9 +1953,6 @@ class QuarterEnd(QuarterOffset):
1966
_default_startingMonth = 3
1953
_default_startingMonth = 3
1967
_prefix = 'Q'
1954
_prefix = 'Q'
1968

1955

1969-
def isAnchored(self):
1970-
return (self.n == 1 and self.startingMonth is not None)
1971-
1972
@apply_wraps
1956
@apply_wraps
1973
def apply(self, other):
1957
def apply(self, other):
1974
n = self.n
1958
n = self.n
@@ -2004,9 +1988,6 @@ class QuarterBegin(QuarterOffset):
2004
_from_name_startingMonth = 1
1988
_from_name_startingMonth = 1
2005
_prefix = 'QS'
1989
_prefix = 'QS'
2006

1990

2007-
def isAnchored(self):
2008-
return (self.n == 1 and self.startingMonth is not None)
2009-
2010
@apply_wraps
1991
@apply_wraps
2011
def apply(self, other):
1992
def apply(self, other):
2012
n = self.n
1993
n = self.n

0 commit comments

Comments
 (0)