Lenient timestamp deserializer for Transaction + Order time fields#3
Open
dem23-h wants to merge 1 commit into
Open
Lenient timestamp deserializer for Transaction + Order time fields#3dem23-h wants to merge 1 commit into
dem23-h wants to merge 1 commit into
Conversation
Tiger's paper /order_transactions endpoint has been observed returning
`transactedAt` and `time` as "YYYY-MM-DD HH:MM:SS" naive datetime
strings instead of epoch milliseconds. The strict `i64` typing in
v0.3.0 makes `Vec<Transaction>` decode fail on the first such row,
breaking every call site that lists transactions:
invalid type: string "2026-05-08 22:57:14", expected i64
This patch adds `model::serde_helpers::deserialize_lenient_timestamp`
which accepts:
- JSON number (epoch ms) — pass through
- numeric string ("1700000000000")
- naive datetime ("YYYY-MM-DD HH:MM:SS") — interpreted as UTC
- RFC 3339 ("2026-05-08T22:57:14Z" or with offset)
- null / empty string / unknown shape — fall back to 0 + tracing::warn
Applied to:
- Transaction.transacted_at, Transaction.time (the actual bug)
- Order.open_time, Order.update_time, Order.latest_time (defensive —
Tiger may flip these to strings on a future release)
Naive strings without timezone are interpreted as UTC; Tiger does not
document the actual TZ for these strings, but UTC is consistent with
the rest of Tiger's epoch fields (also UTC) so relative ordering
within the events log is preserved.
+10 deserializer tests (every shape) + 2 integration tests covering
Vec<Transaction> with mixed shapes. Total fork tests: 241 pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Tiger's paper
/order_transactionsendpoint has been observed returningtransactedAtandtimeas"YYYY-MM-DD HH:MM:SS"naive datetime strings instead of epoch milliseconds. With the stricti64typing inTransaction, decodingVec<Transaction>fails on the first such row, breaking every call site that lists transactions:Reproduced on a fresh paper account against
https://openapi-sandbox.tigerfintech.comafter placing a few orders that filled. Once any transaction in the response carries the string-shaped timestamp, the entire response fails to deserialize —get_order_transactionsreturnsErr(TigerError::Config("decode items failed: ..."))instead of the rows.Fix
Adds
model::serde_helpers::deserialize_lenient_timestampwhich accepts:"1700000000000") —parse::<i64>"YYYY-MM-DD HH:MM:SS") — interpreted as UTC, returned as epoch ms viaNaiveDateTime::and_utc().timestamp_millis()"2026-05-08T22:57:14Z"/ with offset) —DateTime::parse_from_rfc33390+tracing::warn!Applied to:
Transaction.transacted_atandTransaction.time— the actual bugOrder.open_time,Order.update_time,Order.latest_time— defensive (Tiger may flip these to strings on a future release; same parser handles either shape)TZ note
Tiger does not document the timezone of the naive datetime strings. The patch interprets them as UTC since (a) the rest of Tiger's
i64timestamp fields are already epoch-ms UTC, and (b) interpreting naive strings as UTC keeps relative ordering within aVec<Transaction>consistent against the existing UTC-typed fields — important for downstream consumers that key offtransacted_atfor chronological replay. If Tiger documents a different convention later, the parser is one place to update.Tests
Transactionwith string timestamps and aVec<Transaction>with mixed shapes (the exact patterncall_into_items::<Transaction>chokes on).Backward compatibility
Pure additive — the deserializer is permissive; any existing consumer that depended on
i64epoch-ms behaviour gets identical results.tracing::warnon unknown-shape strings only fires for values that would have previously panicked the decode entirely.Why this matters downstream
In our trading engine, this bug fired on:
events_boot_reconcilecallsget_orders+get_order_transactions; the latter blew up on every restart against a paper account with prior fills.list_transactions(HTTP 500 on the per-position transaction-history dropdown).A permissive deserializer is the simplest fix that doesn't require us to fork the crate or duplicate the wire model. Happy to iterate on the patch shape (alternative TZ assumption, different fallback behaviour, narrower field selection) — let me know what you'd prefer.