You went to bed at 11 pm on Monday and woke at 7 am on Tuesday. Is that Monday’s sleep or Tuesday’s?
Every health app answers that question. Almost none of them tell you how, and the platforms they read from answer it differently or decline to answer at all. The choice looks trivial and it is not: it decides what “last night” means in a readiness score, whether a streak survives a red-eye flight, which day a nap counts for, and whether your daily summary is describing one 24-hour window or two overlapping ones.
This post sets out what each platform actually does, from its own documentation, then what breaks at midnight, at 6 pm, on a flight and when the clocks change.
What each platform does
| Platform | Which day a night belongs to | Source |
|---|---|---|
| Apple HealthKit | No rule. A sample has a start date, an end date and a category value. Apple documents no day assignment, and developers report being unable to match the day the Health app displays | Apple support [1], Apple developer forums [2] |
| Google Health Connect | No rule. SleepSessionRecord has startTime, endTime, two zone offsets and stages. No date field, no guidance on which day a session belongs to | Android developer documentation [3] |
| Fitbit | The day it ended. dateOfSleep is “the date the sleep log ended” | Fitbit Web API reference [4] |
| Garmin | The wake day for sleep, midnight-to-midnight for everything else, with a dedicated calendarDate field added to say which | Garmin developer portal, as reported [5] |
| Oura | A 6 pm boundary. Sleep recorded after 6 pm is held for the following morning’s scores | Oura support [6] |
| WHOOP | No calendar day at all. A cycle runs from one sleep onset to the next | WHOOP engineering blog [7] |
| Samsung Health | ”If the session start hour and minute is later than the end hour and minute, the session is interpreted as starting on the previous day” | Samsung developer blog [8] |
Two of the seven, and the two that most apps actually read from, have no rule. Fitbit and Garmin agree on the wake day. Oura moves the boundary to 6 pm. WHOOP removed the concept. Samsung infers it from clock arithmetic.
Why the platforms that chose, chose what they did
WHOOP’s engineering team gave the clearest account, in a post from October 2022 whose title is the whole argument: “Your body doesn’t know what day it is.” Members work night shifts, stay awake for extended periods, and fly across the world, and Brian Kachmarck wrote that forcing them into “a calendar system that resets at 12:00 AM would make any representation of their physiological data incorrect” [7]. WHOOP’s answer was to bound a cycle by sleep onset instead, expose it through the API as a cycle_id, and accept that a cycle can be revised after the fact through a recovery.updated webhook when the algorithm learns more [7].
Garmin’s answer was to admit the ambiguity and add a field for it. Its developer portal announced calendarDate for sleep and daily summaries as the day on which a record appears in Garmin Connect, to be used for matching records “without the ambiguity of timezones or start time comparison” [5]. The same post is candid about the edge case the field exists to handle: daily summaries are midnight-to-midnight, sleep summaries are dated to the wake day, and “it is possible for two different sleep summaries to begin on the same day if, for example, the user goes to bed after midnight, wakes up, and then goes to bed prior to midnight the next evening” [5].
Oura’s answer was to move the boundary to where people are least likely to be asleep. Its documentation states that if a nap is recorded after 6 pm, Sleep and Readiness Scores “won’t be updated until the following morning” [6]. The corollary, that a nap before 6 pm counts toward the current day, follows from the rule rather than being separately stated. A nap is 15 minutes to three hours; anything longer becomes the longest sleep period [6].
Fitbit’s is the simplest and the most common: the log belongs to the date it ended, and a request for one date “may return a log entry that began the previous night” [4].
None of these is wrong. Each is a defensible answer to a question that has no natural one. The problem is that a product reading more than one of them receives four different answers under a single label, and the platforms most products read from provide none.
What breaks, and where
The 6 pm nap. By Oura’s rule, a 5 pm nap and a 7 pm nap on the same afternoon belong to different days [6]. Under a wake-date rule they belong to the same one. An app that reads Oura through its API and also reads a watch through HealthKit will, for the same user on the same afternoon, get two answers to “did you nap today.”
Two sleeps, one date. Garmin’s own example: bed after midnight, wake, bed again before the next midnight. Two sleep summaries with the same calendarDate [5]. A pipeline keyed on one sleep record per day silently overwrites the first with the second, or sums them, and neither is right.
One sleep, two records. Fitbit splits a night into two logs when the person is awake for more than an hour, and merges two logs when the gap is under an hour [4]. A bad night with an hour awake at 3 am is two records with the same dateOfSleep. A count of nights slept goes wrong in the direction that penalises the people with the worst sleep.
Clock arithmetic. Samsung’s rule, that a start hour later than the end hour means the sleep began yesterday [8], is correct for every ordinary night and wrong for any sleep that crosses a second midnight, any nap that starts and ends on the same clock face at different dates, and any session the user edits.
“Yesterday” is two windows. Garmin documents that its daily summary is midnight-to-midnight while its sleep summary is dated to the wake day [5]. That means a daily summary for Tuesday holds Tuesday’s steps and the sleep that began on Monday night. Every platform that pairs a calendar-day activity total with a wake-dated sleep is doing this, and “last night’s sleep and today’s steps” is exactly the comparison recovery features are built on.
Then the timezone, which the day is derived from
A day boundary is a time in some clock. Which clock turns out to be the harder question.
HealthKit stores an instant and, optionally, a zone. A sample’s start and end dates are absolute. Apple provides HKMetadataKeyTimeZone, defined as “the user’s time zone when the HealthKit object was created,” and its documentation says that “for best results when analyzing sleep samples, it’s recommended that you store time zone metadata with your sleep sample data” [9]. Recommended. Developers report the practical consequence: sleep data arriving as UTC timestamps with no timezone at all, and no way to know “exactly when start/end to sleep as happened” for a user in an unknown location [10].
Health Connect asks for two zones, because one is not enough. SleepSessionRecord carries startZoneOffset and endZoneOffset, and Google’s example computes each separately from the device’s zone rules [3]. The reason for two is that a person can change timezone during a single sleep. Anyone who has slept on an overnight flight has done so.
Fitbit stored the local clock and not the instant. A developer analysing years of their own Fitbit export found that “all the nights of sleep are recorded in device-local timestamps instead of UTC,” contrary to what the export claimed. Moving between Europe, Asia and the United States produced no shift in recorded bedtimes, because the recorded times were always local, which made cross-timezone analysis impossible without reconstruction. Their workaround recovered timezone for about 82% of nights by cross-referencing a second file that happened to carry UTC [11].
The 23-hour day
Twice a year, in most of the countries these products sell in, a day is not 24 hours long. The night the clocks change is 23 or 25 hours, and sleep software that assumes otherwise breaks on schedule.
On 7 November 2022, the morning after the United States returned to standard time, Fitbit users reported sleep times shifted by an hour. The reporter’s own device recorded a 7:20 am wake for an 8:20 am one. Fitbit acknowledged it publicly, said it was “working to address this,” and that the issue “usually resolves itself when your Fitbit resets next midnight” [12].
Garmin users at the New Zealand transition in 2020, on firmware 19.20, reported that the watch “decided not to record any sleep data at all,” that a night was logged as “3:40 instead of 8 hrs,” that the sleep start timestamp reset, and that Body Battery “dropped off a cliff at 3am when the clocks changed.” No Garmin staff response appears in the thread [13].
These are not exotic failures. They are the same two dates every year, and they hit the boundary computation in the same place: the day was assumed to be 24 hours, and it was not.
How it cascades
The day boundary is not a display detail. It is upstream of almost everything a health product shows.
A readiness or recovery score is built on “last night’s sleep.” Which night that is depends on the boundary, and a score computed on the wrong night is confidently wrong.
A streak breaks when a day is missing. A red-eye flight, a DST transition, or a late-night bedtime that a midnight rule pushes into the next day, can all produce a day with no sleep record for a person who slept every night.
A daily total that pairs midnight-to-midnight activity with wake-dated sleep is describing two overlapping windows as one day, and any correlation drawn between them inherits the overlap.
A comparison across users on different devices compares Oura’s 6 pm day with Fitbit’s wake day with an app’s own midnight rule, and the differences are not noise, they are systematic and they point in known directions.
And deduplication across sources, which we covered in how to deduplicate health data across wearables, depends on knowing that two records describe the same night. If two sources date the same night differently, they do not match, and the night is counted twice.
What to actually do
Choose a boundary explicitly, write it down, and expose it. The wake-date rule is the most common and matches Fitbit and Garmin. A late-afternoon boundary like Oura’s handles late bedtimes and early naps more gracefully. Either is defensible. Leaving it to whatever a date formatter does by default is not, and the rule belongs in the API documentation so that consumers can align with it rather than guess.
Store the zone at write time, on every sample. Apple recommends it for sleep. Do it for everything. A sample without its zone cannot be placed on a day after the fact, and the zone cannot be recovered later, because the person has moved.
Derive the day from local time at the moment of recording, never from UTC. UTC has no days. A sleep that ends at 22:00 UTC ended on one calendar day in London and the next in Auckland, and only the zone at the time tells you which.
Treat the day as variable length. The day of a DST transition is 23 or 25 hours. A boundary computed by adding 24 hours to the previous one drifts by an hour twice a year and produces exactly the failures above.
Carry the boundary across timezone changes, do not reset it. A person who flies east has a short day, not a missing one. A rule anchored to local sleep onset or local wake handles this. A rule anchored to midnight in the app’s home zone does not.
Key sleep records on a stable identifier, not on the day. Garmin’s two-sleeps-one-date case and Fitbit’s split logs both defeat a pipeline that assumes one record per day. Store the records, then derive the day.
Where we sit
Sahha reads HealthKit and Health Connect, which means we inherit no rule and have to impose one, and we read Oura, Garmin and WHOOP through their APIs, which means we receive three others. So here is ours, from the pipeline code rather than a policy document.
Every data log we store carries a timezone offset in minutes alongside its start and end instants, so the day is derived from local time at the moment of recording and can be re-derived later. Daily activity is bucketed on the local calendar day. Sleep is not: a night belongs to the day in which it ended, with the boundary at 6 pm rather than midnight, so a sleep ending between 6 pm yesterday and 6 pm today is today’s. The code comment says why: “a 6-hour offset for day bucketing to handle overnight sleep.”
Which means we have the same two day definitions inside one product that Garmin does, and a 5 pm nap and a 7 pm nap land on different days for us as they do for Oura. Neither is a defect. Both are choices, and the point of this post is that they should be written down. The pipeline consequences, including sleep landed on the wrong day across a timezone change, are in the hard parts of building a health data pipeline.
The short version
Fitbit dates sleep to the morning it ended. Garmin does the same but dates everything else midnight-to-midnight, and added a field to say so. Oura draws the line at 6 pm. WHOOP got rid of the day. Samsung infers it from clock arithmetic. Apple and Google, whose stores most apps read, provide no rule at all, which means every app reading them has invented one.
Underneath that, HealthKit carries a timezone only if the writer chose to add it, Fitbit stored local clock times instead of instants, and the night the clocks change is not 24 hours long. Every one of those has produced a documented, dated failure.
None of it is hard to handle once it is written down. It is only hard when it is assumed.
References
- Track your sleep on Apple Watch and use Sleep on iPhone. Apple Support. States that a Sleep Score arrives every morning after waking; documents no rule for which date a night is assigned to. Retrieved 6 September 2026. https://support.apple.com/en-us/108906
- HealthKit sleep analysis. Apple Developer Forums, thread 725166. Developer report of no reliable way to match calculated sleep to the day the Health app credits it to; Apple engineer response provides a query predicate only. https://developer.apple.com/forums/thread/725166
- Track sleep sessions. Android Developers, Health Connect. SleepSessionRecord fields including startZoneOffset and endZoneOffset. Retrieved 6 September 2026. https://developer.android.com/health-and-fitness/health-connect/features/sleep-sessions
- Get Sleep Log by Date. Fitbit Web API reference. Definition of dateOfSleep and the split and merge rules. Retrieved 6 September 2026. https://dev.fitbit.com/build/reference/web-api/sleep/get-sleep-log-by-date/
- New field: calendarDate for Sleep and Daily summaries. Garmin Developer Portal. Garmin’s site did not serve to automated retrieval; the definition and the two-sleeps-one-day example are cited as reported in search results of the post and corroborated by Labfront’s description of Garmin sleep data. https://developerportal.garmin.com/blog/new-field-calendardate-sleep-and-daily-summaries
- Nap Detection. Oura Member Care. The 6 pm rule and the nap length definition. Retrieved 6 September 2026. https://support.ouraring.com/hc/en-us/articles/1500009653181-Nap-Detection
- Kachmarck B. WHOOP Developer Platform: Your body doesn’t know what day it is. WHOOP Engineering, 28 October 2022. https://engineering.prod.whoop.com/dev-platform-2/
- Managing Sleep Data with Samsung Health and Health Connect. Samsung Developer, 22 January 2024. https://developer.samsung.com/health/blog/en/managing-sleep-data-with-samsung-health-and-health-connect
- HKMetadataKeyTimeZone. Apple Developer Documentation. https://developer.apple.com/documentation/healthkit/hkmetadatakeytimezone
- iOS Health Kit Sleep Data not able to get Timezone. Issue 301, cordova-plugin-health, 1 December 2023. https://github.com/dariosalvi78/cordova-plugin-health/issues/301
- Kong A. Timezone correcting Fitbit sleep data. 30 November 2023. https://andykong.org/blog/fitbitsleeptzcorrection/
- Schoon B. Fitbit owners seeing inaccurate sleep tracking following Daylight Savings Time switch. 9to5Google, 7 November 2022. https://9to5google.com/2022/11/07/fitbit-daylight-savings/
- Daylight Saving Bug with Sleep and Body Battery? 19.20. Garmin Forums, fēnix 6 series, 2020. https://forums.garmin.com/outdoor-recreation/outdoor-recreation/f/fenix-6-series/274561/daylight-saving-bug-with-sleep-and-body-battery-19-20