|
| 1 | +# Temporal semantics |
| 2 | + |
| 3 | +Windshift uses four distinct temporal types. Callers must identify which type |
| 4 | +they have before parsing, formatting, comparing, or persisting it. |
| 5 | + |
| 6 | +## Instants |
| 7 | + |
| 8 | +An instant identifies one point on the UTC timeline. API timestamps and Unix |
| 9 | +timestamps are instants. |
| 10 | + |
| 11 | +- Persist and exchange instants as UTC timestamps. |
| 12 | +- Authenticated surfaces display instants in the acting user's validated IANA |
| 13 | + timezone. |
| 14 | +- If a stored user timezone is missing or invalid, display the instant in UTC. |
| 15 | +- Public surfaces without an acting user display instants in UTC unless the |
| 16 | + surface explicitly owns another timezone. |
| 17 | +- Browser and server local timezones are never implicit presentation defaults. |
| 18 | + |
| 19 | +Frontend code formats instants through `formatInstant` or a formatter returned |
| 20 | +by `createTemporalFormatter`. Backend code uses `ResolveTimezoneOrUTC` only at |
| 21 | +boundaries where invalid stored user data must safely fall back. |
| 22 | + |
| 23 | +## Date-only values |
| 24 | + |
| 25 | +A date-only value is a Gregorian calendar label in `YYYY-MM-DD` form. Due |
| 26 | +dates, iteration dates, leave dates, and custom date fields are date-only unless |
| 27 | +their API explicitly says otherwise. |
| 28 | + |
| 29 | +- Do not convert date-only values through browser or user timezones. |
| 30 | +- Preserve the stored year, month, and day when formatting. |
| 31 | +- Do not infer a midnight instant from a date-only value except at a boundary |
| 32 | + that explicitly converts a civil range to instants. |
| 33 | + |
| 34 | +Frontend code formats these values through `formatDateOnly`. |
| 35 | + |
| 36 | +## Schedule-local civil time |
| 37 | + |
| 38 | +A civil time combines calendar fields with an IANA timezone. Recurrences, |
| 39 | +on-call handoffs, and other schedules retain their own timezone even when the |
| 40 | +viewer uses a different timezone. |
| 41 | + |
| 42 | +- Validate schedule and request-supplied timezone names strictly. |
| 43 | +- Reject nonexistent or ambiguous wall-clock times unless that feature has a |
| 44 | + documented DST policy. |
| 45 | +- Do not replace a schedule timezone with the user's display timezone. |
| 46 | + |
| 47 | +Backend code uses `ResolveTimezone`, `ParseCivilDate`, and the feature's civil |
| 48 | +clock resolver for these values. |
| 49 | + |
| 50 | +## Civil date ranges |
| 51 | + |
| 52 | +User-facing inclusive date ranges become half-open instant ranges before they |
| 53 | +reach timestamp queries: |
| 54 | + |
| 55 | +```text |
| 56 | +[start date at 00:00 local, day after end date at 00:00 local) |
| 57 | +``` |
| 58 | + |
| 59 | +Convert both boundaries to UTC after constructing them in the relevant IANA |
| 60 | +timezone. Advance the exclusive boundary with calendar-day arithmetic, not a |
| 61 | +24-hour duration, so DST transition days remain correct. Backend code uses |
| 62 | +`CivilDateRangeUTC` for this conversion. |
| 63 | + |
| 64 | +## Durations |
| 65 | + |
| 66 | +A duration is elapsed time and has no timezone. Format and compare durations |
| 67 | +without calendar conversion. Labels such as "3 hours ago" compare instants but |
| 68 | +express the resulting duration. |
| 69 | + |
| 70 | +For worklogs, a duration submitted without explicit start and end clocks is |
| 71 | +anchored at the start of the submitted civil date in the resolved worklog |
| 72 | +timezone. Cookie, REST, and MCP entry points use this same deterministic rule. |
0 commit comments