1import datetime
 2import decimal
 3import json
 4import uuid
 5from typing import Any
 6
 7from plain.utils.duration import duration_iso_string
 8from plain.utils.functional import Promise
 9from plain.utils.timezone import is_aware
10
11
12class PlainJSONEncoder(json.JSONEncoder):
13    """
14    JSONEncoder subclass that knows how to encode date/time, decimal types, and
15    UUIDs.
16    """
17
18    def default(self, o: Any) -> Any:
19        # See "Date Time String Format" in the ECMA-262 specification.
20        if isinstance(o, datetime.datetime):
21            r = o.isoformat()
22            if o.microsecond:
23                r = r[:23] + r[26:]
24            if r.endswith("+00:00"):
25                r = r.removesuffix("+00:00") + "Z"
26            return r
27        elif isinstance(o, datetime.date):
28            return o.isoformat()
29        elif isinstance(o, datetime.time):
30            if is_aware(o):
31                raise ValueError("JSON can't represent timezone-aware times.")
32            r = o.isoformat()
33            if o.microsecond:
34                r = r[:12]
35            return r
36        elif isinstance(o, datetime.timedelta):
37            return duration_iso_string(o)
38        elif isinstance(o, decimal.Decimal | uuid.UUID | Promise):
39            return str(o)
40        else:
41            return super().default(o)