Patterned Date and Time¶
Note
Important: The current patterned date and time functionality is being phased out. Please refer to the new docs for V1 Opt-in features, which introduces enhanced support for patterned date-time strings. For more details, see the Field Guide to V1 Opt‐in and the V1 Patterned Date and Time documentation.
This change is part of the ongoing improvements in version v0.35.0+
, and the old functionality will no longer be maintained in future releases.
Loading an ISO 8601 format string into a date
/ time
/
datetime
object is already handled as part of the de-serialization
process by default. For example, a date string in ISO format such as
2022-01-17T21:52:18.000Z
is correctly parsed to datetime
as expected.
However, what happens when you have a date string in another format, such
as November 2, 2021
, and you want to load it to a date
or datetime
object?
As of v0.20.0, the accepted solution is to use the builtin support for
parsing strings with custom date-time patterns; this internally calls
datetime.strptime()
to match input strings against a specified pattern.
There are two approaches (shown below) that can be used to specify custom patterns
for date-time strings. The simplest approach is to annotate fields as either
a DatePattern
, TimePattern
, or a DateTimePattern
.
Note
The input date-time strings are parsed in the following sequence:
In case it’s an ISO 8601 format string, or a numeric timestamp, we attempt to parse with the default load function such as
as_datetime()
. Note that we initially parse strings using the builtinfromisoformat()
method, as this is much faster than usingdatetime.strptime()
. If the date string is matched, we immediately return the new date-time object.Next, we parse with
datetime.strptime()
by passing in the pattern to match against. If the pattern is invalid, aParseError
is raised at this stage.
In any case, the date
, time
, and datetime
objects
are dumped (serialized) as ISO 8601 format strings, which is the default
behavior. As we initially attempt to parse with fromisoformat()
in the
load (de-serialization) process as mentioned, it turns out
much faster to load any data that has been previously serialized in
ISO-8601 format.
The usage is shown below, and is again pretty straightforward.
from dataclasses import dataclass
from datetime import datetime
from typing import Annotated
from dataclass_wizard import JSONWizard, Pattern, DatePattern, TimePattern
@dataclass
class MyClass(JSONWizard):
# 1 -- Annotate with `DatePattern`, `TimePattern`, or `DateTimePattern`.
# Upon de-serialization, the underlying types will be `date`,
# `time`, and `datetime` respectively.
date_field: DatePattern['%b %d, %Y']
time_field: TimePattern['%I:%M %p']
# 2 -- Use `Annotated` to annotate the field as `list[time]` for example,
# and pass in `Pattern` as an extra.
dt_field: Annotated[datetime, Pattern('%m/%d/%y %H:%M:%S')]
data = {'date_field': 'Jan 3, 2022',
'time_field': '3:45 PM',
'dt_field': '01/02/23 02:03:52'}
# Deserialize the data into a `MyClass` object
c1 = MyClass.from_dict(data)
print('Deserialized object:', repr(c1))
# MyClass(date_field=datetime.date(2022, 1, 3),
# time_field=datetime.time(15, 45),
# dt_field=datetime.datetime(2023, 1, 2, 2, 3, 52))
# Print the prettified JSON representation. Note that date/times are
# converted to ISO 8601 format here.
print(c1)
# {
# "dateField": "2022-01-03",
# "timeField": "15:45:00",
# "dtField": "2023-01-02T02:03:52"
# }
# Confirm that we can load the serialized data as expected.
c2 = MyClass.from_json(c1.to_json())
# Assert that the data is the same
assert c1 == c2
Containers of Date and Time¶
Suppose the type annotation for a dataclass field is more complex – for example,
an annotation might be a list[date]
instead, representing an ordered
collection of date
objects.
In such cases, you can use Annotated
along with Pattern()
, as shown
below. Note that this also allows you to more easily annotate using a subtype
of date-time, for example a subclass of date
if so desired.
from dataclasses import dataclass
from datetime import datetime, time
from typing import Annotated
from dataclass_wizard import JSONWizard, Pattern
class MyTime(time):
"""A custom `time` subclass"""
def get_hour(self):
return self.hour
@dataclass
class MyClass(JSONWizard):
time_field: Annotated[list[MyTime], Pattern('%I:%M %p')]
dt_mapping: Annotated[dict[int, datetime], Pattern('%b.%d.%y %H,%M,%S')]
data = {'time_field': ['3:45 PM', '1:20 am', '12:30 pm'],
'dt_mapping': {'1133': 'Jan.2.20 15,20,57',
'5577': 'Nov.27.23 2,52,11'},
}
# Deserialize the data into a `MyClass` object
c1 = MyClass.from_dict(data)
print('Deserialized object:', repr(c1))
# MyClass(time_field=[MyTime(15, 45), MyTime(1, 20), MyTime(12, 30)],
# dt_mapping={1133: datetime.datetime(2020, 1, 2, 15, 20, 57),
# 5577: datetime.datetime(2023, 11, 27, 2, 52, 11)})
# Print the prettified JSON representation. Note that date/times are
# converted to ISO 8601 format here.
print(c1)
# {
# "timeField": [
# "15:45:00",
# "01:20:00",
# "12:30:00"
# ],
# "dtMapping": {
# "1133": "2020-01-02T15:20:57",
# "5577": "2023-11-27T02:52:11"
# }
# }
# Confirm that we can load the serialized data as expected.
c2 = MyClass.from_json(c1.to_json())
# Assert that the data is the same
assert c1 == c2