7
7
from pandas ._libs .algos import unique_deltas
8
8
from pandas ._libs .tslibs import (
9
9
Timestamp ,
10
+ get_unit_from_dtype ,
11
+ periods_per_day ,
10
12
tz_convert_from_utc ,
11
13
)
12
14
from pandas ._libs .tslibs .ccalendar import (
37
39
is_period_dtype ,
38
40
is_timedelta64_dtype ,
39
41
)
40
- from pandas .core .dtypes .generic import ABCSeries
42
+ from pandas .core .dtypes .generic import (
43
+ ABCIndex ,
44
+ ABCSeries ,
45
+ )
41
46
42
47
from pandas .core .algorithms import unique
43
48
44
- _ONE_MICRO = 1000
45
- _ONE_MILLI = _ONE_MICRO * 1000
46
- _ONE_SECOND = _ONE_MILLI * 1000
47
- _ONE_MINUTE = 60 * _ONE_SECOND
48
- _ONE_HOUR = 60 * _ONE_MINUTE
49
- _ONE_DAY = 24 * _ONE_HOUR
50
-
51
49
# ---------------------------------------------------------------------
52
50
# Offset names ("time rules") and related functions
53
51
@@ -213,6 +211,18 @@ def __init__(self, index, warn: bool = True) -> None:
213
211
self .index = index
214
212
self .i8values = index .asi8
215
213
214
+ # For get_unit_from_dtype we need the dtype to the underlying ndarray,
215
+ # which for tz-aware is not the same as index.dtype
216
+ if isinstance (index , ABCIndex ):
217
+ # error: Item "ndarray[Any, Any]" of "Union[ExtensionArray,
218
+ # ndarray[Any, Any]]" has no attribute "_ndarray"
219
+ self ._reso = get_unit_from_dtype (
220
+ index ._data ._ndarray .dtype # type: ignore[union-attr]
221
+ )
222
+ else :
223
+ # otherwise we have DTA/TDA
224
+ self ._reso = get_unit_from_dtype (index ._ndarray .dtype )
225
+
216
226
# This moves the values, which are implicitly in UTC, to the
217
227
# the timezone so they are in local time
218
228
if hasattr (index , "tz" ):
@@ -266,7 +276,8 @@ def get_freq(self) -> str | None:
266
276
return None
267
277
268
278
delta = self .deltas [0 ]
269
- if delta and _is_multiple (delta , _ONE_DAY ):
279
+ ppd = periods_per_day (self ._reso )
280
+ if delta and _is_multiple (delta , ppd ):
270
281
return self ._infer_daily_rule ()
271
282
272
283
# Business hourly, maybe. 17: one day / 65: one weekend
@@ -280,36 +291,41 @@ def get_freq(self) -> str | None:
280
291
return None
281
292
282
293
delta = self .deltas_asi8 [0 ]
283
- if _is_multiple (delta , _ONE_HOUR ):
294
+ pph = ppd // 24
295
+ ppm = pph // 60
296
+ pps = ppm // 60
297
+ if _is_multiple (delta , pph ):
284
298
# Hours
285
- return _maybe_add_count ("H" , delta / _ONE_HOUR )
286
- elif _is_multiple (delta , _ONE_MINUTE ):
299
+ return _maybe_add_count ("H" , delta / pph )
300
+ elif _is_multiple (delta , ppm ):
287
301
# Minutes
288
- return _maybe_add_count ("T" , delta / _ONE_MINUTE )
289
- elif _is_multiple (delta , _ONE_SECOND ):
302
+ return _maybe_add_count ("T" , delta / ppm )
303
+ elif _is_multiple (delta , pps ):
290
304
# Seconds
291
- return _maybe_add_count ("S" , delta / _ONE_SECOND )
292
- elif _is_multiple (delta , _ONE_MILLI ):
305
+ return _maybe_add_count ("S" , delta / pps )
306
+ elif _is_multiple (delta , ( pps // 1000 ) ):
293
307
# Milliseconds
294
- return _maybe_add_count ("L" , delta / _ONE_MILLI )
295
- elif _is_multiple (delta , _ONE_MICRO ):
308
+ return _maybe_add_count ("L" , delta / ( pps // 1000 ) )
309
+ elif _is_multiple (delta , ( pps // 1_000_000 ) ):
296
310
# Microseconds
297
- return _maybe_add_count ("U" , delta / _ONE_MICRO )
311
+ return _maybe_add_count ("U" , delta / ( pps // 1_000_000 ) )
298
312
else :
299
313
# Nanoseconds
300
314
return _maybe_add_count ("N" , delta )
301
315
302
316
@cache_readonly
303
317
def day_deltas (self ):
304
- return [x / _ONE_DAY for x in self .deltas ]
318
+ ppd = periods_per_day (self ._reso )
319
+ return [x / ppd for x in self .deltas ]
305
320
306
321
@cache_readonly
307
322
def hour_deltas (self ):
308
- return [x / _ONE_HOUR for x in self .deltas ]
323
+ pph = periods_per_day (self ._reso ) // 24
324
+ return [x / pph for x in self .deltas ]
309
325
310
326
@cache_readonly
311
327
def fields (self ) -> np .ndarray : # structured array of fields
312
- return build_field_sarray (self .i8values )
328
+ return build_field_sarray (self .i8values , reso = self . _reso )
313
329
314
330
@cache_readonly
315
331
def rep_stamp (self ):
@@ -360,7 +376,8 @@ def _infer_daily_rule(self) -> str | None:
360
376
return None
361
377
362
378
def _get_daily_rule (self ) -> str | None :
363
- days = self .deltas [0 ] / _ONE_DAY
379
+ ppd = periods_per_day (self ._reso )
380
+ days = self .deltas [0 ] / ppd
364
381
if days % 7 == 0 :
365
382
# Weekly
366
383
wd = int_to_weekday [self .rep_stamp .weekday ()]
@@ -403,7 +420,8 @@ def _is_business_daily(self) -> bool:
403
420
# probably business daily, but need to confirm
404
421
first_weekday = self .index [0 ].weekday ()
405
422
shifts = np .diff (self .index .asi8 )
406
- shifts = np .floor_divide (shifts , _ONE_DAY )
423
+ ppd = periods_per_day (self ._reso )
424
+ shifts = np .floor_divide (shifts , ppd )
407
425
weekdays = np .mod (first_weekday + np .cumsum (shifts ), 7 )
408
426
409
427
return bool (
0 commit comments