Skip to content

Commit

Permalink
Merge pull request #1 from min0625/feat/implement-time-zone-type
Browse files Browse the repository at this point in the history
feat: implement time zone type
  • Loading branch information
min0625 authored Nov 20, 2022
2 parents ef00685 + f7449d9 commit b5409c5
Show file tree
Hide file tree
Showing 7 changed files with 770 additions and 2 deletions.
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
format:
@go install mvdan.cc/gofumpt@latest
gofumpt -l -w -extra .

lint:
@go install github.com/golangci/golangci-lint/cmd/[email protected]
golangci-lint run ./...

test:
go test ./...

check: format lint test
go mod tidy
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,51 @@
# tz
golang time zone type
# Golang Time Zone Type
[![Go Reference](https://pkg.go.dev/badge/github.com/min0625/tz.svg)](https://pkg.go.dev/github.com/min0625/tz)

## Features
- TimeZone based on time.LoadLocation format, Not support the "Local" time zone.
- The format is "UTC" or IANA time zone database name. See: https://www.iana.org/time-zones.
- The zero value means UTC time zone.
- The UTC time zone always uses a zero value.
- Implement the fmt.Stringer interface.
- Implement the sql.Scanner interface.
- Implement the driver.Valuer interface.
- Implement the encoding.TextMarshaler interface.
- Implement the encoding.TextUnmarshaler interface.
- Implement the json.Marshaler interface.
- Implement the json.Unmarshaler interface.

## Installation
```sh
go get github.com/min0625/tz
```

## Quick start
```go
package main

import (
"fmt"
"time"
_ "time/tzdata"

"github.com/min0625/tz"
)

func main() {
z, err := tz.LoadTimeZone("America/New_York")
if err != nil {
panic(err)
}

fmt.Println(z.String())
fmt.Println(time.Time{}.In(z.Location()).Location().String())

// Output:
// America/New_York
// America/New_York
}

```

## Example
See: [./time_zone_example_test.go](./time_zone_example_test.go)
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/min0625/tz

go 1.18

require github.com/stretchr/testify v1.8.1

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
116 changes: 116 additions & 0 deletions time_zone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package tz

import (
"database/sql"
"database/sql/driver"
"encoding"
"encoding/json"
"errors"
"fmt"
"time"
)

// TimeZone based on time.LoadLocation format, Not support the "Local" time zone.
// The format is "UTC" or IANA time zone database name.
// See: https://www.iana.org/time-zones.
// The zero value means UTC time zone.
// The UTC time zone always uses a zero value.
type TimeZone struct {
loc *time.Location
}

var UTCTimeZone = TimeZone{}

var (
_ fmt.Stringer = TimeZone{}
_ sql.Scanner = &TimeZone{}
_ driver.Valuer = TimeZone{}
_ encoding.TextMarshaler = TimeZone{}
_ encoding.TextUnmarshaler = &TimeZone{}
_ json.Marshaler = TimeZone{}
_ json.Unmarshaler = &TimeZone{}
)

func LoadTimeZone(name string) (TimeZone, error) {
var z TimeZone
if err := z.loadString(name); err != nil {
return TimeZone{}, err
}

return z, nil
}

// Location always returns a non-nil location.
func (z TimeZone) Location() *time.Location {
if loc := z.loc; loc != nil {
return loc
}

return time.UTC
}

func (z *TimeZone) loadString(s string) error {
loc, err := time.LoadLocation(s)
if err != nil {
return err
}

if loc == time.Local {
return errors.New("invalid TimeZone: Local")
}

if loc == time.UTC {
loc = nil
}

z.loc = loc
return nil
}

func (z TimeZone) String() string {
return z.Location().String()
}

func (z *TimeZone) Scan(value any) error {
var ns sql.NullString
if err := ns.Scan(value); err != nil {
return err
}

if !ns.Valid {
return errors.New("converting NULL to TimeZone is unsupported")
}

return z.loadString(ns.String)
}

func (z TimeZone) Value() (driver.Value, error) {
return z.String(), nil
}

func (z TimeZone) MarshalText() (text []byte, err error) {
return []byte(z.String()), nil
}

func (z *TimeZone) UnmarshalText(text []byte) error {
return z.loadString(string(text))
}

func (z TimeZone) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%q", z.String())), nil
}

func (z *TimeZone) UnmarshalJSON(data []byte) error {
// Ignore null, like in the main JSON package.
// See: https://pkg.go.dev/encoding/json#Unmarshaler.
if string(data) == "null" {
return nil
}

var unquote string
if _, err := fmt.Sscanf(string(data), `%q`, &unquote); err != nil {
return err
}

return z.loadString(unquote)
}
70 changes: 70 additions & 0 deletions time_zone_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package tz_test

import (
"fmt"
"time"
_ "time/tzdata"

"github.com/min0625/tz"
)

func ExampleTimeZone() {
z, err := tz.LoadTimeZone("America/New_York")
if err != nil {
panic(err)
}

fmt.Println(z.String())
fmt.Println(time.Time{}.In(z.Location()).Location().String())

// Output:
// America/New_York
// America/New_York
}

func ExampleTimeZone_zeroValue() {
z, err := tz.LoadTimeZone("UTC")
if err != nil {
panic(err)
}

fmt.Println(z == tz.TimeZone{})

// Output:
// true
}

func ExampleTimeZone_Scan() {
var z tz.TimeZone
if err := z.Scan("America/New_York"); err != nil {
panic(err)
}

fmt.Println(time.Time{}.In(z.Location()).Location().String())

// Output:
// America/New_York
}

func ExampleTimeZone_Value() {
z, err := tz.LoadTimeZone("America/New_York")
if err != nil {
panic(err)
}

v, err := z.Value()
fmt.Printf("%v %T %v\n", v, v, err)

// Output:
// America/New_York string <nil>
}

func ExampleTimeZone_Value_zeroValue() {
var z tz.TimeZone

v, err := z.Value()
fmt.Printf("%v %T %v\n", v, v, err)

// Output:
// UTC string <nil>
}
Loading

0 comments on commit b5409c5

Please sign in to comment.