LIFEOS/TOOLS/healthsync/ pins every day-boundary calculation to America/Los_Angeles:
| File |
What it pins |
store.ts (dayKeyLA()) |
which day-file a reading lands in |
store.ts (isoNowLA()) |
the fetched_at / lastSuccess stamp |
eightsleep.ts |
the tz= query param sent to the Eight Sleep trends API |
For any operator outside Pacific time the day key is wrong near local midnight, so readings are silently filed under the previous or next day. Nothing errors. The data is just in the wrong bucket, and HealthSync.ts then builds trend windows off those mis-keyed files.
Reproduced on a UTC-5 host on 2026-07-21: an export at 01:13 local was written to the previous day file, because 01:13 at UTC-5 is 23:13 the day before in Los Angeles. Two hours of every day land in the wrong file; for UTC+ operators the window is larger.
eightsleep.ts compounds it: the provider is asked to bucket by LA while the files are keyed by LA too, so the two agree only for LA users and the from/to window silently covers the wrong span for everyone else.
Fix
A resolveTimeZone() in store.ts taking the first usable IANA zone from:
LIFEOS_HEALTH_TZ (explicit override for this subsystem)
TZ (the POSIX standard, which launchd/cron operators already set)
- the host zone via
Intl.DateTimeFormat().resolvedOptions().timeZone
America/Los_Angeles (the current value, so behaviour is unchanged when nothing else resolves)
Candidates validated by constructing an Intl.DateTimeFormat and falling through on throw, so a typo in TZ degrades instead of crashing the sync. Memoised, with a reset seam for tests.
Compatibility: no call site changes
dayKeyLA and isoNowLA stay as @deprecated aliases of a renamed dayKey / isoNow. There are 12 call sites across HealthSync.ts, apple.ts, oura.ts, function.ts and eightsleep.ts; none need to change. They can be migrated whenever, or never.
The behaviour change for an operator who sets nothing is "always LA" becoming "the machine own zone", which is the intended semantics of a personal health log. Anyone who genuinely wants LA sets TZ or LIFEOS_HEALTH_TZ.
Verification
Same instant (2026-07-21T06:13:00Z) through dayKey():
LIFEOS_HEALTH_TZ |
day key |
America/Bogota |
2026-07-21 |
America/Los_Angeles |
2026-07-20 (old behaviour preserved) |
Europe/Madrid |
2026-07-21 |
Asia/Tokyo |
2026-07-21 |
| unset |
2026-07-21 (host zone) |
| an invalid zone string |
2026-07-21 (falls through, no throw) |
HealthSync.ts pull runs clean against all four providers after the change.
Related, and what this does NOT cover
Same class as the timezone work in #762 / #780 / #806, which fixed hooks/lib/time.ts and statusline-command.sh but did not reach LIFEOS/TOOLS/. Tools/FailureCapture.ts and Tools/RelationshipReflect.ts still carry the same hardcode and are not covered here.
Diff is written and tested locally (+54/-6 in store.ts, +4/-1 in eightsleep.ts). Happy to open it as a PR if that is easier than porting from the description.
LIFEOS/TOOLS/healthsync/pins every day-boundary calculation toAmerica/Los_Angeles:store.ts(dayKeyLA())store.ts(isoNowLA())fetched_at/lastSuccessstampeightsleep.tstz=query param sent to the Eight Sleep trends APIFor any operator outside Pacific time the day key is wrong near local midnight, so readings are silently filed under the previous or next day. Nothing errors. The data is just in the wrong bucket, and
HealthSync.tsthen builds trend windows off those mis-keyed files.Reproduced on a UTC-5 host on 2026-07-21: an export at 01:13 local was written to the previous day file, because 01:13 at UTC-5 is 23:13 the day before in Los Angeles. Two hours of every day land in the wrong file; for UTC+ operators the window is larger.
eightsleep.tscompounds it: the provider is asked to bucket by LA while the files are keyed by LA too, so the two agree only for LA users and thefrom/towindow silently covers the wrong span for everyone else.Fix
A
resolveTimeZone()instore.tstaking the first usable IANA zone from:LIFEOS_HEALTH_TZ(explicit override for this subsystem)TZ(the POSIX standard, which launchd/cron operators already set)Intl.DateTimeFormat().resolvedOptions().timeZoneAmerica/Los_Angeles(the current value, so behaviour is unchanged when nothing else resolves)Candidates validated by constructing an
Intl.DateTimeFormatand falling through on throw, so a typo inTZdegrades instead of crashing the sync. Memoised, with a reset seam for tests.Compatibility: no call site changes
dayKeyLAandisoNowLAstay as@deprecatedaliases of a renameddayKey/isoNow. There are 12 call sites acrossHealthSync.ts,apple.ts,oura.ts,function.tsandeightsleep.ts; none need to change. They can be migrated whenever, or never.The behaviour change for an operator who sets nothing is "always LA" becoming "the machine own zone", which is the intended semantics of a personal health log. Anyone who genuinely wants LA sets
TZorLIFEOS_HEALTH_TZ.Verification
Same instant (
2026-07-21T06:13:00Z) throughdayKey():LIFEOS_HEALTH_TZAmerica/Bogota2026-07-21America/Los_Angeles2026-07-20(old behaviour preserved)Europe/Madrid2026-07-21Asia/Tokyo2026-07-212026-07-21(host zone)2026-07-21(falls through, no throw)HealthSync.ts pullruns clean against all four providers after the change.Related, and what this does NOT cover
Same class as the timezone work in #762 / #780 / #806, which fixed
hooks/lib/time.tsandstatusline-command.shbut did not reachLIFEOS/TOOLS/.Tools/FailureCapture.tsandTools/RelationshipReflect.tsstill carry the same hardcode and are not covered here.Diff is written and tested locally (+54/-6 in
store.ts, +4/-1 ineightsleep.ts). Happy to open it as a PR if that is easier than porting from the description.