GeezerVison is showing github.com — A list item field with value 'PT' being converted to Datetime object · Issue #1340 · microsoftgraph/msgraph-sdk-python
A list item field with value 'PT' being converted to Datetime object #1340DescriptionDescribe the bug
I am trying to iterate through a SharePoint list entries/rows for further processing:
...
list = await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.get(request_configuration=request_config)
rows = []
for item_id in list.value:
fields = item_id['fields']
rows.append(fields.additional_data.get("code")) # test
rows
but when text/string field have value 'PT' it is retrieved as a datetime (exactly: "datetime.timedelta(0)"). It makes my code useless for keeping for example 'Country Code' as list's field. I have seen similar issue before with 4-digit strings, so maybe it is related.
Please, take a moment to look at this.
here some screenshots:
list in SP (just field 'code' and its values) :
and my code results:
Expected behavior
Expected behavior would be to display 'code' the same way as it is in SharePoint list, means:
PT
PTA
..
How to reproduce
To reproduce please just create new list with text field. Then add value 'PT' to this field and try to use await client.sites.by_site_id(site_id).lists.by_list_id(list_id).items.get(request_configuration=request_config) for entried reading.
SDK Version
1.40.0
Latest version known to work for scenario above?
No response
Known Workarounds
No response
Debug output
Click to expand log
```
</details>
### Configuration
_No response_
### Other information
_No response_
Activitymkkubins commented on Jan 27, 2026 I have similar issue that text column is converted to datetime.time:
'xxx': datetime.time(10, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=20280))),
smonsalve-IMC commented on Apr 13, 2026 Similar issue:
The raw Graph API wire payload is
"[email protected]": "https://graph.microsoft.com/v1.0/$metadata#sites/fields/$entity",
"fields": {"column_name": "23.085"}
column
{
"columnGroup": "Custom Columns",
"description": "",
"displayName": "display_name",
"enforceUniqueValues": false,
"hidden": false,
"indexed": false,
"name": "column_name",
"readOnly": false,
"required": false,
"text": {
"allowMultipleLines": false,
"appendChangesToExistingText": false,
"linesForEditing": 0,
"maxLength": 6
}
}
but
item.fields.additional_data["column_name"]
returns
datetime.time(23, 0, 0, 85000)
after Kiota parsing.
Environment
- microsoft-kiota-serialization-json 1.9.8
- msgraph-sdk 1.52.0
- Python 3.13, Windows
lramosvea commented on May 18, 2026 SirTomRat commented on May 25, 2026 Luis Ramos (@lramosvea)
json for existing two rows looks like that:
{'ID': '1', 'fields': FieldValueSet(additional_data={'@odata.etag': '"5674dfd1-047b-4b9d-a49a-4a34c9263872,11"', 'Title': 'aa', 'LinkTitleNoMenu': 'aa', 'Category': 'categpry1', 'code1': datetime.time(1, 0), 'ContentType': 'Item', 'Modified': datetime.datetime(2026, 2, 11, 13, 48, 2, tzinfo=datetime.timezone.utc), 'Created': datetime.datetime(2025, 7, 2, 10, 40, 36, tzinfo=datetime.timezone.utc), 'AuthorLookupId': '13', 'EditorLookupId': '13', '_UIVersionString': datetime.time(11, 0), 'Attachments': False, 'Edit': '', 'LinkTitle': 'aa', 'ItemChildCount': '0', 'FolderChildCount': '0', '_ComplianceFlags': '', '_ComplianceTag': '', '_ComplianceTagWrittenTime': '', '_ComplianceTagUserId': '', 'AppAuthorLookupId': '18'}, id='1', odata_type=None), 'Created By': 'xyz', 'Modified By': 'xyz'}
{'ID': '2', 'fields': FieldValueSet(additional_data={'@odata.etag': '"765b81af-1e73-4d93-aa80-d2d8bd8f9988,1"', 'Title': 'bb', 'LinkTitleNoMenu': 'bb', 'Category': datetime.timedelta(0), 'code1': 'ff', 'ContentType': 'Item', 'Modified': datetime.datetime(2026, 5, 25, 7, 6, 29, tzinfo=datetime.timezone.utc), 'Created': datetime.datetime(2026, 5, 25, 7, 6, 29, tzinfo=datetime.timezone.utc), 'AuthorLookupId': '13', 'EditorLookupId': '13', '_UIVersionString': '1.0', 'Attachments': False, 'Edit': '', 'LinkTitle': 'bb', 'ItemChildCount': '0', 'FolderChildCount': '0', '_ComplianceFlags': '', '_ComplianceTag': '', '_ComplianceTagWrittenTime': '', '_ComplianceTagUserId': '', 'AppAuthorLookupId': '18', 'AppEditorLookupId': '18'}, id='2', odata_type=None), 'Created By': 'xyz', 'Modified By': 'xyz'}
for the SharePoint list:
 56badkarma commented on Jul 18, 2026 Luis Ramos (@lramosvea) We hit the same bug in a production AP-invoicing app, and in our case it caused actual data corruption, not just display errors. We've also traced the root cause to the exact lines in kiota and have a tested fix — details below.
Real captured wire payload
Live tenant, PII redacted. InvoiceId is a "Single line of text" column:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites('...')/lists('...')/items(fields(InvoiceId))/$entity",
"@odata.etag": "\"1342017a-ff3b-463c-a6e3-cefaf61836f0,13\"",
"id": "42151",
"webUrl": "https://<tenant>.sharepoint.com/sites/<site>/Extraction%20Library/INV%2019%202026_....pdf",
"fields": {
"@odata.etag": "\"1342017a-ff3b-463c-a6e3-cefaf61836f0,13\"",
"InvoiceId": "19:00:00-20:26"
}
}
item.fields.additional_data["InvoiceId"]
# datetime.time(19, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=12840)))
That stored value was originally the invoice number 19-2026 (visible in the document filename). Kiota parsed "19-2026" as 19:00 with a UTC offset of -20:26 fabricated from the year fragment "-2026"; the app later stringified the object on an update and the mangled value is now what's persisted. We found three invoices corrupted this way (18-2026, 19-2026, 20-2026). Any invoice number ending in a plausible-offset suffix does this — for us, that's every invoice numbered with the current year.
Root cause
JsonParseNode.try_get_anything in microsoft-kiota-serialization-json (json_parse_node.py) speculatively runs every untyped string (i.e. everything landing in additional_data) through five lenient parsers in sequence: datetime_from_iso_format_compat, UUID, parse_timedelta_string, date.fromisoformat, time_from_iso_format_compat.
The killer is the last one: on Python 3.11+, time.fromisoformat accepts strings like "19-2026" (hour 19, offset -20:26) and "23.085" (23:00:00.085). parse_timedelta_string accepts "PT" (empty ISO-8601 duration → timedelta(0)) and "10:30" (hh:mm). None of these are plausible as JSON wire formats for time values.
Minimal repro, no Graph call needed (microsoft-kiota-serialization-json 1.9.3, Python 3.12.10):
from kiota_serialization_json.json_parse_node import JsonParseNode
for v in ["19-2026", "100-012863299", "PT", "10:30", "11.0"]:
print(repr(v), "->", repr(JsonParseNode(v).try_get_anything(v)))
'19-2026' -> datetime.time(19, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=12840)))
'100-012863299' -> datetime.time(10, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=81056, microseconds=701000)))
'PT' -> datetime.timedelta(0)
'10:30' -> datetime.timedelta(seconds=37800)
'11.0' -> datetime.time(11, 0)
('11.0' explains the _UIVersionString corruption shown earlier in this thread.)
The existing __is_four_digit_number / isdigit() guards (added for earlier reports like #653) patch individual symptoms but leave the underlying speculative parsing in place, which is why this class of bug keeps coming back.
Proposed fix
The .NET serializer already does this right: JsonParseNode.TryGetAnything only attempts strict DateTime/DateTimeOffset/Guid parses on strings and otherwise returns the string — it never guesses TimeSpan/TimeOnly/DateOnly. Aligning Python with that:
_ISO_DATETIME_OR_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}([Tt ]\d{2}:\d{2}|$)")
_CANONICAL_UUID = re.compile(
r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
)
# in try_get_anything, replacing the entire string-handling block:
if isinstance(value, str):
if _ISO_DATETIME_OR_DATE.match(value):
try:
return datetime_from_iso_format_compat(value)
except ValueError:
pass
elif _CANONICAL_UUID.match(value):
return UUID(value)
return value
Verified against both the failure cases and the values that must keep converting:
| input |
current |
proposed |
"19-2026" |
time(19, 0, tz-20:26) |
"19-2026" ✅ |
"100-012863299" |
time(10, 0, ...) |
unchanged string ✅ |
"PT" |
timedelta(0) |
"PT" ✅ |
"10:30" |
timedelta(seconds=37800) |
"10:30" ✅ |
"23.085" |
time(23, 0, 0, 85000) |
"23.085" ✅ |
"11.0" |
time(11, 0) |
"11.0" ✅ |
"d41d8cd98f00b204e9800998ecf8427e" |
UUID(...) |
unchanged string (fixes microsoft/kiota-python#487) |
"2026-02-11T13:48:02Z" |
datetime(...) |
datetime(...) — unchanged |
"2026-02-11" |
datetime(2026, 2, 11, 0, 0) |
datetime(2026, 2, 11, 0, 0) — unchanged |
"9f614123-06fb-...-5bb014d7d3c4" |
UUID(...) |
UUID(...) — unchanged |
Notes:
- Routing date-only strings through
datetime_from_iso_format_compat deliberately preserves the current datetime (not date) result, so real Graph payloads (Created, Modified, date columns) behave identically — the only behavior change is that strings which were never date/time/duration values stay strings.
- The
timedelta guess has to go entirely: Graph never sends bare durations in untyped additional_data, and ISO-8601 durations (P.../PT...) overlap with too many real-world strings.
- The
__is_four_digit_number and isdigit() special cases become unnecessary (all-digit strings can't match either anchored pattern) and can be deleted.
- This also resolves try_get_anything - converts a 32 hex string to UUID microsoft/kiota-python#487 (32-hex → UUID) by requiring the canonical hyphenated form.
- Typed model fields are unaffected — they go through declared field deserializers and never reach
try_get_anything.
Happy to open a PR against microsoft/kiota-python with this change and tests if the maintainers agree with the direction.
Environment: msgraph-sdk 1.37.0, msgraph-core 1.3.4, microsoft-kiota-serialization-json 1.9.3, Python 3.12.10. The same code is present on kiota-python main today.
Related: #653 (2024, closed without addressing the root cause), microsoft/kiota-python#487. 56badkarma commented on Jul 18, 2026 Follow-up: opened microsoft/kiota-python#664 with the fix described above (strict anchored date/datetime/UUID matching in try_get_anything, mirroring the .NET serializer) plus regression tests covering every corruption pattern reported in this thread. 56badkarma commented on Aug 19, 2026
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|