diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index dd5b849b42a08..708f6c29c8dd1 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -45,6 +45,7 @@ Other API Changes - :class:`Timestamp` will no longer silently ignore unused or invalid ``tz`` or ``tzinfo`` keyword arguments (:issue:`17690`) - :class:`Timestamp` will no longer silently ignore invalid ``freq`` arguments (:issue:`5168`) - :class:`CacheableOffset` and :class:`WeekDay` are no longer available in the ``pandas.tseries.offsets`` module (:issue:`17830`) +- `tseries.frequencies.get_freq_group()` and `tseries.frequencies.DAYS` are removed from the public API (:issue:`18034`) .. _whatsnew_0220.deprecations: diff --git a/pandas/_libs/period.pyx b/pandas/_libs/period.pyx index 72523a19b9595..bd21fb97ede20 100644 --- a/pandas/_libs/period.pyx +++ b/pandas/_libs/period.pyx @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# cython: profile=False from datetime import datetime, date, timedelta import operator @@ -27,14 +28,16 @@ from util cimport is_period_object, is_string_object, INT32_MIN from lib cimport is_null_datetimelike from pandas._libs import tslib -from pandas._libs.tslib import Timestamp, iNaT, NaT +from pandas._libs.tslib import Timestamp, iNaT from tslibs.timezones cimport ( is_utc, is_tzlocal, get_utcoffset, get_dst_info, maybe_get_tz) from tslibs.timedeltas cimport delta_to_nanoseconds -from tslibs.parsing import parse_time_string, NAT_SENTINEL +from tslibs.parsing import (parse_time_string, NAT_SENTINEL, + _get_rule_month, _MONTH_NUMBERS) from tslibs.frequencies cimport get_freq_code -from tslibs.nattype import nat_strings +from tslibs.resolution import resolution, Resolution +from tslibs.nattype import nat_strings, NaT from tslibs.nattype cimport _nat_scalar_rules from pandas.tseries import offsets @@ -42,13 +45,6 @@ from pandas.tseries import frequencies cdef int64_t NPY_NAT = util.get_nat() -cdef int RESO_US = frequencies.RESO_US -cdef int RESO_MS = frequencies.RESO_MS -cdef int RESO_SEC = frequencies.RESO_SEC -cdef int RESO_MIN = frequencies.RESO_MIN -cdef int RESO_HR = frequencies.RESO_HR -cdef int RESO_DAY = frequencies.RESO_DAY - cdef extern from "period_helper.h": ctypedef struct date_info: int64_t absdate @@ -487,98 +483,10 @@ def extract_freq(ndarray[object] values): raise ValueError('freq not specified and cannot be inferred') -cpdef resolution(ndarray[int64_t] stamps, tz=None): - cdef: - Py_ssize_t i, n = len(stamps) - pandas_datetimestruct dts - int reso = RESO_DAY, curr_reso - - if tz is not None: - tz = maybe_get_tz(tz) - return _reso_local(stamps, tz) - else: - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i], &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - return reso - - -cdef inline int _reso_stamp(pandas_datetimestruct *dts): - if dts.us != 0: - if dts.us % 1000 == 0: - return RESO_MS - return RESO_US - elif dts.sec != 0: - return RESO_SEC - elif dts.min != 0: - return RESO_MIN - elif dts.hour != 0: - return RESO_HR - return RESO_DAY - -cdef _reso_local(ndarray[int64_t] stamps, object tz): - cdef: - Py_ssize_t n = len(stamps) - int reso = RESO_DAY, curr_reso - ndarray[int64_t] trans, deltas, pos - pandas_datetimestruct dts - - if is_utc(tz): - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i], &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - elif is_tzlocal(tz): - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i], &dts) - dt = datetime(dts.year, dts.month, dts.day, dts.hour, - dts.min, dts.sec, dts.us, tz) - delta = int(get_utcoffset(tz, dt).total_seconds()) * 1000000000 - dt64_to_dtstruct(stamps[i] + delta, &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - else: - # Adjust datetime64 timestamp, recompute datetimestruct - trans, deltas, typ = get_dst_info(tz) - - _pos = trans.searchsorted(stamps, side='right') - 1 - if _pos.dtype != np.int64: - _pos = _pos.astype(np.int64) - pos = _pos - - # statictzinfo - if typ not in ['pytz', 'dateutil']: - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i] + deltas[0], &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - else: - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i] + deltas[pos[i]], &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - - return reso - - +# ----------------------------------------------------------------------- # period helpers + cdef ndarray[int64_t] localize_dt64arr_to_period(ndarray[int64_t] stamps, int freq, object tz): cdef: @@ -1191,7 +1099,7 @@ class Period(_Period): if freq is None: try: - freq = frequencies.Resolution.get_freq(reso) + freq = Resolution.get_freq(reso) except KeyError: raise ValueError( "Invalid frequency or could not infer: %s" % reso) @@ -1236,7 +1144,7 @@ def _quarter_to_myear(year, quarter, freq): if quarter <= 0 or quarter > 4: raise ValueError('Quarter must be 1 <= q <= 4') - mnum = tslib._MONTH_NUMBERS[tslib._get_rule_month(freq)] + 1 + mnum = _MONTH_NUMBERS[_get_rule_month(freq)] + 1 month = (mnum + (quarter - 1) * 3) % 12 + 1 if month > mnum: year -= 1 diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index b5285d158b1ed..540a081bdda2e 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -50,6 +50,7 @@ from datetime cimport ( # stdlib datetime imports from datetime import time as datetime_time + from tslibs.np_datetime cimport (check_dts_bounds, reverse_ops, cmp_scalar, @@ -61,12 +62,6 @@ from tslibs.np_datetime cimport (check_dts_bounds, get_timedelta64_value) from tslibs.np_datetime import OutOfBoundsDatetime -from khash cimport ( - khiter_t, - kh_destroy_int64, kh_put_int64, - kh_init_int64, kh_int64_t, - kh_resize_int64, kh_get_int64) - from .tslibs.parsing import parse_datetime_string cimport cython @@ -877,33 +872,6 @@ Timestamp.min = Timestamp(_NS_LOWER_BOUND) Timestamp.max = Timestamp(_NS_UPPER_BOUND) -# ---------------------------------------------------------------------- -# Frequency inference - -def unique_deltas(ndarray[int64_t] arr): - cdef: - Py_ssize_t i, n = len(arr) - int64_t val - khiter_t k - kh_int64_t *table - int ret = 0 - list uniques = [] - - table = kh_init_int64() - kh_resize_int64(table, 10) - for i in range(n - 1): - val = arr[i + 1] - arr[i] - k = kh_get_int64(table, val) - if k == table.n_buckets: - kh_put_int64(table, val, &ret) - uniques.append(val) - kh_destroy_int64(table) - - result = np.array(uniques, dtype=np.int64) - result.sort() - return result - - cdef str _NDIM_STRING = "ndim" # This is PITA. Because we inherit from datetime, which has very specific @@ -1388,27 +1356,6 @@ _MONTH_NUMBERS = {k: i for i, k in enumerate(_MONTHS)} _MONTH_ALIASES = {(k + 1): v for k, v in enumerate(_MONTHS)} -cpdef object _get_rule_month(object source, object default='DEC'): - """ - Return starting month of given freq, default is December. - - Example - ------- - >>> _get_rule_month('D') - 'DEC' - - >>> _get_rule_month('A-JAN') - 'JAN' - """ - if hasattr(source, 'freqstr'): - source = source.freqstr - source = source.upper() - if '-' not in source: - return default - else: - return source.split('-')[1] - - cpdef array_with_unit_to_datetime(ndarray values, unit, errors='coerce'): """ convert the ndarray according to the unit diff --git a/pandas/_libs/tslibs/resolution.pyx b/pandas/_libs/tslibs/resolution.pyx new file mode 100644 index 0000000000000..b590121b9021a --- /dev/null +++ b/pandas/_libs/tslibs/resolution.pyx @@ -0,0 +1,652 @@ +# -*- coding: utf-8 -*- +# cython: profile=False + +from cython cimport Py_ssize_t + +import numpy as np +cimport numpy as np +from numpy cimport ndarray, int64_t +np.import_array() + +from util cimport is_string_object, get_nat + +from khash cimport ( + khiter_t, + kh_destroy_int64, kh_put_int64, + kh_init_int64, kh_int64_t, + kh_resize_int64, kh_get_int64) + +from cpython.datetime cimport datetime + +from np_datetime cimport (pandas_datetimestruct, + dtstruct_to_dt64, dt64_to_dtstruct) +from frequencies cimport get_freq_code +from timezones cimport ( + is_utc, is_tzlocal, + maybe_get_tz, get_dst_info, get_utcoffset) +from fields import build_field_sarray +from conversion import tz_convert + +from pandas._libs.properties import cache_readonly +from pandas._libs.tslib import Timestamp + +from pandas.core.algorithms import unique # TODO: Avoid this non-cython import + +# ---------------------------------------------------------------------- +# Constants + +cdef int64_t NPY_NAT = get_nat() + +cdef int RESO_NS = 0 +cdef int RESO_US = 1 +cdef int RESO_MS = 2 +cdef int RESO_SEC = 3 +cdef int RESO_MIN = 4 +cdef int RESO_HR = 5 +cdef int RESO_DAY = 6 + +_ONE_MICRO = 1000L +_ONE_MILLI = _ONE_MICRO * 1000 +_ONE_SECOND = _ONE_MILLI * 1000 +_ONE_MINUTE = 60 * _ONE_SECOND +_ONE_HOUR = 60 * _ONE_MINUTE +_ONE_DAY = 24 * _ONE_HOUR + +DAYS = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] +_weekday_rule_aliases = dict((k, v) for k, v in enumerate(DAYS)) + +_MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', + 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] +_MONTH_ALIASES = {(k + 1): v for k, v in enumerate(_MONTHS)} + +# ---------------------------------------------------------------------- + +cpdef resolution(ndarray[int64_t] stamps, tz=None): + cdef: + Py_ssize_t i, n = len(stamps) + pandas_datetimestruct dts + int reso = RESO_DAY, curr_reso + + if tz is not None: + tz = maybe_get_tz(tz) + return _reso_local(stamps, tz) + else: + for i in range(n): + if stamps[i] == NPY_NAT: + continue + dt64_to_dtstruct(stamps[i], &dts) + curr_reso = _reso_stamp(&dts) + if curr_reso < reso: + reso = curr_reso + return reso + + +cdef _reso_local(ndarray[int64_t] stamps, object tz): + cdef: + Py_ssize_t n = len(stamps) + int reso = RESO_DAY, curr_reso + ndarray[int64_t] trans, deltas, pos + pandas_datetimestruct dts + + if is_utc(tz): + for i in range(n): + if stamps[i] == NPY_NAT: + continue + dt64_to_dtstruct(stamps[i], &dts) + curr_reso = _reso_stamp(&dts) + if curr_reso < reso: + reso = curr_reso + elif is_tzlocal(tz): + for i in range(n): + if stamps[i] == NPY_NAT: + continue + dt64_to_dtstruct(stamps[i], &dts) + dt = datetime(dts.year, dts.month, dts.day, dts.hour, + dts.min, dts.sec, dts.us, tz) + delta = int(get_utcoffset(tz, dt).total_seconds()) * 1000000000 + dt64_to_dtstruct(stamps[i] + delta, &dts) + curr_reso = _reso_stamp(&dts) + if curr_reso < reso: + reso = curr_reso + else: + # Adjust datetime64 timestamp, recompute datetimestruct + trans, deltas, typ = get_dst_info(tz) + + _pos = trans.searchsorted(stamps, side='right') - 1 + if _pos.dtype != np.int64: + _pos = _pos.astype(np.int64) + pos = _pos + + # statictzinfo + if typ not in ['pytz', 'dateutil']: + for i in range(n): + if stamps[i] == NPY_NAT: + continue + dt64_to_dtstruct(stamps[i] + deltas[0], &dts) + curr_reso = _reso_stamp(&dts) + if curr_reso < reso: + reso = curr_reso + else: + for i in range(n): + if stamps[i] == NPY_NAT: + continue + dt64_to_dtstruct(stamps[i] + deltas[pos[i]], &dts) + curr_reso = _reso_stamp(&dts) + if curr_reso < reso: + reso = curr_reso + + return reso + + +cdef inline int _reso_stamp(pandas_datetimestruct *dts): + if dts.us != 0: + if dts.us % 1000 == 0: + return RESO_MS + return RESO_US + elif dts.sec != 0: + return RESO_SEC + elif dts.min != 0: + return RESO_MIN + elif dts.hour != 0: + return RESO_HR + return RESO_DAY + + +def get_freq_group(freq): + """ + Return frequency code group of given frequency str or offset. + + Example + ------- + >>> get_freq_group('W-MON') + 4000 + + >>> get_freq_group('W-FRI') + 4000 + """ + if getattr(freq, '_typ', None) == 'dateoffset': + freq = freq.rule_code + + if is_string_object(freq): + base, mult = get_freq_code(freq) + freq = base + elif isinstance(freq, int): + pass + else: + raise ValueError('input must be str, offset or int') + return (freq // 1000) * 1000 + + +class Resolution(object): + + # Note: cython won't allow us to reference the cdef versions at the + # module level + RESO_NS = 0 + RESO_US = 1 + RESO_MS = 2 + RESO_SEC = 3 + RESO_MIN = 4 + RESO_HR = 5 + RESO_DAY = 6 + + _reso_str_map = { + RESO_NS: 'nanosecond', + RESO_US: 'microsecond', + RESO_MS: 'millisecond', + RESO_SEC: 'second', + RESO_MIN: 'minute', + RESO_HR: 'hour', + RESO_DAY: 'day'} + + # factor to multiply a value by to convert it to the next finer grained + # resolution + _reso_mult_map = { + RESO_NS: None, + RESO_US: 1000, + RESO_MS: 1000, + RESO_SEC: 1000, + RESO_MIN: 60, + RESO_HR: 60, + RESO_DAY: 24} + + _reso_str_bump_map = { + 'D': 'H', + 'H': 'T', + 'T': 'S', + 'S': 'L', + 'L': 'U', + 'U': 'N', + 'N': None} + + _str_reso_map = dict([(v, k) for k, v in _reso_str_map.items()]) + + _reso_freq_map = { + 'year': 'A', + 'quarter': 'Q', + 'month': 'M', + 'day': 'D', + 'hour': 'H', + 'minute': 'T', + 'second': 'S', + 'millisecond': 'L', + 'microsecond': 'U', + 'nanosecond': 'N'} + + _freq_reso_map = dict([(v, k) + for k, v in _reso_freq_map.items()]) + + @classmethod + def get_str(cls, reso): + """ + Return resolution str against resolution code. + + Example + ------- + >>> Resolution.get_str(Resolution.RESO_SEC) + 'second' + """ + return cls._reso_str_map.get(reso, 'day') + + @classmethod + def get_reso(cls, resostr): + """ + Return resolution str against resolution code. + + Example + ------- + >>> Resolution.get_reso('second') + 2 + + >>> Resolution.get_reso('second') == Resolution.RESO_SEC + True + """ + return cls._str_reso_map.get(resostr, cls.RESO_DAY) + + @classmethod + def get_freq_group(cls, resostr): + """ + Return frequency str against resolution str. + + Example + ------- + >>> f.Resolution.get_freq_group('day') + 4000 + """ + return get_freq_group(cls.get_freq(resostr)) + + @classmethod + def get_freq(cls, resostr): + """ + Return frequency str against resolution str. + + Example + ------- + >>> f.Resolution.get_freq('day') + 'D' + """ + return cls._reso_freq_map[resostr] + + @classmethod + def get_str_from_freq(cls, freq): + """ + Return resolution str against frequency str. + + Example + ------- + >>> Resolution.get_str_from_freq('H') + 'hour' + """ + return cls._freq_reso_map.get(freq, 'day') + + @classmethod + def get_reso_from_freq(cls, freq): + """ + Return resolution code against frequency str. + + Example + ------- + >>> Resolution.get_reso_from_freq('H') + 4 + + >>> Resolution.get_reso_from_freq('H') == Resolution.RESO_HR + True + """ + return cls.get_reso(cls.get_str_from_freq(freq)) + + @classmethod + def get_stride_from_decimal(cls, value, freq): + """ + Convert freq with decimal stride into a higher freq with integer stride + + Parameters + ---------- + value : integer or float + freq : string + Frequency string + + Raises + ------ + ValueError + If the float cannot be converted to an integer at any resolution. + + Example + ------- + >>> Resolution.get_stride_from_decimal(1.5, 'T') + (90, 'S') + + >>> Resolution.get_stride_from_decimal(1.04, 'H') + (3744, 'S') + + >>> Resolution.get_stride_from_decimal(1, 'D') + (1, 'D') + """ + if np.isclose(value % 1, 0): + return int(value), freq + else: + start_reso = cls.get_reso_from_freq(freq) + if start_reso == 0: + raise ValueError("Could not convert to integer offset " + "at any resolution") + + next_value = cls._reso_mult_map[start_reso] * value + next_name = cls._reso_str_bump_map[freq] + return cls.get_stride_from_decimal(next_value, next_name) + + +# ---------------------------------------------------------------------- +# Frequency Inference + + +# TODO: this is non performiant logic here (and duplicative) and this +# simply should call unique_1d directly +# plus no reason to depend on khash directly +cdef unique_deltas(ndarray[int64_t] arr): + cdef: + Py_ssize_t i, n = len(arr) + int64_t val + khiter_t k + kh_int64_t *table + int ret = 0 + list uniques = [] + + table = kh_init_int64() + kh_resize_int64(table, 10) + for i in range(n - 1): + val = arr[i + 1] - arr[i] + k = kh_get_int64(table, val) + if k == table.n_buckets: + kh_put_int64(table, val, &ret) + uniques.append(val) + kh_destroy_int64(table) + + result = np.array(uniques, dtype=np.int64) + result.sort() + return result + + +def _is_multiple(us, mult): + return us % mult == 0 + + +def _maybe_add_count(base, count): + if count != 1: + return '{count}{base}'.format(count=int(count), base=base) + else: + return base + + +class _FrequencyInferer(object): + """ + Not sure if I can avoid the state machine here + """ + + def __init__(self, index, warn=True): + self.index = index + self.values = np.asarray(index).view('i8') + + # This moves the values, which are implicitly in UTC, to the + # the timezone so they are in local time + if hasattr(index, 'tz'): + if index.tz is not None: + self.values = tz_convert(self.values, 'UTC', index.tz) + + self.warn = warn + + if len(index) < 3: + raise ValueError('Need at least 3 dates to infer frequency') + + self.is_monotonic = (self.index.is_monotonic_increasing or + self.index.is_monotonic_decreasing) + + @cache_readonly + def deltas(self): + return unique_deltas(self.values) + + @cache_readonly + def deltas_asi8(self): + return unique_deltas(self.index.asi8) + + @cache_readonly + def is_unique(self): + return len(self.deltas) == 1 + + @cache_readonly + def is_unique_asi8(self): + return len(self.deltas_asi8) == 1 + + def get_freq(self): + if not self.is_monotonic or not self.index.is_unique: + return None + + delta = self.deltas[0] + if _is_multiple(delta, _ONE_DAY): + return self._infer_daily_rule() + else: + # Business hourly, maybe. 17: one day / 65: one weekend + if self.hour_deltas in ([1, 17], [1, 65], [1, 17, 65]): + return 'BH' + # Possibly intraday frequency. Here we use the + # original .asi8 values as the modified values + # will not work around DST transitions. See #8772 + elif not self.is_unique_asi8: + return None + delta = self.deltas_asi8[0] + if _is_multiple(delta, _ONE_HOUR): + # Hours + return _maybe_add_count('H', delta / _ONE_HOUR) + elif _is_multiple(delta, _ONE_MINUTE): + # Minutes + return _maybe_add_count('T', delta / _ONE_MINUTE) + elif _is_multiple(delta, _ONE_SECOND): + # Seconds + return _maybe_add_count('S', delta / _ONE_SECOND) + elif _is_multiple(delta, _ONE_MILLI): + # Milliseconds + return _maybe_add_count('L', delta / _ONE_MILLI) + elif _is_multiple(delta, _ONE_MICRO): + # Microseconds + return _maybe_add_count('U', delta / _ONE_MICRO) + else: + # Nanoseconds + return _maybe_add_count('N', delta) + + @cache_readonly + def day_deltas(self): + return [x / _ONE_DAY for x in self.deltas] + + @cache_readonly + def hour_deltas(self): + return [x / _ONE_HOUR for x in self.deltas] + + @cache_readonly + def fields(self): + return build_field_sarray(self.values) + + @cache_readonly + def rep_stamp(self): + return Timestamp(self.values[0]) + + def month_position_check(self): + # TODO: cythonize this, very slow + calendar_end = True + business_end = True + calendar_start = True + business_start = True + + years = self.fields['Y'] + months = self.fields['M'] + days = self.fields['D'] + weekdays = self.index.dayofweek + + from calendar import monthrange + for y, m, d, wd in zip(years, months, days, weekdays): + + if calendar_start: + calendar_start &= d == 1 + if business_start: + business_start &= d == 1 or (d <= 3 and wd == 0) + + if calendar_end or business_end: + _, daysinmonth = monthrange(y, m) + cal = d == daysinmonth + if calendar_end: + calendar_end &= cal + if business_end: + business_end &= cal or (daysinmonth - d < 3 and wd == 4) + elif not calendar_start and not business_start: + break + + if calendar_end: + return 'ce' + elif business_end: + return 'be' + elif calendar_start: + return 'cs' + elif business_start: + return 'bs' + else: + return None + + @cache_readonly + def mdiffs(self): + nmonths = self.fields['Y'] * 12 + self.fields['M'] + return unique_deltas(nmonths.astype('i8')) + + @cache_readonly + def ydiffs(self): + return unique_deltas(self.fields['Y'].astype('i8')) + + def _infer_daily_rule(self): + annual_rule = self._get_annual_rule() + if annual_rule: + nyears = self.ydiffs[0] + month = _MONTH_ALIASES[self.rep_stamp.month] + alias = '{prefix}-{month}'.format(prefix=annual_rule, month=month) + return _maybe_add_count(alias, nyears) + + quarterly_rule = self._get_quarterly_rule() + if quarterly_rule: + nquarters = self.mdiffs[0] / 3 + mod_dict = {0: 12, 2: 11, 1: 10} + month = _MONTH_ALIASES[mod_dict[self.rep_stamp.month % 3]] + alias = '{prefix}-{month}'.format(prefix=quarterly_rule, + month=month) + return _maybe_add_count(alias, nquarters) + + monthly_rule = self._get_monthly_rule() + if monthly_rule: + return _maybe_add_count(monthly_rule, self.mdiffs[0]) + + if self.is_unique: + days = self.deltas[0] / _ONE_DAY + if days % 7 == 0: + # Weekly + day = _weekday_rule_aliases[self.rep_stamp.weekday()] + return _maybe_add_count('W-{day}'.format(day=day), days / 7) + else: + return _maybe_add_count('D', days) + + if self._is_business_daily(): + return 'B' + + wom_rule = self._get_wom_rule() + if wom_rule: + return wom_rule + + def _get_annual_rule(self): + if len(self.ydiffs) > 1: + return None + + if len(unique(self.fields['M'])) > 1: + return None + + pos_check = self.month_position_check() + return {'cs': 'AS', 'bs': 'BAS', + 'ce': 'A', 'be': 'BA'}.get(pos_check) + + def _get_quarterly_rule(self): + if len(self.mdiffs) > 1: + return None + + if not self.mdiffs[0] % 3 == 0: + return None + + pos_check = self.month_position_check() + return {'cs': 'QS', 'bs': 'BQS', + 'ce': 'Q', 'be': 'BQ'}.get(pos_check) + + def _get_monthly_rule(self): + if len(self.mdiffs) > 1: + return None + pos_check = self.month_position_check() + return {'cs': 'MS', 'bs': 'BMS', + 'ce': 'M', 'be': 'BM'}.get(pos_check) + + def _is_business_daily(self): + # quick check: cannot be business daily + if self.day_deltas != [1, 3]: + return False + + # probably business daily, but need to confirm + first_weekday = self.index[0].weekday() + shifts = np.diff(self.index.asi8) + shifts = np.floor_divide(shifts, _ONE_DAY) + weekdays = np.mod(first_weekday + np.cumsum(shifts), 7) + return np.all(((weekdays == 0) & (shifts == 3)) | + ((weekdays > 0) & (weekdays <= 4) & (shifts == 1))) + + def _get_wom_rule(self): + # wdiffs = unique(np.diff(self.index.week)) + # We also need -47, -49, -48 to catch index spanning year boundary + # if not lib.ismember(wdiffs, set([4, 5, -47, -49, -48])).all(): + # return None + + weekdays = unique(self.index.weekday) + if len(weekdays) > 1: + return None + + week_of_months = unique((self.index.day - 1) // 7) + # Only attempt to infer up to WOM-4. See #9425 + week_of_months = week_of_months[week_of_months < 4] + if len(week_of_months) == 0 or len(week_of_months) > 1: + return None + + # get which week + week = week_of_months[0] + 1 + wd = _weekday_rule_aliases[weekdays[0]] + + return 'WOM-{week}{weekday}'.format(week=week, weekday=wd) + + +class _TimedeltaFrequencyInferer(_FrequencyInferer): + + def _infer_daily_rule(self): + if self.is_unique: + days = self.deltas[0] / _ONE_DAY + if days % 7 == 0: + # Weekly + wd = _weekday_rule_aliases[self.rep_stamp.weekday()] + alias = 'W-{weekday}'.format(weekday=wd) + return _maybe_add_count(alias, days / 7) + else: + return _maybe_add_count('D', days) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index a6d5690767c10..df242e657c9d7 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -36,6 +36,7 @@ get_period_field_arr, _validate_end_alias, _quarter_to_myear) from pandas._libs.tslibs.fields import isleapyear_arr +from pandas._libs.tslibs import resolution from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds from pandas.core.base import _shared_docs @@ -752,8 +753,8 @@ def get_value(self, series, key): except (KeyError, IndexError): try: asdt, parsed, reso = parse_time_string(key, self.freq) - grp = frequencies.Resolution.get_freq_group(reso) - freqn = frequencies.get_freq_group(self.freq) + grp = resolution.Resolution.get_freq_group(reso) + freqn = resolution.get_freq_group(self.freq) vals = self._values @@ -912,8 +913,8 @@ def _get_string_slice(self, key): 'ordered time series') key, parsed, reso = parse_time_string(key, self.freq) - grp = frequencies.Resolution.get_freq_group(reso) - freqn = frequencies.get_freq_group(self.freq) + grp = resolution.Resolution.get_freq_group(reso) + freqn = resolution.get_freq_group(self.freq) if reso in ['day', 'hour', 'minute', 'second'] and not grp < freqn: raise KeyError(key) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 75c9d0c48c82e..aadd5a1beb28b 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -28,6 +28,7 @@ from pandas.core.indexes.datetimes import date_range import pandas.core.tools.datetimes as tools +from pandas._libs.tslibs import resolution import pandas.tseries.frequencies as frequencies from pandas.tseries.frequencies import FreqGroup from pandas.core.indexes.period import Period, PeriodIndex @@ -517,7 +518,7 @@ def _daily_finder(vmin, vmax, freq): elif freq == FreqGroup.FR_DAY: periodsperyear = 365 periodspermonth = 28 - elif frequencies.get_freq_group(freq) == FreqGroup.FR_WK: + elif resolution.get_freq_group(freq) == FreqGroup.FR_WK: periodsperyear = 52 periodspermonth = 3 else: # pragma: no cover @@ -855,7 +856,7 @@ def _annual_finder(vmin, vmax, freq): def get_finder(freq): if isinstance(freq, compat.string_types): freq = frequencies.get_freq(freq) - fgroup = frequencies.get_freq_group(freq) + fgroup = resolution.get_freq_group(freq) if fgroup == FreqGroup.FR_ANN: return _annual_finder diff --git a/pandas/tests/scalar/test_period.py b/pandas/tests/scalar/test_period.py index 28d85c52604d9..8cfdf7a461879 100644 --- a/pandas/tests/scalar/test_period.py +++ b/pandas/tests/scalar/test_period.py @@ -13,7 +13,7 @@ from pandas._libs import tslib, period as libperiod from pandas._libs.tslibs.parsing import DateParseError from pandas import Period, Timestamp, offsets -from pandas.tseries.frequencies import DAYS, MONTHS +from pandas._libs.tslibs.resolution import DAYS, _MONTHS as MONTHS class TestPeriodProperties(object): diff --git a/pandas/tests/test_resample.py b/pandas/tests/test_resample.py index ba1a2ad1f42e2..61b2b689bffd6 100644 --- a/pandas/tests/test_resample.py +++ b/pandas/tests/test_resample.py @@ -21,7 +21,8 @@ from pandas.core.base import SpecificationError, AbstractMethodError from pandas.errors import UnsupportedFunctionCall from pandas.core.groupby import DataError -from pandas.tseries.frequencies import MONTHS, DAYS +from pandas._libs.tslibs.resolution import DAYS +from pandas.tseries.frequencies import MONTHS from pandas.tseries.frequencies import to_offset from pandas.core.indexes.datetimes import date_range from pandas.tseries.offsets import Minute, BDay diff --git a/pandas/tests/tseries/test_frequencies.py b/pandas/tests/tseries/test_frequencies.py index 745f3ef2ec5cb..9666a4c154c63 100644 --- a/pandas/tests/tseries/test_frequencies.py +++ b/pandas/tests/tseries/test_frequencies.py @@ -7,6 +7,7 @@ from pandas import (Index, DatetimeIndex, Timestamp, Series, date_range, period_range) +from pandas._libs.tslibs import resolution import pandas.tseries.frequencies as frequencies from pandas.core.tools.datetimes import to_datetime @@ -383,35 +384,35 @@ def test_freq_code(self): result = frequencies.get_freq(freqstr) assert result == code - result = frequencies.get_freq_group(freqstr) + result = resolution.get_freq_group(freqstr) assert result == code // 1000 * 1000 - result = frequencies.get_freq_group(code) + result = resolution.get_freq_group(code) assert result == code // 1000 * 1000 def test_freq_group(self): - assert frequencies.get_freq_group('A') == 1000 - assert frequencies.get_freq_group('3A') == 1000 - assert frequencies.get_freq_group('-1A') == 1000 - assert frequencies.get_freq_group('A-JAN') == 1000 - assert frequencies.get_freq_group('A-MAY') == 1000 - - assert frequencies.get_freq_group('Y') == 1000 - assert frequencies.get_freq_group('3Y') == 1000 - assert frequencies.get_freq_group('-1Y') == 1000 - assert frequencies.get_freq_group('Y-JAN') == 1000 - assert frequencies.get_freq_group('Y-MAY') == 1000 - - assert frequencies.get_freq_group(offsets.YearEnd()) == 1000 - assert frequencies.get_freq_group(offsets.YearEnd(month=1)) == 1000 - assert frequencies.get_freq_group(offsets.YearEnd(month=5)) == 1000 - - assert frequencies.get_freq_group('W') == 4000 - assert frequencies.get_freq_group('W-MON') == 4000 - assert frequencies.get_freq_group('W-FRI') == 4000 - assert frequencies.get_freq_group(offsets.Week()) == 4000 - assert frequencies.get_freq_group(offsets.Week(weekday=1)) == 4000 - assert frequencies.get_freq_group(offsets.Week(weekday=5)) == 4000 + assert resolution.get_freq_group('A') == 1000 + assert resolution.get_freq_group('3A') == 1000 + assert resolution.get_freq_group('-1A') == 1000 + assert resolution.get_freq_group('A-JAN') == 1000 + assert resolution.get_freq_group('A-MAY') == 1000 + + assert resolution.get_freq_group('Y') == 1000 + assert resolution.get_freq_group('3Y') == 1000 + assert resolution.get_freq_group('-1Y') == 1000 + assert resolution.get_freq_group('Y-JAN') == 1000 + assert resolution.get_freq_group('Y-MAY') == 1000 + + assert resolution.get_freq_group(offsets.YearEnd()) == 1000 + assert resolution.get_freq_group(offsets.YearEnd(month=1)) == 1000 + assert resolution.get_freq_group(offsets.YearEnd(month=5)) == 1000 + + assert resolution.get_freq_group('W') == 4000 + assert resolution.get_freq_group('W-MON') == 4000 + assert resolution.get_freq_group('W-FRI') == 4000 + assert resolution.get_freq_group(offsets.Week()) == 4000 + assert resolution.get_freq_group(offsets.Week(weekday=1)) == 4000 + assert resolution.get_freq_group(offsets.Week(weekday=5)) == 4000 def test_get_to_timestamp_base(self): tsb = frequencies.get_to_timestamp_base @@ -523,7 +524,7 @@ def test_get_freq_code(self): (frequencies.get_freq('W-FRI'), -2)) def test_frequency_misc(self): - assert (frequencies.get_freq_group('T') == + assert (resolution.get_freq_group('T') == frequencies.FreqGroup.FR_MIN) code, stride = frequencies.get_freq_code(offsets.Hour()) diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index 128dd51a2abea..fef88587a7282 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from datetime import timedelta -from pandas.compat import long, zip +from pandas.compat import zip from pandas import compat import re import warnings @@ -13,19 +13,21 @@ is_timedelta64_dtype, is_datetime64_dtype) -import pandas.core.algorithms as algos -from pandas.core.algorithms import unique from pandas.tseries.offsets import DateOffset -from pandas.util._decorators import cache_readonly, deprecate_kwarg +from pandas.util._decorators import deprecate_kwarg import pandas.tseries.offsets as offsets -from pandas._libs import lib, tslib +from pandas._libs import tslib from pandas._libs.tslib import Timedelta -from pandas._libs.tslibs import conversion from pandas._libs.tslibs.frequencies import ( # noqa get_freq_code, _base_and_stride, _period_str_to_code, _INVALID_FREQ_ERROR, opattern, _lite_rule_alias, _dont_uppercase, _period_code_map, _reverse_period_code_map) +from pandas._libs.tslibs.resolution import (Resolution, + _FrequencyInferer, + _TimedeltaFrequencyInferer) +from pandas._libs.tslibs.parsing import _get_rule_month + from pytz import AmbiguousTimeError @@ -53,184 +55,6 @@ class FreqGroup(object): RESO_DAY = 6 -class Resolution(object): - - RESO_US = RESO_US - RESO_MS = RESO_MS - RESO_SEC = RESO_SEC - RESO_MIN = RESO_MIN - RESO_HR = RESO_HR - RESO_DAY = RESO_DAY - - _reso_str_map = { - RESO_NS: 'nanosecond', - RESO_US: 'microsecond', - RESO_MS: 'millisecond', - RESO_SEC: 'second', - RESO_MIN: 'minute', - RESO_HR: 'hour', - RESO_DAY: 'day' - } - - # factor to multiply a value by to convert it to the next finer grained - # resolution - _reso_mult_map = { - RESO_NS: None, - RESO_US: 1000, - RESO_MS: 1000, - RESO_SEC: 1000, - RESO_MIN: 60, - RESO_HR: 60, - RESO_DAY: 24 - } - - _reso_str_bump_map = { - 'D': 'H', - 'H': 'T', - 'T': 'S', - 'S': 'L', - 'L': 'U', - 'U': 'N', - 'N': None - } - - _str_reso_map = dict([(v, k) for k, v in compat.iteritems(_reso_str_map)]) - - _reso_freq_map = { - 'year': 'A', - 'quarter': 'Q', - 'month': 'M', - 'day': 'D', - 'hour': 'H', - 'minute': 'T', - 'second': 'S', - 'millisecond': 'L', - 'microsecond': 'U', - 'nanosecond': 'N'} - - _freq_reso_map = dict([(v, k) - for k, v in compat.iteritems(_reso_freq_map)]) - - @classmethod - def get_str(cls, reso): - """ - Return resolution str against resolution code. - - Example - ------- - >>> Resolution.get_str(Resolution.RESO_SEC) - 'second' - """ - return cls._reso_str_map.get(reso, 'day') - - @classmethod - def get_reso(cls, resostr): - """ - Return resolution str against resolution code. - - Example - ------- - >>> Resolution.get_reso('second') - 2 - - >>> Resolution.get_reso('second') == Resolution.RESO_SEC - True - """ - return cls._str_reso_map.get(resostr, cls.RESO_DAY) - - @classmethod - def get_freq_group(cls, resostr): - """ - Return frequency str against resolution str. - - Example - ------- - >>> f.Resolution.get_freq_group('day') - 4000 - """ - return get_freq_group(cls.get_freq(resostr)) - - @classmethod - def get_freq(cls, resostr): - """ - Return frequency str against resolution str. - - Example - ------- - >>> f.Resolution.get_freq('day') - 'D' - """ - return cls._reso_freq_map[resostr] - - @classmethod - def get_str_from_freq(cls, freq): - """ - Return resolution str against frequency str. - - Example - ------- - >>> Resolution.get_str_from_freq('H') - 'hour' - """ - return cls._freq_reso_map.get(freq, 'day') - - @classmethod - def get_reso_from_freq(cls, freq): - """ - Return resolution code against frequency str. - - Example - ------- - >>> Resolution.get_reso_from_freq('H') - 4 - - >>> Resolution.get_reso_from_freq('H') == Resolution.RESO_HR - True - """ - return cls.get_reso(cls.get_str_from_freq(freq)) - - @classmethod - def get_stride_from_decimal(cls, value, freq): - """ - Convert freq with decimal stride into a higher freq with integer stride - - Parameters - ---------- - value : integer or float - freq : string - Frequency string - - Raises - ------ - ValueError - If the float cannot be converted to an integer at any resolution. - - Example - ------- - >>> Resolution.get_stride_from_decimal(1.5, 'T') - (90, 'S') - - >>> Resolution.get_stride_from_decimal(1.04, 'H') - (3744, 'S') - - >>> Resolution.get_stride_from_decimal(1, 'D') - (1, 'D') - """ - - if np.isclose(value % 1, 0): - return int(value), freq - else: - start_reso = cls.get_reso_from_freq(freq) - if start_reso == 0: - raise ValueError( - "Could not convert to integer offset at any resolution" - ) - - next_value = cls._reso_mult_map[start_reso] * value - next_name = cls._reso_str_bump_map[freq] - return cls.get_stride_from_decimal(next_value, next_name) - - def get_to_timestamp_base(base): """ Return frequency code group used for base of to_timestamp against @@ -259,31 +83,6 @@ def get_to_timestamp_base(base): return base -def get_freq_group(freq): - """ - Return frequency code group of given frequency str or offset. - - Example - ------- - >>> get_freq_group('W-MON') - 4000 - - >>> get_freq_group('W-FRI') - 4000 - """ - if isinstance(freq, offsets.DateOffset): - freq = freq.rule_code - - if isinstance(freq, compat.string_types): - base, mult = get_freq_code(freq) - freq = base - elif isinstance(freq, int): - pass - else: - raise ValueError('input must be str, offset or int') - return (freq // 1000) * 1000 - - def get_freq(freq): """ Return frequency code of given frequency str. @@ -563,279 +362,6 @@ def infer_freq(index, warn=True): return inferer.get_freq() -_ONE_MICRO = long(1000) -_ONE_MILLI = _ONE_MICRO * 1000 -_ONE_SECOND = _ONE_MILLI * 1000 -_ONE_MINUTE = 60 * _ONE_SECOND -_ONE_HOUR = 60 * _ONE_MINUTE -_ONE_DAY = 24 * _ONE_HOUR - - -class _FrequencyInferer(object): - """ - Not sure if I can avoid the state machine here - """ - - def __init__(self, index, warn=True): - self.index = index - self.values = np.asarray(index).view('i8') - - # This moves the values, which are implicitly in UTC, to the - # the timezone so they are in local time - if hasattr(index, 'tz'): - if index.tz is not None: - self.values = conversion.tz_convert(self.values, - 'UTC', index.tz) - - self.warn = warn - - if len(index) < 3: - raise ValueError('Need at least 3 dates to infer frequency') - - self.is_monotonic = (self.index.is_monotonic_increasing or - self.index.is_monotonic_decreasing) - - @cache_readonly - def deltas(self): - return tslib.unique_deltas(self.values) - - @cache_readonly - def deltas_asi8(self): - return tslib.unique_deltas(self.index.asi8) - - @cache_readonly - def is_unique(self): - return len(self.deltas) == 1 - - @cache_readonly - def is_unique_asi8(self): - return len(self.deltas_asi8) == 1 - - def get_freq(self): - if not self.is_monotonic or not self.index.is_unique: - return None - - delta = self.deltas[0] - if _is_multiple(delta, _ONE_DAY): - return self._infer_daily_rule() - else: - # Business hourly, maybe. 17: one day / 65: one weekend - if self.hour_deltas in ([1, 17], [1, 65], [1, 17, 65]): - return 'BH' - # Possibly intraday frequency. Here we use the - # original .asi8 values as the modified values - # will not work around DST transitions. See #8772 - elif not self.is_unique_asi8: - return None - delta = self.deltas_asi8[0] - if _is_multiple(delta, _ONE_HOUR): - # Hours - return _maybe_add_count('H', delta / _ONE_HOUR) - elif _is_multiple(delta, _ONE_MINUTE): - # Minutes - return _maybe_add_count('T', delta / _ONE_MINUTE) - elif _is_multiple(delta, _ONE_SECOND): - # Seconds - return _maybe_add_count('S', delta / _ONE_SECOND) - elif _is_multiple(delta, _ONE_MILLI): - # Milliseconds - return _maybe_add_count('L', delta / _ONE_MILLI) - elif _is_multiple(delta, _ONE_MICRO): - # Microseconds - return _maybe_add_count('U', delta / _ONE_MICRO) - else: - # Nanoseconds - return _maybe_add_count('N', delta) - - @cache_readonly - def day_deltas(self): - return [x / _ONE_DAY for x in self.deltas] - - @cache_readonly - def hour_deltas(self): - return [x / _ONE_HOUR for x in self.deltas] - - @cache_readonly - def fields(self): - return tslib.build_field_sarray(self.values) - - @cache_readonly - def rep_stamp(self): - return lib.Timestamp(self.values[0]) - - def month_position_check(self): - # TODO: cythonize this, very slow - calendar_end = True - business_end = True - calendar_start = True - business_start = True - - years = self.fields['Y'] - months = self.fields['M'] - days = self.fields['D'] - weekdays = self.index.dayofweek - - from calendar import monthrange - for y, m, d, wd in zip(years, months, days, weekdays): - - if calendar_start: - calendar_start &= d == 1 - if business_start: - business_start &= d == 1 or (d <= 3 and wd == 0) - - if calendar_end or business_end: - _, daysinmonth = monthrange(y, m) - cal = d == daysinmonth - if calendar_end: - calendar_end &= cal - if business_end: - business_end &= cal or (daysinmonth - d < 3 and wd == 4) - elif not calendar_start and not business_start: - break - - if calendar_end: - return 'ce' - elif business_end: - return 'be' - elif calendar_start: - return 'cs' - elif business_start: - return 'bs' - else: - return None - - @cache_readonly - def mdiffs(self): - nmonths = self.fields['Y'] * 12 + self.fields['M'] - return tslib.unique_deltas(nmonths.astype('i8')) - - @cache_readonly - def ydiffs(self): - return tslib.unique_deltas(self.fields['Y'].astype('i8')) - - def _infer_daily_rule(self): - annual_rule = self._get_annual_rule() - if annual_rule: - nyears = self.ydiffs[0] - month = _month_aliases[self.rep_stamp.month] - alias = '{prefix}-{month}'.format(prefix=annual_rule, month=month) - return _maybe_add_count(alias, nyears) - - quarterly_rule = self._get_quarterly_rule() - if quarterly_rule: - nquarters = self.mdiffs[0] / 3 - mod_dict = {0: 12, 2: 11, 1: 10} - month = _month_aliases[mod_dict[self.rep_stamp.month % 3]] - alias = '{prefix}-{month}'.format(prefix=quarterly_rule, - month=month) - return _maybe_add_count(alias, nquarters) - - monthly_rule = self._get_monthly_rule() - if monthly_rule: - return _maybe_add_count(monthly_rule, self.mdiffs[0]) - - if self.is_unique: - days = self.deltas[0] / _ONE_DAY - if days % 7 == 0: - # Weekly - day = _weekday_rule_aliases[self.rep_stamp.weekday()] - return _maybe_add_count('W-{day}'.format(day=day), days / 7) - else: - return _maybe_add_count('D', days) - - if self._is_business_daily(): - return 'B' - - wom_rule = self._get_wom_rule() - if wom_rule: - return wom_rule - - def _get_annual_rule(self): - if len(self.ydiffs) > 1: - return None - - if len(algos.unique(self.fields['M'])) > 1: - return None - - pos_check = self.month_position_check() - return {'cs': 'AS', 'bs': 'BAS', - 'ce': 'A', 'be': 'BA'}.get(pos_check) - - def _get_quarterly_rule(self): - if len(self.mdiffs) > 1: - return None - - if not self.mdiffs[0] % 3 == 0: - return None - - pos_check = self.month_position_check() - return {'cs': 'QS', 'bs': 'BQS', - 'ce': 'Q', 'be': 'BQ'}.get(pos_check) - - def _get_monthly_rule(self): - if len(self.mdiffs) > 1: - return None - pos_check = self.month_position_check() - return {'cs': 'MS', 'bs': 'BMS', - 'ce': 'M', 'be': 'BM'}.get(pos_check) - - def _is_business_daily(self): - # quick check: cannot be business daily - if self.day_deltas != [1, 3]: - return False - - # probably business daily, but need to confirm - first_weekday = self.index[0].weekday() - shifts = np.diff(self.index.asi8) - shifts = np.floor_divide(shifts, _ONE_DAY) - weekdays = np.mod(first_weekday + np.cumsum(shifts), 7) - return np.all(((weekdays == 0) & (shifts == 3)) | - ((weekdays > 0) & (weekdays <= 4) & (shifts == 1))) - - def _get_wom_rule(self): - # wdiffs = unique(np.diff(self.index.week)) - # We also need -47, -49, -48 to catch index spanning year boundary - # if not lib.ismember(wdiffs, set([4, 5, -47, -49, -48])).all(): - # return None - - weekdays = unique(self.index.weekday) - if len(weekdays) > 1: - return None - - week_of_months = unique((self.index.day - 1) // 7) - # Only attempt to infer up to WOM-4. See #9425 - week_of_months = week_of_months[week_of_months < 4] - if len(week_of_months) == 0 or len(week_of_months) > 1: - return None - - # get which week - week = week_of_months[0] + 1 - wd = _weekday_rule_aliases[weekdays[0]] - - return 'WOM-{week}{weekday}'.format(week=week, weekday=wd) - - -class _TimedeltaFrequencyInferer(_FrequencyInferer): - - def _infer_daily_rule(self): - if self.is_unique: - days = self.deltas[0] / _ONE_DAY - if days % 7 == 0: - # Weekly - wd = _weekday_rule_aliases[self.rep_stamp.weekday()] - alias = 'W-{weekday}'.format(weekday=wd) - return _maybe_add_count(alias, days / 7) - else: - return _maybe_add_count('D', days) - - -def _maybe_add_count(base, count): - if count != 1: - return '{count}{base}'.format(count=int(count), base=base) - else: - return base - - def _maybe_coerce_freq(code): """ we might need to coerce a code to a rule_code and uppercase it @@ -965,9 +491,6 @@ def is_superperiod(source, target): return target in ['N'] -_get_rule_month = tslib._get_rule_month - - def _is_annual(rule): rule = rule.upper() return rule == 'A' or rule.startswith('A-') @@ -994,13 +517,5 @@ def _is_weekly(rule): return rule == 'W' or rule.startswith('W-') -DAYS = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] - MONTHS = tslib._MONTHS _month_numbers = tslib._MONTH_NUMBERS -_month_aliases = tslib._MONTH_ALIASES -_weekday_rule_aliases = dict((k, v) for k, v in enumerate(DAYS)) - - -def _is_multiple(us, mult): - return us % mult == 0 diff --git a/setup.py b/setup.py index dd24c5c14ee69..56f3881cc4760 100755 --- a/setup.py +++ b/setup.py @@ -350,6 +350,7 @@ class CheckSDist(sdist_class): 'pandas/_libs/tslibs/fields.pyx', 'pandas/_libs/tslibs/offsets.pyx', 'pandas/_libs/tslibs/frequencies.pyx', + 'pandas/_libs/tslibs/resolution.pyx', 'pandas/_libs/tslibs/parsing.pyx', 'pandas/io/sas/sas.pyx'] @@ -580,6 +581,13 @@ def pxd(name): 'pyxfile': '_libs/tslibs/parsing', 'pxdfiles': ['_libs/src/util', '_libs/src/khash']}, + '_libs.tslibs.resolution': { + 'pyxfile': '_libs/tslibs/resolution', + 'pxdfiles': ['_libs/src/util', + '_libs/src/khash', + '_libs/tslibs/frequencies', + '_libs/tslibs/timezones'], + 'depends': tseries_depends}, '_libs.tslibs.strptime': { 'pyxfile': '_libs/tslibs/strptime', 'pxdfiles': ['_libs/src/util',