Type-safe timezone handling for Go using generics.
Meridian solves a fundamental problem: timezone information in time.Time is data, not type, and can be lost without the compiler noticing. With Meridian, timezone information is encoded directly into the type system, making wrong timezone handling impossible to compile.
- ✅ Type-safe timezones:
utc.Timeandet.Timeare different types - ✅ Compiler-enforced correctness: Prevents accidental timezone mixing
- ✅ Clean, ergonomic API:
utc.Now(),et.Date(...),pt.Time - ✅ 16 built-in timezone packages: Covers major global business centers
- ✅ Extensible: Easy to add custom timezone packages
- ✅ Full GitHub Actions CI/CD pipeline
- ✅ Automated testing with coverage reports
- ✅ Race condition detection
Install the package in your Go project:
go get github.com/matthalp/go-meridianImport and use timezone-specific packages:
package main
import (
"fmt"
"time"
"github.com/matthalp/go-meridian/et"
"github.com/matthalp/go-meridian/utc"
)
func main() {
// Get current time in different timezones
now := utc.Now()
fmt.Println(now.Format(time.RFC3339))
// Create a specific date/time
meeting := et.Date(2024, time.December, 25, 10, 30, 0, 0)
fmt.Println(meeting.Format(time.Kitchen))
// Type-safe function signatures
storeInDatabase(utc.Now()) // ✅ Compiles
// storeInDatabase(et.Now()) // ❌ Won't compile!
}
// Functions can require specific timezones
func storeInDatabase(t utc.Time) {
// Always receives UTC time, guaranteed by the compiler
}Problem: Standard Go time.Time loses timezone information easily:
t := time.Now().UTC()
// Later in code...
formatted := t.Format(time.Kitchen) // What timezone is this? 🤷Solution: Meridian encodes timezone in the type:
t := utc.Now()
// Later in code...
formatted := t.Format(time.Kitchen) // Definitely UTC! ✅For backwards compatibility, all existing timezones are available at both root and in the timezones/ directory:
Root-level imports (backwards compatible):
github.com/matthalp/go-meridian/aest- Australian Eastern Time (Australia/Sydney)github.com/matthalp/go-meridian/brt- Brasília Time (America/Sao_Paulo)github.com/matthalp/go-meridian/cet- Central European Time (Europe/Paris)github.com/matthalp/go-meridian/cst- China Standard Time (Asia/Shanghai)github.com/matthalp/go-meridian/ct- Central Time (America/Chicago)github.com/matthalp/go-meridian/est- Eastern Standard Time (America/New_York)github.com/matthalp/go-meridian/et- Eastern Time (America/New_York)github.com/matthalp/go-meridian/gmt- Greenwich Mean Time (Europe/London)github.com/matthalp/go-meridian/hkt- Hong Kong Time (Asia/Hong_Kong)github.com/matthalp/go-meridian/ist- India Standard Time (Asia/Kolkata)github.com/matthalp/go-meridian/jst- Japan Standard Time (Asia/Tokyo)github.com/matthalp/go-meridian/mt- Mountain Time (America/Denver)github.com/matthalp/go-meridian/pt- Pacific Time (America/Los_Angeles)github.com/matthalp/go-meridian/pst- Pacific Standard Time (America/Los_Angeles)github.com/matthalp/go-meridian/sgt- Singapore Time (Asia/Singapore)github.com/matthalp/go-meridian/utc- Coordinated Universal Time
Going forward, all timezone packages are located in the timezones/ directory:
github.com/matthalp/go-meridian/timezones/aest- Australian Eastern Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/brt- Brasília Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/cet- Central European Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/cst- China Standard Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/ct- Central Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/est- Eastern Standard Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/et- Eastern Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/gmt- Greenwich Mean Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/hkt- Hong Kong Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/ist- India Standard Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/jst- Japan Standard Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/mt- Mountain Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/pt- Pacific Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/pst- Pacific Standard Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/sgt- Singapore Timegithub.zerozr99.workers.dev/matthalp/go-meridian/timezones/utc- Coordinated Universal Time
When adding new timezones, they will only be generated in the timezones/ directory. The root-level packages are maintained for backwards compatibility with existing code.
Each timezone package provides:
Now()- Get current time in that timezoneDate()- Create a specific date/timeParse()- Parse a formatted string in that timezoneUnix(),UnixMilli(),UnixMicro()- Create from Unix timestampsFromMoment()- Convert any time to that timezoneTime- Type alias for clean function signatures
Note: ParseInLocation is not needed as timezone packages already have their location built-in.
Meridian provides seamless timezone conversion while preserving type safety:
// Convert between timezone types
etTime := et.Date(2024, time.December, 25, 10, 30, 0, 0)
utcTime := utc.FromMoment(etTime) // Same moment, displayed as UTC
ptTime := pt.FromMoment(etTime) // Same moment, displayed as PT
// Convert from standard time.Time
stdTime := time.Now()
typedTime := utc.FromMoment(stdTime) // Now type-safe!
// All conversions preserve the moment in time
fmt.Println(etTime.UTC().Equal(utcTime.UTC())) // trueThe Moment interface allows both time.Time and meridian.Time[TZ] to be used interchangeably for conversions, providing flexibility while maintaining type safety where it matters.
As of v2.0.0, timezone packages are automatically generated from the timezones.yaml configuration file. To add a new timezone:
-
Edit
timezones.yamland add your timezone definition:timezones: - name: jst location: Asia/Tokyo description: Japan Standard Time generate_at_root: false # New timezones go in timezones/ directory only
-
Generate the package:
make generate
-
Import and use:
import "github.com/matthalp/go-meridian/timezones/jst" now := jst.Now()
The generator creates both the package implementation and comprehensive tests automatically. For more details, see AGENTS.md.
This repository includes an example program demonstrating usage:
go run cmd/example/main.go# Run all tests
go test -v ./...
# Run tests with coverage
go test -v -race -coverprofile=coverage.out ./...
# View coverage report
go tool cover -html=coverage.out# Install golangci-lint (if not already installed)
# https://golangci-lint.run/usage/install/
# Run linter
golangci-lint runThe project includes a comprehensive GitHub Actions workflow that:
- Test Job: Runs unit tests with race detection and generates coverage reports
- Lint Job: Runs golangci-lint to ensure code quality
- Build Job: Verifies the project builds successfully and go.mod is tidy
Coverage reports are automatically uploaded to Codecov for tracking test coverage over time.
.
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions workflow
├── cmd/
<<<<<<< HEAD
│ └── example/
│ └── main.go # Example usage program
├── et/ # Eastern Time timezone package
│ ├── et.go
│ └── et_test.go
├── pt/ # Pacific Time timezone package
│ ├── pt.go
│ └── pt_test.go
├── utc/ # UTC timezone package
=======
│ ├── example/
│ │ └── main.go # Example usage program
│ └── generate-timezones/
│ └── main.go # Timezone package generator
├── timezones/ # Generated timezone packages (v2.0.0+)
│ ├── est/ # Eastern Time timezone package
│ │ ├── est.go
│ │ └── est_test.go
│ ├── pst/ # Pacific Time timezone package
│ │ ├── pst.go
│ │ └── pst_test.go
│ └── utc/ # UTC timezone package
│ ├── utc.go
│ └── utc_test.go
├── est/ # Eastern Time (backwards compatibility)
│ ├── est.go
│ └── est_test.go
├── pst/ # Pacific Time (backwards compatibility)
│ ├── pst.go
│ └── pst_test.go
├── utc/ # UTC (backwards compatibility)
>>>>>>> 2608a6a (Release v2.0.0: Add timezones/ directory structure with backwards compatibility)
│ ├── utc.go
│ └── utc_test.go
├── .golangci.yml # Linter configuration
├── doc.go # Package documentation
├── example_test.go # Testable examples
├── go.mod # Go module file
├── meridian.go # Core generic types and functions
├── meridian_test.go # Core package tests
├── timezones.yaml # Timezone definitions for generator
├── Makefile # Development tasks
└── README.md # This file
For detailed API documentation, see pkg.go.dev once the package is published.
To make your package available for others to use:
-
Push to GitHub:
git add . git commit -m "Initial commit" git remote add origin https://github.com/matthalp/go-meridian.git git push -u origin main
-
Create a version tag:
git tag v0.1.0 git push origin v0.1.0
-
The package will be automatically available via
go get:- Others can install with:
go get github.com/matthalp/[email protected] - Documentation will appear on pkg.go.dev within minutes
- Others can install with:
-
Update the version in
meridian.gowhen releasing new versions
This project follows Semantic Versioning:
- MAJOR version (v1.0.0) for incompatible API changes
- MINOR version (v0.1.0) for new functionality in a backwards compatible manner
- PATCH version (v0.0.1) for backwards compatible bug fixes
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
All PRs must pass CI checks before merging.
This project is licensed under the MIT License.