Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ class TripDayWeatherRepository {
Stream<void> watchWeatherChanges() =>
_db.tableUpdates(TableUpdateQuery.onTable(_db.tripDayWeather));

/// Stored weather for a trip, keyed by `date.millisecondsSinceEpoch`.
/// Stored weather for a trip, keyed by `tripDayMillis(date)`: the calendar
/// day at UTC midnight, not `date.millisecondsSinceEpoch`.
///
/// The distinction is the contract. Local-midnight millis differ in every
/// timezone, so a caller keying a lookup that way silently misses every
/// stored row rather than failing.
///
/// One entry per calendar day. Where more than one row lands on the same
/// day, [_preferred] picks which one shows, and explains how a second row
Expand Down
20 changes: 19 additions & 1 deletion lib/features/trips/domain/entities/trip_day_weather.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,25 @@ class TripDayWeather extends Equatable {
final String id;
final String tripId;

/// Local midnight for the day this describes.
/// The calendar day this describes.
///
/// Read as calendar fields, not as an instant: identity is
/// `tripDayMillis(date)`, which takes y/m/d and pins them to UTC midnight.
/// UTC because the day is part of the row identity, and a local-midnight
/// instant has a different epoch value in every timezone, so two devices
/// would key the same trip day differently and never converge. See
/// [tripDayMillis].
///
/// So `isUtc` is not enforced here, and varies by provenance: the
/// repository hands back a normalized UTC-midnight instant, while a row
/// built from a freshly fetched day carries the backfill target's local
/// `DateTime(y, m, d)`. Both name the same day and derive the same key, and
/// no path that stores, keys, or looks a row up reads anything else.
///
/// Except `==`. DateTime compares its epoch value and `isUtc`, and this
/// field is in [props], so two entities for the same day compare unequal
/// across provenance. Compare day keys, or normalize both sides, rather
/// than the entities.
final DateTime date;

/// The coordinates the lookup used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import 'package:submersion/features/trips/domain/entities/trip_story_day.dart';
/// One trip day that needs a weather lookup, with the coordinates to look it
/// up at.
class TripDayWeatherTarget extends Equatable {
/// Local midnight for the day.
/// The day to look up, at local midnight.
///
/// Local is right here and irrelevant to identity: [localNoon] reads the
/// calendar fields to ask the archive for that day's noon at the dive site,
/// and the storage key is derived separately by `tripDayMillis`, which
/// takes the same calendar fields and pins them to UTC. Nothing keys a
/// stored row off this instant.
final DateTime date;
final double latitude;
final double longitude;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ final tripDayWeatherRepositoryProvider = Provider<TripDayWeatherRepository>(
(ref) => TripDayWeatherRepository(),
);

/// Stored weather for a trip, keyed by `date.millisecondsSinceEpoch`.
/// Stored weather for a trip, keyed by `tripDayMillis(date)`: the calendar
/// day at UTC midnight, not `date.millisecondsSinceEpoch`.
///
/// Stated precisely because getting it wrong is silent. A caller that keys a
/// lookup with local-midnight millis finds nothing on any device that is not
/// on UTC, and the day simply renders no badge.
///
/// Subscribes to the table tick, so a row written by the backfill or arriving
/// through sync re-renders the day headers without the widget knowing a fetch
Expand Down