fix: correct Rockbox's local wall-clock timestamps to UTC #11

Open
lyrathorpe wants to merge 1 commits from fix/scrobble-timezone into main
Owner

Scrobbles submitted while the UK is on BST land an hour in the future. Winter
submissions were correct by coincidence, not design.

Cause

Rockbox has no concept of a timezone. Its clock holds local wall time, and it
builds log timestamps with mktime(get_time()) — but
firmware/libc/mktime.c
is plain calendar arithmetic that applies no offset. Fed the RTC's local
fields, it returns them as though they were UTC, so the logged number is ahead
of the real instant by whatever the offset was. Last.fm stores UTC, and both
log readers passed the number through untouched.

Rockbox is explicit about this. Its scrobbler plugin writes #TZ/UNKNOWN, and
the AUDIOSCROBBLER spec permits #TZ/UTC only for a device that genuinely
converted. The correction is the consumer's responsibility.

Fix

Each timestamp is decoded back into the wall-clock fields it came from and
reinterpreted in the player's zone.

Per play, not one offset applied to the whole log: a week's listening can
straddle a daylight saving change, and the two sides need different offsets. A
log declaring #TZ/UTC is left alone rather than shifted twice.

The zone defaults to this machine's, overridable with --device-timezone or
ROCKBOX_TIMEZONE. Resolving it needs the whole IANA name — /etc/localtime
is a symlink into the tzdata tree, and taking only the final component gives
London, which no database holds, silently falling back to a fixed offset that
is wrong for half the year.

Operational note

Rockbox cannot adjust for daylight saving itself, so the player's clock has to
be changed by hand twice a year. If that is missed, its times are an hour out
and no zone arithmetic recovers them. Any play converting to a time in the
future is now reported, which is what a missed adjustment looks like:

  37 plays are timestamped up to 58 minutes in the future, converting from
  Europe/London. Either the player's clock is wrong or that is not the zone
  it is set to.

--dry-run now prints each play's local time beside the epoch, so the
conversion can be checked against when the listening actually happened.

Scrobbles already submitted are not corrected by this; doing so would mean
deleting them and resubmitting from the .submitted logs still on the card.

Verification

Nine tests covering the summer offset, the winter no-op, the round trip back to
the clock the device displayed, a log spanning the October change, both header
declarations, the future-play report, and the zone name resolution.

Mutating the conversion back to a pass-through fails exactly four of them; the
winter and #TZ/UTC cases still pass, since both assert that nothing changes.
The zone-name test is a regression test — the unit tests passed while the
fallback path was quietly using a fixed offset, and only an end-to-end run
against a log spanning the October change surfaced it.

166 tests pass. shellcheck clean.

Scrobbles submitted while the UK is on BST land an hour in the future. Winter submissions were correct by coincidence, not design. ## Cause Rockbox has no concept of a timezone. Its clock holds local wall time, and it builds log timestamps with `mktime(get_time())` — but [`firmware/libc/mktime.c`](https://git.rockbox.org/cgit/rockbox.git/tree/firmware/libc/mktime.c) is plain calendar arithmetic that applies no offset. Fed the RTC's local fields, it returns them as though they were UTC, so the logged number is ahead of the real instant by whatever the offset was. Last.fm stores UTC, and both log readers passed the number through untouched. Rockbox is explicit about this. Its scrobbler plugin writes `#TZ/UNKNOWN`, and the AUDIOSCROBBLER spec permits `#TZ/UTC` only for a device that genuinely converted. The correction is the consumer's responsibility. ## Fix Each timestamp is decoded back into the wall-clock fields it came from and reinterpreted in the player's zone. Per play, not one offset applied to the whole log: a week's listening can straddle a daylight saving change, and the two sides need different offsets. A log declaring `#TZ/UTC` is left alone rather than shifted twice. The zone defaults to this machine's, overridable with `--device-timezone` or `ROCKBOX_TIMEZONE`. Resolving it needs the whole IANA name — `/etc/localtime` is a symlink into the tzdata tree, and taking only the final component gives `London`, which no database holds, silently falling back to a fixed offset that is wrong for half the year. ## Operational note Rockbox cannot adjust for daylight saving itself, so the player's clock has to be changed by hand twice a year. If that is missed, its times are an hour out and no zone arithmetic recovers them. Any play converting to a time in the future is now reported, which is what a missed adjustment looks like: ``` 37 plays are timestamped up to 58 minutes in the future, converting from Europe/London. Either the player's clock is wrong or that is not the zone it is set to. ``` `--dry-run` now prints each play's local time beside the epoch, so the conversion can be checked against when the listening actually happened. Scrobbles already submitted are not corrected by this; doing so would mean deleting them and resubmitting from the `.submitted` logs still on the card. ## Verification Nine tests covering the summer offset, the winter no-op, the round trip back to the clock the device displayed, a log spanning the October change, both header declarations, the future-play report, and the zone name resolution. Mutating the conversion back to a pass-through fails exactly four of them; the winter and `#TZ/UTC` cases still pass, since both assert that nothing changes. The zone-name test is a regression test — the unit tests passed while the fallback path was quietly using a fixed offset, and only an end-to-end run against a log spanning the October change surfaced it. 166 tests pass. `shellcheck` clean.
lyrathorpe added 1 commit 2026-08-27 15:37:34 +01:00
fix: correct Rockbox's local wall-clock timestamps to UTC
Build and publish container / build (pull_request) Successful in 4m23s
c12d2328e5
Rockbox has no concept of a timezone. Its clock holds local time, and it
builds log timestamps with mktime(get_time()) -- but firmware/libc/mktime.c
is plain calendar arithmetic applying no offset, so the RTC's local fields
come out as though they were UTC. The number in the log is ahead of the real
instant by whatever the offset was, and Last.fm stores UTC, so every play
submitted during BST landed an hour in the future.

Rockbox states this itself: its scrobbler plugin writes #TZ/UNKNOWN, and the
AUDIOSCROBBLER spec allows #TZ/UTC only for a device that actually converted.
The correction belongs to the consumer.

Each timestamp is decoded back to its wall-clock fields and reinterpreted in
the player's zone, per play rather than as one offset over the whole log, so
a log spanning a daylight saving change converts each side correctly. A log
declaring #TZ/UTC is left alone rather than shifted twice.

The zone defaults to this machine's, overridable with --device-timezone or
ROCKBOX_TIMEZONE. Deriving it needs the whole IANA name: /etc/localtime
resolves into the tzdata tree, and taking only the final component yields
"London", which no database holds, silently falling back to a fixed offset
that is wrong for half the year.

Since Rockbox cannot adjust for daylight saving on its own, the player's
clock has to be changed by hand twice a year. Any play converting to a future
time is now reported, which is what a forgotten adjustment looks like.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All checks were successful
Build and publish container / build (pull_request) Successful in 4m23s
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/scrobble-timezone:fix/scrobble-timezone
git checkout fix/scrobble-timezone
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: lyrathorpe/music-mirror#11