django.utils.dateparse.py

$ find . -name "*dateparse.py" -print
./tests/utils_tests/test_dateparse.py
./django/utils/dateparse.py

django.utils.dateparse.py

  1"""Functions to parse datetime objects."""
  2
  3# We're using regular expressions rather than time.strptime because:
  4# - They provide both validation and parsing.
  5# - They're more flexible for datetimes.
  6# - The date/datetime/time constructors produce friendlier error messages.
  7
  8import datetime
  9
 10from django.utils.regex_helper import _lazy_re_compile
 11from django.utils.timezone import get_fixed_timezone, utc
 12
 13date_re = _lazy_re_compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
 14
 15time_re = _lazy_re_compile(
 16    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
 17    r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?"
 18)
 19
 20datetime_re = _lazy_re_compile(
 21    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
 22    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
 23    r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?"
 24    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
 25)
 26
 27standard_duration_re = _lazy_re_compile(
 28    r"^"
 29    r"(?:(?P<days>-?\d+) (days?, )?)?"
 30    r"(?P<sign>-?)"
 31    r"((?:(?P<hours>\d+):)(?=\d+:\d+))?"
 32    r"(?:(?P<minutes>\d+):)?"
 33    r"(?P<seconds>\d+)"
 34    r"(?:[\.,](?P<microseconds>\d{1,6})\d{0,6})?"
 35    r"$"
 36)
 37
 38# Support the sections of ISO 8601 date representation that are accepted by
 39# timedelta
 40iso8601_duration_re = _lazy_re_compile(
 41    r"^(?P<sign>[-+]?)"
 42    r"P"
 43    r"(?:(?P<days>\d+(.\d+)?)D)?"
 44    r"(?:T"
 45    r"(?:(?P<hours>\d+(.\d+)?)H)?"
 46    r"(?:(?P<minutes>\d+(.\d+)?)M)?"
 47    r"(?:(?P<seconds>\d+(.\d+)?)S)?"
 48    r")?"
 49    r"$"
 50)
 51
 52# Support PostgreSQL's day-time interval format, e.g. "3 days 04:05:06". The
 53# year-month and mixed intervals cannot be converted to a timedelta and thus
 54# aren't accepted.
 55postgres_interval_re = _lazy_re_compile(
 56    r"^"
 57    r"(?:(?P<days>-?\d+) (days? ?))?"
 58    r"(?:(?P<sign>[-+])?"
 59    r"(?P<hours>\d+):"
 60    r"(?P<minutes>\d\d):"
 61    r"(?P<seconds>\d\d)"
 62    r"(?:\.(?P<microseconds>\d{1,6}))?"
 63    r")?$"
 64)
 65
 66
 67def parse_date(value):
 68    """Parse a string and return a datetime.date.
 69
 70    Raise ValueError if the input is well formatted but not a valid date.
 71    Return None if the input isn't well formatted.
 72    """
 73    match = date_re.match(value)
 74    if match:
 75        kw = {k: int(v) for k, v in match.groupdict().items()}
 76        return datetime.date(**kw)
 77
 78
 79def parse_time(value):
 80    """Parse a string and return a datetime.time.
 81
 82    This function doesn't support time zone offsets.
 83
 84    Raise ValueError if the input is well formatted but not a valid time.
 85    Return None if the input isn't well formatted, in particular if it
 86    contains an offset.
 87    """
 88    match = time_re.match(value)
 89    if match:
 90        kw = match.groupdict()
 91        kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
 92        kw = {k: int(v) for k, v in kw.items() if v is not None}
 93        return datetime.time(**kw)
 94
 95
 96def parse_datetime(value):
 97    """Parse a string and return a datetime.datetime.
 98
 99    This function supports time zone offsets. When the input contains one,
100    the output uses a timezone with a fixed offset from UTC.
101
102    Raise ValueError if the input is well formatted but not a valid datetime.
103    Return None if the input isn't well formatted.
104    """
105    match = datetime_re.match(value)
106    if match:
107        kw = match.groupdict()
108        kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
109        tzinfo = kw.pop("tzinfo")
110        if tzinfo == "Z":
111            tzinfo = utc
112        elif tzinfo is not None:
113            offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0
114            offset = 60 * int(tzinfo[1:3]) + offset_mins
115            if tzinfo[0] == "-":
116                offset = -offset
117            tzinfo = get_fixed_timezone(offset)
118        kw = {k: int(v) for k, v in kw.items() if v is not None}
119        kw["tzinfo"] = tzinfo
120        return datetime.datetime(**kw)
121
122
123def parse_duration(value):
124    """Parse a duration string and return a datetime.timedelta.
125
126    The preferred format for durations in Django is '%d %H:%M:%S.%f'.
127
128    Also supports ISO 8601 representation and PostgreSQL's day-time interval
129    format.
130    """
131    match = (
132        standard_duration_re.match(value)
133        or iso8601_duration_re.match(value)
134        or postgres_interval_re.match(value)
135    )
136    if match:
137        kw = match.groupdict()
138        sign = -1 if kw.pop("sign", "+") == "-" else 1
139        if kw.get("microseconds"):
140            kw["microseconds"] = kw["microseconds"].ljust(6, "0")
141        if (
142            kw.get("seconds")
143            and kw.get("microseconds")
144            and kw["seconds"].startswith("-")
145        ):
146            kw["microseconds"] = "-" + kw["microseconds"]
147        kw = {k: float(v.replace(",", ".")) for k, v in kw.items() if v is not None}
148        days = datetime.timedelta(kw.pop("days", 0.0) or 0.0)
149        return days + sign * datetime.timedelta(**kw)

