|
10 | 10 | from pandas._libs.tslibs.period import (
|
11 | 11 | DIFFERENT_FREQ_INDEX, IncompatibleFrequency, Period)
|
12 | 12 | from pandas._libs.tslibs.timedeltas import Timedelta, delta_to_nanoseconds
|
13 |
| -from pandas._libs.tslibs.timestamps import maybe_integer_op_deprecated |
| 13 | +from pandas._libs.tslibs.timestamps import ( |
| 14 | + RoundTo, maybe_integer_op_deprecated, round_nsint64) |
14 | 15 | import pandas.compat as compat
|
15 | 16 | from pandas.errors import (
|
16 | 17 | AbstractMethodError, NullFrequencyError, PerformanceWarning)
|
17 |
| -from pandas.util._decorators import deprecate_kwarg |
| 18 | +from pandas.util._decorators import Appender, deprecate_kwarg |
18 | 19 |
|
19 | 20 | from pandas.core.dtypes.common import (
|
20 | 21 | is_bool_dtype, is_datetime64_any_dtype, is_datetime64_dtype,
|
@@ -80,6 +81,189 @@ def _get_attributes_dict(self):
|
80 | 81 | return {k: getattr(self, k, None) for k in self._attributes}
|
81 | 82 |
|
82 | 83 |
|
| 84 | +class DatelikeOps(object): |
| 85 | + """ |
| 86 | + Common ops for DatetimeIndex/PeriodIndex, but not TimedeltaIndex. |
| 87 | + """ |
| 88 | + |
| 89 | + def strftime(self, date_format): |
| 90 | + from pandas import Index |
| 91 | + return Index(self.format(date_format=date_format), |
| 92 | + dtype=compat.text_type) |
| 93 | + strftime.__doc__ = """ |
| 94 | + Convert to Index using specified date_format. |
| 95 | +
|
| 96 | + Return an Index of formatted strings specified by date_format, which |
| 97 | + supports the same string format as the python standard library. Details |
| 98 | + of the string format can be found in `python string format doc <{0}>`__ |
| 99 | +
|
| 100 | + Parameters |
| 101 | + ---------- |
| 102 | + date_format : str |
| 103 | + Date format string (e.g. "%Y-%m-%d"). |
| 104 | +
|
| 105 | + Returns |
| 106 | + ------- |
| 107 | + Index |
| 108 | + Index of formatted strings |
| 109 | +
|
| 110 | + See Also |
| 111 | + -------- |
| 112 | + to_datetime : Convert the given argument to datetime. |
| 113 | + DatetimeIndex.normalize : Return DatetimeIndex with times to midnight. |
| 114 | + DatetimeIndex.round : Round the DatetimeIndex to the specified freq. |
| 115 | + DatetimeIndex.floor : Floor the DatetimeIndex to the specified freq. |
| 116 | +
|
| 117 | + Examples |
| 118 | + -------- |
| 119 | + >>> rng = pd.date_range(pd.Timestamp("2018-03-10 09:00"), |
| 120 | + ... periods=3, freq='s') |
| 121 | + >>> rng.strftime('%B %d, %Y, %r') |
| 122 | + Index(['March 10, 2018, 09:00:00 AM', 'March 10, 2018, 09:00:01 AM', |
| 123 | + 'March 10, 2018, 09:00:02 AM'], |
| 124 | + dtype='object') |
| 125 | + """.format("https://docs.python.org/3/library/datetime.html" |
| 126 | + "#strftime-and-strptime-behavior") |
| 127 | + |
| 128 | + |
| 129 | +class TimelikeOps(object): |
| 130 | + """ |
| 131 | + Common ops for TimedeltaIndex/DatetimeIndex, but not PeriodIndex. |
| 132 | + """ |
| 133 | + |
| 134 | + _round_doc = ( |
| 135 | + """ |
| 136 | + Perform {op} operation on the data to the specified `freq`. |
| 137 | +
|
| 138 | + Parameters |
| 139 | + ---------- |
| 140 | + freq : str or Offset |
| 141 | + The frequency level to {op} the index to. Must be a fixed |
| 142 | + frequency like 'S' (second) not 'ME' (month end). See |
| 143 | + :ref:`frequency aliases <timeseries.offset_aliases>` for |
| 144 | + a list of possible `freq` values. |
| 145 | + ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise' |
| 146 | + Only relevant for DatetimeIndex: |
| 147 | +
|
| 148 | + - 'infer' will attempt to infer fall dst-transition hours based on |
| 149 | + order |
| 150 | + - bool-ndarray where True signifies a DST time, False designates |
| 151 | + a non-DST time (note that this flag is only applicable for |
| 152 | + ambiguous times) |
| 153 | + - 'NaT' will return NaT where there are ambiguous times |
| 154 | + - 'raise' will raise an AmbiguousTimeError if there are ambiguous |
| 155 | + times |
| 156 | +
|
| 157 | + .. versionadded:: 0.24.0 |
| 158 | + nonexistent : 'shift', 'NaT', default 'raise' |
| 159 | + A nonexistent time does not exist in a particular timezone |
| 160 | + where clocks moved forward due to DST. |
| 161 | +
|
| 162 | + - 'shift' will shift the nonexistent time forward to the closest |
| 163 | + existing time |
| 164 | + - 'NaT' will return NaT where there are nonexistent times |
| 165 | + - 'raise' will raise an NonExistentTimeError if there are |
| 166 | + nonexistent times |
| 167 | +
|
| 168 | + .. versionadded:: 0.24.0 |
| 169 | +
|
| 170 | + Returns |
| 171 | + ------- |
| 172 | + DatetimeIndex, TimedeltaIndex, or Series |
| 173 | + Index of the same type for a DatetimeIndex or TimedeltaIndex, |
| 174 | + or a Series with the same index for a Series. |
| 175 | +
|
| 176 | + Raises |
| 177 | + ------ |
| 178 | + ValueError if the `freq` cannot be converted. |
| 179 | +
|
| 180 | + Examples |
| 181 | + -------- |
| 182 | + **DatetimeIndex** |
| 183 | +
|
| 184 | + >>> rng = pd.date_range('1/1/2018 11:59:00', periods=3, freq='min') |
| 185 | + >>> rng |
| 186 | + DatetimeIndex(['2018-01-01 11:59:00', '2018-01-01 12:00:00', |
| 187 | + '2018-01-01 12:01:00'], |
| 188 | + dtype='datetime64[ns]', freq='T') |
| 189 | + """) |
| 190 | + |
| 191 | + _round_example = ( |
| 192 | + """>>> rng.round('H') |
| 193 | + DatetimeIndex(['2018-01-01 12:00:00', '2018-01-01 12:00:00', |
| 194 | + '2018-01-01 12:00:00'], |
| 195 | + dtype='datetime64[ns]', freq=None) |
| 196 | +
|
| 197 | + **Series** |
| 198 | +
|
| 199 | + >>> pd.Series(rng).dt.round("H") |
| 200 | + 0 2018-01-01 12:00:00 |
| 201 | + 1 2018-01-01 12:00:00 |
| 202 | + 2 2018-01-01 12:00:00 |
| 203 | + dtype: datetime64[ns] |
| 204 | + """) |
| 205 | + |
| 206 | + _floor_example = ( |
| 207 | + """>>> rng.floor('H') |
| 208 | + DatetimeIndex(['2018-01-01 11:00:00', '2018-01-01 12:00:00', |
| 209 | + '2018-01-01 12:00:00'], |
| 210 | + dtype='datetime64[ns]', freq=None) |
| 211 | +
|
| 212 | + **Series** |
| 213 | +
|
| 214 | + >>> pd.Series(rng).dt.floor("H") |
| 215 | + 0 2018-01-01 11:00:00 |
| 216 | + 1 2018-01-01 12:00:00 |
| 217 | + 2 2018-01-01 12:00:00 |
| 218 | + dtype: datetime64[ns] |
| 219 | + """ |
| 220 | + ) |
| 221 | + |
| 222 | + _ceil_example = ( |
| 223 | + """>>> rng.ceil('H') |
| 224 | + DatetimeIndex(['2018-01-01 12:00:00', '2018-01-01 12:00:00', |
| 225 | + '2018-01-01 13:00:00'], |
| 226 | + dtype='datetime64[ns]', freq=None) |
| 227 | +
|
| 228 | + **Series** |
| 229 | +
|
| 230 | + >>> pd.Series(rng).dt.ceil("H") |
| 231 | + 0 2018-01-01 12:00:00 |
| 232 | + 1 2018-01-01 12:00:00 |
| 233 | + 2 2018-01-01 13:00:00 |
| 234 | + dtype: datetime64[ns] |
| 235 | + """ |
| 236 | + ) |
| 237 | + |
| 238 | + def _round(self, freq, mode, ambiguous, nonexistent): |
| 239 | + # round the local times |
| 240 | + values = _ensure_datetimelike_to_i8(self) |
| 241 | + result = round_nsint64(values, mode, freq) |
| 242 | + result = self._maybe_mask_results(result, fill_value=NaT) |
| 243 | + |
| 244 | + attribs = self._get_attributes_dict() |
| 245 | + attribs['freq'] = None |
| 246 | + if 'tz' in attribs: |
| 247 | + attribs['tz'] = None |
| 248 | + return self._ensure_localized( |
| 249 | + self._shallow_copy(result, **attribs), ambiguous, nonexistent |
| 250 | + ) |
| 251 | + |
| 252 | + @Appender((_round_doc + _round_example).format(op="round")) |
| 253 | + def round(self, freq, ambiguous='raise', nonexistent='raise'): |
| 254 | + return self._round( |
| 255 | + freq, RoundTo.NEAREST_HALF_EVEN, ambiguous, nonexistent |
| 256 | + ) |
| 257 | + |
| 258 | + @Appender((_round_doc + _floor_example).format(op="floor")) |
| 259 | + def floor(self, freq, ambiguous='raise', nonexistent='raise'): |
| 260 | + return self._round(freq, RoundTo.MINUS_INFTY, ambiguous, nonexistent) |
| 261 | + |
| 262 | + @Appender((_round_doc + _ceil_example).format(op="ceil")) |
| 263 | + def ceil(self, freq, ambiguous='raise', nonexistent='raise'): |
| 264 | + return self._round(freq, RoundTo.PLUS_INFTY, ambiguous, nonexistent) |
| 265 | + |
| 266 | + |
83 | 267 | class DatetimeLikeArrayMixin(ExtensionOpsMixin, AttributesMixin):
|
84 | 268 | """
|
85 | 269 | Shared Base/Mixin class for DatetimeArray, TimedeltaArray, PeriodArray
|
@@ -1023,3 +1207,39 @@ def validate_dtype_freq(dtype, freq):
|
1023 | 1207 | raise IncompatibleFrequency('specified freq and dtype '
|
1024 | 1208 | 'are different')
|
1025 | 1209 | return freq
|
| 1210 | + |
| 1211 | + |
| 1212 | +def _ensure_datetimelike_to_i8(other, to_utc=False): |
| 1213 | + """ |
| 1214 | + Helper for coercing an input scalar or array to i8. |
| 1215 | +
|
| 1216 | + Parameters |
| 1217 | + ---------- |
| 1218 | + other : 1d array |
| 1219 | + to_utc : bool, default False |
| 1220 | + If True, convert the values to UTC before extracting the i8 values |
| 1221 | + If False, extract the i8 values directly. |
| 1222 | +
|
| 1223 | + Returns |
| 1224 | + ------- |
| 1225 | + i8 1d array |
| 1226 | + """ |
| 1227 | + from pandas import Index |
| 1228 | + from pandas.core.arrays import PeriodArray |
| 1229 | + |
| 1230 | + if lib.is_scalar(other) and isna(other): |
| 1231 | + return iNaT |
| 1232 | + elif isinstance(other, (PeriodArray, ABCIndexClass)): |
| 1233 | + # convert tz if needed |
| 1234 | + if getattr(other, 'tz', None) is not None: |
| 1235 | + if to_utc: |
| 1236 | + other = other.tz_convert('UTC') |
| 1237 | + else: |
| 1238 | + other = other.tz_localize(None) |
| 1239 | + else: |
| 1240 | + try: |
| 1241 | + return np.array(other, copy=False).view('i8') |
| 1242 | + except TypeError: |
| 1243 | + # period array cannot be coerced to int |
| 1244 | + other = Index(other) |
| 1245 | + return other.asi8 |
0 commit comments