11from __future__ import annotations
22
33import re
4+ import warnings
5+ from datetime import datetime , timezone
6+ from typing import Literal
47
5- import pendulum
6- from pendulum .datetime import DateTime
8+ from whenever import Date , Instant , LocalDateTime , Time , ZonedDateTime
9+
10+ UTC = timezone .utc # Required for older versions of Python
711
812REGEX_MAPPING = {
913 "seconds" : r"(\d+)(s|sec|second|seconds)" ,
@@ -16,76 +20,171 @@ class TimestampFormatError(ValueError): ...
1620
1721
1822class Timestamp :
19- def __init__ (self , value : str | DateTime | Timestamp | None = None ):
20- if value and isinstance (value , DateTime ):
21- self .obj = value
23+ _obj : ZonedDateTime
24+
25+ def __init__ (self , value : str | ZonedDateTime | Timestamp | None = None ):
26+ if value and isinstance (value , ZonedDateTime ):
27+ self ._obj = value
2228 elif value and isinstance (value , self .__class__ ):
23- self .obj = value .obj
29+ self ._obj = value ._obj
2430 elif isinstance (value , str ):
25- self .obj = self ._parse_string (value )
31+ self ._obj = self ._parse_string (value )
2632 else :
27- self .obj = DateTime .now (tz = "UTC" )
33+ self ._obj = ZonedDateTime .now ("UTC" ).round (unit = "microsecond" )
34+
35+ @property
36+ def obj (self ) -> ZonedDateTime :
37+ warnings .warn (
38+ "Direct access to obj property is deprecated. Use to_string(), to_timestamp(), or to_datetime() instead." ,
39+ UserWarning ,
40+ stacklevel = 2 ,
41+ )
42+ return self ._obj
2843
2944 @classmethod
30- def _parse_string (cls , value : str ) -> DateTime :
45+ def _parse_string (cls , value : str ) -> ZonedDateTime :
3146 try :
32- parsed_date = pendulum .parse (value )
33- if isinstance (parsed_date , DateTime ):
34- return parsed_date
35- except (pendulum .parsing .exceptions .ParserError , ValueError ):
47+ zoned_date = ZonedDateTime .parse_common_iso (value )
48+ return zoned_date
49+ except ValueError :
3650 pass
3751
38- params = {}
52+ try :
53+ instant_date = Instant .parse_common_iso (value )
54+ return instant_date .to_tz ("UTC" )
55+ except ValueError :
56+ pass
57+
58+ try :
59+ local_date_time = LocalDateTime .parse_common_iso (value )
60+ return local_date_time .assume_utc ().to_tz ("UTC" )
61+ except ValueError :
62+ pass
63+
64+ try :
65+ date = Date .parse_common_iso (value )
66+ local_date = date .at (Time (12 , 00 ))
67+ return local_date .assume_tz ("UTC" , disambiguate = "compatible" )
68+ except ValueError :
69+ pass
70+
71+ params : dict [str , float ] = {}
3972 for key , regex in REGEX_MAPPING .items ():
4073 match = re .search (regex , value )
4174 if match :
42- params [key ] = int (match .group (1 ))
75+ params [key ] = float (match .group (1 ))
4376
44- if not params :
45- raise TimestampFormatError ( f"Invalid time format for { value } " )
77+ if params :
78+ return ZonedDateTime . now ( "UTC" ). subtract ( ** params ) # type: ignore[call-overload]
4679
47- return DateTime . now ( tz = "UTC" ). subtract ( ** params )
80+ raise TimestampFormatError ( f"Invalid time format for { value } " )
4881
4982 def __repr__ (self ) -> str :
5083 return f"Timestamp: { self .to_string ()} "
5184
5285 def to_string (self , with_z : bool = True ) -> str :
53- iso8601_string = self .obj .to_iso8601_string ()
54- if not with_z and iso8601_string [- 1 ] == "Z" :
55- iso8601_string = iso8601_string [:- 1 ] + "+00:00"
56- return iso8601_string
86+ if with_z :
87+ return self ._obj .instant ().format_common_iso ()
88+ return self .to_datetime ().isoformat ()
5789
5890 def to_timestamp (self ) -> int :
59- return self .obj .int_timestamp
91+ return self ._obj .timestamp ()
92+
93+ def to_datetime (self ) -> datetime :
94+ return self ._obj .py_datetime ()
6095
6196 def __eq__ (self , other : object ) -> bool :
6297 if not isinstance (other , Timestamp ):
6398 return NotImplemented
64- return self .obj == other .obj
99+ return self ._obj == other ._obj
65100
66101 def __lt__ (self , other : object ) -> bool :
67102 if not isinstance (other , Timestamp ):
68103 return NotImplemented
69- return self .obj < other .obj
104+ return self ._obj < other ._obj
70105
71106 def __gt__ (self , other : object ) -> bool :
72107 if not isinstance (other , Timestamp ):
73108 return NotImplemented
74- return self .obj > other .obj
109+ return self ._obj > other ._obj
75110
76111 def __le__ (self , other : object ) -> bool :
77112 if not isinstance (other , Timestamp ):
78113 return NotImplemented
79- return self .obj <= other .obj
114+ return self ._obj <= other ._obj
80115
81116 def __ge__ (self , other : object ) -> bool :
82117 if not isinstance (other , Timestamp ):
83118 return NotImplemented
84- return self .obj >= other .obj
119+ return self ._obj >= other ._obj
85120
86121 def __hash__ (self ) -> int :
87122 return hash (self .to_string ())
88123
89124 def add_delta (self , hours : int = 0 , minutes : int = 0 , seconds : int = 0 , microseconds : int = 0 ) -> Timestamp :
90- time = self .obj .add (hours = hours , minutes = minutes , seconds = seconds , microseconds = microseconds )
91- return Timestamp (time )
125+ warnings .warn (
126+ "add_delta() is deprecated. Use add() instead." ,
127+ UserWarning ,
128+ stacklevel = 2 ,
129+ )
130+ return self .add (hours = hours , minutes = minutes , seconds = seconds , microseconds = microseconds )
131+
132+ def add (
133+ self ,
134+ years : int = 0 ,
135+ months : int = 0 ,
136+ weeks : int = 0 ,
137+ days : int = 0 ,
138+ hours : float = 0 ,
139+ minutes : float = 0 ,
140+ seconds : float = 0 ,
141+ milliseconds : float = 0 ,
142+ microseconds : float = 0 ,
143+ nanoseconds : int = 0 ,
144+ disambiguate : Literal ["compatible" ] = "compatible" ,
145+ ) -> Timestamp :
146+ return Timestamp (
147+ self ._obj .add (
148+ years = years ,
149+ months = months ,
150+ weeks = weeks ,
151+ days = days ,
152+ hours = hours ,
153+ minutes = minutes ,
154+ seconds = seconds ,
155+ milliseconds = milliseconds ,
156+ microseconds = microseconds ,
157+ nanoseconds = nanoseconds ,
158+ disambiguate = disambiguate ,
159+ )
160+ )
161+
162+ def subtract (
163+ self ,
164+ years : int = 0 ,
165+ months : int = 0 ,
166+ weeks : int = 0 ,
167+ days : int = 0 ,
168+ hours : float = 0 ,
169+ minutes : float = 0 ,
170+ seconds : float = 0 ,
171+ milliseconds : float = 0 ,
172+ microseconds : float = 0 ,
173+ nanoseconds : int = 0 ,
174+ disambiguate : Literal ["compatible" ] = "compatible" ,
175+ ) -> Timestamp :
176+ return Timestamp (
177+ self ._obj .subtract (
178+ years = years ,
179+ months = months ,
180+ weeks = weeks ,
181+ days = days ,
182+ hours = hours ,
183+ minutes = minutes ,
184+ seconds = seconds ,
185+ milliseconds = milliseconds ,
186+ microseconds = microseconds ,
187+ nanoseconds = nanoseconds ,
188+ disambiguate = disambiguate ,
189+ )
190+ )
0 commit comments