Skip to content

Commit e744be7

Browse files
Return raw int from UNIXTIME decode to match Go and preserve round-trip
decode_value for UNIXTIME previously returned datetime.datetime, but encode_value with a datetime auto-infers ISO8601 — silently changing the value type and wire format on re-encode. Now returns raw int64, matching Go's getInt64() behavior and preserving round-trip identity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c68471e commit e744be7

2 files changed

Lines changed: 39 additions & 28 deletions

File tree

src/dqlitewire/types.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,10 @@ def decode_value(data: bytes, value_type: ValueType) -> tuple[Any, int]:
265265
elif value_type == ValueType.INTEGER:
266266
return decode_int64(data), 8
267267
elif value_type == ValueType.UNIXTIME:
268-
timestamp = decode_int64(data)
269-
try:
270-
dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.UTC)
271-
except (OverflowError, OSError) as exc:
272-
raise DecodeError(
273-
f"UNIXTIME value {timestamp} is outside the representable datetime range"
274-
) from exc
275-
return dt, 8
268+
# Return raw int64 to match Go's getInt64() behavior and preserve
269+
# round-trip identity. Previously returned datetime.datetime, which
270+
# caused type-changing re-encode (UNIXTIME → ISO8601).
271+
return decode_int64(data), 8
276272
elif value_type == ValueType.FLOAT:
277273
return decode_double(data), 8
278274
elif value_type == ValueType.TEXT:

tests/test_types.py

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -423,41 +423,56 @@ def test_iso8601_no_timezone_returns_utc_aware_datetime(self) -> None:
423423
assert decoded == datetime.datetime(2024, 1, 15, 10, 30, 45, tzinfo=datetime.UTC)
424424

425425
def test_encode_decode_unixtime(self) -> None:
426-
"""Test Unix timestamp decodes to datetime, matching Go's time.Unix."""
427-
import datetime
428-
426+
"""UNIXTIME should decode to raw int, matching Go's getInt64()."""
429427
timestamp = 1705312245
430428
encoded, vtype = encode_value(timestamp, ValueType.UNIXTIME)
431429
assert vtype == ValueType.UNIXTIME
432430
decoded, _ = decode_value(encoded, ValueType.UNIXTIME)
433-
assert isinstance(decoded, datetime.datetime)
434-
expected = datetime.datetime(2024, 1, 15, 9, 50, 45, tzinfo=datetime.UTC)
435-
assert decoded == expected
431+
assert isinstance(decoded, int)
432+
assert decoded == timestamp
436433

437434
def test_encode_decode_unixtime_zero(self) -> None:
438-
"""Unix timestamp 0 should decode to epoch."""
439-
import datetime
440-
435+
"""Unix timestamp 0 should decode to integer 0."""
441436
encoded, _ = encode_value(0, ValueType.UNIXTIME)
442437
decoded, _ = decode_value(encoded, ValueType.UNIXTIME)
443-
assert isinstance(decoded, datetime.datetime)
444-
assert decoded == datetime.datetime(1970, 1, 1, tzinfo=datetime.UTC)
438+
assert isinstance(decoded, int)
439+
assert decoded == 0
445440

446441
def test_encode_decode_unixtime_negative(self) -> None:
447-
"""Negative Unix timestamps (before epoch) should decode."""
448-
import datetime
449-
442+
"""Negative Unix timestamps should decode to negative int."""
450443
encoded, _ = encode_value(-86400, ValueType.UNIXTIME)
451444
decoded, _ = decode_value(encoded, ValueType.UNIXTIME)
452-
assert isinstance(decoded, datetime.datetime)
453-
assert decoded == datetime.datetime(1969, 12, 31, tzinfo=datetime.UTC)
445+
assert isinstance(decoded, int)
446+
assert decoded == -86400
454447

455-
def test_unixtime_extreme_values_raise_decode_error(self) -> None:
456-
"""Extreme UNIXTIME values outside datetime range should raise DecodeError, not OSError."""
448+
def test_unixtime_extreme_values_roundtrip(self) -> None:
449+
"""Extreme UNIXTIME values should roundtrip as raw ints (no datetime conversion)."""
457450
for timestamp in [2**63 - 1, -(2**63)]:
458451
data = encode_int64(timestamp)
459-
with pytest.raises(DecodeError, match="outside the representable datetime range"):
460-
decode_value(data, ValueType.UNIXTIME)
452+
decoded, consumed = decode_value(data, ValueType.UNIXTIME)
453+
assert isinstance(decoded, int)
454+
assert decoded == timestamp
455+
assert consumed == 8
456+
457+
def test_unixtime_roundtrip_preserves_type(self) -> None:
458+
"""UNIXTIME encode→decode should return an int, matching Go's getInt64().
459+
460+
Previously, decode returned a datetime.datetime, which when re-encoded
461+
would auto-infer ISO8601 — changing the value type and wire format.
462+
"""
463+
timestamp = 1710000000
464+
encoded, vtype = encode_value(timestamp, ValueType.UNIXTIME)
465+
assert vtype == ValueType.UNIXTIME
466+
467+
decoded, consumed = decode_value(encoded, ValueType.UNIXTIME)
468+
assert consumed == 8
469+
assert isinstance(decoded, int)
470+
assert decoded == timestamp
471+
472+
# Re-encoding with explicit UNIXTIME should produce identical bytes
473+
re_encoded, re_vtype = encode_value(decoded, ValueType.UNIXTIME)
474+
assert re_vtype == ValueType.UNIXTIME
475+
assert re_encoded == encoded
461476

462477
def test_encode_datetime_with_explicit_unixtime_raises_encode_error(self) -> None:
463478
"""Passing a datetime with ValueType.UNIXTIME should raise EncodeError, not TypeError."""

0 commit comments

Comments
 (0)