Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/iso8601/iso8601.py
7801 views
1
"""ISO 8601 date time string parsing
2
3
Basic usage:
4
>>> import iso8601
5
>>> iso8601.parse_date("2007-01-25T12:00:00Z")
6
datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.Utc ...>)
7
>>>
8
9
"""
10
11
import datetime
12
import re
13
import typing
14
from decimal import Decimal
15
16
__all__ = ["parse_date", "ParseError", "UTC", "FixedOffset"]
17
18
# Adapted from http://delete.me.uk/2005/03/iso8601.html
19
ISO8601_REGEX = re.compile(
20
r"""
21
(?P<year>[0-9]{4})
22
(
23
(
24
(-(?P<monthdash>[0-9]{1,2}))
25
|
26
(?P<month>[0-9]{2})
27
(?!$) # Don't allow YYYYMM
28
)
29
(
30
(
31
(-(?P<daydash>[0-9]{1,2}))
32
|
33
(?P<day>[0-9]{2})
34
)
35
(
36
(
37
(?P<separator>[ T])
38
(?P<hour>[0-9]{2})
39
(:{0,1}(?P<minute>[0-9]{2})){0,1}
40
(
41
:{0,1}(?P<second>[0-9]{1,2})
42
([.,](?P<second_fraction>[0-9]+)){0,1}
43
){0,1}
44
(?P<timezone>
45
Z
46
|
47
(
48
(?P<tz_sign>[-+])
49
(?P<tz_hour>[0-9]{2})
50
:{0,1}
51
(?P<tz_minute>[0-9]{2}){0,1}
52
)
53
){0,1}
54
){0,1}
55
)
56
){0,1} # YYYY-MM
57
){0,1} # YYYY only
58
$
59
""",
60
re.VERBOSE,
61
)
62
63
64
class ParseError(ValueError):
65
"""Raised when there is a problem parsing a date string"""
66
67
68
UTC = datetime.timezone.utc
69
70
71
def FixedOffset(
72
offset_hours: float, offset_minutes: float, name: str
73
) -> datetime.timezone:
74
return datetime.timezone(
75
datetime.timedelta(hours=offset_hours, minutes=offset_minutes), name
76
)
77
78
79
def parse_timezone(
80
matches: typing.Dict[str, str],
81
default_timezone: typing.Optional[datetime.timezone] = UTC,
82
) -> typing.Optional[datetime.timezone]:
83
"""Parses ISO 8601 time zone specs into tzinfo offsets"""
84
tz = matches.get("timezone", None)
85
if tz == "Z":
86
return UTC
87
# This isn't strictly correct, but it's common to encounter dates without
88
# timezones so I'll assume the default (which defaults to UTC).
89
# Addresses issue 4.
90
if tz is None:
91
return default_timezone
92
sign = matches.get("tz_sign", None)
93
hours = int(matches.get("tz_hour", 0))
94
minutes = int(matches.get("tz_minute", 0))
95
description = f"{sign}{hours:02d}:{minutes:02d}"
96
if sign == "-":
97
hours = -hours
98
minutes = -minutes
99
return FixedOffset(hours, minutes, description)
100
101
102
def parse_date(
103
datestring: str, default_timezone: typing.Optional[datetime.timezone] = UTC
104
) -> datetime.datetime:
105
"""Parses ISO 8601 dates into datetime objects
106
107
The timezone is parsed from the date string. However it is quite common to
108
have dates without a timezone (not strictly correct). In this case the
109
default timezone specified in default_timezone is used. This is UTC by
110
default.
111
112
:param datestring: The date to parse as a string
113
:param default_timezone: A datetime tzinfo instance to use when no timezone
114
is specified in the datestring. If this is set to
115
None then a naive datetime object is returned.
116
:returns: A datetime.datetime instance
117
:raises: ParseError when there is a problem parsing the date or
118
constructing the datetime instance.
119
120
"""
121
try:
122
m = ISO8601_REGEX.match(datestring)
123
except Exception as e:
124
raise ParseError(e)
125
126
if not m:
127
raise ParseError(f"Unable to parse date string {datestring!r}")
128
129
# Drop any Nones from the regex matches
130
# TODO: check if there's a way to omit results in regexes
131
groups: typing.Dict[str, str] = {
132
k: v for k, v in m.groupdict().items() if v is not None
133
}
134
135
try:
136
return datetime.datetime(
137
year=int(groups.get("year", 0)),
138
month=int(groups.get("month", groups.get("monthdash", 1))),
139
day=int(groups.get("day", groups.get("daydash", 1))),
140
hour=int(groups.get("hour", 0)),
141
minute=int(groups.get("minute", 0)),
142
second=int(groups.get("second", 0)),
143
microsecond=int(
144
Decimal(f"0.{groups.get('second_fraction', 0)}") * Decimal("1000000.0")
145
),
146
tzinfo=parse_timezone(groups, default_timezone=default_timezone),
147
)
148
except Exception as e:
149
raise ParseError(e)
150
151