django.utils.dateparse.parse_duration

  1"""Functions to parse datetime objects."""
  2
  3# We're using regular expressions rather than time.strptime because:
  4# - They provide both validation and parsing.
  5# - They're more flexible for datetimes.
  6# - The date/datetime/time constructors produce friendlier error messages.
  7
  8import datetime
  9
 10from django.utils.regex_helper import _lazy_re_compile
 11from django.utils.timezone import get_fixed_timezone, utc
 12
 13date_re = _lazy_re_compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
 14
 15time_re = _lazy_re_compile(
 16    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
 17    r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?"
 18)
 19
 20datetime_re = _lazy_re_compile(
 21    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
 22    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
 23    r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?"
 24    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
 25)
 26
 27standard_duration_re = _lazy_re_compile(
 28    r"^"
 29    r"(?:(?P<days>-?\d+) (days?, )?)?"
 30    r"(?P<sign>-?)"
 31    r"((?:(?P<hours>\d+):)(?=\d+:\d+))?"
 32    r"(?:(?P<minutes>\d+):)?"
 33    r"(?P<seconds>\d+)"
 34    r"(?:[\.,](?P<microseconds>\d{1,6})\d{0,6})?"
 35    r"$"
 36)
 37
 38# Support the sections of ISO 8601 date representation that are accepted by
 39# timedelta
 40iso8601_duration_re = _lazy_re_compile(
 41    r"^(?P<sign>[-+]?)"
 42    r"P"
 43    r"(?:(?P<days>\d+(.\d+)?)D)?"
 44    r"(?:T"
 45    r"(?:(?P<hours>\d+(.\d+)?)H)?"
 46    r"(?:(?P<minutes>\d+(.\d+)?)M)?"
 47    r"(?:(?P<seconds>\d+(.\d+)?)S)?"
 48    r")?"
 49    r"$"
 50)
 51
 52# Support PostgreSQL's day-time interval format, e.g. "3 days 04:05:06". The
 53# year-month and mixed intervals cannot be converted to a timedelta and thus
 54# aren't accepted.
 55postgres_interval_re = _lazy_re_compile(
 56    r"^"
 57    r"(?:(?P<days>-?\d+) (days? ?))?"
 58    r"(?:(?P<sign>[-+])?"
 59    r"(?P<hours>\d+):"
 60    r"(?P<minutes>\d\d):"
 61    r"(?P<seconds>\d\d)"
 62    r"(?:\.(?P<microseconds>\d{1,6}))?"
 63    r")?$"
 64)
 65
 66
 67def parse_duration(value):
 68    """Parse a duration string and return a datetime.timedelta.
 69
 70    The preferred format for durations in Django is '%d %H:%M:%S.%f'.
 71
 72    Also supports ISO 8601 representation and PostgreSQL's day-time interval
 73    format.
 74    """
 75    match = (
 76        standard_duration_re.match(value)
 77        or iso8601_duration_re.match(value)
 78        or postgres_interval_re.match(value)
 79    )
 80    if match:
 81        kw = match.groupdict()
 82        sign = -1 if kw.pop("sign", "+") == "-" else 1
 83        if kw.get("microseconds"):
 84            kw["microseconds"] = kw["microseconds"].ljust(6, "0")
 85        if (
 86            kw.get("seconds")
 87            and kw.get("microseconds")
 88            and kw["seconds"].startswith("-")
 89        ):
 90            kw["microseconds"] = "-" + kw["microseconds"]
 91        kw = {k: float(v.replace(",", ".")) for k, v in kw.items() if v is not None}
 92        days = datetime.timedelta(kw.pop("days", 0.0) or 0.0)
 93        return days + sign * datetime.timedelta(**kw)
 94
 95
 96class DurationParseTests(unittest.TestCase):
 97    def test_parse_python_format(self):
 98        timedeltas = [
 99            timedelta(
100                days=4, minutes=15, seconds=30, milliseconds=100
101            ),  # fractions of seconds
102            timedelta(hours=10, minutes=15, seconds=30),  # hours, minutes, seconds
103            timedelta(days=4, minutes=15, seconds=30),  # multiple days
104            timedelta(days=1, minutes=00, seconds=00),  # single day
105            timedelta(days=-4, minutes=15, seconds=30),  # negative durations
106            timedelta(minutes=15, seconds=30),  # minute & seconds
107            timedelta(seconds=30),  # seconds
108        ]
109        for delta in timedeltas:
110            with self.subTest(delta=delta):
111                self.assertEqual(parse_duration(format(delta)), delta)
112
113    def test_parse_postgresql_format(self):
114        test_values = (
115            ("1 day", timedelta(1)),
116            ("1 day 0:00:01", timedelta(days=1, seconds=1)),
117            ("1 day -0:00:01", timedelta(days=1, seconds=-1)),
118            ("-1 day -0:00:01", timedelta(days=-1, seconds=-1)),
119            ("-1 day +0:00:01", timedelta(days=-1, seconds=1)),
120            (
121                "4 days 0:15:30.1",
122                timedelta(days=4, minutes=15, seconds=30, milliseconds=100),
123            ),
124            (
125                "4 days 0:15:30.0001",
126                timedelta(days=4, minutes=15, seconds=30, microseconds=100),
127            ),
128            ("-4 days -15:00:30", timedelta(days=-4, hours=-15, seconds=-30)),
129        )
130        for source, expected in test_values:
131            with self.subTest(source=source):
132                self.assertEqual(parse_duration(source), expected)
133
134    def test_seconds(self):
135        self.assertEqual(parse_duration("30"), timedelta(seconds=30))
136
137    def test_minutes_seconds(self):
138        self.assertEqual(parse_duration("15:30"), timedelta(minutes=15, seconds=30))
139        self.assertEqual(parse_duration("5:30"), timedelta(minutes=5, seconds=30))
140
141    def test_hours_minutes_seconds(self):
142        self.assertEqual(
143            parse_duration("10:15:30"), timedelta(hours=10, minutes=15, seconds=30)
144        )
145        self.assertEqual(
146            parse_duration("1:15:30"), timedelta(hours=1, minutes=15, seconds=30)
147        )
148        self.assertEqual(
149            parse_duration("100:200:300"),
150            timedelta(hours=100, minutes=200, seconds=300),
151        )
152
153    def test_days(self):
154        self.assertEqual(
155            parse_duration("4 15:30"), timedelta(days=4, minutes=15, seconds=30)
156        )
157        self.assertEqual(
158            parse_duration("4 10:15:30"),
159            timedelta(days=4, hours=10, minutes=15, seconds=30),
160        )
161
162    def test_fractions_of_seconds(self):
163        test_values = (
164            ("15:30.1", timedelta(minutes=15, seconds=30, milliseconds=100)),
165            ("15:30.01", timedelta(minutes=15, seconds=30, milliseconds=10)),
166            ("15:30.001", timedelta(minutes=15, seconds=30, milliseconds=1)),
167            ("15:30.0001", timedelta(minutes=15, seconds=30, microseconds=100)),
168            ("15:30.00001", timedelta(minutes=15, seconds=30, microseconds=10)),
169            ("15:30.000001", timedelta(minutes=15, seconds=30, microseconds=1)),
170            ("15:30,000001", timedelta(minutes=15, seconds=30, microseconds=1)),
171        )
172        for source, expected in test_values:
173            with self.subTest(source=source):
174                self.assertEqual(parse_duration(source), expected)
175
176    def test_negative(self):
177        test_values = (
178            ("-4 15:30", timedelta(days=-4, minutes=15, seconds=30)),
179            ("-172800", timedelta(days=-2)),
180            ("-15:30", timedelta(minutes=-15, seconds=-30)),
181            ("-1:15:30", timedelta(hours=-1, minutes=-15, seconds=-30)),
182            ("-30.1", timedelta(seconds=-30, milliseconds=-100)),
183            ("-30,1", timedelta(seconds=-30, milliseconds=-100)),
184            ("-00:01:01", timedelta(minutes=-1, seconds=-1)),
185            ("-01:01", timedelta(seconds=-61)),
186            ("-01:-01", None),
187        )
188        for source, expected in test_values:
189            with self.subTest(source=source):
190                self.assertEqual(parse_duration(source), expected)
191
192    def test_iso_8601(self):
193        test_values = (
194            ("P4Y", None),
195            ("P4M", None),
196            ("P4W", None),
197            ("P4D", timedelta(days=4)),
198            ("P0.5D", timedelta(hours=12)),
199            ("P0,5D", timedelta(hours=12)),
200            ("PT5H", timedelta(hours=5)),
201            ("PT5M", timedelta(minutes=5)),
202            ("PT5S", timedelta(seconds=5)),
203            ("PT0.000005S", timedelta(microseconds=5)),
204            ("PT0,000005S", timedelta(microseconds=5)),
205        )
206        for source, expected in test_values:
207            with self.subTest(source=source):
208                self.assertEqual(parse_duration(source), expected)

