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.
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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())— butfirmware/libc/mktime.cis 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, andthe AUDIOSCROBBLER spec permits
#TZ/UTConly for a device that genuinelyconverted. 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/UTCis left alone rather than shifted twice.The zone defaults to this machine's, overridable with
--device-timezoneorROCKBOX_TIMEZONE. Resolving it needs the whole IANA name —/etc/localtimeis 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 thatis 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:
--dry-runnow prints each play's local time beside the epoch, so theconversion 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
.submittedlogs 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/UTCcases 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.
shellcheckclean.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.