@@ -52,6 +52,7 @@ from pandas._libs.tslibs.conversion cimport (
52
52
convert_datetime_to_tsobject,
53
53
convert_to_tsobject,
54
54
)
55
+ from pandas._libs.tslibs.dtypes cimport npy_unit_to_abbrev
55
56
from pandas._libs.tslibs.util cimport (
56
57
is_array,
57
58
is_datetime64_object,
@@ -72,10 +73,16 @@ from pandas._libs.tslibs.nattype cimport (
72
73
c_NaT as NaT,
73
74
)
74
75
from pandas._libs.tslibs.np_datetime cimport (
76
+ NPY_DATETIMEUNIT,
77
+ NPY_FR_ns,
75
78
check_dts_bounds,
79
+ cmp_dtstructs,
76
80
cmp_scalar,
77
81
dt64_to_dtstruct,
82
+ get_datetime64_unit,
83
+ get_datetime64_value,
78
84
npy_datetimestruct,
85
+ pandas_datetime_to_datetimestruct,
79
86
pydatetime_to_dt64,
80
87
)
81
88
@@ -114,24 +121,39 @@ _no_input = object()
114
121
# ----------------------------------------------------------------------
115
122
116
123
117
- cdef inline _Timestamp create_timestamp_from_ts(int64_t value,
118
- npy_datetimestruct dts,
119
- tzinfo tz, BaseOffset freq, bint fold):
124
+ cdef inline _Timestamp create_timestamp_from_ts(
125
+ int64_t value,
126
+ npy_datetimestruct dts,
127
+ tzinfo tz,
128
+ BaseOffset freq,
129
+ bint fold,
130
+ NPY_DATETIMEUNIT reso = NPY_FR_ns,
131
+ ):
120
132
""" convenience routine to construct a Timestamp from its parts """
121
- cdef _Timestamp ts_base
133
+ cdef:
134
+ _Timestamp ts_base
135
+
122
136
ts_base = _Timestamp.__new__ (Timestamp, dts.year, dts.month,
123
137
dts.day, dts.hour, dts.min,
124
138
dts.sec, dts.us, tz, fold = fold)
125
139
ts_base.value = value
126
140
ts_base._freq = freq
127
141
ts_base.nanosecond = dts.ps // 1000
142
+ ts_base._reso = reso
128
143
129
144
return ts_base
130
145
131
146
132
- def _unpickle_timestamp (value , freq , tz ):
147
+ def _unpickle_timestamp (value , freq , tz , reso ):
133
148
# GH#41949 dont warn on unpickle if we have a freq
134
- ts = Timestamp(value, tz = tz)
149
+ if reso == NPY_FR_ns:
150
+ ts = Timestamp(value, tz = tz)
151
+ else :
152
+ if tz is not None :
153
+ raise NotImplementedError
154
+ abbrev = npy_unit_to_abbrev(reso)
155
+ dt64 = np.datetime64(value, abbrev)
156
+ ts = Timestamp._from_dt64(dt64)
135
157
ts._set_freq(freq)
136
158
return ts
137
159
@@ -177,12 +199,36 @@ cdef class _Timestamp(ABCTimestamp):
177
199
)
178
200
return self ._freq
179
201
202
+ # -----------------------------------------------------------------
203
+ # Constructors
204
+
205
+ @classmethod
206
+ def _from_dt64 (cls , dt64: np.datetime64 ):
207
+ # construct a Timestamp from a np.datetime64 object, keeping the
208
+ # resolution of the input.
209
+ # This is herely mainly so we can incrementally implement non-nano
210
+ # (e.g. only tznaive at first)
211
+ cdef:
212
+ npy_datetimestruct dts
213
+ int64_t value
214
+ NPY_DATETIMEUNIT reso
215
+
216
+ reso = get_datetime64_unit(dt64)
217
+ value = get_datetime64_value(dt64)
218
+ pandas_datetime_to_datetimestruct(value, reso, & dts)
219
+ return create_timestamp_from_ts(
220
+ value, dts, tz = None , freq = None , fold = 0 , reso = reso
221
+ )
222
+
223
+ # -----------------------------------------------------------------
224
+
180
225
def __hash__ (_Timestamp self ):
181
226
if self .nanosecond:
182
227
return hash (self .value)
183
228
if self .fold:
184
229
return datetime.__hash__ (self .replace(fold = 0 ))
185
230
return datetime.__hash__ (self )
231
+ # TODO(non-nano): what if we are out of bounds for pydatetime?
186
232
187
233
def __richcmp__ (_Timestamp self , object other , int op ):
188
234
cdef:
@@ -193,17 +239,16 @@ cdef class _Timestamp(ABCTimestamp):
193
239
ots = other
194
240
elif other is NaT:
195
241
return op == Py_NE
196
- elif PyDateTime_Check(other) or is_datetime64_object(other):
197
- if self .nanosecond == 0 and PyDateTime_Check(other):
242
+ elif is_datetime64_object(other):
243
+ ots = _Timestamp._from_dt64(other)
244
+ elif PyDateTime_Check(other):
245
+ if self .nanosecond == 0 :
198
246
val = self .to_pydatetime()
199
247
return PyObject_RichCompareBool(val, other, op)
200
248
201
249
try :
202
250
ots = type (self )(other)
203
251
except ValueError :
204
- if is_datetime64_object(other):
205
- # cast non-nano dt64 to pydatetime
206
- other = other.astype(object )
207
252
return self ._compare_outside_nanorange(other, op)
208
253
209
254
elif is_array(other):
@@ -253,7 +298,21 @@ cdef class _Timestamp(ABCTimestamp):
253
298
raise TypeError (
254
299
" Cannot compare tz-naive and tz-aware timestamps"
255
300
)
256
- return cmp_scalar(self .value, ots.value, op)
301
+ if self ._reso == ots._reso:
302
+ return cmp_scalar(self .value, ots.value, op)
303
+ return self ._compare_mismatched_resos(ots, op)
304
+
305
+ # TODO: copied from Timedelta; try to de-duplicate
306
+ cdef inline bint _compare_mismatched_resos(self , _Timestamp other, int op):
307
+ # Can't just dispatch to numpy as they silently overflow and get it wrong
308
+ cdef:
309
+ npy_datetimestruct dts_self
310
+ npy_datetimestruct dts_other
311
+
312
+ # dispatch to the datetimestruct utils instead of writing new ones!
313
+ pandas_datetime_to_datetimestruct(self .value, self ._reso, & dts_self)
314
+ pandas_datetime_to_datetimestruct(other.value, other._reso, & dts_other)
315
+ return cmp_dtstructs(& dts_self, & dts_other, op)
257
316
258
317
cdef bint _compare_outside_nanorange(_Timestamp self , datetime other,
259
318
int op) except - 1 :
@@ -286,6 +345,9 @@ cdef class _Timestamp(ABCTimestamp):
286
345
cdef:
287
346
int64_t nanos = 0
288
347
348
+ if isinstance (self , _Timestamp) and self ._reso != NPY_FR_ns:
349
+ raise NotImplementedError (self ._reso)
350
+
289
351
if is_any_td_scalar(other):
290
352
nanos = delta_to_nanoseconds(other)
291
353
try :
@@ -325,6 +387,8 @@ cdef class _Timestamp(ABCTimestamp):
325
387
return NotImplemented
326
388
327
389
def __sub__ (self , other ):
390
+ if isinstance (self , _Timestamp) and self ._reso != NPY_FR_ns:
391
+ raise NotImplementedError (self ._reso)
328
392
329
393
if is_any_td_scalar(other) or is_integer_object(other):
330
394
neg_other = - other
@@ -387,6 +451,9 @@ cdef class _Timestamp(ABCTimestamp):
387
451
return NotImplemented
388
452
389
453
def __rsub__ (self , other ):
454
+ if self ._reso != NPY_FR_ns:
455
+ raise NotImplementedError (self ._reso)
456
+
390
457
if PyDateTime_Check(other):
391
458
try :
392
459
return type (self )(other) - self
@@ -421,6 +488,9 @@ cdef class _Timestamp(ABCTimestamp):
421
488
ndarray[uint8_t, cast= True ] out
422
489
int month_kw
423
490
491
+ if self ._reso != NPY_FR_ns:
492
+ raise NotImplementedError (self ._reso)
493
+
424
494
if freq:
425
495
kwds = freq.kwds
426
496
month_kw = kwds.get(' startingMonth' , kwds.get(' month' , 12 ))
@@ -591,6 +661,9 @@ cdef class _Timestamp(ABCTimestamp):
591
661
int64_t val
592
662
object [::1 ] out
593
663
664
+ if self ._reso != NPY_FR_ns:
665
+ raise NotImplementedError (self ._reso)
666
+
594
667
val = self ._maybe_convert_value_to_local()
595
668
out = get_date_name_field(np.array([val], dtype = np.int64),
596
669
field, locale = locale)
@@ -743,6 +816,9 @@ cdef class _Timestamp(ABCTimestamp):
743
816
local_val = self ._maybe_convert_value_to_local()
744
817
int64_t normalized
745
818
819
+ if self._reso != NPY_FR_ns:
820
+ raise NotImplementedError(self._reso )
821
+
746
822
normalized = normalize_i8_stamp(local_val)
747
823
return Timestamp(normalized ).tz_localize(self.tzinfo )
748
824
@@ -760,8 +836,16 @@ cdef class _Timestamp(ABCTimestamp):
760
836
self ._freq = state[1 ]
761
837
self .tzinfo = state[2 ]
762
838
839
+ if len (state) == 3 :
840
+ # pre-non-nano pickle
841
+ reso = NPY_FR_ns
842
+ assert False # checking for coverage
843
+ else :
844
+ reso = state[4 ]
845
+ self ._reso = reso
846
+
763
847
def __reduce__ (self ):
764
- object_state = self .value, self ._freq, self .tzinfo
848
+ object_state = self .value, self ._freq, self .tzinfo, self ._reso
765
849
return (_unpickle_timestamp, object_state)
766
850
767
851
# -----------------------------------------------------------------
@@ -888,7 +972,7 @@ cdef class _Timestamp(ABCTimestamp):
888
972
>>> ts.asm8
889
973
numpy.datetime64('2020-03-14T15:00:00.000000000')
890
974
"""
891
- return np.datetime64( self.value , 'ns' )
975
+ return self.to_datetime64( )
892
976
893
977
def timestamp(self ):
894
978
"""
@@ -902,6 +986,9 @@ cdef class _Timestamp(ABCTimestamp):
902
986
"""
903
987
# GH 17329
904
988
# Note: Naive timestamps will not match datetime.stdlib
989
+ if self ._reso != NPY_FR_ns:
990
+ raise NotImplementedError (self ._reso)
991
+
905
992
return round (self .value / 1e9 , 6 )
906
993
907
994
cpdef datetime to_pydatetime(_Timestamp self , bint warn = True ):
@@ -933,7 +1020,9 @@ cdef class _Timestamp(ABCTimestamp):
933
1020
"""
934
1021
Return a numpy.datetime64 object with 'ns' precision.
935
1022
"""
936
- return np.datetime64(self .value, " ns" )
1023
+ # TODO: find a way to construct dt64 directly from _reso
1024
+ abbrev = npy_unit_to_abbrev(self ._reso)
1025
+ return np.datetime64(self .value, abbrev)
937
1026
938
1027
def to_numpy (self , dtype = None , copy = False ) -> np.datetime64:
939
1028
"""
@@ -995,6 +1084,9 @@ cdef class _Timestamp(ABCTimestamp):
995
1084
"""
996
1085
from pandas import Period
997
1086
1087
+ if self ._reso != NPY_FR_ns:
1088
+ raise NotImplementedError (self ._reso)
1089
+
998
1090
if self .tz is not None :
999
1091
# GH#21333
1000
1092
warnings.warn(
@@ -1470,6 +1562,9 @@ class Timestamp(_Timestamp):
1470
1562
cdef:
1471
1563
int64_t nanos = to_offset(freq).nanos
1472
1564
1565
+ if self ._reso != NPY_FR_ns:
1566
+ raise NotImplementedError (self ._reso)
1567
+
1473
1568
if self .tz is not None :
1474
1569
value = self .tz_localize(None ).value
1475
1570
else :
@@ -1865,6 +1960,9 @@ default 'raise'
1865
1960
>>> pd.NaT.tz_localize()
1866
1961
NaT
1867
1962
"""
1963
+ if self ._reso != NPY_FR_ns:
1964
+ raise NotImplementedError (self ._reso)
1965
+
1868
1966
if ambiguous == ' infer' :
1869
1967
raise ValueError (' Cannot infer offset with only one time.' )
1870
1968
@@ -1942,6 +2040,9 @@ default 'raise'
1942
2040
>>> pd.NaT.tz_convert(tz='Asia/Tokyo')
1943
2041
NaT
1944
2042
"""
2043
+ if self ._reso != NPY_FR_ns:
2044
+ raise NotImplementedError (self ._reso)
2045
+
1945
2046
if self .tzinfo is None :
1946
2047
# tz naive, use tz_localize
1947
2048
raise TypeError (
@@ -2021,6 +2122,9 @@ default 'raise'
2021
2122
datetime ts_input
2022
2123
tzinfo_type tzobj
2023
2124
2125
+ if self ._reso != NPY_FR_ns:
2126
+ raise NotImplementedError (self ._reso)
2127
+
2024
2128
# set to naive if needed
2025
2129
tzobj = self .tzinfo
2026
2130
value = self .value
0 commit comments