diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index 91a8283..0000000 --- a/PLAN.md +++ /dev/null @@ -1,87 +0,0 @@ -# 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:00` format -- **`now` field**: intentionally UTC (offset=0). The `format` pipe 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"` (for `region_load`, `datetime_to_tz`, `shortname`, `region_destroy`) - -## Odin timezone API reference - -- `timezone.region_load(name: string) -> (^datetime.TZ_Region, bool)` — `"local"` reads `$TZ` env, falls back to `/etc/localtime` -- `timezone.region_destroy(region: ^datetime.TZ_Region)` -- `timezone.datetime_to_tz(dt: DateTime, tz: ^TZ_Region) -> (DateTime, bool)` — DST-aware. If `dt.tz == tz`, no-op. If `dt.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 == nil` means UTC