From c97c1a0cb4125e33a0951b0bc4d73434e3d8a773 Mon Sep 17 00:00:00 2001 From: weigang Date: Fri, 17 Jul 2026 09:57:31 +0800 Subject: [PATCH 1/8] time: add IANA zoneinfo support --- vlib/time/README.md | 31 ++ vlib/time/operator.v | 6 + vlib/time/time.c.v | 3 + vlib/time/time.js.v | 3 + vlib/time/time.v | 39 ++- vlib/time/tzdata/tzdata.v | 83 +++++ vlib/time/tzdata/tzdata_test.v | 11 + vlib/time/tzdata/zoneinfo.zip | Bin 0 -> 408467 bytes vlib/time/zoneinfo.c.v | 618 +++++++++++++++++++++++++++++++++ vlib/time/zoneinfo.js.v | 56 +++ vlib/time/zoneinfo_test.v | 216 ++++++++++++ vlib/time/zoneinfo_windows.c.v | 264 ++++++++++++++ 12 files changed, 1329 insertions(+), 1 deletion(-) create mode 100644 vlib/time/tzdata/tzdata.v create mode 100644 vlib/time/tzdata/tzdata_test.v create mode 100644 vlib/time/tzdata/zoneinfo.zip create mode 100644 vlib/time/zoneinfo.c.v create mode 100644 vlib/time/zoneinfo.js.v create mode 100644 vlib/time/zoneinfo_test.v create mode 100644 vlib/time/zoneinfo_windows.c.v diff --git a/vlib/time/README.md b/vlib/time/README.md index 39a5ea9efa2d8f..527238fc3483b1 100644 --- a/vlib/time/README.md +++ b/vlib/time/README.md @@ -69,6 +69,37 @@ Omitted `month` and `day` values default to `1`, and out-of-range values panic. Use `t.is_zero()` to check whether a `time.Time` is still its zero value before formatting or serializing it. +IANA time zone data can be loaded by name and used to convert Unix timestamps +or UTC `Time` values to that location's calendar time. `load_location` searches +`ZONEINFO`, system zoneinfo paths, and V's installed `zoneinfo.zip`. Programs +that need embedded time zone data (no system zoneinfo, portable binaries) can +import `time.tzdata`. + +```v +import time +import time.tzdata as _ + +shanghai := time.load_location('Asia/Shanghai')! +local := time.unix(1_704_067_200).in(shanghai)! +assert local.format_ss() == '2024-01-01 08:00:00' +assert local.unix() == 1_704_067_200 +assert (local.zone()!).offset == 28_800 +``` + +IANA-zoned values keep `unix` as the absolute UTC epoch instant. Calendar +fields (`year`, `hour`, ...) are wall time in that location. Prefer +`t.location()` / `t.zone()` over the older `is_local` flag: `is_local` only +marks system-local wall time with a fixed process offset and is left `false` +on IANA-zoned `Time` values. + +The bundled `vlib/time/tzdata/zoneinfo.zip` is a store-only (uncompressed) zip +of IANA zoneinfo files for offline use via `import time.tzdata`. Refresh it +from a system zoneinfo tree when updating tzdata, for example: + +```sh +cd /usr/share/zoneinfo && zip -0 -r /path/to/v/vlib/time/tzdata/zoneinfo.zip . +``` + Another very useful feature of the `time` module is the stop watch, for when you want to measure short time periods, elapsed while you executed other tasks. [See](https://play.vlang.io/?query=f6c008bc34): diff --git a/vlib/time/operator.v b/vlib/time/operator.v index 66dcdb0bd4cbf9..01a4a0e816e582 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -3,6 +3,12 @@ module time // operator `==` returns true if provided time is equal to time @[inline] pub fn (t1 Time) == (t2 Time) bool { + if _ := t1.loc { + return t1.nanosecond == t2.nanosecond && t1.unix() == t2.unix() + } + if _ := t2.loc { + return t1.nanosecond == t2.nanosecond && t1.unix() == t2.unix() + } return t1.nanosecond == t2.nanosecond && t1.is_local == t2.is_local && t1.local_unix() == t2.local_unix() } diff --git a/vlib/time/time.c.v b/vlib/time/time.c.v index 883eb813d9d4c5..c91d58d283cc61 100644 --- a/vlib/time/time.c.v +++ b/vlib/time/time.c.v @@ -57,6 +57,9 @@ pub fn utc() Time { } fn time_with_unix(t Time) Time { + if _ := t.loc { + return t + } if t.unix != 0 { return t } diff --git a/vlib/time/time.js.v b/vlib/time/time.js.v index 88f32230f14b3f..0f2001f5e0a530 100644 --- a/vlib/time/time.js.v +++ b/vlib/time/time.js.v @@ -47,6 +47,9 @@ pub fn sleep(dur Duration) { } fn time_with_unix(t Time) Time { + if _ := t.loc { + return t + } if t.unix != 0 { return t } diff --git a/vlib/time/time.v b/vlib/time/time.v index df7d536119bab0..19e7e1a8e6bb32 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -38,6 +38,7 @@ pub const days_before = [ @[markused] pub struct Time { unix i64 + loc ?&Location pub: year int month int @@ -143,12 +144,29 @@ pub fn (t Time) smonth() string { // unix returns the UNIX time with second resolution. @[inline] pub fn (t Time) unix() i64 { + if _ := t.loc { + return t.unix + } return time_with_unix(t.local_to_utc()).unix } // local_unix returns the UNIX local time with second resolution. +// For IANA locations this is the absolute unix instant plus the zone offset +// active at that instant (wall-clock seconds since epoch in that location). @[inline] pub fn (t Time) local_unix() i64 { + if loc := t.loc { + // zone_at only fails for empty/corrupt locations; use the first zone + // offset rather than silently treating the instant as UTC. + zone_offset := loc.offset_at(t.unix) or { + if loc.zones.len > 0 { + loc.zones[0].offset + } else { + 0 + } + } + return t.unix + i64(zone_offset) + } return time_with_unix(t).unix } @@ -185,7 +203,8 @@ pub fn (t Time) add(duration_in_nanosecond Duration) Time { // ... so instead, handle the addition manually in parts ¯\_(ツ)_/¯ mut increased_time_nanosecond := i64(t.nanosecond) + duration_in_nanosecond.nanoseconds() // increased_time_second - mut increased_time_second := t.local_unix() + (increased_time_nanosecond / second) + base_unix := if _ := t.loc { t.unix } else { t.local_unix() } + mut increased_time_second := base_unix + (increased_time_nanosecond / second) increased_time_nanosecond = increased_time_nanosecond % second if increased_time_nanosecond < 0 { increased_time_second-- @@ -193,6 +212,18 @@ pub fn (t Time) add(duration_in_nanosecond Duration) Time { } res := unix_nanosecond(increased_time_second, int(increased_time_nanosecond)) + if loc := t.loc { + return loc.unix_nanosecond_to_local(res.unix, res.nanosecond) or { + // Keep the location even if projection fails so later operations + // still treat this value as an IANA-zoned instant. + Time{ + ...res + loc: loc + unix: res.unix + } + } + } + if t.is_local { // we need to reset unix to 0, because we don't know the offset // and we can't calculate it without it without causing infinite recursion @@ -441,6 +472,9 @@ pub fn offset() int { // local_to_utc converts the receiver `t` to the corresponding UTC time, if it contains local time. // If the receiver already does contain UTC time, it returns it unchanged. pub fn (t Time) local_to_utc() Time { + if _ := t.loc { + return unix_nanosecond(t.unix, t.nanosecond) + } if !t.is_local { return t } @@ -453,6 +487,9 @@ pub fn (t Time) local_to_utc() Time { // utc_to_local converts the receiver `u` to the corresponding local time, if it contains UTC time. // If the receiver already does contain local time, it returns it unchanged. pub fn (u Time) utc_to_local() Time { + if _ := u.loc { + return u + } if u.is_local { return u } diff --git a/vlib/time/tzdata/tzdata.v b/vlib/time/tzdata/tzdata.v new file mode 100644 index 00000000000000..865e9ba335f396 --- /dev/null +++ b/vlib/time/tzdata/tzdata.v @@ -0,0 +1,83 @@ +module tzdata + +import time + +const embedded_zoneinfo = $embed_file('zoneinfo.zip') + +fn init() { + time.register_zoneinfo_loader(load_from_embedded_zoneinfo) +} + +fn load_from_embedded_zoneinfo(name string) ![]u8 { + return read_uncompressed_zip_entry(embedded_zoneinfo.to_bytes(), name) +} + +fn read_uncompressed_zip_entry(data []u8, name string) ![]u8 { + eocd := find_zip_end_of_central_directory(data)! + entries := read_zip_u16(data, eocd + 10) + mut pos := int(read_zip_u32(data, eocd + 16)) + for _ in 0 .. entries { + if pos + 46 > data.len || read_zip_u32(data, pos) != 0x0201_4b50 { + return error('invalid embedded zoneinfo.zip') + } + method := read_zip_u16(data, pos + 10) + size := int(read_zip_u32(data, pos + 24)) + name_len := int(read_zip_u16(data, pos + 28)) + extra_len := int(read_zip_u16(data, pos + 30)) + comment_len := int(read_zip_u16(data, pos + 32)) + local_header := int(read_zip_u32(data, pos + 42)) + if pos + 46 + name_len > data.len { + return error('invalid embedded zoneinfo.zip') + } + entry_name := data[pos + 46..pos + 46 + name_len].bytestr() + pos += 46 + name_len + extra_len + comment_len + if entry_name != name { + continue + } + if method != 0 { + return error('unsupported compressed time zone entry "${name}"') + } + return read_zip_file_data(data, local_header, name, size) + } + return error('unknown time zone location "${name}"') +} + +fn read_zip_file_data(data []u8, pos int, name string, size int) ![]u8 { + if pos + 30 > data.len || read_zip_u32(data, pos) != 0x0403_4b50 { + return error('invalid embedded time zone entry "${name}"') + } + name_len := int(read_zip_u16(data, pos + 26)) + extra_len := int(read_zip_u16(data, pos + 28)) + data_start := pos + 30 + name_len + extra_len + data_end := data_start + size + if data_end > data.len { + return error('truncated embedded time zone entry "${name}"') + } + return data[data_start..data_end].clone() +} + +fn find_zip_end_of_central_directory(data []u8) !int { + max_comment_len := 65_535 + min_pos := if data.len > max_comment_len + 22 { data.len - max_comment_len - 22 } else { 0 } + for pos := data.len - 22; pos >= min_pos; pos-- { + if read_zip_u32(data, pos) == 0x0605_4b50 { + return pos + } + } + return error('invalid embedded zoneinfo.zip') +} + +fn read_zip_u16(data []u8, offset int) int { + if offset + 2 > data.len { + return 0 + } + return int(u16(data[offset]) | (u16(data[offset + 1]) << 8)) +} + +fn read_zip_u32(data []u8, offset int) u32 { + if offset + 4 > data.len { + return 0 + } + return u32(data[offset]) | (u32(data[offset + 1]) << 8) | (u32(data[offset + 2]) << 16) | (u32(data[ + offset + 3]) << 24) +} diff --git a/vlib/time/tzdata/tzdata_test.v b/vlib/time/tzdata/tzdata_test.v new file mode 100644 index 00000000000000..e4b36c61da8f74 --- /dev/null +++ b/vlib/time/tzdata/tzdata_test.v @@ -0,0 +1,11 @@ +import time +import time.tzdata as _ + +fn test_import_tzdata_registers_embedded_loader() { + loc := time.load_location('Asia/Shanghai')! + t := loc.unix_to_local(1_704_067_200)! + assert t.year == 2024 + assert t.month == 1 + assert t.day == 1 + assert t.hour == 8 +} diff --git a/vlib/time/tzdata/zoneinfo.zip b/vlib/time/tzdata/zoneinfo.zip new file mode 100644 index 0000000000000000000000000000000000000000..46c90a2b806a92fcd3291449fe448ce8db4d1753 GIT binary patch literal 408467 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 C$dJAO literal 0 HcmV?d00001 diff --git a/vlib/time/zoneinfo.c.v b/vlib/time/zoneinfo.c.v new file mode 100644 index 00000000000000..798097c7adcbad --- /dev/null +++ b/vlib/time/zoneinfo.c.v @@ -0,0 +1,618 @@ +@[has_globals] +module time + +import compress.szip +import os + +const zoneinfo_unix_sources = [ + '/usr/share/zoneinfo', + '/usr/share/lib/zoneinfo', + '/usr/lib/locale/TZ', + '/etc/zoneinfo', +]! + +const zoneinfo_vroot_zip = os.join_path(@VEXEROOT, 'vlib', 'time', 'tzdata', 'zoneinfo.zip') + +pub type ZoneinfoLoaderFn = fn (name string) ![]u8 + +__global zoneinfo_loaders = []ZoneinfoLoaderFn{} + +// register_zoneinfo_loader registers a fallback loader for IANA time zone data. +// Registered loaders are used after ZONEINFO, system zoneinfo paths, and V's +// installed zoneinfo.zip have been tried. +pub fn register_zoneinfo_loader(loader ZoneinfoLoaderFn) { + zoneinfo_loaders << loader +} + +// Zone describes one time zone rule in an IANA location. +pub struct Zone { +pub: + name string + offset int + is_dst bool +} + +struct ZoneTransition { + when i64 + index int +} + +// Location contains parsed IANA time zone data. +// +// A `Time` with a non-none `location()` carries IANA zone rules. Prefer that +// over the older `is_local` flag, which only means "system local wall time +// with a fixed process offset" and does not model DST transitions by name. +@[heap] +pub struct Location { +pub: + name string +mut: + zones []Zone + transitions []ZoneTransition + posix PosixZoneRule + has_posix bool +} + +struct PosixZoneRule { + std_name string + std_offset int + dst_name string + dst_offset int + start PosixRule + end PosixRule +} + +struct PosixRule { + month int + week int + weekday int + seconds int +} + +// load_location loads an IANA time zone location from ZONEINFO, system zoneinfo +// paths, V's installed zoneinfo.zip, or registered fallback loaders. +pub fn load_location(name string) !&Location { + if name == '' || name.contains('..') || name.starts_with('/') || name.starts_with('\\') { + return error('invalid time zone location "${name}"') + } + if name == 'Local' { + return local_location() + } + if name in ['UTC', 'Etc/UTC', 'Etc/GMT', 'GMT'] { + return &Location{ + name: name + zones: [Zone{ + name: 'UTC' + offset: 0 + }] + } + } + data := load_zoneinfo_data(name)! + return parse_tzif_location(name, data)! +} + +// zone_at returns the active zone rule for `unix_time`. +pub fn (loc &Location) zone_at(unix_time i64) !Zone { + if loc.zones.len == 0 { + return error('time zone location "${loc.name}" has no zone rules') + } + if loc.transitions.len == 0 { + return loc.zones[0] + } + if loc.has_posix && unix_time >= loc.transitions[loc.transitions.len - 1].when { + return loc.posix.zone_at(unix_time) + } + if unix_time < loc.transitions[0].when { + return loc.zones[loc.first_standard_zone_index()] + } + mut lo := 0 + mut hi := loc.transitions.len + for lo < hi { + mid := lo + (hi - lo) / 2 + if loc.transitions[mid].when <= unix_time { + lo = mid + 1 + } else { + hi = mid + } + } + tx := loc.transitions[lo - 1] + if tx.index < 0 || tx.index >= loc.zones.len { + return error('time zone location "${loc.name}" has an invalid transition') + } + return loc.zones[tx.index] +} + +// offset_at returns the UTC offset in seconds for `unix_time` in the location. +pub fn (loc &Location) offset_at(unix_time i64) !int { + return (loc.zone_at(unix_time)!).offset +} + +// unix_to_local resolves a Unix timestamp to a Time in the location. +pub fn (loc &Location) unix_to_local(unix_time i64) !Time { + return loc.unix_nanosecond_to_local(unix_time, 0) +} + +// unix_nanosecond_to_local resolves a Unix timestamp and nanosecond to a Time +// in the location. +// The returned `Time` keeps `unix` as the absolute UTC epoch instant; the +// calendar fields are the wall time in `loc`. `is_local` stays false. +pub fn (loc &Location) unix_nanosecond_to_local(unix_time i64, nanosecond int) !Time { + zone := loc.zone_at(unix_time)! + local := unix_nanosecond(unix_time + i64(zone.offset), nanosecond) + return Time{ + loc: loc + unix: unix_time + year: local.year + month: local.month + day: local.day + hour: local.hour + minute: local.minute + second: local.second + nanosecond: nanosecond + } +} + +// in resolves `t` as a Time in `loc`. +pub fn (t Time) in(loc &Location) !Time { + return loc.unix_nanosecond_to_local(t.unix(), t.nanosecond) +} + +// location returns the IANA location associated with `t`, if any. +pub fn (t Time) location() ?&Location { + return t.loc +} + +// zone returns the active zone rule for `t`, if it has an IANA location. +pub fn (t Time) zone() !Zone { + loc := t.loc or { return error('time has no IANA location') } + return loc.zone_at(t.unix()) +} + +fn local_location() !&Location { + $if windows { + return windows_local_location() + } + tz := os.getenv('TZ') + if tz != '' { + if tz.starts_with(':') { + path := tz[1..] + if path != '' { + if data := os.read_bytes(path) { + return parse_tzif_location('Local', data) or { fixed_local_location() } + } + if !os.is_abs_path(path) { + return load_location(path) or { fixed_local_location() } + } + } + } else { + return load_location(tz) or { fixed_local_location() } + } + } + localtime := '/etc/localtime' + if os.is_link(localtime) { + target := os.readlink(localtime) or { '' } + if target != '' { + if name := zoneinfo_name_from_path(target) { + return load_location(name) or { fixed_local_location() } + } + } + } + // Regular file or symlink whose target is not under .../zoneinfo/... + if os.exists(localtime) { + if data := os.read_bytes(localtime) { + return parse_tzif_location('Local', data) or { fixed_local_location() } + } + } + return fixed_local_location() +} + +fn fixed_local_location() &Location { + return &Location{ + name: 'Local' + zones: [Zone{ + name: 'Local' + offset: offset() + }] + } +} + +fn zoneinfo_name_from_path(path string) ?string { + marker := '/zoneinfo/' + index := path.index(marker) or { return none } + return path[index + marker.len..] +} + +fn (loc &Location) first_standard_zone_index() int { + for i, zone in loc.zones { + if !zone.is_dst { + return i + } + } + return 0 +} + +fn load_zoneinfo_data(name string) ![]u8 { + zoneinfo := os.getenv('ZONEINFO') + if zoneinfo != '' { + if data := load_zoneinfo_from_source(zoneinfo, name) { + return data + } + } + for source in platform_zoneinfo_sources() { + if data := load_zoneinfo_from_source(source, name) { + return data + } + } + for loader in zoneinfo_loaders { + if data := loader(name) { + return data + } + } + return error('unknown time zone location "${name}"') +} + +fn platform_zoneinfo_sources() []string { + mut sources := []string{} + $if !windows { + for source in zoneinfo_unix_sources { + sources << source + } + } + sources << zoneinfo_vroot_zip + return sources +} + +fn load_zoneinfo_from_source(source string, name string) ![]u8 { + if os.is_dir(source) { + return os.read_bytes(os.join_path(source, name)) + } + if os.is_file(source) { + return read_zoneinfo_zip_entry(source, name) + } + return error('time zone source "${source}" does not exist') +} + +fn read_zoneinfo_zip_entry(zip_path string, name string) ![]u8 { + mut zip := szip.open(zip_path, .no_compression, .read_only)! + defer { + zip.close() + } + // miniz open_entry can succeed with size 0 for missing names; treat that + // as unknown so callers keep searching other sources. + zip.open_entry(name) or { return error('unknown time zone location "${name}"') } + defer { + zip.close_entry() + } + size := int(zip.size()) + if size <= 0 { + return error('unknown time zone location "${name}"') + } + mut data := []u8{len: size} + zip.read_entry_buf(data.data, size)! + return data +} + +fn parse_tzif_location(name string, data []u8) !&Location { + mut pos := 0 + header := read_tzif_header(data, pos)! + pos += 44 + if header.version == `2` || header.version == `3` { + pos += tzif_data_size(header, 4) + header2 := read_tzif_header(data, pos)! + pos += 44 + return parse_tzif_data(name, data, pos, header2, 8)! + } + return parse_tzif_data(name, data, pos, header, 4)! +} + +struct TzifHeader { + version u8 + ttisgmt int + ttisstd int + leap int + time int + typ int + character int +} + +fn read_tzif_header(data []u8, offset int) !TzifHeader { + if offset + 44 > data.len || data[offset..offset + 4].bytestr() != 'TZif' { + return error('invalid TZif data') + } + return TzifHeader{ + version: data[offset + 4] + ttisgmt: read_be_i32(data, offset + 20) + ttisstd: read_be_i32(data, offset + 24) + leap: read_be_i32(data, offset + 28) + time: read_be_i32(data, offset + 32) + typ: read_be_i32(data, offset + 36) + character: read_be_i32(data, offset + 40) + } +} + +fn tzif_data_size(header TzifHeader, time_size int) int { + return header.time * time_size + header.time + header.typ * 6 + header.character + + header.leap * (time_size + 4) + header.ttisstd + header.ttisgmt +} + +fn parse_tzif_data(name string, data []u8, start int, header TzifHeader, time_size int) !&Location { + if header.typ <= 0 { + return error('time zone location "${name}" has no zone rules') + } + if start + tzif_data_size(header, time_size) > data.len { + return error('truncated TZif data for "${name}"') + } + mut pos := start + mut times := []i64{len: header.time} + for i in 0 .. header.time { + times[i] = if time_size == 8 { + read_be_i64(data, pos) + } else { + i64(read_be_i32(data, pos)) + } + pos += time_size + } + mut indices := []int{len: header.time} + for i in 0 .. header.time { + indices[i] = int(data[pos]) + pos++ + } + mut zone_offsets := []int{len: header.typ} + mut zone_is_dst := []bool{len: header.typ} + mut zone_abbr_indices := []int{len: header.typ} + for i in 0 .. header.typ { + zone_offsets[i] = read_be_i32(data, pos) + zone_is_dst[i] = data[pos + 4] != 0 + zone_abbr_indices[i] = int(data[pos + 5]) + pos += 6 + } + abbreviations := data[pos..pos + header.character] + pos += header.character + mut zones := []Zone{cap: header.typ} + for i in 0 .. header.typ { + zones << Zone{ + name: tzif_abbreviation(abbreviations, zone_abbr_indices[i]) + offset: zone_offsets[i] + is_dst: zone_is_dst[i] + } + } + mut transitions := []ZoneTransition{cap: header.time} + for i in 0 .. header.time { + if indices[i] >= zones.len { + return error('invalid TZif transition in "${name}"') + } + transitions << ZoneTransition{ + when: times[i] + index: indices[i] + } + } + pos += header.leap * (time_size + 4) + header.ttisstd + header.ttisgmt + posix, has_posix := parse_posix_tail(data, pos) + return &Location{ + name: name + zones: zones + transitions: transitions + posix: posix + has_posix: has_posix + } +} + +fn parse_posix_tail(data []u8, start int) (PosixZoneRule, bool) { + if start >= data.len || data[start] != `\n` { + return PosixZoneRule{}, false + } + mut end := start + 1 + for end < data.len && data[end] != `\n` { + end++ + } + if end <= start + 1 { + return PosixZoneRule{}, false + } + rule := parse_posix_zone_rule(data[start + 1..end].bytestr()) or { + return PosixZoneRule{}, false + } + return rule, true +} + +fn parse_posix_zone_rule(text string) !PosixZoneRule { + std_name, mut pos := parse_posix_name(text, 0)! + std_offset, next_pos := parse_posix_offset(text, pos)! + pos = next_pos + dst_name, dst_name_end := parse_posix_name(text, pos)! + pos = dst_name_end + mut dst_offset := std_offset + seconds_per_hour + if pos < text.len && text[pos] != `,` { + parsed_dst_offset, offset_end := parse_posix_offset(text, pos)! + dst_offset = parsed_dst_offset + pos = offset_end + } + if pos >= text.len || text[pos] != `,` { + return error('unsupported POSIX time zone rule "${text}"') + } + parts := text[pos + 1..].split(',') + if parts.len != 2 { + return error('unsupported POSIX time zone rule "${text}"') + } + return PosixZoneRule{ + std_name: std_name + std_offset: std_offset + dst_name: dst_name + dst_offset: dst_offset + start: parse_posix_rule(parts[0])! + end: parse_posix_rule(parts[1])! + } +} + +fn parse_posix_name(text string, start int) !(string, int) { + if start >= text.len { + return error('missing POSIX time zone name') + } + if text[start] == `<` { + mut pos := start + 1 + for pos < text.len && text[pos] != `>` { + pos++ + } + if pos >= text.len { + return error('unterminated POSIX time zone name') + } + return text[start + 1..pos], pos + 1 + } + mut pos := start + for pos < text.len && ((text[pos] >= `A` && text[pos] <= `Z`) + || (text[pos] >= `a` && text[pos] <= `z`)) { + pos++ + } + if pos == start { + return error('missing POSIX time zone name') + } + return text[start..pos], pos +} + +fn parse_posix_offset(text string, start int) !(int, int) { + mut pos := start + mut sign := -1 + if pos < text.len && text[pos] == `-` { + sign = 1 + pos++ + } else if pos < text.len && text[pos] == `+` { + pos++ + } + hour_value, hour_end := parse_posix_number(text, pos)! + pos = hour_end + mut minute_value := 0 + mut second_value := 0 + if pos < text.len && text[pos] == `:` { + minute_value, pos = parse_posix_number(text, pos + 1)! + } + if pos < text.len && text[pos] == `:` { + second_value, pos = parse_posix_number(text, pos + 1)! + } + return sign * (hour_value * seconds_per_hour + minute_value * seconds_per_minute + second_value), pos +} + +fn parse_posix_rule(text string) !PosixRule { + rule_parts := text.split('/') + date := rule_parts[0] + if !date.starts_with('M') { + return error('unsupported POSIX time zone date rule "${text}"') + } + date_parts := date[1..].split('.') + if date_parts.len != 3 { + return error('unsupported POSIX time zone date rule "${text}"') + } + return PosixRule{ + month: date_parts[0].int() + week: date_parts[1].int() + weekday: date_parts[2].int() + seconds: if rule_parts.len > 1 { + parse_posix_time(rule_parts[1])! + } else { + 2 * seconds_per_hour + } + } +} + +fn parse_posix_time(text string) !int { + parts := text.split(':') + if parts.len == 0 || parts.len > 3 { + return error('unsupported POSIX time "${text}"') + } + hour_value := parts[0].int() + minute_value := if parts.len > 1 { parts[1].int() } else { 0 } + second_value := if parts.len > 2 { parts[2].int() } else { 0 } + return hour_value * seconds_per_hour + minute_value * seconds_per_minute + second_value +} + +fn parse_posix_number(text string, start int) !(int, int) { + mut pos := start + for pos < text.len && text[pos] >= `0` && text[pos] <= `9` { + pos++ + } + if pos == start { + return error('missing POSIX number') + } + return text[start..pos].int(), pos +} + +fn (rule PosixZoneRule) zone_at(unix_time i64) Zone { + year := unix(unix_time).year + start := rule.transition_utc(year, rule.start, rule.std_offset) + end := rule.transition_utc(year, rule.end, rule.dst_offset) + is_dst := if start <= end { + unix_time >= start && unix_time < end + } else { + unix_time >= start || unix_time < end + } + if is_dst { + return Zone{ + name: rule.dst_name + offset: rule.dst_offset + is_dst: true + } + } + return Zone{ + name: rule.std_name + offset: rule.std_offset + } +} + +fn (rule PosixZoneRule) transition_utc(year int, date_rule PosixRule, offset_before int) i64 { + day := posix_month_week_day(year, date_rule) + hour_value := date_rule.seconds / seconds_per_hour + minute_value := (date_rule.seconds % seconds_per_hour) / seconds_per_minute + second_value := date_rule.seconds % seconds_per_minute + local := time_fields_to_unix(Time{ + year: year + month: date_rule.month + day: day + hour: hour_value + minute: minute_value + second: second_value + }) + return local - i64(offset_before) +} + +fn posix_month_week_day(year int, rule PosixRule) int { + first_weekday := day_of_week(year, rule.month, 1) % 7 + mut day := 1 + ((rule.weekday - first_weekday + 7) % 7) + (rule.week - 1) * 7 + days := days_in_month(rule.month, year) or { 31 } + if rule.week == 5 { + for day + 7 <= days { + day += 7 + } + } + return day +} + +fn tzif_abbreviation(data []u8, start int) string { + if start < 0 || start >= data.len { + return '' + } + mut end := start + for end < data.len && data[end] != 0 { + end++ + } + return data[start..end].bytestr() +} + +fn read_be_i32(data []u8, offset int) int { + mut value := u32(0) + for i in 0 .. 4 { + value = (value << 8) | u32(data[offset + i]) + } + if value & 0x8000_0000 != 0 { + return -int((~value) + 1) + } + return int(value) +} + +fn read_be_i64(data []u8, offset int) i64 { + mut value := u64(0) + for i in 0 .. 8 { + value = (value << 8) | u64(data[offset + i]) + } + if value & 0x8000_0000_0000_0000 != 0 { + return -i64((~value) + 1) + } + return i64(value) +} diff --git a/vlib/time/zoneinfo.js.v b/vlib/time/zoneinfo.js.v new file mode 100644 index 00000000000000..f3af99b4e8a49d --- /dev/null +++ b/vlib/time/zoneinfo.js.v @@ -0,0 +1,56 @@ +module time + +// Zone describes one time zone rule in an IANA location. +pub struct Zone { +pub: + name string + offset int + is_dst bool +} + +// Location contains parsed IANA time zone data. +pub struct Location { +pub: + name string +} + +// load_location loads an IANA time zone location. +pub fn load_location(name string) !&Location { + return error('IANA time zone locations are not supported by the JS backend') +} + +// zone_at returns the active zone rule for `unix_time`. +pub fn (loc &Location) zone_at(unix_time i64) !Zone { + return error('IANA time zone locations are not supported by the JS backend') +} + +// offset_at returns the UTC offset in seconds for `unix_time` in the location. +pub fn (loc &Location) offset_at(unix_time i64) !int { + return error('IANA time zone locations are not supported by the JS backend') +} + +// unix_to_local resolves a Unix timestamp to a Time in the location. +pub fn (loc &Location) unix_to_local(unix_time i64) !Time { + return error('IANA time zone locations are not supported by the JS backend') +} + +// unix_nanosecond_to_local resolves a Unix timestamp and nanosecond to a Time +// in the location. +pub fn (loc &Location) unix_nanosecond_to_local(unix_time i64, nanosecond int) !Time { + return error('IANA time zone locations are not supported by the JS backend') +} + +// in resolves `t` as a Time in `loc`. +pub fn (t Time) in(loc &Location) !Time { + return error('IANA time zone locations are not supported by the JS backend') +} + +// location returns the IANA location associated with `t`, if any. +pub fn (t Time) location() ?&Location { + return t.loc +} + +// zone returns the active zone rule for `t`, if it has an IANA location. +pub fn (t Time) zone() !Zone { + return error('IANA time zone locations are not supported by the JS backend') +} diff --git a/vlib/time/zoneinfo_test.v b/vlib/time/zoneinfo_test.v new file mode 100644 index 00000000000000..2cdc02ca7d3f46 --- /dev/null +++ b/vlib/time/zoneinfo_test.v @@ -0,0 +1,216 @@ +import os +import time + +fn test_load_location_utc() { + loc := time.load_location('UTC')! + assert loc.offset_at(1_704_067_200)! == 0 + t := loc.unix_to_local(1_704_067_200)! + assert t.unix() == 1_704_067_200 + t_loc := t.location() or { panic('missing location') } + assert t_loc.name == 'UTC' + assert (t.zone()!).name == 'UTC' + assert t.year == 2024 + assert t.month == 1 + assert t.day == 1 + assert t.hour == 0 +} + +fn test_load_location_from_embedded_zoneinfo_zip() { + loc := time.load_location('Asia/Shanghai')! + assert loc.offset_at(1_704_067_200)! == 28_800 + t := loc.unix_to_local(1_704_067_200)! + assert t.unix() == 1_704_067_200 + t_loc := t.location() or { panic('missing location') } + assert t_loc.name == 'Asia/Shanghai' + assert (t.zone()!).offset == 28_800 + assert t.year == 2024 + assert t.month == 1 + assert t.day == 1 + assert t.hour == 8 + assert t.minute == 0 + assert t.second == 0 +} + +fn test_load_location_from_zoneinfo_env_zip() { + old_zoneinfo := os.getenv_opt('ZONEINFO') + zoneinfo_zip := os.join_path(@VEXEROOT, 'vlib', 'time', 'tzdata', 'zoneinfo.zip') + os.setenv('ZONEINFO', zoneinfo_zip, true) + defer { + if old := old_zoneinfo { + os.setenv('ZONEINFO', old, true) + } else { + os.unsetenv('ZONEINFO') + } + } + loc := time.load_location('Pacific/Niue')! + t := loc.unix_to_local(1_704_067_200)! + assert t.year == 2023 + assert t.month == 12 + assert t.day == 31 + assert t.hour == 13 + assert t.unix() == 1_704_067_200 +} + +fn test_load_location_with_dst_transition() { + loc := time.load_location('America/New_York')! + before := loc.unix_to_local(1_710_053_940)! + after := loc.unix_to_local(1_710_054_000)! + assert loc.offset_at(1_705_320_000)! == -18_000 + assert loc.offset_at(1_721_044_800)! == -14_400 + before_zone := before.zone()! + assert before_zone.name == 'EST' + assert before_zone.is_dst == false + assert before.year == 2024 + assert before.month == 3 + assert before.day == 10 + assert before.hour == 1 + assert before.minute == 59 + assert after.year == 2024 + assert after.month == 3 + assert after.day == 10 + assert after.hour == 3 + assert after.minute == 0 + after_zone := after.zone()! + assert after_zone.name == 'EDT' + assert after_zone.is_dst == true +} + +fn test_load_location_local() { + loc := time.load_location('Local')! + t := time.utc().in(loc)! + t_loc := t.location() or { panic('missing location') } + assert t_loc.name == 'Local' || t_loc.name.len > 0 + assert t.zone()!.name.len > 0 +} + +fn test_time_in_keeps_nanosecond() { + loc := time.load_location('Asia/Shanghai')! + utc_time := time.unix_nanosecond(1_704_067_200, 123_456_789) + local := utc_time.in(loc)! + assert local.unix() == 1_704_067_200 + local_loc := local.location() or { panic('missing location') } + assert local_loc.name == 'Asia/Shanghai' + assert local.year == 2024 + assert local.month == 1 + assert local.day == 1 + assert local.hour == 8 + assert local.nanosecond == 123_456_789 +} + +fn test_location_time_add_keeps_instant_and_location() { + loc := time.load_location('Asia/Shanghai')! + local := time.unix_nanosecond(1_704_067_200, 123_456_789).in(loc)! + added := local.add(1 * time.second) + added_loc := added.location() or { panic('missing location') } + assert added_loc.name == 'Asia/Shanghai' + assert added.unix() == 1_704_067_201 + assert added.year == 2024 + assert added.month == 1 + assert added.day == 1 + assert added.hour == 8 + assert added.minute == 0 + assert added.second == 1 + assert added.nanosecond == 123_456_789 +} + +fn test_location_time_add_seconds_keeps_unix_epoch() { + loc := time.load_location('Asia/Shanghai')! + local := time.unix(0).in(loc)! + added := local.add_seconds(1) + added_loc := added.location() or { panic('missing location') } + assert added_loc.name == 'Asia/Shanghai' + assert added.unix() == 1 + assert added.year == 1970 + assert added.month == 1 + assert added.day == 1 + assert added.hour == 8 + assert added.minute == 0 + assert added.second == 1 +} + +fn test_location_time_equals_same_instant() { + loc := time.load_location('Asia/Shanghai')! + utc_time := time.unix_nanosecond(1_704_067_200, 123_456_789) + local := utc_time.in(loc)! + assert utc_time == local +} + +fn test_invalid_location_name() { + if _ := time.load_location('../UTC') { + assert false + } else { + assert err.msg().contains('invalid time zone location') + } + if _ := time.load_location('/UTC') { + assert false + } else { + assert err.msg().contains('invalid time zone location') + } + if _ := time.load_location('') { + assert false + } else { + assert err.msg().contains('invalid time zone location') + } +} + +fn test_load_location_southern_hemisphere_dst() { + loc := time.load_location('America/Santiago')! + // Southern-hemisphere DST: offset is larger (less negative) in summer. + summer := loc.zone_at(1_709_251_200)! // 2024-02-29 21:00 UTC + winter := loc.zone_at(1_719_792_000)! // 2024-06-30 20:00 UTC + assert summer.is_dst == true + assert summer.offset == -10_800 + assert winter.is_dst == false + assert winter.offset == -14_400 + summer_local := loc.unix_to_local(1_709_251_200)! + assert summer_local.year == 2024 + assert summer_local.month == 2 + assert summer_local.day == 29 + assert summer_local.hour == 21 +} + +fn test_load_location_posix_future_dst() { + // Beyond the last packed transition, rules come from the POSIX TZ tail. + loc := time.load_location('Europe/London')! + winter := loc.zone_at(2_524_608_000)! // 2050-01-01 00:00 UTC + summer := loc.zone_at(2_540_246_400)! // 2050-07-01 00:00 UTC + assert winter.offset == 0 + assert winter.is_dst == false + assert winter.name == 'GMT' + assert summer.offset == 3_600 + assert summer.is_dst == true + assert summer.name == 'BST' + summer_local := loc.unix_to_local(2_540_246_400)! + assert summer_local.year == 2050 + assert summer_local.month == 7 + assert summer_local.day == 1 + assert summer_local.hour == 1 +} + +fn test_fixed_offset_etc_gmt() { + // Note: Etc/GMT+N uses POSIX sign (opposite of civil intuition). + loc := time.load_location('Etc/GMT+5')! + assert loc.offset_at(1_704_067_200)! == -18_000 + t := loc.unix_to_local(1_704_067_200)! + assert t.year == 2023 + assert t.month == 12 + assert t.day == 31 + assert t.hour == 19 +} + +fn test_location_time_is_not_is_local() { + loc := time.load_location('Asia/Shanghai')! + local := time.unix(1_704_067_200).in(loc)! + assert local.is_local == false + assert local.location() != none + // Same absolute instant as UTC; equality compares the unix epoch. + assert local == time.unix(1_704_067_200) +} + +fn test_unknown_location_name() { + if _ := time.load_location('Not/ARealZone') { + assert false + } else { + assert err.msg().contains('unknown time zone location') + } +} diff --git a/vlib/time/zoneinfo_windows.c.v b/vlib/time/zoneinfo_windows.c.v new file mode 100644 index 00000000000000..c7e7eb667138e1 --- /dev/null +++ b/vlib/time/zoneinfo_windows.c.v @@ -0,0 +1,264 @@ +module time + +struct C.TIME_ZONE_INFORMATION { +pub mut: + bias i32 + standard_name [32]u16 + standard_date SystemTime + standard_bias i32 + daylight_name [32]u16 + daylight_date SystemTime + daylight_bias i32 +} + +fn C.GetTimeZoneInformation(&C.TIME_ZONE_INFORMATION) u32 + +fn windows_local_location() !&Location { + mut info := C.TIME_ZONE_INFORMATION{} + C.GetTimeZoneInformation(&info) + std_name := unsafe { string_from_wide(&u16(&info.standard_name[0])) } + dst_name := unsafe { string_from_wide(&u16(&info.daylight_name[0])) } + abbr := windows_abbr(std_name) + std_abbr := if abbr.std != '' { abbr.std } else { windows_abbr_from_name(std_name) } + dst_abbr := if abbr.dst != '' { abbr.dst } else { windows_abbr_from_name(dst_name) } + if info.daylight_date.month == 0 || info.standard_date.month == 0 || dst_name == '' { + return &Location{ + name: 'Local' + zones: [ + Zone{ + name: if std_abbr == '' { 'Local' } else { std_abbr } + offset: -int(info.bias) * seconds_per_minute + }, + ] + } + } + std_offset := -int(info.bias + info.standard_bias) * seconds_per_minute + dst_offset := -int(info.bias + info.daylight_bias) * seconds_per_minute + std_zone := Zone{ + name: if std_abbr == '' { 'Standard' } else { std_abbr } + offset: std_offset + } + dst_zone := Zone{ + name: if dst_abbr == '' { 'Daylight' } else { dst_abbr } + offset: dst_offset + is_dst: true + } + mut loc := &Location{ + name: 'Local' + zones: [std_zone, dst_zone] + } + current_year := utc().year + for year in current_year - 100 .. current_year + 101 { + dst_start := windows_transition_utc(year, info.daylight_date, std_offset) + std_start := windows_transition_utc(year, info.standard_date, dst_offset) + if dst_start < std_start { + loc.transitions << ZoneTransition{ + when: dst_start + index: 1 + } + loc.transitions << ZoneTransition{ + when: std_start + index: 0 + } + } else { + loc.transitions << ZoneTransition{ + when: std_start + index: 0 + } + loc.transitions << ZoneTransition{ + when: dst_start + index: 1 + } + } + } + return loc +} + +fn windows_transition_utc(year int, st SystemTime, offset_before int) i64 { + day := windows_month_week_day(year, int(st.month), int(st.day), int(st.day_of_week)) + local := time_fields_to_unix(Time{ + year: year + month: int(st.month) + day: day + hour: int(st.hour) + minute: int(st.minute) + second: int(st.second) + }) + return local - i64(offset_before) +} + +fn windows_month_week_day(year int, month int, week int, weekday int) int { + first_weekday := day_of_week(year, month, 1) % 7 + mut day := 1 + ((weekday - first_weekday + 7) % 7) + (week - 1) * 7 + days := days_in_month(month, year) or { 31 } + if week == 5 { + for day + 7 <= days { + day += 7 + } + } + return day +} + +struct WindowsAbbr { + std string + dst string +} + +fn windows_abbr(name string) WindowsAbbr { + return windows_abbrs[name] or { WindowsAbbr{} } +} + +fn windows_abbr_from_name(name string) string { + mut out := []u8{} + for c in name { + if c >= `A` && c <= `Z` { + out << u8(c) + } + } + if out.len == 0 { + return name + } + return out.bytestr() +} + +const windows_abbrs = { + 'Egypt Standard Time': WindowsAbbr{'EET', 'EEST'} + 'Morocco Standard Time': WindowsAbbr{'+00', '+01'} + 'South Africa Standard Time': WindowsAbbr{'SAST', 'SAST'} + 'South Sudan Standard Time': WindowsAbbr{'CAT', 'CAT'} + 'Sudan Standard Time': WindowsAbbr{'CAT', 'CAT'} + 'W. Central Africa Standard Time': WindowsAbbr{'WAT', 'WAT'} + 'E. Africa Standard Time': WindowsAbbr{'EAT', 'EAT'} + 'Sao Tome Standard Time': WindowsAbbr{'GMT', 'GMT'} + 'Libya Standard Time': WindowsAbbr{'EET', 'EET'} + 'Namibia Standard Time': WindowsAbbr{'CAT', 'CAT'} + 'Aleutian Standard Time': WindowsAbbr{'HST', 'HDT'} + 'Alaskan Standard Time': WindowsAbbr{'AKST', 'AKDT'} + 'Tocantins Standard Time': WindowsAbbr{'-03', '-03'} + 'Paraguay Standard Time': WindowsAbbr{'-04', '-03'} + 'Bahia Standard Time': WindowsAbbr{'-03', '-03'} + 'SA Pacific Standard Time': WindowsAbbr{'-05', '-05'} + 'Argentina Standard Time': WindowsAbbr{'-03', '-03'} + 'Eastern Standard Time (Mexico)': WindowsAbbr{'EST', 'EST'} + 'Venezuela Standard Time': WindowsAbbr{'-04', '-04'} + 'SA Eastern Standard Time': WindowsAbbr{'-03', '-03'} + 'Central Standard Time': WindowsAbbr{'CST', 'CDT'} + 'Central Brazilian Standard Time': WindowsAbbr{'-04', '-04'} + 'Mountain Standard Time': WindowsAbbr{'MST', 'MDT'} + 'Greenland Standard Time': WindowsAbbr{'-02', '-01'} + 'Turks And Caicos Standard Time': WindowsAbbr{'EST', 'EDT'} + 'Central America Standard Time': WindowsAbbr{'CST', 'CST'} + 'Atlantic Standard Time': WindowsAbbr{'AST', 'ADT'} + 'Cuba Standard Time': WindowsAbbr{'CST', 'CDT'} + 'US Eastern Standard Time': WindowsAbbr{'EST', 'EDT'} + 'SA Western Standard Time': WindowsAbbr{'-04', '-04'} + 'Pacific Standard Time': WindowsAbbr{'PST', 'PDT'} + 'Mountain Standard Time (Mexico)': WindowsAbbr{'MST', 'MST'} + 'Central Standard Time (Mexico)': WindowsAbbr{'CST', 'CST'} + 'Saint Pierre Standard Time': WindowsAbbr{'-03', '-02'} + 'Montevideo Standard Time': WindowsAbbr{'-03', '-03'} + 'Eastern Standard Time': WindowsAbbr{'EST', 'EDT'} + 'US Mountain Standard Time': WindowsAbbr{'MST', 'MST'} + 'Haiti Standard Time': WindowsAbbr{'EST', 'EDT'} + 'Magallanes Standard Time': WindowsAbbr{'-03', '-03'} + 'Canada Central Standard Time': WindowsAbbr{'CST', 'CST'} + 'Pacific SA Standard Time': WindowsAbbr{'-04', '-03'} + 'E. South America Standard Time': WindowsAbbr{'-03', '-03'} + 'Newfoundland Standard Time': WindowsAbbr{'NST', 'NDT'} + 'Pacific Standard Time (Mexico)': WindowsAbbr{'PST', 'PDT'} + 'Yukon Standard Time': WindowsAbbr{'MST', 'MST'} + 'Jordan Standard Time': WindowsAbbr{'+03', '+03'} + 'Arabic Standard Time': WindowsAbbr{'+03', '+03'} + 'Azerbaijan Standard Time': WindowsAbbr{'+04', '+04'} + 'SE Asia Standard Time': WindowsAbbr{'+07', '+07'} + 'Altai Standard Time': WindowsAbbr{'+07', '+07'} + 'Middle East Standard Time': WindowsAbbr{'EET', 'EEST'} + 'Central Asia Standard Time': WindowsAbbr{'+06', '+06'} + 'India Standard Time': WindowsAbbr{'IST', 'IST'} + 'Transbaikal Standard Time': WindowsAbbr{'+09', '+09'} + 'Sri Lanka Standard Time': WindowsAbbr{'+0530', '+0530'} + 'Syria Standard Time': WindowsAbbr{'+03', '+03'} + 'Bangladesh Standard Time': WindowsAbbr{'+06', '+06'} + 'Arabian Standard Time': WindowsAbbr{'+04', '+04'} + 'West Bank Standard Time': WindowsAbbr{'EET', 'EEST'} + 'W. Mongolia Standard Time': WindowsAbbr{'+07', '+07'} + 'North Asia East Standard Time': WindowsAbbr{'+08', '+08'} + 'Israel Standard Time': WindowsAbbr{'IST', 'IDT'} + 'Afghanistan Standard Time': WindowsAbbr{'+0430', '+0430'} + 'Russia Time Zone 11': WindowsAbbr{'+12', '+12'} + 'Pakistan Standard Time': WindowsAbbr{'PKT', 'PKT'} + 'Nepal Standard Time': WindowsAbbr{'+0545', '+0545'} + 'North Asia Standard Time': WindowsAbbr{'+07', '+07'} + 'Magadan Standard Time': WindowsAbbr{'+11', '+11'} + 'N. Central Asia Standard Time': WindowsAbbr{'+07', '+07'} + 'Omsk Standard Time': WindowsAbbr{'+06', '+06'} + 'North Korea Standard Time': WindowsAbbr{'KST', 'KST'} + 'Qyzylorda Standard Time': WindowsAbbr{'+05', '+05'} + 'Myanmar Standard Time': WindowsAbbr{'+0630', '+0630'} + 'Arab Standard Time': WindowsAbbr{'+03', '+03'} + 'Sakhalin Standard Time': WindowsAbbr{'+11', '+11'} + 'Korea Standard Time': WindowsAbbr{'KST', 'KST'} + 'China Standard Time': WindowsAbbr{'CST', 'CST'} + 'Singapore Standard Time': WindowsAbbr{'+08', '+08'} + 'Russia Time Zone 10': WindowsAbbr{'+11', '+11'} + 'Taipei Standard Time': WindowsAbbr{'CST', 'CST'} + 'West Asia Standard Time': WindowsAbbr{'+05', '+05'} + 'Georgian Standard Time': WindowsAbbr{'+04', '+04'} + 'Iran Standard Time': WindowsAbbr{'+0330', '+0330'} + 'Tokyo Standard Time': WindowsAbbr{'JST', 'JST'} + 'Tomsk Standard Time': WindowsAbbr{'+07', '+07'} + 'Ulaanbaatar Standard Time': WindowsAbbr{'+08', '+08'} + 'Vladivostok Standard Time': WindowsAbbr{'+10', '+10'} + 'Yakutsk Standard Time': WindowsAbbr{'+09', '+09'} + 'Ekaterinburg Standard Time': WindowsAbbr{'+05', '+05'} + 'Caucasus Standard Time': WindowsAbbr{'+04', '+04'} + 'Azores Standard Time': WindowsAbbr{'-01', '+00'} + 'Cape Verde Standard Time': WindowsAbbr{'-01', '-01'} + 'Greenwich Standard Time': WindowsAbbr{'GMT', 'GMT'} + 'Cen. Australia Standard Time': WindowsAbbr{'ACST', 'ACDT'} + 'E. Australia Standard Time': WindowsAbbr{'AEST', 'AEST'} + 'AUS Central Standard Time': WindowsAbbr{'ACST', 'ACST'} + 'Aus Central W. Standard Time': WindowsAbbr{'+0845', '+0845'} + 'Tasmania Standard Time': WindowsAbbr{'AEST', 'AEDT'} + 'Lord Howe Standard Time': WindowsAbbr{'+1030', '+11'} + 'W. Australia Standard Time': WindowsAbbr{'AWST', 'AWST'} + 'AUS Eastern Standard Time': WindowsAbbr{'AEST', 'AEDT'} + 'UTC-11': WindowsAbbr{'-11', '-11'} + 'Dateline Standard Time': WindowsAbbr{'-12', '-12'} + 'UTC-02': WindowsAbbr{'-02', '-02'} + 'UTC-08': WindowsAbbr{'-08', '-08'} + 'UTC-09': WindowsAbbr{'-09', '-09'} + 'UTC+12': WindowsAbbr{'+12', '+12'} + 'UTC+13': WindowsAbbr{'+13', '+13'} + 'UTC': WindowsAbbr{'UTC', 'UTC'} + 'Astrakhan Standard Time': WindowsAbbr{'+04', '+04'} + 'W. Europe Standard Time': WindowsAbbr{'CET', 'CEST'} + 'GTB Standard Time': WindowsAbbr{'EET', 'EEST'} + 'Central Europe Standard Time': WindowsAbbr{'CET', 'CEST'} + 'E. Europe Standard Time': WindowsAbbr{'EET', 'EEST'} + 'Turkey Standard Time': WindowsAbbr{'+03', '+03'} + 'Kaliningrad Standard Time': WindowsAbbr{'EET', 'EET'} + 'FLE Standard Time': WindowsAbbr{'EET', 'EEST'} + 'GMT Standard Time': WindowsAbbr{'GMT', 'BST'} + 'Belarus Standard Time': WindowsAbbr{'+03', '+03'} + 'Russian Standard Time': WindowsAbbr{'MSK', 'MSK'} + 'Romance Standard Time': WindowsAbbr{'CET', 'CEST'} + 'Russia Time Zone 3': WindowsAbbr{'+04', '+04'} + 'Saratov Standard Time': WindowsAbbr{'+04', '+04'} + 'Volgograd Standard Time': WindowsAbbr{'MSK', 'MSK'} + 'Central European Standard Time': WindowsAbbr{'CET', 'CEST'} + 'Mauritius Standard Time': WindowsAbbr{'+04', '+04'} + 'Samoa Standard Time': WindowsAbbr{'+13', '+13'} + 'New Zealand Standard Time': WindowsAbbr{'NZST', 'NZDT'} + 'Bougainville Standard Time': WindowsAbbr{'+11', '+11'} + 'Chatham Islands Standard Time': WindowsAbbr{'+1245', '+1345'} + 'Easter Island Standard Time': WindowsAbbr{'-06', '-05'} + 'Fiji Standard Time': WindowsAbbr{'+12', '+12'} + 'Central Pacific Standard Time': WindowsAbbr{'+11', '+11'} + 'Hawaiian Standard Time': WindowsAbbr{'HST', 'HST'} + 'Line Islands Standard Time': WindowsAbbr{'+14', '+14'} + 'Marquesas Standard Time': WindowsAbbr{'-0930', '-0930'} + 'Norfolk Standard Time': WindowsAbbr{'+11', '+12'} + 'West Pacific Standard Time': WindowsAbbr{'+10', '+10'} + 'Tonga Standard Time': WindowsAbbr{'+13', '+13'} +} From e432a0d57cfaabb6daa9d63fc5768ffccc4ba725 Mon Sep 17 00:00:00 2001 From: weigang Date: Sat, 18 Jul 2026 21:56:21 +0800 Subject: [PATCH 2/8] time: fix zoneinfo CI portability --- vlib/time/time.v | 2 +- vlib/time/time_windows.c.v | 2 +- vlib/time/zoneinfo.c.v | 59 ++-------------------------------- vlib/time/zoneinfo.js.v | 2 +- vlib/time/zoneinfo_nix.c.v | 54 +++++++++++++++++++++++++++++++ vlib/time/zoneinfo_windows.c.v | 8 ++--- 6 files changed, 63 insertions(+), 64 deletions(-) create mode 100644 vlib/time/zoneinfo_nix.c.v diff --git a/vlib/time/time.v b/vlib/time/time.v index 19e7e1a8e6bb32..9ae0e57aeb70f7 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -38,7 +38,7 @@ pub const days_before = [ @[markused] pub struct Time { unix i64 - loc ?&Location + loc ?Location pub: year int month int diff --git a/vlib/time/time_windows.c.v b/vlib/time/time_windows.c.v index 8048c7b62dc2e7..111aab8ac3b0d0 100644 --- a/vlib/time/time_windows.c.v +++ b/vlib/time/time_windows.c.v @@ -37,7 +37,7 @@ fn C.GetSystemTimeAsFileTime(lpSystemTimeAsFileTime &C._FILETIME) fn C.FileTimeToSystemTime(lpFileTime &C._FILETIME, lpSystemTime &SystemTime) -fn C.SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation &C.TIME_ZONE_INFORMATION, lpUniversalTime &SystemTime, +fn C.SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation voidptr, lpUniversalTime &SystemTime, lpLocalTime &SystemTime) fn C.localtime_s(t &C.time_t, tm &C.tm) diff --git a/vlib/time/zoneinfo.c.v b/vlib/time/zoneinfo.c.v index 798097c7adcbad..85801a6dfe135b 100644 --- a/vlib/time/zoneinfo.c.v +++ b/vlib/time/zoneinfo.c.v @@ -42,7 +42,6 @@ struct ZoneTransition { // A `Time` with a non-none `location()` carries IANA zone rules. Prefer that // over the older `is_local` flag, which only means "system local wall time // with a fixed process offset" and does not model DST transitions by name. -@[heap] pub struct Location { pub: name string @@ -140,7 +139,7 @@ pub fn (loc &Location) unix_nanosecond_to_local(unix_time i64, nanosecond int) ! zone := loc.zone_at(unix_time)! local := unix_nanosecond(unix_time + i64(zone.offset), nanosecond) return Time{ - loc: loc + loc: *loc unix: unix_time year: local.year month: local.month @@ -158,7 +157,7 @@ pub fn (t Time) in(loc &Location) !Time { } // location returns the IANA location associated with `t`, if any. -pub fn (t Time) location() ?&Location { +pub fn (t Time) location() ?Location { return t.loc } @@ -168,60 +167,6 @@ pub fn (t Time) zone() !Zone { return loc.zone_at(t.unix()) } -fn local_location() !&Location { - $if windows { - return windows_local_location() - } - tz := os.getenv('TZ') - if tz != '' { - if tz.starts_with(':') { - path := tz[1..] - if path != '' { - if data := os.read_bytes(path) { - return parse_tzif_location('Local', data) or { fixed_local_location() } - } - if !os.is_abs_path(path) { - return load_location(path) or { fixed_local_location() } - } - } - } else { - return load_location(tz) or { fixed_local_location() } - } - } - localtime := '/etc/localtime' - if os.is_link(localtime) { - target := os.readlink(localtime) or { '' } - if target != '' { - if name := zoneinfo_name_from_path(target) { - return load_location(name) or { fixed_local_location() } - } - } - } - // Regular file or symlink whose target is not under .../zoneinfo/... - if os.exists(localtime) { - if data := os.read_bytes(localtime) { - return parse_tzif_location('Local', data) or { fixed_local_location() } - } - } - return fixed_local_location() -} - -fn fixed_local_location() &Location { - return &Location{ - name: 'Local' - zones: [Zone{ - name: 'Local' - offset: offset() - }] - } -} - -fn zoneinfo_name_from_path(path string) ?string { - marker := '/zoneinfo/' - index := path.index(marker) or { return none } - return path[index + marker.len..] -} - fn (loc &Location) first_standard_zone_index() int { for i, zone in loc.zones { if !zone.is_dst { diff --git a/vlib/time/zoneinfo.js.v b/vlib/time/zoneinfo.js.v index f3af99b4e8a49d..3ff393c93205bb 100644 --- a/vlib/time/zoneinfo.js.v +++ b/vlib/time/zoneinfo.js.v @@ -46,7 +46,7 @@ pub fn (t Time) in(loc &Location) !Time { } // location returns the IANA location associated with `t`, if any. -pub fn (t Time) location() ?&Location { +pub fn (t Time) location() ?Location { return t.loc } diff --git a/vlib/time/zoneinfo_nix.c.v b/vlib/time/zoneinfo_nix.c.v new file mode 100644 index 00000000000000..9930ec51192c39 --- /dev/null +++ b/vlib/time/zoneinfo_nix.c.v @@ -0,0 +1,54 @@ +module time + +import os + +fn local_location() !&Location { + tz := os.getenv('TZ') + if tz != '' { + if tz.starts_with(':') { + path := tz[1..] + if path != '' { + if data := os.read_bytes(path) { + return parse_tzif_location('Local', data) or { fixed_local_location() } + } + if !os.is_abs_path(path) { + return load_location(path) or { fixed_local_location() } + } + } + } else { + return load_location(tz) or { fixed_local_location() } + } + } + localtime := '/etc/localtime' + if os.is_link(localtime) { + target := os.readlink(localtime) or { '' } + if target != '' { + if name := zoneinfo_name_from_path(target) { + return load_location(name) or { fixed_local_location() } + } + } + } + // Regular file or symlink whose target is not under .../zoneinfo/... + if os.exists(localtime) { + if data := os.read_bytes(localtime) { + return parse_tzif_location('Local', data) or { fixed_local_location() } + } + } + return fixed_local_location() +} + +fn fixed_local_location() &Location { + return &Location{ + name: 'Local' + zones: [Zone{ + name: 'Local' + offset: offset() + }] + } +} + +fn zoneinfo_name_from_path(path string) ?string { + marker := '/zoneinfo/' + index := path.index(marker) or { return none } + return path[index + marker.len..] +} diff --git a/vlib/time/zoneinfo_windows.c.v b/vlib/time/zoneinfo_windows.c.v index c7e7eb667138e1..2bf5eef606eb4c 100644 --- a/vlib/time/zoneinfo_windows.c.v +++ b/vlib/time/zoneinfo_windows.c.v @@ -1,6 +1,6 @@ module time -struct C.TIME_ZONE_INFORMATION { +struct TimeZoneInformation { pub mut: bias i32 standard_name [32]u16 @@ -11,10 +11,10 @@ pub mut: daylight_bias i32 } -fn C.GetTimeZoneInformation(&C.TIME_ZONE_INFORMATION) u32 +fn C.GetTimeZoneInformation(&TimeZoneInformation) u32 -fn windows_local_location() !&Location { - mut info := C.TIME_ZONE_INFORMATION{} +fn local_location() !&Location { + mut info := TimeZoneInformation{} C.GetTimeZoneInformation(&info) std_name := unsafe { string_from_wide(&u16(&info.standard_name[0])) } dst_name := unsafe { string_from_wide(&u16(&info.daylight_name[0])) } From b551330bbd3529750b3cbbbcaf409fe6dc69ef6b Mon Sep 17 00:00:00 2001 From: weigang Date: Sun, 19 Jul 2026 06:20:48 +0800 Subject: [PATCH 3/8] time: address zoneinfo review feedback --- vlib/time/format.v | 32 +++++--- vlib/time/tzdata/{tzdata.v => tzdata.c.v} | 0 vlib/time/tzdata/tzdata.js.v | 4 + vlib/time/tzdata/tzdata.wasm.v | 4 + vlib/time/zoneinfo.c.v | 97 ++++++++++++++++++----- vlib/time/zoneinfo.js.v | 8 ++ vlib/time/zoneinfo.wasm.v | 64 +++++++++++++++ vlib/time/zoneinfo_nix.c.v | 2 +- vlib/time/zoneinfo_test.v | 35 ++++++++ vlib/time/zoneinfo_windows.c.v | 28 ++++++- 10 files changed, 236 insertions(+), 38 deletions(-) rename vlib/time/tzdata/{tzdata.v => tzdata.c.v} (100%) create mode 100644 vlib/time/tzdata/tzdata.js.v create mode 100644 vlib/time/tzdata/tzdata.wasm.v create mode 100644 vlib/time/zoneinfo.wasm.v diff --git a/vlib/time/format.v b/vlib/time/format.v index 39ce7907013d9d..841beea032ba94 100644 --- a/vlib/time/format.v +++ b/vlib/time/format.v @@ -184,7 +184,7 @@ pub fn (t Time) format_rfc3339() string { } t_ := time_with_unix(t) - if t_.is_local { + if t_.is_local || t_.location() != none { utc_time := t_.local_to_utc() int_to_byte_array_no_pad(utc_time.year, mut buf, 4) int_to_byte_array_no_pad(utc_time.month, mut buf, 7) @@ -217,7 +217,7 @@ pub fn (t Time) format_rfc3339_micro() string { } t_ := time_with_unix(t) - if t_.is_local { + if t_.is_local || t_.location() != none { utc_time := t_.local_to_utc() int_to_byte_array_no_pad(utc_time.year, mut buf, 4) int_to_byte_array_no_pad(utc_time.month, mut buf, 7) @@ -250,7 +250,7 @@ pub fn (t Time) format_rfc3339_nano() string { } t_ := time_with_unix(t) - if t_.is_local { + if t_.is_local || t_.location() != none { utc_time := t_.local_to_utc() int_to_byte_array_no_pad(utc_time.year, mut buf, 4) int_to_byte_array_no_pad(utc_time.month, mut buf, 7) @@ -720,9 +720,10 @@ pub fn (t Time) get_fmt_str(fmt_dlmtr FormatDelimiter, fmt_time FormatTime, fmt_ @[deprecated: 'use `http_header_string()` instead'] @[deprecated_after: '2026-09-30'] pub fn (t Time) utc_string() string { - day_str := t.weekday_str() - month_str := t.smonth() - utc_string := '${day_str}, ${t.day} ${month_str} ${t.year} ${t.hour:02d}:${t.minute:02d}:${t.second:02d} UTC' + t_ := if t.location() != none { t.local_to_utc() } else { t } + day_str := t_.weekday_str() + month_str := t_.smonth() + utc_string := '${day_str}, ${t_.day} ${month_str} ${t_.year} ${t_.hour:02d}:${t_.minute:02d}:${t_.second:02d} UTC' return utc_string } @@ -750,10 +751,15 @@ pub fn (t Time) write_http_header(dst &u8, dst_len int) ! { if dst_len < http_date_len { return error('time.write_http_header: dst_len must be >= 29') } - day_str := long_days[iclamp(0, t.day_of_week() - 1, 6)] // read in place: no substr + if t.location() != none { + unsafe { t.local_to_utc().write_http_header(dst, dst_len)! } + return + } + t_ := t + day_str := long_days[iclamp(0, t_.day_of_week() - 1, 6)] // read in place: no substr // months_string is indexed in place (no smonth() substr allocation); out-of-range // months keep smonth()'s historical '---' fallback. - mi := if t.month >= 1 && t.month <= 12 { (t.month - 1) * 3 } else { -1 } + mi := if t_.month >= 1 && t_.month <= 12 { (t_.month - 1) * 3 } else { -1 } m0 := if mi >= 0 { months_string[mi] } else { `-` } m1 := if mi >= 0 { months_string[mi + 1] } else { `-` } m2 := if mi >= 0 { months_string[mi + 2] } else { `-` } @@ -761,11 +767,11 @@ pub fn (t Time) write_http_header(dst &u8, dst_len int) ! { mut buf := [day_str[0], day_str[1], day_str[2], `,`, ` `, `0`, `0`, ` `, m0, m1, m2, ` `, `0`, `0`, `0`, `0`, ` `, `0`, `0`, `:`, `0`, `0`, `:`, `0`, `0`, ` `, `G`, `M`, `T`]! unsafe { - int_to_ptr_byte_array_no_pad(t.day, &buf[5], 2) - int_to_ptr_byte_array_no_pad(t.year, &buf[12], 4) - int_to_ptr_byte_array_no_pad(t.hour, &buf[17], 2) - int_to_ptr_byte_array_no_pad(t.minute, &buf[20], 2) - int_to_ptr_byte_array_no_pad(t.second, &buf[23], 2) + int_to_ptr_byte_array_no_pad(t_.day, &buf[5], 2) + int_to_ptr_byte_array_no_pad(t_.year, &buf[12], 4) + int_to_ptr_byte_array_no_pad(t_.hour, &buf[17], 2) + int_to_ptr_byte_array_no_pad(t_.minute, &buf[20], 2) + int_to_ptr_byte_array_no_pad(t_.second, &buf[23], 2) // plain byte loop instead of vmemcpy: compiles on every backend (JS has // no vmemcpy) and C compilers turn it into a memcpy at -prod anyway for i in 0 .. 29 { diff --git a/vlib/time/tzdata/tzdata.v b/vlib/time/tzdata/tzdata.c.v similarity index 100% rename from vlib/time/tzdata/tzdata.v rename to vlib/time/tzdata/tzdata.c.v diff --git a/vlib/time/tzdata/tzdata.js.v b/vlib/time/tzdata/tzdata.js.v new file mode 100644 index 00000000000000..06c2cad5077259 --- /dev/null +++ b/vlib/time/tzdata/tzdata.js.v @@ -0,0 +1,4 @@ +module tzdata + +fn init() { +} diff --git a/vlib/time/tzdata/tzdata.wasm.v b/vlib/time/tzdata/tzdata.wasm.v new file mode 100644 index 00000000000000..06c2cad5077259 --- /dev/null +++ b/vlib/time/tzdata/tzdata.wasm.v @@ -0,0 +1,4 @@ +module tzdata + +fn init() { +} diff --git a/vlib/time/zoneinfo.c.v b/vlib/time/zoneinfo.c.v index 85801a6dfe135b..6e9329e20f0a80 100644 --- a/vlib/time/zoneinfo.c.v +++ b/vlib/time/zoneinfo.c.v @@ -61,10 +61,18 @@ struct PosixZoneRule { end PosixRule } +enum PosixRuleKind { + month_week_day + julian_no_leap + day_of_year +} + struct PosixRule { + kind PosixRuleKind month int week int weekday int + day int seconds int } @@ -438,23 +446,74 @@ fn parse_posix_offset(text string, start int) !(int, int) { fn parse_posix_rule(text string) !PosixRule { rule_parts := text.split('/') date := rule_parts[0] - if !date.starts_with('M') { - return error('unsupported POSIX time zone date rule "${text}"') + seconds := if rule_parts.len > 1 { + parse_posix_time(rule_parts[1])! + } else { + 2 * seconds_per_hour + } + if date.starts_with('M') { + date_parts := date[1..].split('.') + if date_parts.len != 3 { + return error('unsupported POSIX time zone date rule "${text}"') + } + return PosixRule{ + kind: .month_week_day + month: date_parts[0].int() + week: date_parts[1].int() + weekday: date_parts[2].int() + seconds: seconds + } + } + if date.starts_with('J') { + day := date[1..].int() + if day < 1 || day > 365 { + return error('unsupported POSIX time zone date rule "${text}"') + } + return PosixRule{ + kind: .julian_no_leap + day: day + seconds: seconds + } } - date_parts := date[1..].split('.') - if date_parts.len != 3 { + day := date.int() + if day < 0 || day > 365 { return error('unsupported POSIX time zone date rule "${text}"') } return PosixRule{ - month: date_parts[0].int() - week: date_parts[1].int() - weekday: date_parts[2].int() - seconds: if rule_parts.len > 1 { - parse_posix_time(rule_parts[1])! - } else { - 2 * seconds_per_hour + kind: .day_of_year + day: day + seconds: seconds + } +} + +fn posix_rule_month_day(year int, rule PosixRule) (int, int) { + match rule.kind { + .month_week_day { + return rule.month, posix_month_week_day(year, rule) + } + .julian_no_leap { + mut ordinal := rule.day + if is_leap_year(year) && ordinal >= 60 { + ordinal++ + } + return month_day_from_year_day(year, ordinal) + } + .day_of_year { + return month_day_from_year_day(year, rule.day + 1) + } + } +} + +fn month_day_from_year_day(year int, ordinal int) (int, int) { + mut day := ordinal + for month in 1 .. 13 { + days := month_days[month - 1] + if month == 2 && is_leap_year(year) { 1 } else { 0 } + if day <= days { + return month, day } + day -= days } + return 12, 31 } fn parse_posix_time(text string) !int { @@ -502,19 +561,13 @@ fn (rule PosixZoneRule) zone_at(unix_time i64) Zone { } fn (rule PosixZoneRule) transition_utc(year int, date_rule PosixRule, offset_before int) i64 { - day := posix_month_week_day(year, date_rule) - hour_value := date_rule.seconds / seconds_per_hour - minute_value := (date_rule.seconds % seconds_per_hour) / seconds_per_minute - second_value := date_rule.seconds % seconds_per_minute + month, day := posix_rule_month_day(year, date_rule) local := time_fields_to_unix(Time{ - year: year - month: date_rule.month - day: day - hour: hour_value - minute: minute_value - second: second_value + year: year + month: month + day: day }) - return local - i64(offset_before) + return local + i64(date_rule.seconds) - i64(offset_before) } fn posix_month_week_day(year int, rule PosixRule) int { diff --git a/vlib/time/zoneinfo.js.v b/vlib/time/zoneinfo.js.v index 3ff393c93205bb..dbe4d9876072ec 100644 --- a/vlib/time/zoneinfo.js.v +++ b/vlib/time/zoneinfo.js.v @@ -1,5 +1,11 @@ module time +pub type ZoneinfoLoaderFn = fn (name string) ![]u8 + +// register_zoneinfo_loader registers a fallback loader for IANA time zone data. +pub fn register_zoneinfo_loader(loader ZoneinfoLoaderFn) { +} + // Zone describes one time zone rule in an IANA location. pub struct Zone { pub: @@ -12,6 +18,8 @@ pub: pub struct Location { pub: name string +mut: + zones []Zone } // load_location loads an IANA time zone location. diff --git a/vlib/time/zoneinfo.wasm.v b/vlib/time/zoneinfo.wasm.v new file mode 100644 index 00000000000000..89972431336fbb --- /dev/null +++ b/vlib/time/zoneinfo.wasm.v @@ -0,0 +1,64 @@ +module time + +pub type ZoneinfoLoaderFn = fn (name string) ![]u8 + +// register_zoneinfo_loader registers a fallback loader for IANA time zone data. +pub fn register_zoneinfo_loader(loader ZoneinfoLoaderFn) { +} + +// Zone describes one time zone rule in an IANA location. +pub struct Zone { +pub: + name string + offset int + is_dst bool +} + +// Location contains parsed IANA time zone data. +pub struct Location { +pub: + name string +mut: + zones []Zone +} + +// load_location loads an IANA time zone location. +pub fn load_location(name string) !&Location { + return error('IANA time zone locations are not supported by the WASM backend') +} + +// zone_at returns the active zone rule for `unix_time`. +pub fn (loc &Location) zone_at(unix_time i64) !Zone { + return error('IANA time zone locations are not supported by the WASM backend') +} + +// offset_at returns the UTC offset in seconds for `unix_time` in the location. +pub fn (loc &Location) offset_at(unix_time i64) !int { + return error('IANA time zone locations are not supported by the WASM backend') +} + +// unix_to_local resolves a Unix timestamp to a Time in the location. +pub fn (loc &Location) unix_to_local(unix_time i64) !Time { + return error('IANA time zone locations are not supported by the WASM backend') +} + +// unix_nanosecond_to_local resolves a Unix timestamp and nanosecond to a Time +// in the location. +pub fn (loc &Location) unix_nanosecond_to_local(unix_time i64, nanosecond int) !Time { + return error('IANA time zone locations are not supported by the WASM backend') +} + +// in resolves `t` as a Time in `loc`. +pub fn (t Time) in(loc &Location) !Time { + return error('IANA time zone locations are not supported by the WASM backend') +} + +// location returns the IANA location associated with `t`, if any. +pub fn (t Time) location() ?Location { + return t.loc +} + +// zone returns the active zone rule for `t`, if it has an IANA location. +pub fn (t Time) zone() !Zone { + return error('IANA time zone locations are not supported by the WASM backend') +} diff --git a/vlib/time/zoneinfo_nix.c.v b/vlib/time/zoneinfo_nix.c.v index 9930ec51192c39..775f035e873900 100644 --- a/vlib/time/zoneinfo_nix.c.v +++ b/vlib/time/zoneinfo_nix.c.v @@ -15,7 +15,7 @@ fn local_location() !&Location { return load_location(path) or { fixed_local_location() } } } - } else { + } else if tz != 'Local' { return load_location(tz) or { fixed_local_location() } } } diff --git a/vlib/time/zoneinfo_test.v b/vlib/time/zoneinfo_test.v index 2cdc02ca7d3f46..2a19f348e31ce7 100644 --- a/vlib/time/zoneinfo_test.v +++ b/vlib/time/zoneinfo_test.v @@ -97,6 +97,17 @@ fn test_time_in_keeps_nanosecond() { assert local.nanosecond == 123_456_789 } +fn test_location_time_utc_formats_use_instant() { + loc := time.load_location('Asia/Shanghai')! + local := time.unix_nanosecond(1_704_067_200, 123_456_789).in(loc)! + assert local.year == 2024 + assert local.hour == 8 + assert local.format_rfc3339() == '2024-01-01T00:00:00.123Z' + assert local.format_rfc3339_micro() == '2024-01-01T00:00:00.123456Z' + assert local.format_rfc3339_nano() == '2024-01-01T00:00:00.123456789Z' + assert local.http_header_string() == 'Mon, 01 Jan 2024 00:00:00 GMT' +} + fn test_location_time_add_keeps_instant_and_location() { loc := time.load_location('Asia/Shanghai')! local := time.unix_nanosecond(1_704_067_200, 123_456_789).in(loc)! @@ -187,6 +198,16 @@ fn test_load_location_posix_future_dst() { assert summer_local.hour == 1 } +fn test_load_location_posix_julian_future_rule() { + // Africa/Casablanca uses a TZif POSIX tail with day-of-year and Julian + // transition rules (`0/0,J365/23`) for far-future transitions. + loc := time.load_location('Africa/Casablanca')! + start_of_year := loc.zone_at(2_524_608_000)! // 2050-01-01 00:00 UTC + end_of_year := loc.zone_at(2_556_057_600)! // 2050-12-31 00:00 UTC + assert start_of_year.offset == 3_600 + assert end_of_year.offset == 3_600 +} + fn test_fixed_offset_etc_gmt() { // Note: Etc/GMT+N uses POSIX sign (opposite of civil intuition). loc := time.load_location('Etc/GMT+5')! @@ -214,3 +235,17 @@ fn test_unknown_location_name() { assert err.msg().contains('unknown time zone location') } } + +fn test_load_location_local_ignores_tz_local() { + old_tz := os.getenv_opt('TZ') + os.setenv('TZ', 'Local', true) + defer { + if old := old_tz { + os.setenv('TZ', old, true) + } else { + os.unsetenv('TZ') + } + } + loc := time.load_location('Local')! + assert loc.name.len > 0 +} diff --git a/vlib/time/zoneinfo_windows.c.v b/vlib/time/zoneinfo_windows.c.v index 2bf5eef606eb4c..a57eb79ad9a07f 100644 --- a/vlib/time/zoneinfo_windows.c.v +++ b/vlib/time/zoneinfo_windows.c.v @@ -44,8 +44,10 @@ fn local_location() !&Location { is_dst: true } mut loc := &Location{ - name: 'Local' - zones: [std_zone, dst_zone] + name: 'Local' + zones: [std_zone, dst_zone] + posix: windows_posix_rule(std_zone, dst_zone, info) + has_posix: true } current_year := utc().year for year in current_year - 100 .. current_year + 101 { @@ -74,6 +76,28 @@ fn local_location() !&Location { return loc } +fn windows_posix_rule(std_zone Zone, dst_zone Zone, info TimeZoneInformation) PosixZoneRule { + return PosixZoneRule{ + std_name: std_zone.name + std_offset: std_zone.offset + dst_name: dst_zone.name + dst_offset: dst_zone.offset + start: windows_system_time_rule(info.daylight_date) + end: windows_system_time_rule(info.standard_date) + } +} + +fn windows_system_time_rule(st SystemTime) PosixRule { + return PosixRule{ + kind: .month_week_day + month: int(st.month) + week: int(st.day) + weekday: int(st.day_of_week) + seconds: int(st.hour) * seconds_per_hour + int(st.minute) * seconds_per_minute + + int(st.second) + } +} + fn windows_transition_utc(year int, st SystemTime, offset_before int) i64 { day := windows_month_week_day(year, int(st.month), int(st.day), int(st.day_of_week)) local := time_fields_to_unix(Time{ From 04eca23fa0125df9b3615ecabc5088fe1bf3cc60 Mon Sep 17 00:00:00 2001 From: weigang Date: Sun, 19 Jul 2026 08:40:51 +0800 Subject: [PATCH 4/8] time: harden zoneinfo posix tests --- vlib/time/zoneinfo.c.v | 14 ++++++++++++++ vlib/time/zoneinfo_internal_test.v | 21 +++++++++++++++++++++ vlib/time/zoneinfo_test.v | 21 ++++++++------------- vlib/time/zoneinfo_windows.c.v | 1 + 4 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 vlib/time/zoneinfo_internal_test.v diff --git a/vlib/time/zoneinfo.c.v b/vlib/time/zoneinfo.c.v index 6e9329e20f0a80..24437766a8a84a 100644 --- a/vlib/time/zoneinfo.c.v +++ b/vlib/time/zoneinfo.c.v @@ -59,6 +59,7 @@ struct PosixZoneRule { dst_offset int start PosixRule end PosixRule + has_dst bool } enum PosixRuleKind { @@ -371,6 +372,12 @@ fn parse_posix_zone_rule(text string) !PosixZoneRule { std_name, mut pos := parse_posix_name(text, 0)! std_offset, next_pos := parse_posix_offset(text, pos)! pos = next_pos + if pos == text.len { + return PosixZoneRule{ + std_name: std_name + std_offset: std_offset + } + } dst_name, dst_name_end := parse_posix_name(text, pos)! pos = dst_name_end mut dst_offset := std_offset + seconds_per_hour @@ -393,6 +400,7 @@ fn parse_posix_zone_rule(text string) !PosixZoneRule { dst_offset: dst_offset start: parse_posix_rule(parts[0])! end: parse_posix_rule(parts[1])! + has_dst: true } } @@ -539,6 +547,12 @@ fn parse_posix_number(text string, start int) !(int, int) { } fn (rule PosixZoneRule) zone_at(unix_time i64) Zone { + if !rule.has_dst { + return Zone{ + name: rule.std_name + offset: rule.std_offset + } + } year := unix(unix_time).year start := rule.transition_utc(year, rule.start, rule.std_offset) end := rule.transition_utc(year, rule.end, rule.dst_offset) diff --git a/vlib/time/zoneinfo_internal_test.v b/vlib/time/zoneinfo_internal_test.v new file mode 100644 index 00000000000000..c060c73f207ef3 --- /dev/null +++ b/vlib/time/zoneinfo_internal_test.v @@ -0,0 +1,21 @@ +module time + +fn test_parse_posix_tail_without_dst() { + rule := parse_posix_zone_rule('CST-8')! + zone := rule.zone_at(2_524_608_000) + assert zone.name == 'CST' + assert zone.offset == 28_800 + assert zone.is_dst == false +} + +fn test_parse_posix_tail_julian_and_day_of_year_rules() { + rule := parse_posix_zone_rule('<+00>0<+01>-1,0/0,J365/23')! + start_of_year := rule.zone_at(2_524_608_000) // 2050-01-01 00:00 UTC + end_of_year := rule.zone_at(2_556_057_600) // 2050-12-31 00:00 UTC + assert start_of_year.name == '+01' + assert start_of_year.offset == 3_600 + assert start_of_year.is_dst == true + assert end_of_year.name == '+01' + assert end_of_year.offset == 3_600 + assert end_of_year.is_dst == true +} diff --git a/vlib/time/zoneinfo_test.v b/vlib/time/zoneinfo_test.v index 2a19f348e31ce7..cfd000e9785a59 100644 --- a/vlib/time/zoneinfo_test.v +++ b/vlib/time/zoneinfo_test.v @@ -1,6 +1,10 @@ import os import time +fn testsuite_begin() { + os.setenv('ZONEINFO', os.join_path(@VEXEROOT, 'vlib', 'time', 'tzdata', 'zoneinfo.zip'), true) +} + fn test_load_location_utc() { loc := time.load_location('UTC')! assert loc.offset_at(1_704_067_200)! == 0 @@ -32,16 +36,8 @@ fn test_load_location_from_embedded_zoneinfo_zip() { } fn test_load_location_from_zoneinfo_env_zip() { - old_zoneinfo := os.getenv_opt('ZONEINFO') zoneinfo_zip := os.join_path(@VEXEROOT, 'vlib', 'time', 'tzdata', 'zoneinfo.zip') os.setenv('ZONEINFO', zoneinfo_zip, true) - defer { - if old := old_zoneinfo { - os.setenv('ZONEINFO', old, true) - } else { - os.unsetenv('ZONEINFO') - } - } loc := time.load_location('Pacific/Niue')! t := loc.unix_to_local(1_704_067_200)! assert t.year == 2023 @@ -198,14 +194,13 @@ fn test_load_location_posix_future_dst() { assert summer_local.hour == 1 } -fn test_load_location_posix_julian_future_rule() { - // Africa/Casablanca uses a TZif POSIX tail with day-of-year and Julian - // transition rules (`0/0,J365/23`) for far-future transitions. +fn test_load_location_posix_fixed_future_rule() { + // The bundled zoneinfo.zip currently uses a fixed POSIX tail for Morocco. loc := time.load_location('Africa/Casablanca')! start_of_year := loc.zone_at(2_524_608_000)! // 2050-01-01 00:00 UTC end_of_year := loc.zone_at(2_556_057_600)! // 2050-12-31 00:00 UTC - assert start_of_year.offset == 3_600 - assert end_of_year.offset == 3_600 + assert start_of_year.offset == 0 + assert end_of_year.offset == 0 } fn test_fixed_offset_etc_gmt() { diff --git a/vlib/time/zoneinfo_windows.c.v b/vlib/time/zoneinfo_windows.c.v index a57eb79ad9a07f..0d6fb68e831162 100644 --- a/vlib/time/zoneinfo_windows.c.v +++ b/vlib/time/zoneinfo_windows.c.v @@ -84,6 +84,7 @@ fn windows_posix_rule(std_zone Zone, dst_zone Zone, info TimeZoneInformation) Po dst_offset: dst_zone.offset start: windows_system_time_rule(info.daylight_date) end: windows_system_time_rule(info.standard_date) + has_dst: true } } From fb31cc5cc442deab5c85c1f2cf0612453fd8de48 Mon Sep 17 00:00:00 2001 From: weigang Date: Sun, 19 Jul 2026 17:54:30 +0800 Subject: [PATCH 5/8] time: address additional zoneinfo review --- vlib/encoding/cbor/generic.v | 2 +- vlib/encoding/cbor/tests/time_test.v | 26 +++++++++++++++++++++++++ vlib/time/time.c.v | 5 +++-- vlib/time/zoneinfo.c.v | 12 ++++++++---- vlib/time/zoneinfo_internal_test.v | 26 +++++++++++++++++++++++++ vlib/time/zoneinfo_nix.c.v | 29 +++++++++++++++++++++++++++- vlib/time/zoneinfo_test.v | 27 ++++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 8 deletions(-) diff --git a/vlib/encoding/cbor/generic.v b/vlib/encoding/cbor/generic.v index 78e7c6c1288078..ed7368ddc3832c 100644 --- a/vlib/encoding/cbor/generic.v +++ b/vlib/encoding/cbor/generic.v @@ -587,7 +587,7 @@ fn (mut u Unpacker) unpack_into[T](mut out T) ! { // session is encoded as the correct instant rather than as wall-clock // digits without an offset. fn format_rfc3339_nano(t time.Time) string { - utc := if t.is_local { t.local_to_utc() } else { t } + utc := if t.is_local || t.location() != none { t.local_to_utc() } else { t } return '${utc.year:04d}-${utc.month:02d}-${utc.day:02d}T${utc.hour:02d}:${utc.minute:02d}:${utc.second:02d}.${utc.nanosecond:09d}Z' } diff --git a/vlib/encoding/cbor/tests/time_test.v b/vlib/encoding/cbor/tests/time_test.v index 96240e3d67b775..45920e2ef05c57 100644 --- a/vlib/encoding/cbor/tests/time_test.v +++ b/vlib/encoding/cbor/tests/time_test.v @@ -5,6 +5,7 @@ module main import encoding.cbor import encoding.hex +import os import time fn h(s string) []u8 { @@ -37,3 +38,28 @@ fn test_time_decode_tag1_float() { // Sub-second component is non-zero (~500ms). assert got.nanosecond > 0 } + +fn test_time_encode_tag0_zoned_subsecond_uses_utc_instant() { + old_zoneinfo := os.getenv_opt('ZONEINFO') + os.setenv('ZONEINFO', os.join_path(@VEXEROOT, 'vlib', 'time', 'tzdata', 'zoneinfo.zip'), true) + defer { + if old := old_zoneinfo { + os.setenv('ZONEINFO', old, true) + } else { + os.unsetenv('ZONEINFO') + } + } + loc := time.load_location('Asia/Shanghai')! + t := time.unix_nanosecond(1_704_067_200, 1).in(loc)! + bytes := cbor.encode[time.Time](t, cbor.EncodeOpts{})! + v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{})! + assert v is cbor.Tag + if v is cbor.Tag { + assert v.number == 0 + content := v.content() + assert content is cbor.Text + if content is cbor.Text { + assert content.value == '2024-01-01T00:00:00.000000001Z' + } + } +} diff --git a/vlib/time/time.c.v b/vlib/time/time.c.v index c91d58d283cc61..6c03d365a496e3 100644 --- a/vlib/time/time.c.v +++ b/vlib/time/time.c.v @@ -140,11 +140,12 @@ fn convert_ctime(t C.tm, nanosecond int) Time { // strftime returns the formatted time using `strftime(3)`. pub fn (t Time) strftime(fmt string) string { + mut strftime_unix := if t.location() != none { t.local_unix() } else { t.unix } mut tm := &C.tm{} $if windows { - tm = C.gmtime(voidptr(&t.unix)) + tm = C.gmtime(voidptr(&strftime_unix)) } $else { - C.gmtime_r(voidptr(&t.unix), tm) + C.gmtime_r(voidptr(&strftime_unix), tm) } mut buf := [1024]char{} fmt_c := unsafe { &char(fmt.str) } diff --git a/vlib/time/zoneinfo.c.v b/vlib/time/zoneinfo.c.v index 24437766a8a84a..3e8cd6ad941618 100644 --- a/vlib/time/zoneinfo.c.v +++ b/vlib/time/zoneinfo.c.v @@ -104,12 +104,13 @@ pub fn (loc &Location) zone_at(unix_time i64) !Zone { if loc.zones.len == 0 { return error('time zone location "${loc.name}" has no zone rules') } + if loc.has_posix + && (loc.transitions.len == 0 || unix_time >= loc.transitions[loc.transitions.len - 1].when) { + return loc.posix.zone_at(unix_time) + } if loc.transitions.len == 0 { return loc.zones[0] } - if loc.has_posix && unix_time >= loc.transitions[loc.transitions.len - 1].when { - return loc.posix.zone_at(unix_time) - } if unix_time < loc.transitions[0].when { return loc.zones[loc.first_standard_zone_index()] } @@ -250,7 +251,7 @@ fn parse_tzif_location(name string, data []u8) !&Location { mut pos := 0 header := read_tzif_header(data, pos)! pos += 44 - if header.version == `2` || header.version == `3` { + if header.version == `2` || header.version == `3` || header.version == `4` { pos += tzif_data_size(header, 4) header2 := read_tzif_header(data, pos)! pos += 44 @@ -592,6 +593,9 @@ fn posix_month_week_day(year int, rule PosixRule) int { for day + 7 <= days { day += 7 } + if day > days { + day -= 7 + } } return day } diff --git a/vlib/time/zoneinfo_internal_test.v b/vlib/time/zoneinfo_internal_test.v index c060c73f207ef3..71321c19b8115b 100644 --- a/vlib/time/zoneinfo_internal_test.v +++ b/vlib/time/zoneinfo_internal_test.v @@ -19,3 +19,29 @@ fn test_parse_posix_tail_julian_and_day_of_year_rules() { assert end_of_year.offset == 3_600 assert end_of_year.is_dst == true } + +fn test_posix_last_weekday_stays_in_month() { + rule := parse_posix_zone_rule('GMT0BST,M3.5.0/1,M10.5.0')! + after_transition := time_fields_to_unix(Time{ + year: 2038 + month: 3 + day: 28 + hour: 2 + }) + zone := rule.zone_at(after_transition) + assert zone.name == 'BST' + assert zone.offset == 3_600 + assert zone.is_dst == true +} + +fn test_tzif_v4_uses_64bit_data_and_posix_tail() { + mut data := load_zoneinfo_from_source(zoneinfo_vroot_zip, 'Europe/London')! + data[4] = `4` + loc := parse_tzif_location('Europe/London', data)! + winter := loc.zone_at(2_524_608_000)! // 2050-01-01 00:00 UTC + summer := loc.zone_at(2_540_246_400)! // 2050-07-01 00:00 UTC + assert winter.name == 'GMT' + assert winter.offset == 0 + assert summer.name == 'BST' + assert summer.offset == 3_600 +} diff --git a/vlib/time/zoneinfo_nix.c.v b/vlib/time/zoneinfo_nix.c.v index 775f035e873900..b6494b3f40a6c1 100644 --- a/vlib/time/zoneinfo_nix.c.v +++ b/vlib/time/zoneinfo_nix.c.v @@ -16,7 +16,12 @@ fn local_location() !&Location { } } } else if tz != 'Local' { - return load_location(tz) or { fixed_local_location() } + return load_location(tz) or { + if rule := parse_posix_zone_rule(tz) { + return location_from_posix_rule('Local', rule) + } + fixed_local_location() + } } } localtime := '/etc/localtime' @@ -47,6 +52,28 @@ fn fixed_local_location() &Location { } } +fn location_from_posix_rule(name string, rule PosixZoneRule) &Location { + mut zones := [ + Zone{ + name: rule.std_name + offset: rule.std_offset + }, + ] + if rule.has_dst { + zones << Zone{ + name: rule.dst_name + offset: rule.dst_offset + is_dst: true + } + } + return &Location{ + name: name + zones: zones + posix: rule + has_posix: true + } +} + fn zoneinfo_name_from_path(path string) ?string { marker := '/zoneinfo/' index := path.index(marker) or { return none } diff --git a/vlib/time/zoneinfo_test.v b/vlib/time/zoneinfo_test.v index cfd000e9785a59..00f469af55f825 100644 --- a/vlib/time/zoneinfo_test.v +++ b/vlib/time/zoneinfo_test.v @@ -104,6 +104,12 @@ fn test_location_time_utc_formats_use_instant() { assert local.http_header_string() == 'Mon, 01 Jan 2024 00:00:00 GMT' } +fn test_location_time_strftime_uses_wall_clock() { + loc := time.load_location('Asia/Shanghai')! + local := time.unix(1_704_067_200).in(loc)! + assert local.strftime('%Y-%m-%d %H:%M:%S') == '2024-01-01 08:00:00' +} + fn test_location_time_add_keeps_instant_and_location() { loc := time.load_location('Asia/Shanghai')! local := time.unix_nanosecond(1_704_067_200, 123_456_789).in(loc)! @@ -244,3 +250,24 @@ fn test_load_location_local_ignores_tz_local() { loc := time.load_location('Local')! assert loc.name.len > 0 } + +fn test_load_location_local_posix_tz() { + old_tz := os.getenv_opt('TZ') + os.setenv('TZ', 'EST5EDT,M3.2.0,M11.1.0', true) + defer { + if old := old_tz { + os.setenv('TZ', old, true) + } else { + os.unsetenv('TZ') + } + } + loc := time.load_location('Local')! + winter := loc.zone_at(1_704_067_200)! // 2024-01-01 00:00 UTC + summer := loc.zone_at(1_719_792_000)! // 2024-06-30 20:00 UTC + assert winter.name == 'EST' + assert winter.offset == -18_000 + assert winter.is_dst == false + assert summer.name == 'EDT' + assert summer.offset == -14_400 + assert summer.is_dst == true +} From 31662fb86c12f313ed1723745fb1d8140c798049 Mon Sep 17 00:00:00 2001 From: weigang Date: Sun, 19 Jul 2026 18:15:11 +0800 Subject: [PATCH 6/8] time: harden zoneinfo behavior --- vlib/time/time.c.v | 64 ++++++++++++++++++++-- vlib/time/time.v | 2 +- vlib/time/zoneinfo.c.v | 87 ++++++++++++++++++++++++------ vlib/time/zoneinfo_internal_test.v | 42 +++++++++++++++ vlib/time/zoneinfo_test.v | 15 ++++++ 5 files changed, 189 insertions(+), 21 deletions(-) diff --git a/vlib/time/time.c.v b/vlib/time/time.c.v index 6c03d365a496e3..9040905e83dde4 100644 --- a/vlib/time/time.c.v +++ b/vlib/time/time.c.v @@ -3,6 +3,8 @@ // that can be found in the LICENSE file. module time +import strings + #include // C.timeval represents a C time value. @@ -140,7 +142,19 @@ fn convert_ctime(t C.tm, nanosecond int) Time { // strftime returns the formatted time using `strftime(3)`. pub fn (t Time) strftime(fmt string) string { - mut strftime_unix := if t.location() != none { t.local_unix() } else { t.unix } + mut strftime_fmt := fmt + mut strftime_unix := t.unix + mut zone_name := '' + mut zone_offset := '' + mut unix_time := '' + if loc := t.location() { + zone := loc.zone_at(t.unix) or { Zone{} } + zone_name = zone.name + zone_offset = strftime_zone_offset(zone.offset) + unix_time = t.unix.str() + strftime_fmt = strftime_location_format(fmt) + strftime_unix = t.local_unix() + } mut tm := &C.tm{} $if windows { tm = C.gmtime(voidptr(&strftime_unix)) @@ -148,9 +162,53 @@ pub fn (t Time) strftime(fmt string) string { C.gmtime_r(voidptr(&strftime_unix), tm) } mut buf := [1024]char{} - fmt_c := unsafe { &char(fmt.str) } + fmt_c := unsafe { &char(strftime_fmt.str) } C.strftime(&buf[0], usize(sizeof(buf)), fmt_c, tm) - return unsafe { cstring_to_vstring(&buf[0]) } + res := unsafe { cstring_to_vstring(&buf[0]) } + if zone_name != '' || zone_offset != '' || unix_time != '' { + return res.replace(strftime_zone_name_placeholder, zone_name).replace(strftime_zone_offset_placeholder, + zone_offset).replace(strftime_unix_placeholder, unix_time) + } + return res +} + +const strftime_zone_name_placeholder = '@V_STRFTIME_ZONE_NAME@' +const strftime_zone_offset_placeholder = '@V_STRFTIME_ZONE_OFFSET@' +const strftime_unix_placeholder = '@V_STRFTIME_UNIX@' + +fn strftime_location_format(fmt string) string { + mut out := strings.new_builder(fmt.len) + for i := 0; i < fmt.len; i++ { + if fmt[i] != `%` || i + 1 >= fmt.len { + out.write_u8(fmt[i]) + continue + } + i++ + match fmt[i] { + `Z` { + out.write_string(strftime_zone_name_placeholder) + } + `z` { + out.write_string(strftime_zone_offset_placeholder) + } + `s` { + out.write_string(strftime_unix_placeholder) + } + else { + out.write_u8(`%`) + out.write_u8(fmt[i]) + } + } + } + return out.str() +} + +fn strftime_zone_offset(offset int) string { + sign := if offset < 0 { '-' } else { '+' } + abs_offset := if offset < 0 { -offset } else { offset } + hours := abs_offset / seconds_per_hour + minutes := (abs_offset % seconds_per_hour) / seconds_per_minute + return '${sign}${hours:02}${minutes:02}' } // some *nix system functions (e.g. `C.poll()`, C.epoll_wait()) accept an `int` diff --git a/vlib/time/time.v b/vlib/time/time.v index 9ae0e57aeb70f7..32b2873db2193f 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -520,5 +520,5 @@ pub fn (t Time) as_utc() Time { // is_utc returns true, when the receiver `t` is a UTC time, and false otherwise. // See also #Time.utc_to_local . pub fn (t Time) is_utc() bool { - return !t.is_local + return !t.is_local && t.location() == none } diff --git a/vlib/time/zoneinfo.c.v b/vlib/time/zoneinfo.c.v index 3e8cd6ad941618..1a9b58f1dcda33 100644 --- a/vlib/time/zoneinfo.c.v +++ b/vlib/time/zoneinfo.c.v @@ -252,7 +252,11 @@ fn parse_tzif_location(name string, data []u8) !&Location { header := read_tzif_header(data, pos)! pos += 44 if header.version == `2` || header.version == `3` || header.version == `4` { - pos += tzif_data_size(header, 4) + size := tzif_data_size(header, 4)! + if pos + size > data.len { + return error('truncated TZif data for "${name}"') + } + pos += size header2 := read_tzif_header(data, pos)! pos += 44 return parse_tzif_data(name, data, pos, header2, 8)! @@ -274,7 +278,7 @@ fn read_tzif_header(data []u8, offset int) !TzifHeader { if offset + 44 > data.len || data[offset..offset + 4].bytestr() != 'TZif' { return error('invalid TZif data') } - return TzifHeader{ + header := TzifHeader{ version: data[offset + 4] ttisgmt: read_be_i32(data, offset + 20) ttisstd: read_be_i32(data, offset + 24) @@ -283,18 +287,29 @@ fn read_tzif_header(data []u8, offset int) !TzifHeader { typ: read_be_i32(data, offset + 36) character: read_be_i32(data, offset + 40) } + if header.ttisgmt < 0 || header.ttisstd < 0 || header.leap < 0 || header.time < 0 + || header.typ < 0 || header.character < 0 { + return error('invalid TZif header counts') + } + return header } -fn tzif_data_size(header TzifHeader, time_size int) int { - return header.time * time_size + header.time + header.typ * 6 + header.character + - header.leap * (time_size + 4) + header.ttisstd + header.ttisgmt +fn tzif_data_size(header TzifHeader, time_size int) !int { + size := i64(header.time) * i64(time_size) + i64(header.time) + i64(header.typ) * 6 + + i64(header.character) + i64(header.leap) * i64(time_size + 4) + i64(header.ttisstd) + + i64(header.ttisgmt) + if size > i64(max_int) { + return error('TZif data is too large') + } + return int(size) } fn parse_tzif_data(name string, data []u8, start int, header TzifHeader, time_size int) !&Location { if header.typ <= 0 { return error('time zone location "${name}" has no zone rules') } - if start + tzif_data_size(header, time_size) > data.len { + size := tzif_data_size(header, time_size)! + if start + size > data.len { return error('truncated TZif data for "${name}"') } mut pos := start @@ -454,6 +469,9 @@ fn parse_posix_offset(text string, start int) !(int, int) { fn parse_posix_rule(text string) !PosixRule { rule_parts := text.split('/') + if rule_parts.len == 0 || rule_parts.len > 2 { + return error('unsupported POSIX time zone date rule "${text}"') + } date := rule_parts[0] seconds := if rule_parts.len > 1 { parse_posix_time(rule_parts[1])! @@ -465,16 +483,22 @@ fn parse_posix_rule(text string) !PosixRule { if date_parts.len != 3 { return error('unsupported POSIX time zone date rule "${text}"') } + month := parse_posix_number_text(date_parts[0])! + week := parse_posix_number_text(date_parts[1])! + weekday := parse_posix_number_text(date_parts[2])! + if month < 1 || month > 12 || week < 1 || week > 5 || weekday < 0 || weekday > 6 { + return error('unsupported POSIX time zone date rule "${text}"') + } return PosixRule{ kind: .month_week_day - month: date_parts[0].int() - week: date_parts[1].int() - weekday: date_parts[2].int() + month: month + week: week + weekday: weekday seconds: seconds } } if date.starts_with('J') { - day := date[1..].int() + day := parse_posix_number_text(date[1..])! if day < 1 || day > 365 { return error('unsupported POSIX time zone date rule "${text}"') } @@ -484,7 +508,7 @@ fn parse_posix_rule(text string) !PosixRule { seconds: seconds } } - day := date.int() + day := parse_posix_number_text(date)! if day < 0 || day > 365 { return error('unsupported POSIX time zone date rule "${text}"') } @@ -526,14 +550,35 @@ fn month_day_from_year_day(year int, ordinal int) (int, int) { } fn parse_posix_time(text string) !int { - parts := text.split(':') - if parts.len == 0 || parts.len > 3 { + if text == '' { return error('unsupported POSIX time "${text}"') } - hour_value := parts[0].int() - minute_value := if parts.len > 1 { parts[1].int() } else { 0 } - second_value := if parts.len > 2 { parts[2].int() } else { 0 } - return hour_value * seconds_per_hour + minute_value * seconds_per_minute + second_value + mut pos := 0 + mut sign := 1 + if text[pos] == `-` { + sign = -1 + pos++ + } else if text[pos] == `+` { + pos++ + } + hour_value, hour_end := parse_posix_number(text, pos)! + pos = hour_end + mut minute_value := 0 + mut second_value := 0 + if pos < text.len && text[pos] == `:` { + minute_value, pos = parse_posix_number(text, pos + 1)! + } + if pos < text.len && text[pos] == `:` { + second_value, pos = parse_posix_number(text, pos + 1)! + } + if pos != text.len || minute_value > 59 || second_value > 59 { + return error('unsupported POSIX time "${text}"') + } + seconds := hour_value * seconds_per_hour + minute_value * seconds_per_minute + second_value + if seconds > 167 * seconds_per_hour { + return error('unsupported POSIX time "${text}"') + } + return sign * seconds } fn parse_posix_number(text string, start int) !(int, int) { @@ -547,6 +592,14 @@ fn parse_posix_number(text string, start int) !(int, int) { return text[start..pos].int(), pos } +fn parse_posix_number_text(text string) !int { + value, pos := parse_posix_number(text, 0)! + if pos != text.len { + return error('missing POSIX number') + } + return value +} + fn (rule PosixZoneRule) zone_at(unix_time i64) Zone { if !rule.has_dst { return Zone{ diff --git a/vlib/time/zoneinfo_internal_test.v b/vlib/time/zoneinfo_internal_test.v index 71321c19b8115b..f0a7b3f8a09cfe 100644 --- a/vlib/time/zoneinfo_internal_test.v +++ b/vlib/time/zoneinfo_internal_test.v @@ -34,6 +34,48 @@ fn test_posix_last_weekday_stays_in_month() { assert zone.is_dst == true } +fn test_parse_posix_rule_rejects_invalid_numbers() { + if _ := parse_posix_zone_rule('EST5EDT,Mx.2.0,M11.1.0') { + assert false + } else { + assert err.msg().contains('POSIX') + } + if _ := parse_posix_zone_rule('EST5EDT,M13.2.0,M11.1.0') { + assert false + } else { + assert err.msg().contains('POSIX') + } + if _ := parse_posix_zone_rule('EST5EDT,M3.2.0/2:99,M11.1.0') { + assert false + } else { + assert err.msg().contains('POSIX') + } +} + +fn test_parse_posix_negative_transition_time_with_minutes() { + rule := parse_posix_zone_rule('<-02>2<-01>,M3.5.0/-1:30,M10.5.0/0')! + transition := rule.transition_utc(2050, rule.start, rule.std_offset) + expected := time_fields_to_unix(Time{ + year: 2050 + month: 3 + day: 27 + hour: 0 + minute: 30 + }) + assert transition == expected +} + +fn test_tzif_rejects_negative_counts() { + mut data := []u8{len: 44} + copy(mut data[0..4], 'TZif'.bytes()) + data[40] = 0xff + if _ := parse_tzif_location('Bad/Zone', data) { + assert false + } else { + assert err.msg().contains('invalid TZif header counts') + } +} + fn test_tzif_v4_uses_64bit_data_and_posix_tail() { mut data := load_zoneinfo_from_source(zoneinfo_vroot_zip, 'Europe/London')! data[4] = `4` diff --git a/vlib/time/zoneinfo_test.v b/vlib/time/zoneinfo_test.v index 00f469af55f825..c8470159fdd0df 100644 --- a/vlib/time/zoneinfo_test.v +++ b/vlib/time/zoneinfo_test.v @@ -110,6 +110,21 @@ fn test_location_time_strftime_uses_wall_clock() { assert local.strftime('%Y-%m-%d %H:%M:%S') == '2024-01-01 08:00:00' } +fn test_location_time_strftime_uses_location_zone_directives() { + loc := time.load_location('America/New_York')! + local := time.unix(1_710_054_000).in(loc)! + assert local.strftime('%Y-%m-%d %H:%M:%S %Z %z') == '2024-03-10 03:00:00 EDT -0400' + assert local.strftime('%s') == '1710054000' + assert local.strftime('%%Z %Z') == '%Z EDT' +} + +fn test_location_time_is_not_utc() { + loc := time.load_location('Asia/Shanghai')! + local := time.unix(1_704_067_200).in(loc)! + assert local.is_utc() == false + assert time.unix(1_704_067_200).is_utc() == true +} + fn test_location_time_add_keeps_instant_and_location() { loc := time.load_location('Asia/Shanghai')! local := time.unix_nanosecond(1_704_067_200, 123_456_789).in(loc)! From f996079911be888cf6b3aa067e3054332b07259b Mon Sep 17 00:00:00 2001 From: weigang Date: Wed, 22 Jul 2026 10:15:28 +0800 Subject: [PATCH 7/8] time: refresh bundled zoneinfo data --- vlib/time/README.md | 6 ++++-- vlib/time/tzdata/zoneinfo.zip | Bin 408467 -> 406908 bytes vlib/time/zoneinfo_test.v | 26 ++++++++++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/vlib/time/README.md b/vlib/time/README.md index 527238fc3483b1..03a072e132d6b3 100644 --- a/vlib/time/README.md +++ b/vlib/time/README.md @@ -94,10 +94,12 @@ on IANA-zoned `Time` values. The bundled `vlib/time/tzdata/zoneinfo.zip` is a store-only (uncompressed) zip of IANA zoneinfo files for offline use via `import time.tzdata`. Refresh it -from a system zoneinfo tree when updating tzdata, for example: +from a full system zoneinfo tree when updating tzdata. On macOS, prefer +`/usr/share/zoneinfo.default` over `/usr/share/zoneinfo`, because the latter may +contain truncated files with fixed POSIX tails. For example: ```sh -cd /usr/share/zoneinfo && zip -0 -r /path/to/v/vlib/time/tzdata/zoneinfo.zip . +cd /usr/share/zoneinfo.default && zip -0 -r /path/to/v/vlib/time/tzdata/zoneinfo.zip . ``` Another very useful feature of the `time` module is the stop watch, diff --git a/vlib/time/tzdata/zoneinfo.zip b/vlib/time/tzdata/zoneinfo.zip index 46c90a2b806a92fcd3291449fe448ce8db4d1753..c7246c5ff093298282bef471de0d2f4807026c69 100644 GIT binary patch delta 45270 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

t#RIyEf9d04eWt%Qc%| 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 delta 52050 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 aqh)$O3QXHjSDQ^JqLJEFeSQ Date: Wed, 22 Jul 2026 10:17:43 +0800 Subject: [PATCH 8/8] time: fix Windows last weekday transitions --- vlib/time/zoneinfo_windows.c.v | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vlib/time/zoneinfo_windows.c.v b/vlib/time/zoneinfo_windows.c.v index 0d6fb68e831162..ae5dc78edf48f5 100644 --- a/vlib/time/zoneinfo_windows.c.v +++ b/vlib/time/zoneinfo_windows.c.v @@ -120,6 +120,9 @@ fn windows_month_week_day(year int, month int, week int, weekday int) int { for day + 7 <= days { day += 7 } + if day > days { + day -= 7 + } } return day }