mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
4.0 KiB
4.0 KiB
Timezone Support
Goal
Add timezone conversion to the format pipe so dates display in the configured site timezone, with correct abbreviations for the MST token.
Context
Dates are stored as raw ISO 8601 strings. The format pipe formats them for display using Go-style layout tokens. Currently parse_iso_date ignores the timezone offset suffix, and MST is hardcoded to "UTC".
Pipes now take ctx: []any, so timezone config can flow through the data context identically to date_format — no new parameters to thread.
Decisions
- MST fallback (no target tz, date has offset):
UTC-04:00format nowfield: intentionally UTC (offset=0). Theformatpipe handles timezone display.- TZ_Region cache: lives in the mustache package (
format.odin)
Files changed
| File | Changes |
|---|---|
mustache/format.odin |
Extend Date_Components (+offset_seconds, has_offset, tz_abbr). Extend parse_iso_date to parse trailing offset. Add tz_cache, get_cached_tz, convert_to_tz, format_offset, destroy_tz_cache. Fix MST token. Import core:time/timezone. |
mustache/pipes.odin |
Resolve date_timezone from ctx in "format" case (optional — nil is fine). Add timezone_name param to apply_format. Orchestrates parse → convert → format. |
render.odin |
Add date_timezone: string to Base_Data, populate from site.date.timezone. Remove // TODO: CAlculate offset (offset=0 is intentional — UTC). |
site.odin |
Call mustache.destroy_tz_cache() from destroy_site. No structural changes — Date_Preferences.timezone already exists and flows through config. |
mustache/pipes_test.odin |
Add date_timezone: string to test data structs. Add timezone conversion tests. |
Conversion logic (in apply_format)
1. parse_iso_date(iso) → components (now includes offset_seconds, has_offset)
2. tz_name := resolve "date_timezone" from ctx (optional)
3. target_tz := get_cached_tz(tz_name) // nil if empty/UTC/not configured
4. if target_tz != nil:
components = convert_to_tz(components, target_tz)
// tz_abbr filled by convert_to_tz via timezone.shortname()
5. else if components.has_offset:
components.tz_abbr = format_offset(components.offset_seconds)
// e.g. "UTC-04:00"
6. else:
components.tz_abbr = "UTC"
7. format_date(components, fmt)
convert_to_tz flow
1. Build DateTime from components (tz=nil=UTC)
2. If has_offset: add offset_seconds to get true UTC
3. timezone.datetime_to_tz(utc_dt, target_tz) → converted DateTime
4. Extract components from converted DateTime
5. tz_abbr = timezone.shortname(converted_dt) // "EST", "EDT", etc.
MST fallback: format_offset
0 → "UTC"
-14400 → "UTC-04:00"
+19800 → "UTC+05:30"
now field
now stays UTC (offset=0). Remove the // TODO: CAlculate offset comment — it's correct as-is. Templates format it with {{now | format}} and the pipe handles timezone display.
Behavior matrix
| Config TZ | ISO has offset | Conversion | MST output |
|---|---|---|---|
"America/New_York" |
yes (-04:00) |
UTC → NY (DST-aware) | "EST"/"EDT" |
"America/New_York" |
no | assume already in NY | "EST"/"EDT" |
| not set | yes (-04:00) |
none — display as-is | "UTC-04:00" |
| not set | no | none | "UTC" |
Imports added
mustache/format.odin:import "core:time/timezone"(forregion_load,datetime_to_tz,shortname,region_destroy)
Odin timezone API reference
timezone.region_load(name: string) -> (^datetime.TZ_Region, bool)—"local"reads$TZenv, falls back to/etc/localtimetimezone.region_destroy(region: ^datetime.TZ_Region)timezone.datetime_to_tz(dt: DateTime, tz: ^TZ_Region) -> (DateTime, bool)— DST-aware. Ifdt.tz == tz, no-op. Ifdt.tz == nil, treats as UTC.timezone.shortname(dt: DateTime) -> (string, bool)— abbreviation from TZ_Region records (e.g."EST","EDT")datetime.DateTime :: struct { using date: Date, using time: Time, tz: ^TZ_Region }—tz == nilmeans UTC