-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
496 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,42 @@ | ||
# date | ||
Golang's missing date package, including ranges | ||
|
||
Date builds on Golang's `time.Time` package to provide a ISO 8601 Date type | ||
|
||
Create a new `Date`: | ||
|
||
```go | ||
import "github.com/aodin/date" | ||
|
||
func main() { | ||
march1st := date.New(2015, 3, 1) | ||
fmt.Println(march1st) // 2015-03-01 | ||
} | ||
``` | ||
|
||
Parse a date: | ||
|
||
```go | ||
Parse("2015-03-01") | ||
``` | ||
|
||
Range operations: | ||
|
||
```go | ||
Range(Today(), Today.AddDays(1)).Intersection(Today()) | ||
``` | ||
|
||
```go | ||
EntireMonth().Union(EntireYear()) | ||
``` | ||
|
||
By default, the `Date` type uses the `time.UTC` location. It can be passed to functions requiring the `time.Time` type using the embedded `Time` field: | ||
|
||
```go | ||
jan1 := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) | ||
jan1.Before(march1.Time) | ||
``` | ||
|
||
Happy Hacking! | ||
|
||
aodin, 2015 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package date | ||
|
||
import ( | ||
"bytes" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"time" | ||
) | ||
|
||
// DateLayout uses ISO 8601 as a default for parsing and rendering | ||
const DateLayout = "2006-01-02" | ||
|
||
type Date struct{ time.Time } | ||
|
||
func (date Date) format() string { | ||
return date.Time.Format(DateLayout) | ||
} | ||
|
||
// AddDays adds the given number of days to the date | ||
func (date Date) AddDays(days int) Date { | ||
return Date{Time: date.Time.AddDate(0, 0, days)} | ||
} | ||
|
||
func (date Date) After(other Date) bool { | ||
return date.Time.After(other.Time) | ||
} | ||
|
||
func (date Date) Before(other Date) bool { | ||
return date.Time.Before(other.Time) | ||
} | ||
|
||
// String returns the Date as a string | ||
func (date Date) String() string { | ||
return date.format() | ||
} | ||
|
||
// Equals returns true if the dates are equal | ||
func (date Date) Equals(other Date) bool { | ||
return date.Time.Equal(other.Time) | ||
} | ||
|
||
// UnmarshalJSON converts a byte array into a Date | ||
func (d *Date) UnmarshalJSON(text []byte) error { | ||
b := bytes.NewBuffer(text) | ||
dec := json.NewDecoder(b) | ||
var s string | ||
if err := dec.Decode(&s); err != nil { | ||
return err | ||
} | ||
value, err := time.Parse(DateLayout, s) | ||
if err != nil { | ||
return err | ||
} | ||
d.Time = value | ||
return nil | ||
} | ||
|
||
// MarshalJSON returns the JSON output of a Date | ||
func (d Date) MarshalJSON() ([]byte, error) { | ||
return []byte(`"` + d.format() + `"`), nil | ||
} | ||
|
||
// Scan converts an SQL value into a Date | ||
func (date *Date) Scan(value interface{}) error { | ||
date.Time = value.(time.Time) | ||
return nil | ||
} | ||
|
||
// Value returns the date formatted for insert into PostgreSQL | ||
func (date Date) Value() (driver.Value, error) { | ||
return date.format(), nil | ||
} | ||
|
||
// Within returns true if the Date is within the range - inclusive | ||
func (date Date) Within(term DateRange) bool { | ||
return !(date.Before(term.Start) || date.After(term.End)) | ||
} | ||
|
||
// Today converts the local time to a Date | ||
func Today() Date { | ||
return FromTime(time.Now()) | ||
} | ||
|
||
// FromTime creates a Date from a time.Time | ||
func FromTime(t time.Time) Date { | ||
return New(t.Year(), t.Month(), t.Day()) | ||
} | ||
|
||
// New creates a new Date | ||
func New(year int, month time.Month, day int) Date { | ||
// Remove all second and nano second information and mark as UTC | ||
return Date{Time: time.Date(year, month, day, 0, 0, 0, 0, time.UTC)} | ||
} | ||
|
||
// Parse converts a ISO 8601 date string to a Date, possibly returning an error | ||
func Parse(value string) (Date, error) { | ||
return ParseUsingLayout(DateLayout, value) | ||
} | ||
|
||
// ParseUsingLayout calls Parse with a different date layout | ||
func ParseUsingLayout(format, value string) (Date, error) { | ||
t, err := time.Parse(format, value) | ||
if err != nil { | ||
return Date{}, err | ||
} | ||
return Date{Time: t}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package date | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestDate(t *testing.T) { | ||
day := New(2015, 3, 1) | ||
assert.Equal(t, "2015-03-01", day.String()) | ||
|
||
output, err := json.Marshal(day) | ||
assert.Nil(t, err, "JSON marshaling of dates should not error") | ||
assert.Equal(t, []byte(`"2015-03-01"`), output) | ||
|
||
nextDay := New(2015, 3, 2) | ||
assert.True(t, nextDay.Equals(day.AddDays(1))) | ||
|
||
parsed, err := Parse("2015-03-01") | ||
assert.Nil(t, err, "Parsing of properly formatted dates should not error") | ||
assert.True(t, parsed.Equals(day)) | ||
|
||
err = day.UnmarshalJSON([]byte(`"2015-03-01"`)) | ||
assert.Nil( | ||
t, err, "UnmarshalJSON of a valid slice of bytes should not error", | ||
) | ||
|
||
jan1 := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) | ||
assert.True(t, jan1.Before(day.Time)) | ||
} | ||
|
||
func TestDate_Within(t *testing.T) { | ||
march1 := New(2015, 3, 1) | ||
|
||
feb := EntireMonth(2015, 2) | ||
march := EntireMonth(2015, 3) | ||
|
||
assert.False(t, march1.Within(feb)) | ||
assert.Equal(t, march1, march.Start) | ||
assert.True(t, march1.Within(march)) | ||
} |
Oops, something went wrong.