tzinfo

package module
v0.0.0-...-fbbfc02 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

README

go-ruby-tzinfo/tzinfo

tzinfo — go-ruby-tzinfo

License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's TZInfo library — the tzinfo gem's timezone engine, minus the Ruby runtime. It embeds a complete offline copy of the compiled IANA time zone database and exposes the TZInfo API on top of it: resolving a zone by identifier, converting between UTC and local time, computing the TimezonePeriod (offset, abbreviation, DST flag, transition boundaries) in force at any instant — historical, present, or far future — and handling the spring-forward gap (PeriodNotFound) and fall-back overlap (AmbiguousTime) exactly as the gem does.

It is a sibling of the other pure-Go Ruby front-ends (go-ruby-regexp — the Onigmo engine, go-ruby-erb — the ERB compiler, go-ruby-yaml — the Psych YAML core), and the timezone backend for go-embedded-ruby. It is a standalone, reusable module with no dependency on the Ruby runtime.

What it is — and isn't. Resolving zones, computing offsets, and finding the period for an instant is fully deterministic and needs no interpreter, so it lives here as pure Go. The one input it needs is the compiled IANA zoneinfo, which is committed in-repo (go:embed) so the library is complete offline with no filesystem or network dependency.

Features

Faithful port of the TZInfo API, validated against the tzinfo gem (Ruby ≥ 4.0) on every supported platform:

  • Get — resolve any of the 598 IANA identifiers, including former backward-link names (US/Eastern, GB, Zulu, …), matching the gem's ZoneinfoDataSource (every name is a full data zone; zero linked zones).
  • Offsets and periodsPeriodForUTC / PeriodForLocal, UTCToLocal / LocalToUTC, CurrentPeriod, Now, plus Abbreviation, UTCOffset and DST at an instant. Every period exposes the gem's exact base_utc_offset / std_offset split (derived from the surrounding standard periods, negative-DST zones included), utc_total_offset, abbreviation and its bounding transitions.
  • Correct DST math — EST↔EDT-style transitions for historical dates (pre-1970 LMT, wartime double DST) and future dates generated from the TZif POSIX-TZ footer rules (Mm.w.d, Jn, and n date forms).
  • Ambiguity handlingPeriodForLocal returns *PeriodNotFound for a spring-forward gap and *AmbiguousTime for a fall-back overlap, with an optional dst preference to disambiguate — exactly like the gem.
  • Transitions & offsetsTransitionsUpTo and OffsetsUpTo over a UTC window, chronological and de-duplicated.
  • TZInfo::CountryGetCountry, Code / Name / ZoneIdentifiers / Zones, AllCountryCodes (249 ISO-3166 countries), from the IANA iso3166.tab / zone1970.tab.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three operating systems.

Install

go get github.com/go-ruby-tzinfo/tzinfo

Usage

package main

import (
	"fmt"
	"time"

	"github.com/go-ruby-tzinfo/tzinfo"
)

func main() {
	tz, _ := tzinfo.Get("America/New_York")

	// Period in force at a UTC instant (summer → EDT).
	p := tz.PeriodForUTC(time.Date(2023, 6, 1, 12, 0, 0, 0, time.UTC))
	fmt.Println(p.Abbreviation(), p.UTCTotalOffset(), p.DST())
	// EDT -14400 true

	// UTC → local, tagged with the resolved offset.
	fmt.Println(tz.UTCToLocal(time.Date(2023, 6, 1, 12, 0, 0, 0, time.UTC)))
	// 2023-06-01 08:00:00 -0400 EDT

	// Local → UTC, with gap / ambiguity errors.
	if _, err := tz.LocalToUTC(time.Date(2023, 3, 12, 2, 30, 0, 0, time.UTC)); err != nil {
		fmt.Println(err) // spring-forward gap: *PeriodNotFound
	}
	if _, err := tz.LocalToUTC(time.Date(2023, 11, 5, 1, 30, 0, 0, time.UTC)); err != nil {
		fmt.Println(err) // fall-back overlap: *AmbiguousTime
	}
	// Disambiguate the overlap by asking for the daylight period.
	utc, _ := tz.LocalToUTC(time.Date(2023, 11, 5, 1, 30, 0, 0, time.UTC), true)
	fmt.Println(utc)
}

API ↔ gem

