|
13 | 13 | from typing import Any, Dict, List, Type, Union |
14 | 14 |
|
15 | 15 |
|
| 16 | +def _parse_sql_datetime(value: str) -> datetime: |
| 17 | + """Parse a SQL Server datetime string into a timezone-aware datetime. |
| 18 | +
|
| 19 | + ODBC/older Python runtimes deliver datetime2 values as strings without a |
| 20 | + ``T`` separator and often carry seven fractional digits (100ns precision), |
| 21 | + e.g. ``'2026-08-09 05:25:26.5205700'``. Python's ``datetime.fromisoformat`` |
| 22 | + rejects values with more than six fractional digits on Python < 3.13, so |
| 23 | + normalize the string first: normalize the separator, then drop any |
| 24 | + fractional component beyond six digits. |
| 25 | + """ |
| 26 | + normalized = value.replace('Z', '+00:00') |
| 27 | + if 'T' not in normalized: |
| 28 | + head, sep, rest = normalized.partition(' ') |
| 29 | + if sep: |
| 30 | + normalized = f"{head}T{rest}" |
| 31 | + if '.' in normalized: |
| 32 | + head, sep, frac = normalized.rpartition('.') |
| 33 | + frac = frac.rstrip('0')[:6] |
| 34 | + if frac: |
| 35 | + normalized = f"{head}.{frac}" |
| 36 | + result = datetime.fromisoformat(normalized) |
| 37 | + if result.tzinfo is None: |
| 38 | + return result.replace(tzinfo=timezone.utc) |
| 39 | + return result |
| 40 | + |
| 41 | + |
16 | 42 | class TypeAdapter(ABC): |
17 | 43 | """Base class for type adapters.""" |
18 | 44 |
|
@@ -114,10 +140,7 @@ def from_database(self, value: Any, target_type: Type = None, options: Dict = No |
114 | 140 | return value |
115 | 141 |
|
116 | 142 | if isinstance(value, str): |
117 | | - result = datetime.fromisoformat(value.replace('Z', '+00:00')) |
118 | | - if result.tzinfo is None: |
119 | | - return result.replace(tzinfo=timezone.utc) |
120 | | - return result |
| 143 | + return _parse_sql_datetime(value) |
121 | 144 |
|
122 | 145 | raise TypeError(f"Cannot convert {type(value)} to datetime") |
123 | 146 |
|
@@ -155,7 +178,7 @@ def from_database(self, value: Any, target_type: Type = None, options: Dict = No |
155 | 178 | return value |
156 | 179 |
|
157 | 180 | if isinstance(value, str): |
158 | | - return datetime.fromisoformat(value) |
| 181 | + return _parse_sql_datetime(value) |
159 | 182 |
|
160 | 183 | raise TypeError(f"Cannot convert {type(value)} to datetime") |
161 | 184 |
|
|
0 commit comments