Skip to content

Commit

Permalink
Merge pull request #23 from Code-Hex/add/iso8601-date-methods
Browse files Browse the repository at this point in the history
copied some google-cloud-go/civil Date methods into iso8601 package
  • Loading branch information
Code-Hex authored Nov 12, 2023
2 parents 69a0991 + de08b38 commit 62b1c32
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 1 deletion.
63 changes: 62 additions & 1 deletion iso8601/date.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,13 @@ var _ interface {
encoding.TextUnmarshaler
} = (*Date)(nil)

// DateOf returns the iso8601 Date in which a time occurs in that time's location.
func DateOf(t time.Time) Date {
var d Date
d.Year, d.Month, d.Day = t.Date()
return d
}

// String returns the ISO8601 string representation of the format "YYYY-MM-DD".
// For example: "2012-12-01".
func (d Date) String() string {
Expand Down Expand Up @@ -422,7 +429,61 @@ func (d Date) Validate() error {

// StdTime converts the Date structure to a time.Time object, using UTC for the time.
func (d Date) StdTime() time.Time {
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, time.UTC)
return d.In(time.UTC)
}

// In returns the time corresponding to time 00:00:00 of the date in the location.
//
// In is always consistent with time.Date, even when time.Date returns a time
// on a different day. For example, if loc is America/Indiana/Vincennes, then both
//
// time.Date(1955, time.May, 1, 0, 0, 0, 0, loc)
//
// and
//
// iso8601.Date{Year: 1955, Month: time.May, Day: 1}.In(loc)
//
// return 23:00:00 on April 30, 1955.
//
// In panics if loc is nil.
func (d Date) In(loc *time.Location) time.Time {
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
}

// AddDays returns the date that is n days in the future.
// n can also be negative to go into the past.
func (d Date) AddDays(n int) Date {
return DateOf(d.StdTime().AddDate(0, 0, n))
}

// DaysSince returns the signed number of days between the date and s, not including the end day.
// This is the inverse operation to AddDays.
func (d Date) DaysSince(s Date) (days int) {
// We convert to Unix time so we do not have to worry about leap seconds:
// Unix time increases by exactly 86400 seconds per day.
deltaUnix := d.StdTime().Unix() - s.StdTime().Unix()
return int(deltaUnix / 86400)
}

// Before reports whether d occurs before d2.
func (d Date) Before(d2 Date) bool {
if d.Year != d2.Year {
return d.Year < d2.Year
}
if d.Month != d2.Month {
return d.Month < d2.Month
}
return d.Day < d2.Day
}

// After reports whether d occurs after d2.
func (d Date) After(d2 Date) bool {
return d2.Before(d)
}

// IsZero reports whether date fields are set to their default value.
func (d Date) IsZero() bool {
return (d.Year == 0) && (int(d.Month) == 0) && (d.Day == 0)
}

// QuarterDate converts a Date to a QuarterDate.
Expand Down
108 changes: 108 additions & 0 deletions iso8601/date_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3430,3 +3430,111 @@ func TestOrdinalDate_UnmarshalText(t *testing.T) {
})
}
}

func TestDateArithmetic(t *testing.T) {
for _, test := range []struct {
desc string
start Date
end Date
days int
}{
{
desc: "zero days noop",
start: Date{2014, 5, 9},
end: Date{2014, 5, 9},
days: 0,
},
{
desc: "crossing a year boundary",
start: Date{2014, 12, 31},
end: Date{2015, 1, 1},
days: 1,
},
{
desc: "negative number of days",
start: Date{2015, 1, 1},
end: Date{2014, 12, 31},
days: -1,
},
{
desc: "full leap year",
start: Date{2004, 1, 1},
end: Date{2005, 1, 1},
days: 366,
},
{
desc: "full non-leap year",
start: Date{2001, 1, 1},
end: Date{2002, 1, 1},
days: 365,
},
{
desc: "crossing a leap second",
start: Date{1972, 6, 30},
end: Date{1972, 7, 1},
days: 1,
},
{
desc: "dates before the unix epoch",
start: Date{101, 1, 1},
end: Date{102, 1, 1},
days: 365,
},
} {
if got := test.start.AddDays(test.days); got != test.end {
t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end)
}
if got := test.end.DaysSince(test.start); got != test.days {
t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days)
}
}
}

func TestDateBefore(t *testing.T) {
for _, test := range []struct {
d1, d2 Date
want bool
}{
{Date{2016, 12, 31}, Date{2017, 1, 1}, true},
{Date{2016, 11, 30}, Date{2016, 12, 30}, true},
{Date{2016, 1, 1}, Date{2016, 1, 1}, false},
{Date{2016, 12, 30}, Date{2016, 12, 31}, true},
} {
if got := test.d1.Before(test.d2); got != test.want {
t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want)
}
}
}

func TestDateAfter(t *testing.T) {
for _, test := range []struct {
d1, d2 Date
want bool
}{
{Date{2016, 12, 31}, Date{2017, 1, 1}, false},
{Date{2016, 1, 1}, Date{2016, 1, 1}, false},
{Date{2016, 12, 30}, Date{2016, 12, 31}, false},
} {
if got := test.d1.After(test.d2); got != test.want {
t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want)
}
}
}

func TestDateIsZero(t *testing.T) {
for _, test := range []struct {
date Date
want bool
}{
{Date{2000, 2, 29}, false},
{Date{10000, 12, 31}, false},
{Date{-1, 0, 0}, false},
{Date{0, 0, 0}, true},
{Date{}, true},
} {
got := test.date.IsZero()
if got != test.want {
t.Errorf("%#v: got %t, want %t", test.date, got, test.want)
}
}
}

0 comments on commit 62b1c32

Please sign in to comment.