django.utils.dateparse.parse_datetime

 1"""Functions to parse datetime objects."""
 2
 3# We're using regular expressions rather than time.strptime because:
 4# - They provide both validation and parsing.
 5# - They're more flexible for datetimes.
 6# - The date/datetime/time constructors produce friendlier error messages.
 7
 8import datetime
 9
10from django.utils.regex_helper import _lazy_re_compile
11from django.utils.timezone import get_fixed_timezone, utc
12
13date_re = _lazy_re_compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
14
15time_re = _lazy_re_compile(
16    r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
17    r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?"
18)
19
20datetime_re = _lazy_re_compile(
21    r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
22    r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
23    r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?"
24    r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
25)
26
27standard_duration_re = _lazy_re_compile(
28    r"^"
29    r"(?:(?P<days>-?\d+) (days?, )?)?"
30    r"(?P<sign>-?)"
31    r"((?:(?P<hours>\d+):)(?=\d+:\d+))?"
32    r"(?:(?P<minutes>\d+):)?"
33    r"(?P<seconds>\d+)"
34    r"(?:[\.,](?P<microseconds>\d{1,6})\d{0,6})?"
35    r"$"
36)
37
38# Support the sections of ISO 8601 date representation that are accepted by
39# timedelta
40iso8601_duration_re = _lazy_re_compile(
41    r"^(?P<sign>[-+]?)"
42    r"P"
43    r"(?:(?P<days>\d+(.\d+)?)D)?"
44    r"(?:T"
45    r"(?:(?P<hours>\d+(.\d+)?)H)?"
46    r"(?:(?P<minutes>\d+(.\d+)?)M)?"
47    r"(?:(?P<seconds>\d+(.\d+)?)S)?"
48    r")?"
49    r"$"
50)
51
52# Support PostgreSQL's day-time interval format, e.g. "3 days 04:05:06". The
53# year-month and mixed intervals cannot be converted to a timedelta and thus
54# aren't accepted.
55postgres_interval_re = _lazy_re_compile(
56    r"^"
57    r"(?:(?P<days>-?\d+) (days? ?))?"
58    r"(?:(?P<sign>[-+])?"
59    r"(?P<hours>\d+):"
60    r"(?P<minutes>\d\d):"
61    r"(?P<seconds>\d\d)"
62    r"(?:\.(?P<microseconds>\d{1,6}))?"
63    r")?$"
64)
65
66
67def parse_datetime(value):
68    """Parse a string and return a datetime.datetime.
69
70    This function supports time zone offsets. When the input contains one,
71    the output uses a timezone with a fixed offset from UTC.
72
73    Raise ValueError if the input is well formatted but not a valid datetime.
74    Return None if the input isn't well formatted.
75    """
76    match = datetime_re.match(value)
77    if match:
78        kw = match.groupdict()
79        kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
80        tzinfo = kw.pop("tzinfo")
81        if tzinfo == "Z":
82            tzinfo = utc
83        elif tzinfo is not None:
84            offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0
85            offset = 60 * int(tzinfo[1:3]) + offset_mins
86            if tzinfo[0] == "-":
87                offset = -offset
88            tzinfo = get_fixed_timezone(offset)
89        kw = {k: int(v) for k, v in kw.items() if v is not None}
90        kw["tzinfo"] = tzinfo
91        return datetime.datetime(**kw)