Ruby (tzinfo gem) Go (this package)
TZInfo::Timezone.get("America/…") tzinfo.Get("America/…")
TZInfo::Timezone.all_identifiers tzinfo.AllIdentifiers()
TZInfo::Timezone.all tzinfo.All()
tz.utc_to_local(t) / local_to_utc tz.UTCToLocal(t) / LocalToUTC
tz.period_for_utc(t) / _for_local tz.PeriodForUTC(t) / PeriodForLocal
tz.transitions_up_to(to, from) tz.TransitionsUpTo(to, from)
tz.offsets_up_to(to, from) tz.OffsetsUpTo(to, from)
tz.current_period / .now tz.CurrentPeriod() / tz.Now()
tz.abbreviation(t) / dst?(t) tz.Abbreviation(t) / tz.DST(t)
TimezonePeriod / TimezoneOffset TimezonePeriod / TimezoneOffset
TZInfo::PeriodNotFound / AmbiguousTime *PeriodNotFound / *AmbiguousTime
TZInfo::Country.get("US") tzinfo.GetCountry("US")

Time zone data

The internal/tzdata package embeds the compiled IANA zoneinfo (the same TZif files the gem's ZoneinfoDataSource reads from the system) as a committed, store-method zip, and parses the TZif v2/v3 binary format itself. Because the base (standard) offset for a DST period is not stored in a zoneinfo file, it is derived from the surrounding non-DST periods exactly as TZInfo's ZoneinfoReader does, so the base_utc_offset / std_offset split matches the gem byte-for-byte.

Tests & coverage

The suite is two-layer. Deterministic, ruby-free golden vectors (captured from the gem and committed) reproduce exact parity on offsets, abbreviations, DST flags, periods and local-time resolution across zones and transition instants, and alone hold 100% statement coverage — so the arch/qemu and Windows lanes stay green with no Ruby present. On the ubuntu/macos lanes an oracle additionally runs the live tzinfo gem (version-gated to Ruby ≥ 4.0) and diffs it against this package.

go test -race -coverpkg=./... -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # total: 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-tzinfo/tzinfo authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package tzinfo is a pure-Go (CGO-free), MRI-faithful reimplementation of the Ruby TZInfo library (the `tzinfo` gem). It embeds a complete offline copy of the compiled IANA time zone database (the TZif files the gem's ZoneinfoDataSource reads from the system zoneinfo) and exposes the TZInfo API on top of it: resolving a zone by identifier, converting between UTC and local time, computing the TimezonePeriod (offset, abbreviation, DST flag, transition boundaries) in force at an instant, and handling the spring-forward gap (PeriodNotFound) and fall-back overlap (AmbiguousTime) exactly as the gem does.

Relationship to the gem

Ruby (tzinfo gem)                      Go (this package)
-----------------                      -----------------
TZInfo::Timezone.get("America/…")      tzinfo.Get("America/…")
TZInfo::Timezone.all_identifiers       tzinfo.AllIdentifiers()
tz.utc_to_local(t)                     tz.UTCToLocal(t)
tz.local_to_utc(t)                     tz.LocalToUTC(t)
tz.period_for_utc(t)                   tz.PeriodForUTC(t)
tz.period_for_local(t)                 tz.PeriodForLocal(t)
tz.transitions_up_to(to, from)         tz.TransitionsUpTo(to, from)
tz.offsets_up_to(to, from)             tz.OffsetsUpTo(to, from)
tz.current_period / .now               tz.CurrentPeriod() / tz.Now()
tz.canonical_identifier                tz.CanonicalIdentifier()
TimezonePeriod / TimezoneOffset        TimezonePeriod / TimezoneOffset
TZInfo::Country.get("US")              tzinfo.GetCountry("US")

Every timestamp crossing the API is a Go time.Time; UTC-to-local conversions return a time.Time carrying a *time.Location fixed to the period's offset and abbreviation, mirroring how TZInfo tags a Time with the resolved offset.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AllCountryCodes

func AllCountryCodes() []string

AllCountryCodes returns every known ISO-3166 code, sorted (TZInfo::Country.all_codes).

func AllDataZoneIdentifiers

func AllDataZoneIdentifiers() ([]string, error)

AllDataZoneIdentifiers returns only the canonical (data) zone identifiers, sorted (TZInfo::Timezone.all_data_zone_identifiers).

func AllIdentifiers

func AllIdentifiers() ([]string, error)

AllIdentifiers returns every timezone identifier, sorted (TZInfo::Timezone.all_identifiers). Because every backward-link name is a full data zone, this equals AllDataZoneIdentifiers.

Types

type AmbiguousTime

type AmbiguousTime struct {
	Local time.Time
}

AmbiguousTime is returned by PeriodForLocal when the local time occurs twice because of a fall-back overlap and no disambiguation was supplied (mirrors TZInfo::AmbiguousTime).

func (*AmbiguousTime) Error

func (e *AmbiguousTime) Error() string

type Country

type Country struct {
	// contains filtered or unexported fields
}

Country is an ISO-3166 country and the timezone identifiers observed within it (TZInfo::Country). The data derives from the IANA zone1970.tab / iso3166.tab.

func GetCountry

func GetCountry(code string) (*Country, error)

GetCountry resolves a country by its ISO-3166 alpha-2 code (TZInfo::Country.get). The lookup is case-insensitive on input but the gem's codes are upper-case. An unknown code returns *InvalidCountryCode.

func (*Country) Code

func (c *Country) Code() string

Code returns the ISO-3166 alpha-2 code (code).

func (*Country) Name

func (c *Country) Name() string

Name returns the country's name (name).

func (*Country) ZoneIdentifiers

func (c *Country) ZoneIdentifiers() []string

ZoneIdentifiers returns the timezone identifiers for the country in the IANA data's order (zone_identifiers).

func (*Country) Zones

func (c *Country) Zones() ([]*Timezone, error)

Zones returns a resolved Timezone for each of the country's identifiers (zones). It returns an error if any identifier fails to resolve (it will not, for the shipped data).

type InvalidCountryCode

type InvalidCountryCode struct {
	Code string
}

InvalidCountryCode is returned by GetCountry for an unknown ISO-3166 code (mirrors TZInfo::InvalidCountryCode).

func (*InvalidCountryCode) Error

func (e *InvalidCountryCode) Error() string

type InvalidTimezoneIdentifier

type InvalidTimezoneIdentifier struct {
	Identifier string
}

InvalidTimezoneIdentifier is returned by Get when the identifier names no zone in the database (mirrors TZInfo::InvalidTimezoneIdentifier).

func (*InvalidTimezoneIdentifier) Error

func (e *InvalidTimezoneIdentifier) Error() string

type PeriodNotFound

type PeriodNotFound struct {
	Local time.Time
}

PeriodNotFound is returned by PeriodForLocal when the local time falls in a spring-forward gap that does not occur in the zone (mirrors TZInfo::PeriodNotFound).

func (*PeriodNotFound) Error

func (e *PeriodNotFound) Error() string

type Timezone

type Timezone struct {
	// contains filtered or unexported fields
}

Timezone is a resolved IANA time zone. It mirrors TZInfo::Timezone (specifically a DataTimezone); linked identifiers resolve to their canonical zone's data while remembering the identifier they were fetched with.

func All

func All() ([]*Timezone, error)

All returns a resolved Timezone for every identifier (TZInfo::Timezone.all).

func Get

func Get(identifier string) (*Timezone, error)

Get resolves a zone by IANA identifier (TZInfo::Timezone.get). The embedded database (matching the gem's ZoneinfoDataSource) materialises every IANA backward-link name — "US/Eastern", "GB", "Zulu", … — as a full data zone, so every valid identifier is its own canonical zone; there are no link indirections at this layer. An unknown identifier returns *InvalidTimezoneIdentifier.

func (*Timezone) Abbreviation

func (tz *Timezone) Abbreviation(t time.Time) string

Abbreviation returns the abbreviation in force at UTC instant t (abbreviation).

func (*Timezone) CanonicalIdentifier

func (tz *Timezone) CanonicalIdentifier() string

CanonicalIdentifier returns the identifier of the underlying data zone (canonical_identifier in the gem). For an unlinked zone it equals Identifier.

func (*Timezone) CurrentPeriod

func (tz *Timezone) CurrentPeriod() TimezonePeriod

CurrentPeriod returns the period in force at the current instant (current_period).

func (*Timezone) DST

func (tz *Timezone) DST(t time.Time) bool

DST reports whether daylight-saving time is in force at UTC instant t (dst?).

func (*Timezone) Identifier

func (tz *Timezone) Identifier() string

Identifier returns the identifier this Timezone was fetched with (identifier in the gem).

func (*Timezone) LocalToUTC

func (tz *Timezone) LocalToUTC(t time.Time, dst ...bool) (time.Time, error)

LocalToUTC converts a local wall-clock time to the corresponding UTC instant (local_to_utc). The wall-clock fields of t are read as local time in this zone; t's own Location is ignored for the wall value. When t falls in a spring-forward gap it returns *PeriodNotFound; when it is ambiguous (fall-back overlap) it returns *AmbiguousTime unless dst disambiguates (see PeriodForLocal).

func (*Timezone) Now

func (tz *Timezone) Now() time.Time

Now returns the current local time in this zone (now), a time.Time in a FixedZone for the current period.

func (*Timezone) OffsetsUpTo

func (tz *Timezone) OffsetsUpTo(to time.Time, from ...time.Time) []TimezoneOffset

OffsetsUpTo returns the distinct offsets observed in the half-open UTC window [from, to) (offsets_up_to). The offset in force at the start of the window is included.

func (*Timezone) PeriodForLocal

func (tz *Timezone) PeriodForLocal(t time.Time, dst ...bool) (TimezonePeriod, error)

PeriodForLocal returns the TimezonePeriod for a local wall-clock time (period_for_local). The wall-clock fields of t are read as local time in this zone. A gap yields *PeriodNotFound; an overlap yields *AmbiguousTime unless a single dst preference is supplied to select the daylight (true) or standard (false) period.

func (*Timezone) PeriodForUTC

func (tz *Timezone) PeriodForUTC(t time.Time) TimezonePeriod

PeriodForUTC returns the TimezonePeriod in force at the given UTC instant (period_for_utc). t is interpreted as an absolute instant regardless of its Location.

func (*Timezone) String

func (tz *Timezone) String() string

String returns the fetched identifier (to_s in the gem returns the identifier).

func (*Timezone) TransitionsUpTo

func (tz *Timezone) TransitionsUpTo(to time.Time, from ...time.Time) []TimezoneTransition

TransitionsUpTo returns the transitions occurring in the half-open UTC window [from, to), in chronological order (transitions_up_to). When from is the zero time, the window is open at the start.

func (*Timezone) UTCOffset

func (tz *Timezone) UTCOffset() int

UTCOffset returns the standard (base) UTC offset in seconds currently in force (utc_offset returns the current base offset in the gem).

func (*Timezone) UTCToLocal

func (tz *Timezone) UTCToLocal(t time.Time) time.Time

UTCToLocal converts a UTC instant to local time, returning a time.Time carrying a *time.Location fixed to the period's offset and abbreviation (utc_to_local).

type TimezoneOffset

type TimezoneOffset struct {
	// BaseUTCOffset is the standard-time offset east of UTC in seconds
	// (base_utc_offset / utc_offset in the gem).
	BaseUTCOffset int
	// STDOffset is the additional offset applied during daylight-saving time in
	// seconds (std_offset in the gem); zero outside DST.
	STDOffset int
	// Abbreviation is the local abbreviation, e.g. "EST", "EDT", "LMT", "IST".
	Abbreviation string
}

TimezoneOffset describes the offset from UTC in force during a period: the base (standard) offset, the additional DST amount, the combined total, and the abbreviation. It mirrors TZInfo::TimezoneOffset.

func (TimezoneOffset) DST

func (o TimezoneOffset) DST() bool

DST reports whether daylight-saving time is in effect for this offset (dst? in the gem).

func (TimezoneOffset) UTCTotalOffset

func (o TimezoneOffset) UTCTotalOffset() int

UTCTotalOffset is the combined offset east of UTC in seconds (utc_total_offset in the gem).

type TimezonePeriod

type TimezonePeriod struct {
	// Offset is the offset that applies throughout the period.
	Offset TimezoneOffset
	// contains filtered or unexported fields
}

TimezonePeriod is the interval during which a single TimezoneOffset is in force, bounded (where known) by the transitions that begin and end it. It mirrors TZInfo::TimezonePeriod.

func (TimezonePeriod) Abbreviation

func (p TimezonePeriod) Abbreviation() string

Abbreviation returns the period's abbreviation (abbreviation / abbr / zone_identifier in the gem).

func (TimezonePeriod) BaseUTCOffset

func (p TimezonePeriod) BaseUTCOffset() int

BaseUTCOffset returns the standard-time offset in seconds.

func (TimezonePeriod) DST

func (p TimezonePeriod) DST() bool

DST reports whether the period is daylight-saving.

func (TimezonePeriod) EndTransition

func (p TimezonePeriod) EndTransition() (t time.Time, ok bool)

EndTransition returns the UTC instant the period ends and whether it is bounded in the future. When ok is false the period is the current, open-ended period.

func (TimezonePeriod) STDOffset

func (p TimezonePeriod) STDOffset() int

STDOffset returns the DST component of the offset in seconds.

func (TimezonePeriod) StartTransition

func (p TimezonePeriod) StartTransition() (t time.Time, ok bool)

StartTransition returns the UTC instant the period begins and whether it is bounded in the past. When ok is false the period extends to the beginning of time.

func (TimezonePeriod) UTCTotalOffset

func (p TimezonePeriod) UTCTotalOffset() int

UTCTotalOffset returns the combined UTC offset in seconds.

type TimezoneTransition

type TimezoneTransition struct {
	// At is the UTC instant of the transition.
	At time.Time
	// Offset is the offset that comes into force at and after At.
	Offset TimezoneOffset
}

TimezoneTransition is a single transition instant and the offset that takes effect at it, as returned by TransitionsUpTo (mirrors TZInfo::TimezoneTransition).

Directories

Path Synopsis
internal
tzdata
Package tzdata embeds a complete, offline copy of the compiled IANA time zone database (the TZif files the tzinfo gem's ZoneinfoDataSource reads) and parses the TZif (zoneinfo) v2/v3 format into transition tables.
Package tzdata embeds a complete, offline copy of the compiled IANA time zone database (the TZif files the tzinfo gem's ZoneinfoDataSource reads) and parses the TZif (zoneinfo) v2/v3 format into transition tables.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL