feat(mustache): apply_format now accepts timezone info.

This commit is contained in:
Spencer Brower
2026-07-24 11:22:00 -04:00
parent 4d847d95d3
commit 4f09312daf
3 changed files with 101 additions and 3 deletions
+2 -1
View File
@@ -32,7 +32,8 @@
## Dates
- [x] Accept "strings"
- [x] Accept keys
- [ ] handle timezones
- [x] handle timezones
- [ ] Fix TZ cache leak in parallel tests: `tz_cache` uses `context.allocator` (tracking allocator in tests), but it's global state shared across parallel test threads. Race causes leak warnings. Fix: use heap allocator for TZ cache.
- [ ] display an error when no part of the date appears in the output.
- [x] use `date.format` as the default format.
- [ ] Handle 0 and whitespace padding i.e. "_2" -> " 2"
+26 -2
View File
@@ -228,7 +228,15 @@ apply_filter :: proc(value: any, filter: ^Pipe_Filter, pos: int, ctx: []any) ->
date_format = df
}
str2, err := apply_format(str, filter.args[:], pos, date_format)
// Resolve timezone (optional — empty is fine, means display as-is)
tz_name := ""
if raw := resolve_name("timezone", ctx); raw != nil {
if s, s_ok := reflect.as_string(raw); s_ok {
tz_name = s
}
}
str2, err := apply_format(str, filter.args[:], pos, date_format, tz_name)
if err != nil {
return value, err
} else {
@@ -249,6 +257,7 @@ apply_format :: proc(
args: []string,
pos: int,
date_format: string,
timezone_name: string,
) -> (
result: string,
err: Error,
@@ -270,7 +279,22 @@ apply_format :: proc(
}
}
log.debugf("date: '%s' format: '%s'", iso, date_format)
target_tz, tz_ok := get_cached_tz(timezone_name)
if !tz_ok {
return "", Error_Body {
msg = fmt.tprintf("unable to load timezone '%s'", timezone_name),
pos = pos,
kind = .Data,
}
}
if target_tz != nil {
components, _ = convert_to_tz(components, target_tz)
} else if components.has_offset {
components.tz_abbr = format_offset(components.offset_seconds)
}
log.debugf("date: '%s' format: '%s' tz: '%s'", iso, date_format, timezone_name)
return format_date(components, fmt_str), nil
}
+73
View File
@@ -511,3 +511,76 @@ test_timezone_empty_when_not_set :: proc(t: ^testing.T) {
result, _ := render(tpl, data, {}, context.temp_allocator)
testing.expect_value(t, result, "[]")
}
// ---------------------------------------------------------------------------
// format pipe with timezone conversion
// ---------------------------------------------------------------------------
@(test)
test_format_with_timezone_summer :: proc(t: ^testing.T) {
tz, tz_ok := get_cached_tz("America/New_York")
defer destroy_tz_cache()
if !tz_ok || tz == nil do return
data := Format_Data {
date = "2026-03-15T12:49:54Z",
date_format = "15:04 MST",
timezone = "America/New_York",
}
tpl, _ := parse("{{date | format}}", "<test>", allocator = context.temp_allocator)
result, _ := render(tpl, data, {}, context.temp_allocator)
testing.expect_value(t, result, "08:49 EDT")
}
@(test)
test_format_with_timezone_winter :: proc(t: ^testing.T) {
tz, tz_ok := get_cached_tz("America/New_York")
defer destroy_tz_cache()
if !tz_ok || tz == nil do return
data := Format_Data {
date = "2026-01-15T12:49:54Z",
date_format = "15:04 MST",
timezone = "America/New_York",
}
tpl, _ := parse("{{date | format}}", "<test>", allocator = context.temp_allocator)
result, _ := render(tpl, data, {}, context.temp_allocator)
testing.expect_value(t, result, "07:49 EST")
}
@(test)
test_format_mst_offset_no_timezone :: proc(t: ^testing.T) {
data := Format_Data {
date = "2026-03-15T08:49:54-04:00",
date_format = "MST",
}
tpl, _ := parse("{{date | format}}", "<test>", allocator = context.temp_allocator)
result, _ := render(tpl, data, {}, context.temp_allocator)
testing.expect_value(t, result, "UTC-04:00")
}
@(test)
test_format_mst_no_offset_no_timezone :: proc(t: ^testing.T) {
data := Format_Data {
date = "2026-03-15",
date_format = "MST",
}
tpl, _ := parse("{{date | format}}", "<test>", allocator = context.temp_allocator)
result, _ := render(tpl, data, {}, context.temp_allocator)
testing.expect_value(t, result, "UTC")
}
@(test)
test_format_invalid_timezone_errors :: proc(t: ^testing.T) {
defer destroy_tz_cache()
data := Format_Data {
date = "2026-03-15",
date_format = "2 Jan 2006",
timezone = "Invalid/Zone",
}
tpl, _ := parse("{{date | format}}", "<test>", allocator = context.temp_allocator)
defer delete_template(&tpl)
_, err := render(tpl, data, {}, context.temp_allocator)
testing.expect(t, err != nil, "invalid timezone should error")
}