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>
77 lines
3.0 KiB
Dart
77 lines
3.0 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:nearle_pos/core/utils/formatters.dart';
|
|
|
|
/// The fault these cover was found in live data, not in a test.
|
|
///
|
|
/// Bill INV-2608-T5EDD-00116 carried `billedat 2026-08-05T12:49:28.245Z` beside
|
|
/// `receivedat 2026-08-05T07:19:28.586Z` — the bill appearing to have been rung
|
|
/// five and a half hours *after* the back office received it. Exactly the IST
|
|
/// offset, every time.
|
|
///
|
|
/// Nothing was lying. `DateTime.toIso8601String()` on a local time emits no
|
|
/// zone marker at all, Go's `time.Parse` fills that silence with UTC, and the
|
|
/// till's wall clock was recorded as though it had been read in London.
|
|
void main() {
|
|
group('a timestamp says which zone it was read in', () {
|
|
test('carries an explicit offset', () {
|
|
final iso = Formatters.isoWithOffset(DateTime(2026, 8, 5, 12, 49, 28));
|
|
|
|
expect(
|
|
iso,
|
|
matches(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*[+-]\d{2}:\d{2}$'),
|
|
reason: 'without an offset the receiver has to guess, and guesses UTC',
|
|
);
|
|
});
|
|
|
|
test('round-trips back to the same instant', () {
|
|
// The whole point. The old form parsed to a different moment than the one
|
|
// the cashier rang the bill at.
|
|
final rung = DateTime(2026, 8, 5, 12, 49, 28, 245);
|
|
|
|
final parsed = DateTime.parse(Formatters.isoWithOffset(rung));
|
|
|
|
expect(
|
|
parsed.toUtc(),
|
|
rung.toUtc(),
|
|
reason: 'the instant must survive the trip to the back office',
|
|
);
|
|
});
|
|
|
|
test('keeps the wall clock the till actually showed', () {
|
|
// Load-bearing for businessdate. The back office derives a day's takings
|
|
// from the wall clock, so a bill rung at 12:49 must still read 12:49 —
|
|
// converting to UTC before sending would have moved it to 07:19 and put
|
|
// late-evening sales on the previous day.
|
|
final iso = Formatters.isoWithOffset(DateTime(2026, 8, 5, 12, 49, 28));
|
|
|
|
expect(iso, startsWith('2026-08-05T12:49:28'));
|
|
});
|
|
|
|
test('does not drop a half-hour offset', () {
|
|
// India is +05:30. An implementation that formatted only whole hours
|
|
// would produce +05:00 here and be wrong by thirty minutes — the kind of
|
|
// error that survives review because it looks almost right.
|
|
final offset = DateTime.now().timeZoneOffset;
|
|
final iso = Formatters.isoWithOffset(DateTime(2026, 8, 5, 12, 0));
|
|
|
|
final minutes = offset.abs().inMinutes % 60;
|
|
expect(
|
|
iso.substring(iso.length - 2),
|
|
minutes.toString().padLeft(2, '0'),
|
|
reason: 'the minutes component must come from the real offset',
|
|
);
|
|
});
|
|
|
|
test('a UTC input is converted, not relabelled', () {
|
|
// Passing an already-UTC DateTime must not stamp it with the local
|
|
// offset while leaving the UTC wall clock in place — that would recreate
|
|
// the original bug in reverse.
|
|
final instant = DateTime.utc(2026, 8, 5, 7, 19, 28);
|
|
|
|
final parsed = DateTime.parse(Formatters.isoWithOffset(instant));
|
|
|
|
expect(parsed.toUtc(), instant);
|
|
});
|
|
});
|
|
}
|