_ _ _
_ _ | |_ ___ | |_ (_)_ __ ___ ___
| | | || __|/ __| | __|| | '_ ` _ \ / _ \
| |_| || |_| (__ | |_ | | | | | | | __/
\___/ \__|\___| \__||_|_| |_| |_|\___| The utc package provides a zero-dependency wrapper around Go's time.Time for applications that want timestamp fields normalized to UTC at package boundaries.
Go's time.Time is flexible, but UTC storage is usually a convention that every caller has to remember. utc.Time makes that convention a package boundary: values are normalized when they enter, stored privately, and emitted as UTC through JSON, text, YAML, SQL, and standard time.Time accessors.
Use it for API models, database records, event payloads, and config structs where timestamps should always represent instants in UTC.
- The underlying
time.Timeis unexported, so callers cannot construct non-UTC values with struct literals. - Constructors, parsers, scanners, and unmarshaling methods normalize values to UTC.
- Use
t.UTC()ort.Time()when you need a standardtime.Time.
- JSON accepts strings or
null; non-string JSON values are rejected. - JSON, text, and YAML output preserve sub-second precision with RFC3339Nano formatting.
- SQL
ValueandScansupport UTC-normalized database boundaries.
- Common US/EU date and time formatting helpers.
- Named US timezone helpers with DST-aware
Pacific,Eastern,Central, andMountainconversions. - Unix timestamp and UTC day-boundary helpers.
- Zero external dependencies.
- Go 1.18 or later.
- A convenient replacement for timestamp fields and serialization/database boundaries; use
t.Time()ort.UTC()when an API requires a concretetime.Time.
To install the utc package, use the following command:
go get github.com/agentstation/utcRequirements: Go 1.18 or later
YAML Usage: The package implements YAML marshal/unmarshal interface methods. To encode or decode YAML in an application, install the YAML library you already use:
go get github.com/goccy/go-yaml@v1.10.1- No external dependencies for core functionality
- Lightweight - adds minimal footprint to your project
- Fast installation -
go getwith no dependency resolution delays
- Go 1.18+ support (broader than most time libraries)
- Cross-platform - works on all Go-supported platforms
- Enforced UTC storage - wrapped values are normalized before storage and output
- Strict JSON input - accidental numbers, booleans, objects, and arrays are rejected
- Precision preserving - nanoseconds survive JSON/text/YAML round trips
- Race condition tested - safe for concurrent use
- Comprehensive test coverage - focused coverage for parsing, formatting, serialization, database, and timezone helpers
- Small API - explicit constructors and explicit conversion back to
time.Time - Rich formatting options - US/EU date formats, RFC standards, custom layouts
- Automatic timezone handling - PST/PDT, EST/EDT transitions handled correctly
- Serialization ready - JSON, YAML, Database, Text encoding
- Debug mode - development-time logging for nil receiver calls on pointer-based methods
- Flexible parsing - handles multiple time formats automatically
Get up and running in seconds:
package main
import (
"fmt"
"github.com/agentstation/utc"
)
func main() {
// Get current time in UTC
now := utc.Now()
fmt.Println("Current UTC time:", now.RFC3339())
// Parse and convert to different formats
t, _ := utc.ParseRFC3339("2024-01-15T10:30:00Z")
fmt.Println("US format:", t.USDateShort()) // "01/15/2024"
fmt.Println("EU format:", t.EUDateShort()) // "15/01/2024"
fmt.Println("Pacific time:", t.Pacific()) // Auto PST/PDT
}See the difference between utc and Go's standard time package:
| Feature | Standard time.Time |
utc.Time |
|---|---|---|
| UTC Storage | Manual convention | Enforced by unexported storage and constructors |
| Dependencies | Zero deps | Zero deps |
| Rich Formatting | Manual layout strings | Built-in US/EU/RFC helpers |
| Timezone Conversion | Manual location loading | Helpers for common US zones plus generic In |
| JSON Support | RFC3339 string support | Strict string/null input, UTC normalization, RFC3339Nano output |
| YAML Support | No built-in YAML methods | MarshalYAML/UnmarshalYAML interface methods |
| Text Encoding | time.Time supports text encoding |
UTC-normalized text encoding |
| Database Ready | time.Time is database-friendly |
UTC-normalized Scan/Value methods |
| Unix Timestamps | Unix/UnixMilli methods |
Unix/UnixMilli plus constructors |
| Day Boundaries | Manual calculation | UTC StartOfDay/EndOfDay methods |
| Debug Support | N/A | Optional debug logging for nil pointer calls |
Start at boundaries: change API, database, event, and config timestamp fields from time.Time to utc.Time. Keep existing code that requires time.Time by calling t.Time() or t.UTC().
createdAt := utc.New(row.CreatedAt) // from time.Time
legacy(createdAt.Time()) // back to time.Time when requiredFor helper functions that should accept both time.Time and utc.Time, use the utc.UTC interface with utc.From. utc.UTC is an interface for values that expose UTC() time.Time, not a timezone value:
func normalize(t utc.UTC) utc.Time {
return utc.From(t)
}- JSON, text encoding, YAML libraries, and
database/sqlcan useutc.Timefields directly through standard marshal/unmarshal, scanner, and valuer interfaces. - Code generators and ORMs such as sqlc, GORM-style models, PostgreSQL/MySQL layers, MongoDB, or DynamoDB integrations vary: use
utc.Timewhen they support custom field types or scanner/valuer interfaces; uset.Time()/utc.New(...)at the boundary when they require concretetime.Time. - The root module has no external dependencies. Compile-time interface assertions that only prove compatibility live in tests;
database/sql/driverremains a production import becauseValue() (driver.Value, error)is the standard SQL value interface.
Before (standard library):
createdAt := time.Now().UTC()
fmt.Println(createdAt.Format("01/02/2006"))
loc, err := time.LoadLocation("America/New_York")
if err != nil {
return err
}
fmt.Println(createdAt.In(loc).Format(time.Kitchen))After (with UTC):
t := utc.Now()
fmt.Println(t.Eastern().Format(time.Kitchen)) // Auto EST/EDT
fmt.Println(t.USDateShort()) // "01/15/2024"- Import the package:
import "github.com/agentstation/utc"- Create a new UTC time:
// Get current time in UTC
now := utc.Now()
// Convert existing time.Time to UTC
myTime := utc.New(someTime)
// Parse a time string
t, err := utc.ParseRFC3339("2023-01-01T12:00:00Z")- Format times using various layouts:
t := utc.Now()
// US formats
fmt.Println(t.USDateShort()) // "01/02/2024"
fmt.Println(t.USDateTime12()) // "01/02/2024 03:04:05 PM"
// EU formats
fmt.Println(t.EUDateShort()) // "02/01/2024"
fmt.Println(t.EUDateTime24()) // "02/01/2024 15:04:05"
// ISO/RFC formats
fmt.Println(t.RFC3339()) // "2024-01-02T15:04:05Z"
fmt.Println(t.ISO8601()) // "2024-01-02T15:04:05Z"
// Components
fmt.Println(t.WeekdayLong()) // "Tuesday"
fmt.Println(t.MonthShort()) // "Jan"- Convert between timezones:
t := utc.Now()
// Get time in different US timezones
pacific := t.Pacific() // Handles PST/PDT automatically
eastern := t.Eastern() // Handles EST/EDT automatically
central := t.Central() // Handles CST/CDT automatically
mountain := t.Mountain() // Handles MST/MDT automatically- Serialization and Database operations:
// JSON marshaling
type Event struct {
StartTime utc.Time `json:"start_time"`
EndTime utc.Time `json:"end_time"`
}
// YAML marshaling (requires a YAML library like go-yaml)
type Config struct {
StartTime utc.Time `yaml:"start_time"`
EndTime utc.Time `yaml:"end_time"`
}
// Database operations
type Record struct {
CreatedAt utc.Time `db:"created_at"`
UpdatedAt utc.Time `db:"updated_at"`
}The package includes YAML marshaling/unmarshaling support through MarshalYAML/UnmarshalYAML methods that follow the common Go YAML interfaces. The package itself does not import a YAML library.
# Run root tests including YAML interface coverage
go test -tags yaml ./...
# Run root YAML tests plus real YAML codec integration tests
make test-yamlNote: Actual YAML file parsing/emission requires a YAML package in your application. utc only provides the methods those packages call. The example above matches the Go 1.18-compatible version used by the integration test; newer YAML library releases may require newer Go versions. The real codec integration test lives in integration/yaml so the root module remains dependency-free.
The project includes comprehensive Makefile targets for testing:
# Run tests (Go 1.18+, no dependencies)
make test
# Run tests with YAML interface and real codec coverage
make test-yaml
# Run all tests (core + YAML)
make test-all
# Generate coverage reports
make coverage # Core tests only
make coverage-yaml # Include YAML tests
make coverage-all # Both coverage reportsThe package includes a debug mode that helps identify potential bugs during development:
# Build with debug mode enabled
go build -tags debug
# Run tests with debug mode
go test -tags debug ./...When debug mode is enabled, the package logs warnings when pointer-based methods that can return errors are called on nil receivers:
[UTC DEBUG] 2024/01/02 15:04:05 debug.go:26: MarshalJSON() called on nil *Time receiver
[UTC DEBUG] 2024/01/02 15:04:05 debug.go:26: Scan() called on nil *Time receiver
The package includes several convenience methods:
// Unix timestamp conversions
t1 := utc.FromUnix(1704199445) // From Unix seconds
t2 := utc.FromUnixMilli(1704199445000) // From Unix milliseconds
seconds := t.Unix() // To Unix seconds
millis := t.UnixMilli() // To Unix milliseconds
// Day boundaries
start := t.StartOfDay() // 2024-01-02 00:00:00.000000000 UTC
end := t.EndOfDay() // 2024-01-02 23:59:59.999999999 UTC
// Generic timezone conversion
eastern, err := t.In("America/New_York")
tokyo, err := t.In("Asia/Tokyo")import "github.com/agentstation/utc"Package utc provides a small time.Time wrapper that stores instants in UTC.
The package keeps the underlying time.Time value unexported, normalizes values through constructors, parsers, scanners, and serializers, and exposes standard time.Time values through the Time and UTC methods and the UTC interface. When compiled with the debug build tag (-tags debug), pointer-based methods log nil receiver calls before returning errors where Go permits that behavior.
Key features:
- Constructors and parsers normalize values to UTC
- JSON marshaling/unmarshaling uses strict string/null inputs and preserves sub-second precision
- Text and YAML marshal/unmarshal support
- SQL database compatibility
- Timezone conversion helpers with automatic DST handling
- Extensive formatting options for US and EU date formats
Debug mode:
To enable debug logging, compile with: go build -tags debug
This logs nil receiver calls for pointer-based methods that can return errors.
- func ValidateTimezoneAvailability() error
- type Time
- func From(t UTC) Time
- func FromUnix(sec int64) Time
- func FromUnixMilli(ms int64) Time
- func New(t time.Time) Time
- func Now() Time
- func Parse(layout string, s string) (Time, error)
- func ParseRFC3339(s string) (Time, error)
- func ParseRFC3339Nano(s string) (Time, error)
- func (t Time) ANSIC() string
- func (t Time) Add(d time.Duration) Time
- func (t Time) After(u Time) bool
- func (t Time) Before(u Time) bool
- func (t Time) CST() time.Time
- func (t Time) Central() time.Time
- func (t Time) DateOnly() string
- func (t Time) EST() time.Time
- func (t Time) EUDateLong() string
- func (t Time) EUDateShort() string
- func (t Time) EUDateTime12() string
- func (t Time) EUDateTime24() string
- func (t Time) EUTime12() string
- func (t Time) EUTime24() string
- func (t Time) Eastern() time.Time
- func (t Time) EndOfDay() Time
- func (t Time) Equal(u Time) bool
- func (t Time) Format(layout string) string
- func (t Time) ISO8601() string
- func (t Time) In(name string) (time.Time, error)
- func (t Time) InLocation(loc *time.Location) time.Time
- func (t Time) IsZero() bool
- func (t Time) Kitchen() string
- func (t Time) MST() time.Time
- func (t *Time) MarshalJSON() ([]byte, error)
- func (t Time) MarshalText() ([]byte, error)
- func (t Time) MarshalYAML() (any, error)
- func (t Time) MonthLong() string
- func (t Time) MonthShort() string
- func (t Time) Mountain() time.Time
- func (t Time) PST() time.Time
- func (t Time) Pacific() time.Time
- func (t Time) RFC3339() string
- func (t Time) RFC3339Nano() string
- func (t Time) RFC822() string
- func (t Time) RFC822Z() string
- func (t Time) RFC850() string
- func (t *Time) Scan(value any) error
- func (t Time) StartOfDay() Time
- func (t Time) String() string
- func (t Time) Sub(u Time) time.Duration
- func (t Time) Time() time.Time
- func (t Time) TimeFormat(layout TimeLayout) string
- func (t Time) TimeOnly() string
- func (t Time) USDateLong() string
- func (t Time) USDateShort() string
- func (t Time) USDateTime12() string
- func (t Time) USDateTime24() string
- func (t Time) USTime12() string
- func (t Time) USTime24() string
- func (t Time) UTC() time.Time
- func (t Time) Unix() int64
- func (t Time) UnixMilli() int64
- func (t *Time) UnmarshalJSON(data []byte) error
- func (t *Time) UnmarshalText(text []byte) error
- func (t *Time) UnmarshalYAML(unmarshal func(any) error) error
- func (t Time) Value() (driver.Value, error)
- func (t Time) WeekdayLong() string
- func (t Time) WeekdayShort() string
- type TimeLayout
- type UTC
func ValidateTimezoneAvailability() errorValidateTimezoneAvailability checks whether package timezone locations were initialized. It returns nil if initialization succeeded.
type Time
Time stores a time instant normalized to UTC.
type Time struct {
// contains filtered or unexported fields
}func From
func From(t UTC) TimeFrom returns a new Time from any value that exposes a UTC time.Time.
func FromUnix
func FromUnix(sec int64) TimeUnix helpers
func FromUnixMilli
func FromUnixMilli(ms int64) Timefunc New
func New(t time.Time) TimeNew returns a new Time from a time.Time.
func Now
func Now() TimeNow returns the current time in UTC.
func Parse
func Parse(layout string, s string) (Time, error)Parse parses a time string in the specified format and returns a Time.
func ParseRFC3339
func ParseRFC3339(s string) (Time, error)ParseRFC3339 parses a time string in RFC3339 format and returns a Time.
func ParseRFC3339Nano
func ParseRFC3339Nano(s string) (Time, error)ParseRFC3339Nano parses a time string in RFC3339Nano format and returns a Time.
func (Time) ANSIC
func (t Time) ANSIC() stringANSIC formats time as "Mon Jan _2 15:04:05 2006"
func (Time) Add
func (t Time) Add(d time.Duration) TimeAdd returns the time t+d
func (Time) After
func (t Time) After(u Time) boolAfter reports whether the time is after u
func (Time) Before
func (t Time) Before(u Time) boolBefore reports whether the time is before u
func (Time) CST
func (t Time) CST() time.TimeCST returns t in CST
func (Time) Central
func (t Time) Central() time.TimeCentral returns t in Central time (handles CST/CDT automatically)
func (Time) DateOnly
func (t Time) DateOnly() stringDateOnly formats time as "2006-01-02"
func (Time) EST
func (t Time) EST() time.TimeEST returns t in EST
func (Time) EUDateLong
func (t Time) EUDateLong() stringEUDateLong formats time as "2 January 2006"
func (Time) EUDateShort
func (t Time) EUDateShort() stringEUDateShort formats time as "02/01/2006"
func (Time) EUDateTime12
func (t Time) EUDateTime12() stringEUDateTime12 formats time as "02/01/2006 03:04:05 PM"
func (Time) EUDateTime24
func (t Time) EUDateTime24() stringEUDateTime24 formats time as "02/01/2006 15:04:05"
func (Time) EUTime12
func (t Time) EUTime12() stringEUTime12 formats time as "3:04 PM"
func (Time) EUTime24
func (t Time) EUTime24() stringEUTime24 formats time as "15:04"
func (Time) Eastern
func (t Time) Eastern() time.TimeEastern returns t in Eastern time (handles EST/EDT automatically)
func (Time) EndOfDay
func (t Time) EndOfDay() Timefunc (Time) Equal
func (t Time) Equal(u Time) boolEqual reports whether t and u represent the same time instant
func (Time) Format
func (t Time) Format(layout string) stringFormat formats the time using the specified layout
func (Time) ISO8601
func (t Time) ISO8601() stringISO8601 formats time as "2006-01-02T15:04:05Z07:00" (same as RFC3339)
func (Time) In
func (t Time) In(name string) (time.Time, error)In converts time to a named location (e.g., "America/Los_Angeles").
func (Time) InLocation
func (t Time) InLocation(loc *time.Location) time.TimeInLocation converts time to a provided *time.Location.
func (Time) IsZero
func (t Time) IsZero() boolAdd the useful utility methods while maintaining chainability
func (Time) Kitchen
func (t Time) Kitchen() stringKitchen formats time as "3:04PM"
func (Time) MST
func (t Time) MST() time.TimeMST returns t in MST
func (*Time) MarshalJSON
func (t *Time) MarshalJSON() ([]byte, error)MarshalJSON implements the json.Marshaler interface for Time. Returns an error for nil receivers to maintain consistency with standard marshaling behavior.
func (Time) MarshalText
func (t Time) MarshalText() ([]byte, error)MarshalText implements encoding.TextMarshaler.
func (Time) MarshalYAML
func (t Time) MarshalYAML() (any, error)MarshalYAML implements the yaml.Marshaler interface for Time.
func (Time) MonthLong
func (t Time) MonthLong() stringMonthLong formats time as "January"
func (Time) MonthShort
func (t Time) MonthShort() stringMonthShort formats time as "Jan"
func (Time) Mountain
func (t Time) Mountain() time.TimeMountain returns t in Mountain time (handles MST/MDT automatically)
func (Time) PST
func (t Time) PST() time.TimePST returns t in PST
func (Time) Pacific
func (t Time) Pacific() time.TimePacific returns t in Pacific time (handles PST/PDT automatically)
func (Time) RFC3339
func (t Time) RFC3339() stringRFC3339 formats time as "2006-01-02T15:04:05Z07:00"
func (Time) RFC3339Nano
func (t Time) RFC3339Nano() stringRFC3339Nano formats time as "2006-01-02T15:04:05.999999999Z07:00"
func (Time) RFC822
func (t Time) RFC822() stringRFC822 formats time as "02 Jan 06 15:04 MST"
func (Time) RFC822Z
func (t Time) RFC822Z() stringRFC822Z formats time as "02 Jan 06 15:04 -0700"
func (Time) RFC850
func (t Time) RFC850() stringRFC850 formats time as "Monday, 02-Jan-06 15:04:05 MST"
func (*Time) Scan
func (t *Time) Scan(value any) errorScan implements sql.Scanner for database operations. It accepts time.Time, string, and []byte values and stores them in UTC.
func (Time) StartOfDay
func (t Time) StartOfDay() TimeDay helpers - times are always in UTC within this package
func (Time) String
func (t Time) String() stringString implements fmt.Stringer. It prints the time in RFC3339Nano format.
func (Time) Sub
func (t Time) Sub(u Time) time.DurationSub returns the duration t-u
func (Time) Time
func (t Time) Time() time.TimeTime returns the underlying time.Time normalized to UTC.
func (Time) TimeFormat
func (t Time) TimeFormat(layout TimeLayout) stringTimeFormat formats the time using the specified layout
func (Time) TimeOnly
func (t Time) TimeOnly() stringTimeOnly formats time as "15:04:05"
func (Time) USDateLong
func (t Time) USDateLong() stringUSDateLong formats time as "January 2, 2006"
func (Time) USDateShort
func (t Time) USDateShort() stringUSDateShort formats time as "01/02/2006"
func (Time) USDateTime12
func (t Time) USDateTime12() stringUSDateTime12 formats time as "01/02/2006 03:04:05 PM"
func (Time) USDateTime24
func (t Time) USDateTime24() stringUSDateTime24 formats time as "01/02/2006 15:04:05"
func (Time) USTime12
func (t Time) USTime12() stringUSTime12 formats time as "3:04 PM"
func (Time) USTime24
func (t Time) USTime24() stringUSTime24 formats time as "15:04"
func (Time) UTC
func (t Time) UTC() time.TimeUTC returns t in UTC
func (Time) Unix
func (t Time) Unix() int64func (Time) UnixMilli
func (t Time) UnixMilli() int64func (*Time) UnmarshalJSON
func (t *Time) UnmarshalJSON(data []byte) errorUnmarshalJSON implements the json.Unmarshaler interface for Time.
func (*Time) UnmarshalText
func (t *Time) UnmarshalText(text []byte) errorUnmarshalText implements encoding.TextUnmarshaler.
func (*Time) UnmarshalYAML
func (t *Time) UnmarshalYAML(unmarshal func(any) error) errorUnmarshalYAML implements the yaml.Unmarshaler interface for Time.
func (Time) Value
func (t Time) Value() (driver.Value, error)Value implements driver.Valuer for database operations. It returns the UTC time.Time value as a driver.Value.
func (Time) WeekdayLong
func (t Time) WeekdayLong() stringWeekdayLong formats time as "Monday"
func (Time) WeekdayShort
func (t Time) WeekdayShort() stringWeekdayShort formats time as "Mon"
type TimeLayout
TimeLayout names one of the package's built-in formatting layouts.
type TimeLayout stringconst (
TimeLayoutUSDateShort TimeLayout = "01/02/2006"
TimeLayoutUSDateLong TimeLayout = "January 2, 2006"
TimeLayoutUSDateTime12 TimeLayout = "01/02/2006 03:04:05 PM"
TimeLayoutUSDateTime24 TimeLayout = "01/02/2006 15:04:05"
TimeLayoutUSTime12 TimeLayout = "3:04 PM"
TimeLayoutUSTime24 TimeLayout = "15:04"
TimeLayoutEUDateShort TimeLayout = "02/01/2006"
TimeLayoutEUDateLong TimeLayout = "2 January 2006"
TimeLayoutEUDateTime12 TimeLayout = "02/01/2006 03:04:05 PM"
TimeLayoutEUDateTime24 TimeLayout = "02/01/2006 15:04:05"
TimeLayoutEUTime12 TimeLayout = "3:04 PM"
TimeLayoutEUTime24 TimeLayout = "15:04"
TimeLayoutDateOnly TimeLayout = "2006-01-02"
TimeLayoutTimeOnly TimeLayout = "15:04:05"
TimeLayoutWeekdayLong TimeLayout = "Monday"
TimeLayoutWeekdayShort TimeLayout = "Mon"
TimeLayoutMonthLong TimeLayout = "January"
TimeLayoutMonthShort TimeLayout = "Jan"
)type UTC
UTC is implemented by values that can expose themselves as a UTC time.Time. It is an interface, not a timezone value. Both time.Time and utc.Time satisfy it.
type UTC interface {
UTC() time.Time
}Generated by gomarkdoc
This project is licensed under the MIT License - see the LICENSE file for details.