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:
Suriya
2026-08-05 20:17:57 +05:30
parent 353c6c1075
commit 908058038a
4 changed files with 132 additions and 6 deletions

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io';
import '../../core/config/sync_config.dart';
import '../../core/utils/formatters.dart';
import '../../domain/repositories/sync_repository.dart';
import '../local/terminal_identity.dart';
import '../remote/order_transport.dart';
@@ -125,19 +126,19 @@ class HealthReporter {
// Queue depth — the number that makes a silent failure visible.
'pending_bills': pendingBills,
'pending_registrations': pendingRegistrations,
'oldest_pending_at': (await _oldestPendingAt())?.toIso8601String(),
'oldest_pending_at': _iso(await _oldestPendingAt()),
'sync_halted': state.isHalted,
'sync_error': state.lastError,
'last_upload_at': state.lastSuccessAt?.toIso8601String(),
'last_upload_at': _iso(state.lastSuccessAt),
// Today's trading. A till that is connected but has rung nothing in
// three hours is usually a jammed printer or an absent cashier, and
// neither shows up on an online/offline board.
'today_bills': today.billCount,
'today_amount': today.grossSales,
'last_bill_at': today.lastBillAt?.toIso8601String(),
'last_bill_at': _iso(today.lastBillAt),
'reported_at': _now().toIso8601String(),
'reported_at': Formatters.isoWithOffset(_now()),
};
final device = await _collectDeviceState();
@@ -214,3 +215,12 @@ class HealthReporter {
_timer?.cancel();
}
}
/// [Formatters.isoWithOffset], tolerating a null.
///
/// Several of these are genuinely absent — a till that has never uploaded has
/// no last upload, and one with an empty queue has no oldest pending bill.
/// Omitting the key is the honest answer; sending an epoch would put 1970 on a
/// dashboard and read as a real reading.
String? _iso(DateTime? time) =>
time == null ? null : Formatters.isoWithOffset(time);