|
29 | 29 | EPOCH_DATE = date.fromisoformat("1970-01-01") |
30 | 30 | EPOCH_TIMESTAMP = datetime.fromisoformat("1970-01-01T00:00:00.000000") |
31 | 31 | ISO_TIMESTAMP = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d{1,6})?") |
| 32 | +ISO_TIMESTAMP_NANO = re.compile(r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(.\d{1,6})?(\d{1,3})?") |
32 | 33 | EPOCH_TIMESTAMPTZ = datetime.fromisoformat("1970-01-01T00:00:00.000000+00:00") |
33 | 34 | ISO_TIMESTAMPTZ = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d{1,6})?[-+]\d{2}:\d{2}") |
| 35 | +ISO_TIMESTAMPTZ_NANO = re.compile(r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(.\d{1,6})?(\d{1,3})?([-+]\d{2}:\d{2})") |
34 | 36 |
|
35 | 37 |
|
36 | 38 | def micros_to_days(timestamp: int) -> int: |
@@ -115,28 +117,30 @@ def datetime_to_nanos(dt: datetime) -> int: |
115 | 117 |
|
116 | 118 |
|
117 | 119 | def timestamp_to_nanos(timestamp_str: str) -> int: |
118 | | - """Convert an ISO-9601 formatted timestamp without zone to microseconds from 1970-01-01T00:00:00.000000. |
119 | | -
|
120 | | - Currently only microsecond precision timestamp_str is supported as python datetime does not have |
121 | | - nanoseconds support. |
122 | | - """ |
123 | | - if ISO_TIMESTAMP.fullmatch(timestamp_str): |
124 | | - return datetime_to_nanos(datetime.fromisoformat(timestamp_str)) |
125 | | - if ISO_TIMESTAMPTZ.fullmatch(timestamp_str): |
| 120 | + """Convert an ISO-9601 formatted timestamp without zone to nanoseconds from 1970-01-01T00:00:00.000000000.""" |
| 121 | + if match := ISO_TIMESTAMP_NANO.fullmatch(timestamp_str): |
| 122 | + # Python datetime does not have native nanoseconds support |
| 123 | + # Hence we need to extract nanoseconds timestamp manually |
| 124 | + ns_str = match.group(3) or "0" |
| 125 | + ms_str = match.group(2) if match.group(2) else "" |
| 126 | + timestamp_str_without_ns_str = match.group(1) + ms_str |
| 127 | + return datetime_to_nanos(datetime.fromisoformat(timestamp_str_without_ns_str)) + int(ns_str) |
| 128 | + if ISO_TIMESTAMPTZ_NANO.fullmatch(timestamp_str): |
126 | 129 | # When we can match a timestamp without a zone, we can give a more specific error |
127 | 130 | raise ValueError(f"Zone offset provided, but not expected: {timestamp_str}") |
128 | 131 | raise ValueError(f"Invalid timestamp without zone: {timestamp_str} (must be ISO-8601)") |
129 | 132 |
|
130 | 133 |
|
131 | 134 | def timestamptz_to_nanos(timestamptz_str: str) -> int: |
132 | | - """Convert an ISO-8601 formatted timestamp with zone to microseconds from 1970-01-01T00:00:00.000000+00:00. |
133 | | -
|
134 | | - Currently only microsecond precision timestamp_str is supported as python datetime does not have |
135 | | - nanoseconds support. |
136 | | - """ |
137 | | - if ISO_TIMESTAMPTZ.fullmatch(timestamptz_str): |
138 | | - return datetime_to_nanos(datetime.fromisoformat(timestamptz_str)) |
139 | | - if ISO_TIMESTAMP.fullmatch(timestamptz_str): |
| 135 | + """Convert an ISO-8601 formatted timestamp with zone to nanoseconds from 1970-01-01T00:00:00.000000000+00:00.""" |
| 136 | + if match := ISO_TIMESTAMPTZ_NANO.fullmatch(timestamptz_str): |
| 137 | + # Python datetime does not have native nanoseconds support |
| 138 | + # Hence we need to extract nanoseconds timestamp manually |
| 139 | + ns_str = match.group(3) or "0" |
| 140 | + ms_str = match.group(2) if match.group(2) else "" |
| 141 | + timestamptz_str_without_ns_str = match.group(1) + ms_str + match.group(4) |
| 142 | + return datetime_to_nanos(datetime.fromisoformat(timestamptz_str_without_ns_str)) + int(ns_str) |
| 143 | + if ISO_TIMESTAMPTZ_NANO.fullmatch(timestamptz_str): |
140 | 144 | # When we can match a timestamp without a zone, we can give a more specific error |
141 | 145 | raise ValueError(f"Missing zone offset: {timestamptz_str} (must be ISO-8601)") |
142 | 146 | raise ValueError(f"Invalid timestamp with zone: {timestamptz_str} (must be ISO-8601)") |
|
0 commit comments