11import operator
22from datetime import date , datetime , time
3- from typing import Optional
3+ from typing import Any , Optional , Type , Union
44
55import dateutil .parser
66
1111_t_args_getter = operator .attrgetter ('hour' , 'minute' , 'second' , 'microsecond' , 'tzinfo' )
1212
1313
14- def _extend_datetime (d , cls : type ):
14+ def extend_datetime (d : Union [date , time , datetime ], cls : Union [Type [date ], Type [time ], Type [datetime ]]) -> Any :
15+ """
16+ Wrap datetime object into datetime subclass
17+
18+ :param d: date/time/datetime instance
19+ :param cls: datetime subclass
20+ :return:
21+ """
1522 if isinstance (d , datetime ):
1623 args = _dt_args_getter
1724 elif isinstance (d , time ):
@@ -28,6 +35,14 @@ def _extend_datetime(d, cls: type):
2835
2936
3037def is_date (s : str ) -> Optional [date ]:
38+ """
39+ Return date instance if given string is a date and None otherwise
40+
41+ :param s: string
42+ :return: date or None
43+ """
44+ # dateutil.parser.parse replaces missing parts of datetime with values from default value
45+ # so if there is hour part in given string then d1 and d2 would be equal and string is not pure date
3146 d1 = dateutil .parser .parse (s , default = _check_values_date [0 ])
3247 d2 = dateutil .parser .parse (s , default = _check_values_date [1 ])
3348 return None if d1 == d2 else d1 .date ()
@@ -40,6 +55,12 @@ def is_date(s: str) -> Optional[date]:
4055
4156
4257def is_time (s : str ) -> Optional [time ]:
58+ """
59+ Return time instance if given string is a time and None otherwise
60+
61+ :param s: string
62+ :return: time or None
63+ """
4364 d1 = dateutil .parser .parse (s , default = _check_values_time [0 ])
4465 d2 = dateutil .parser .parse (s , default = _check_values_time [1 ])
4566 return None if d1 == d2 else d1 .time ()
@@ -48,15 +69,15 @@ def is_time(s: str) -> Optional[time]:
4869class IsoDateString (StringSerializable , date ):
4970 """
5071 Parse date using dateutil.parser.isoparse. Representation format always is ``YYYY-MM-DD``.
51- You can override to_representation method to customize it. Just don't forget to call registry.remove(YourCls )
72+ You can override to_representation method to customize it. Just don't forget to call registry.remove(IsoDateString )
5273 """
5374
5475 @classmethod
5576 def to_internal_value (cls , value : str ) -> 'IsoDateString' :
5677 if not is_date (value ):
5778 raise ValueError (f"'{ value } ' is not valid date" )
5879 dt = dateutil .parser .isoparse (value )
59- return _extend_datetime (dt .date (), cls )
80+ return extend_datetime (dt .date (), cls )
6081
6182 def to_representation (self ):
6283 return self .isoformat ()
@@ -77,7 +98,7 @@ def to_internal_value(cls, value: str) -> 'IsoTimeString':
7798 t = is_time (value )
7899 if not t :
79100 raise ValueError (f"'{ value } ' is not valid time" )
80- return _extend_datetime (t , cls )
101+ return extend_datetime (t , cls )
81102
82103 def to_representation (self ):
83104 return self .isoformat ()
@@ -96,7 +117,7 @@ class IsoDatetimeString(StringSerializable, datetime):
96117 @classmethod
97118 def to_internal_value (cls , value : str ) -> 'IsoDatetimeString' :
98119 dt = dateutil .parser .isoparse (value )
99- return _extend_datetime (dt , cls )
120+ return extend_datetime (dt , cls )
100121
101122 def to_representation (self ):
102123 return self .isoformat ()
0 commit comments