Send timestamps with the offset they were read in
Live bill INV-2608-T5EDD-00116 carried billedat 2026-08-05T12:49:28Z beside receivedat 2026-08-05T07:19:28Z — the sale appearing to have been rung five and a half hours after the back office received it. Exactly the IST offset, on every bill. Nothing was lying. DateTime.toIso8601String() on a local time emits no zone marker at all, and Go's time.Parse fills that silence with UTC, so a Coimbatore wall clock was recorded as though it had been read in London. The daily figures survived by luck: businessdate is derived from the wall clock either way, and the wall clock was always the till's own, so a day's takings landed on the right day even while the instant was wrong. Anything comparing billedat against real time did not. Formatters.isoWithOffset attaches the offset, which fixes both readings at once — the instant parses correctly and the local date still formats correctly. The minutes come from the real offset rather than being assumed zero, because India is +05:30 and a whole-hour implementation would be wrong in a way that looks almost right. Applied to all six timestamps the till sends. date_of_birth is left alone: a birthday is a date, not an instant, and giving it a zone would be meaningless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -83,4 +83,35 @@ class Formatters {
|
||||
: '$terminalCode-';
|
||||
return 'INV-$y$m-$code$seq';
|
||||
}
|
||||
|
||||
/// A timestamp the back office cannot misread, with its UTC offset attached.
|
||||
///
|
||||
/// `DateTime.toIso8601String()` on a local time emits no zone marker at all —
|
||||
/// `2026-08-05T12:49:28.245`. That is not wrong, it is *silent*, and the
|
||||
/// receiver has to guess. Go's `time.Parse` guesses UTC, so a bill rung at
|
||||
/// 12:49 in Coimbatore was stored as 12:49 UTC: five and a half hours in the
|
||||
/// future, and reading as *later than the moment it was received*.
|
||||
///
|
||||
/// The daily figures survived that by luck. `businessdate` is derived from
|
||||
/// the wall clock either way, and the wall clock was always the till's own —
|
||||
/// so a day's takings landed on the right day even while the instant was
|
||||
/// wrong. Anything comparing `billedat` to real time did not survive it.
|
||||
///
|
||||
/// Emitting the offset ends the guessing: `2026-08-05T12:49:28.245+05:30`
|
||||
/// parses to the correct instant *and* still formats to the correct local
|
||||
/// date, so both readings stay right.
|
||||
static String isoWithOffset(DateTime time) {
|
||||
final local = time.toLocal();
|
||||
final offset = local.timeZoneOffset;
|
||||
|
||||
final sign = offset.isNegative ? '-' : '+';
|
||||
final magnitude = offset.abs();
|
||||
final hours = magnitude.inHours.toString().padLeft(2, '0');
|
||||
// India is +05:30, so the minutes are load-bearing here in a way they are
|
||||
// not in a whole-hour zone. Taken from the total rather than assumed zero.
|
||||
final minutes =
|
||||
(magnitude.inMinutes % 60).toString().padLeft(2, '0');
|
||||
|
||||
return '${local.toIso8601String()}$sign$hours:$minutes';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user