@@ -144,12 +144,27 @@ cdef inline _Timestamp create_timestamp_from_ts(
144
144
""" convenience routine to construct a Timestamp from its parts """
145
145
cdef:
146
146
_Timestamp ts_base
147
-
148
- ts_base = _Timestamp.__new__ (Timestamp, dts.year, dts.month,
147
+ int64_t pass_year = dts.year
148
+
149
+ # We pass year=1970/1972 here and set year below because with non-nanosecond
150
+ # resolution we may have datetimes outside of the stdlib pydatetime
151
+ # implementation bounds, which would raise.
152
+ # NB: this means the C-API macro PyDateTime_GET_YEAR is unreliable.
153
+ if 1 <= pass_year <= 9999 :
154
+ # we are in-bounds for pydatetime
155
+ pass
156
+ elif ccalendar.is_leapyear(dts.year):
157
+ pass_year = 1972
158
+ else :
159
+ pass_year = 1970
160
+
161
+ ts_base = _Timestamp.__new__ (Timestamp, pass_year, dts.month,
149
162
dts.day, dts.hour, dts.min,
150
163
dts.sec, dts.us, tz, fold = fold)
164
+
151
165
ts_base.value = value
152
166
ts_base._freq = freq
167
+ ts_base.year = dts.year
153
168
ts_base.nanosecond = dts.ps // 1000
154
169
ts_base._reso = reso
155
170
@@ -180,6 +195,40 @@ def integer_op_not_supported(obj):
180
195
return TypeError (int_addsub_msg)
181
196
182
197
198
+ class MinMaxReso :
199
+ """
200
+ We need to define min/max/resolution on both the Timestamp _instance_
201
+ and Timestamp class. On an instance, these depend on the object's _reso.
202
+ On the class, we default to the values we would get with nanosecond _reso.
203
+
204
+ See also: timedeltas.MinMaxReso
205
+ """
206
+ def __init__ (self , name ):
207
+ self ._name = name
208
+
209
+ def __get__ (self , obj , type = None ):
210
+ cls = Timestamp
211
+ if self ._name == " min" :
212
+ val = np.iinfo(np.int64).min + 1
213
+ elif self ._name == " max" :
214
+ val = np.iinfo(np.int64).max
215
+ else :
216
+ assert self ._name == " resolution"
217
+ val = 1
218
+ cls = Timedelta
219
+
220
+ if obj is None :
221
+ # i.e. this is on the class, default to nanos
222
+ return cls (val)
223
+ elif self ._name == " resolution" :
224
+ return Timedelta._from_value_and_reso(val, obj._reso)
225
+ else :
226
+ return Timestamp._from_value_and_reso(val, obj._reso, tz = None )
227
+
228
+ def __set__ (self , obj , value ):
229
+ raise AttributeError (f" {self._name} is not settable." )
230
+
231
+
183
232
# ----------------------------------------------------------------------
184
233
185
234
cdef class _Timestamp(ABCTimestamp):
@@ -189,6 +238,10 @@ cdef class _Timestamp(ABCTimestamp):
189
238
dayofweek = _Timestamp.day_of_week
190
239
dayofyear = _Timestamp.day_of_year
191
240
241
+ min = MinMaxReso(" min" )
242
+ max = MinMaxReso(" max" )
243
+ resolution = MinMaxReso(" resolution" ) # GH#21336, GH#21365
244
+
192
245
cpdef void _set_freq(self , freq):
193
246
# set the ._freq attribute without going through the constructor,
194
247
# which would issue a warning
@@ -249,10 +302,12 @@ cdef class _Timestamp(ABCTimestamp):
249
302
def __hash__ (_Timestamp self ):
250
303
if self .nanosecond:
251
304
return hash (self .value)
305
+ if not (1 <= self .year <= 9999 ):
306
+ # out of bounds for pydatetime
307
+ return hash (self .value)
252
308
if self .fold:
253
309
return datetime.__hash__ (self .replace(fold = 0 ))
254
310
return datetime.__hash__ (self )
255
- # TODO(non-nano): what if we are out of bounds for pydatetime?
256
311
257
312
def __richcmp__ (_Timestamp self , object other , int op ):
258
313
cdef:
@@ -969,6 +1024,9 @@ cdef class _Timestamp(ABCTimestamp):
969
1024
"""
970
1025
base_ts = " microseconds" if timespec == " nanoseconds" else timespec
971
1026
base = super (_Timestamp, self ).isoformat(sep = sep, timespec = base_ts)
1027
+ # We need to replace the fake year 1970 with our real year
1028
+ base = f" {self.year}-" + base.split(" -" , 1 )[1 ]
1029
+
972
1030
if self.nanosecond == 0 and timespec != "nanoseconds":
973
1031
return base
974
1032
@@ -2318,29 +2376,24 @@ default 'raise'
2318
2376
Return the day of the week represented by the date.
2319
2377
Monday == 1 ... Sunday == 7.
2320
2378
"""
2321
- return super ().isoweekday()
2379
+ # same as super().isoweekday(), but that breaks because of how
2380
+ # we have overriden year, see note in create_timestamp_from_ts
2381
+ return self .weekday() + 1
2322
2382
2323
2383
def weekday (self ):
2324
2384
"""
2325
2385
Return the day of the week represented by the date.
2326
2386
Monday == 0 ... Sunday == 6.
2327
2387
"""
2328
- return super ().weekday()
2388
+ # same as super().weekday(), but that breaks because of how
2389
+ # we have overriden year, see note in create_timestamp_from_ts
2390
+ return ccalendar.dayofweek(self .year, self .month, self .day)
2329
2391
2330
2392
2331
2393
# Aliases
2332
2394
Timestamp.weekofyear = Timestamp.week
2333
2395
Timestamp.daysinmonth = Timestamp.days_in_month
2334
2396
2335
- # Add the min and max fields at the class level
2336
- cdef int64_t _NS_UPPER_BOUND = np.iinfo(np.int64).max
2337
- cdef int64_t _NS_LOWER_BOUND = NPY_NAT + 1
2338
-
2339
- # Resolution is in nanoseconds
2340
- Timestamp.min = Timestamp(_NS_LOWER_BOUND)
2341
- Timestamp.max = Timestamp(_NS_UPPER_BOUND)
2342
- Timestamp.resolution = Timedelta(nanoseconds = 1 ) # GH#21336, GH#21365
2343
-
2344
2397
2345
2398
# ----------------------------------------------------------------------
2346
2399
# Scalar analogues to functions in vectorized.pyx
0 commit comments