Skip to content

Commit

Permalink
DateRange simplified; TimeSpan has new Mark method and has improved p…
Browse files Browse the repository at this point in the history
…arser
  • Loading branch information
rickb777 committed Nov 22, 2023
1 parent a4532d1 commit 8b74c0b
Show file tree
Hide file tree
Showing 11 changed files with 445 additions and 323 deletions.
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,27 @@ Changes since v1:

Renamed methods:

| Was | Use instead |
|--------------|---------------------|
| Date`.Local` | Date`.Midnight` |
| Date`.UTC` | Date`.MidnightUTC` |
| Date`.In` | Date`.MidnightIn` |
| Was | Use instead |
|-------------------|-------------------------|
| date.Date`.Local` | date.Date`.Midnight` |
| date.Date`.UTC` | date.Date`.MidnightUTC` |
| date.Date`.In` | date.Date`.MidnightIn` |

Deleted methods and functions:

| Was | Use instead |
|---------------|--------------------|
| Date`.Add` | `+` |
| Date`.Sub` | `-` |
| Date`.IsZero` | `== 0` |
| Date`.Equal` | `==` |
| Date`.Before` | `<` |
| Date`.After` | `>` |
| `date.IsLeap` | `gregorian.IsLeap` |
| `date.DaysIn` | `gregorian.DaysIn` |

Any v1 dates stored as integers will be incorrect; these can be corrected by adding 719528 to them, which is the number of days between year zero (v2) and 1970 (v1). Dates stored as strings will be unaffected.
| Was | Use instead |
|--------------------------------|--------------------|
| date.Date.`Add` | `+` |
| date.Date.`Sub` | `-` |
| date.Date.`IsZero` | `== 0` |
| date.Date.`Equal` | `==` |
| date.Date.`Before` | `<` |
| date.Date.`After` | `>` |
| `date.IsLeap` | `gregorian.IsLeap` |
| `date.DaysIn` | `gregorian.DaysIn` |
| timespan.DateRange.`Normalise` | (not needed) |

Any v1 dates persistently stored as integers will be incorrect; these can be corrected by adding 719162 (`date.ZeroOffset`) to them, which is the number of days between year zero (v2) and 1970 (v1). Dates stored as strings will be unaffected.

## Credits

Expand Down
10 changes: 5 additions & 5 deletions date.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ const (
// ZeroDay was the day of 1st January year 1 AD.
ZeroDay = time.Monday

// zeroOffset is the number of days between 0001-01-01 and 1970-01-01, using the
// proleptic Gregorian calendar. It is similar to the Rata Die numbering system,
// for which the offset would be 719163 instead.
// ZeroOffset is the number of days between 0001-01-01 and 1970-01-01, using the
// proleptic Gregorian calendar.
// This is based on the same Unix calculation as used by time.Time.
zeroOffset = 719162
// It is similar to the "Rata Die" numbering system, for which the offset would be 719163 instead.
ZeroOffset = 719162
)

// New returns the Date value corresponding to the given year, month, and day.
Expand Down Expand Up @@ -99,7 +99,7 @@ func Min() Date {

// Max returns the largest representable date, which is nearly 6 million years in the future.
func Max() Date {
return Date(math.MaxInt32 - zeroOffset)
return Date(math.MaxInt32 - ZeroOffset)
}

// MidnightUTC returns a Time value corresponding to midnight on the given date d,
Expand Down
199 changes: 113 additions & 86 deletions date_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package date

import (
"fmt"
"runtime/debug"
"testing"
"time"
Expand Down Expand Up @@ -80,73 +81,95 @@ func TestDate_Time(t *testing.T) {
cases := []struct {
d Date
}{
{New(-1234, time.February, 5)},
{New(-1, time.January, 1)},
{New(0, time.April, 12)},
{New(1, time.January, 1)},
{New(1946, time.February, 4)},
{New(1970, time.January, 1)},
{New(1976, time.April, 1)},
{New(1999, time.December, 1)},
{New(1111111, time.June, 21)},
{d: New(-1234, time.February, 5)},
{d: New(-1, time.January, 1)},
{d: New(0, time.April, 12)},
{d: New(1, time.January, 1)},
{d: New(1946, time.February, 4)},
{d: New(1970, time.January, 1)},
{d: New(1976, time.April, 1)},
{d: New(1999, time.December, 1)},
{d: New(1111111, time.June, 21)},
}
zones := []int{-12, -10, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 8, 12}
for i, c := range cases {
d := c.d
tUTC := d.MidnightUTC()
if !same(d, tUTC) {
t.Errorf("%d: %v.MidnightUTC() == %v, want date part %v", i, d, tUTC, d)
}
if tUTC.Location() != time.UTC {
t.Errorf("%d: %v.MidnightUTC() == %v, want %v", i, d, tUTC.Location(), time.UTC)
}

tLocal := d.Midnight()
if !same(d, tLocal) {
t.Errorf("%d: %v.Midnight() == %v, want date part %v", i, d, tLocal, d)
}
if tLocal.Location() != time.Local {
t.Errorf("%d: %v.Midnight() == %v, want %v", i, d, tLocal.Location(), time.Local)
}

for _, z := range zones {
location := time.FixedZone("zone", z*60*60)
tInLoc := d.MidnightIn(location)
if !same(d, tInLoc) {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want date part %v", i, d, z, tInLoc, d)
t.Run(fmt.Sprintf("%d %s", i, c.d), func(t *testing.T) {
d := c.d
tUTC := d.MidnightUTC()
if !same(d, tUTC) {
t.Errorf("%d: %v.MidnightUTC() == %v, want date part %v", i, d, tUTC, d)
}
h, m, s := tInLoc.Clock()
if s != 0 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want zero seconds but got %d", i, d, z, tInLoc.Location(), s)
}
if m != 0 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want zero minutes but got %d", i, d, z, tInLoc.Location(), m)
}
if h != 0 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want zero hours but got %d", i, d, z, tInLoc.Location(), h)
}
if tInLoc.Location() != location {
t.Errorf("%d: MidnightIn(%v) == %v, want %v", i, d, tInLoc.Location(), z)
if tUTC.Location() != time.UTC {
t.Errorf("%d: %v.MidnightUTC() == %v, want %v", i, d, tUTC.Location(), time.UTC)
}

t2 := d.Time(clock.New(1, 2, 3, 0), location)
if !same(d, t2) {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want date part %v", i, d, z, t2, d)
}
h, m, s = t2.Clock()
if s != 3 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want three seconds but got %d", i, d, z, t2.Location(), s)
tLocal := d.Midnight()
if !same(d, tLocal) {
t.Errorf("%d: %v.Midnight() == %v, want date part %v", i, d, tLocal, d)
}
if m != 2 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want two minutes but got %d", i, d, z, t2.Location(), m)
if tLocal.Location() != time.Local {
t.Errorf("%d: %v.Midnight() == %v, want %v", i, d, tLocal.Location(), time.Local)
}
if h != 1 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want one hour but got %d", i, d, z, t2.Location(), h)

for _, z := range zones {
location := time.FixedZone("zone", z*60*60)
tInLoc := d.MidnightIn(location)
if !same(d, tInLoc) {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want date part %v", i, d, z, tInLoc, d)
}
h, m, s := tInLoc.Clock()
if s != 0 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want zero seconds but got %d", i, d, z, tInLoc.Location(), s)
}
if m != 0 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want zero minutes but got %d", i, d, z, tInLoc.Location(), m)
}
if h != 0 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want zero hours but got %d", i, d, z, tInLoc.Location(), h)
}
if tInLoc.Location() != location {
t.Errorf("%d: MidnightIn(%v) == %v, want %v", i, d, tInLoc.Location(), z)
}

t2 := d.Time(clock.New(1, 2, 3, 0), location)
if !same(d, t2) {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want date part %v", i, d, z, t2, d)
}
h, m, s = t2.Clock()
if s != 3 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want three seconds but got %d", i, d, z, t2.Location(), s)
}
if m != 2 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want two minutes but got %d", i, d, z, t2.Location(), m)
}
if h != 1 {
t.Errorf("%d: %v.MidnightIn(%d) == %v, want one hour but got %d", i, d, z, t2.Location(), h)
}
if t2.Location() != location {
t.Errorf("%d: MidnightIn(%v) == %v, want %v", i, d, t2.Location(), z)
}
}
if t2.Location() != location {
t.Errorf("%d: MidnightIn(%v) == %v, want %v", i, d, t2.Location(), z)
})
}
}

func TestDate_LastDayOfMonth(t *testing.T) {
cases := []struct {
d Date
exp int
}{
{d: New(2005, time.January, 15), exp: 31},
{d: New(2006, time.June, 15), exp: 30},
{d: New(1971, time.February, 5), exp: 28},
{d: New(1972, time.February, 5), exp: 29},
}
for i, c := range cases {
t.Run(fmt.Sprintf("%d %s", i, c.d), func(t *testing.T) {
ld := c.d.LastDayOfMonth()
if ld != c.exp {
t.Errorf("%d: want %d, got %d", i, c.exp, ld)
}
}
})
}
}

Expand Down Expand Up @@ -186,21 +209,23 @@ func TestDate_AddDate(t *testing.T) {
years, months, days int
expected Date
}{
{New(1970, time.January, 1), 1, 2, 3, New(1971, time.March, 4)},
{New(1999, time.September, 28), 6, 4, 2, New(2006, time.January, 30)},
{New(1999, time.September, 28), 0, 0, 3, New(1999, time.October, 1)},
{New(1999, time.September, 28), 0, 1, 3, New(1999, time.October, 31)},
{d: New(1970, time.January, 1), years: 1, months: 2, days: 3, expected: New(1971, time.March, 4)},
{d: New(1999, time.September, 28), years: 6, months: 4, days: 2, expected: New(2006, time.January, 30)},
{d: New(1999, time.September, 28), days: 3, expected: New(1999, time.October, 1)},
{d: New(1999, time.September, 28), months: 1, days: 3, expected: New(1999, time.October, 31)},
}
for _, c := range cases {
di := c.d
dj := di.AddDate(c.years, c.months, c.days)
if dj != c.expected {
t.Errorf("%v AddDate(%v,%v,%v) == %v, want %v", di, c.years, c.months, c.days, dj, c.expected)
}
dk := dj.AddDate(-c.years, -c.months, -c.days)
if dk != di {
t.Errorf("%v AddDate(%v,%v,%v) == %v, want %v", dj, -c.years, -c.months, -c.days, dk, di)
}
for i, c := range cases {
t.Run(fmt.Sprintf("%d %s", i, c.d), func(t *testing.T) {
di := c.d
dj := di.AddDate(c.years, c.months, c.days)
if dj != c.expected {
t.Errorf("%v AddDate(%v,%v,%v) == %v, want %v", di, c.years, c.months, c.days, dj, c.expected)
}
dk := dj.AddDate(-c.years, -c.months, -c.days)
if dk != di {
t.Errorf("%v AddDate(%v,%v,%v) == %v, want %v", dj, -c.years, -c.months, -c.days, dk, di)
}
})
}
}

Expand All @@ -210,22 +235,24 @@ func TestDate_AddPeriod(t *testing.T) {
delta period.Period
expected Date
}{
{New(1970, time.January, 1), period.NewYMWD(0, 0, 0, 0), New(1970, time.January, 1)},
{New(1971, time.January, 1), period.NewYMWD(10, 0, 0, 0), New(1981, time.January, 1)},
{New(1972, time.January, 1), period.NewYMWD(0, 10, 0, 0), New(1972, time.November, 1)},
{New(1972, time.January, 1), period.NewYMWD(0, 24, 0, 0), New(1974, time.January, 1)},
{New(1973, time.January, 1), period.NewYMWD(0, 0, 1, 0), New(1973, time.January, 8)},
{New(1973, time.January, 1), period.NewYMWD(0, 0, 0, 10), New(1973, time.January, 11)},
{New(1973, time.January, 1), period.NewYMWD(0, 0, 0, 365), New(1974, time.January, 1)},
{New(1973, time.January, 3), period.NewYMWD(0, 0, 0, -2), New(1973, time.January, 1)},
{New(1974, time.January, 1), period.NewHMS(1, 2, 3), New(1974, time.January, 1)},
{New(1975, time.January, 1), period.NewHMS(24, 2, 3), New(1975, time.January, 2)},
{New(1975, time.January, 1), period.NewHMS(0, 1440, 0), New(1975, time.January, 2)},
{in: New(1970, time.January, 1), delta: period.NewYMWD(0, 0, 0, 0), expected: New(1970, time.January, 1)},
{in: New(1971, time.January, 1), delta: period.NewYMWD(10, 0, 0, 0), expected: New(1981, time.January, 1)},
{in: New(1972, time.January, 1), delta: period.NewYMWD(0, 10, 0, 0), expected: New(1972, time.November, 1)},
{in: New(1972, time.January, 1), delta: period.NewYMWD(0, 24, 0, 0), expected: New(1974, time.January, 1)},
{in: New(1973, time.January, 1), delta: period.NewYMWD(0, 0, 1, 0), expected: New(1973, time.January, 8)},
{in: New(1973, time.January, 1), delta: period.NewYMWD(0, 0, 0, 10), expected: New(1973, time.January, 11)},
{in: New(1973, time.January, 1), delta: period.NewYMWD(0, 0, 0, 365), expected: New(1974, time.January, 1)},
{in: New(1973, time.January, 3), delta: period.NewYMWD(0, 0, 0, -2), expected: New(1973, time.January, 1)},
{in: New(1974, time.January, 1), delta: period.NewHMS(1, 2, 3), expected: New(1974, time.January, 1)},
{in: New(1975, time.January, 1), delta: period.NewHMS(24, 2, 3), expected: New(1975, time.January, 2)},
{in: New(1975, time.January, 1), delta: period.NewHMS(0, 1440, 0), expected: New(1975, time.January, 2)},
}
for i, c := range cases {
out := c.in.AddPeriod(c.delta)
if out != c.expected {
t.Errorf("%d: %v.AddPeriod(%v) == %v, want %v", i, c.in, c.delta, out, c.expected)
}
t.Run(fmt.Sprintf("%d %s", i, c.in), func(t *testing.T) {
out := c.in.AddPeriod(c.delta)
if out != c.expected {
t.Errorf("%d: %v.AddPeriod(%v) == %v, want %v", i, c.in, c.delta, out, c.expected)
}
})
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/rickb777/date/v2

require (
github.com/govalues/decimal v0.1.16
github.com/rickb777/period v1.0.2-beta
github.com/rickb777/period v1.0.3-beta
golang.org/x/text v0.14.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/rickb777/period v1.0.1-beta h1:/+sCJBdraZGfWAlotFRohhuyHPhaoZ+f3vh6EW
github.com/rickb777/period v1.0.1-beta/go.mod h1:YjZyKY4ZR0iTA0G72rSUmhgPzLzeyM9kjzFpchbW9xg=
github.com/rickb777/period v1.0.2-beta h1:8eK5rM6vAq1LzsZ34kuVi5y31rNEqaB8TrIeiiagfOU=
github.com/rickb777/period v1.0.2-beta/go.mod h1:YjZyKY4ZR0iTA0G72rSUmhgPzLzeyM9kjzFpchbW9xg=
github.com/rickb777/period v1.0.3-beta h1:SDLaDRLpvec2o86GVfaBwnlk8g9Ch1u/WP078hD6h7Q=
github.com/rickb777/period v1.0.3-beta/go.mod h1:YjZyKY4ZR0iTA0G72rSUmhgPzLzeyM9kjzFpchbW9xg=
github.com/rickb777/plural v1.4.1 h1:5MMLcbIaapLFmvDGRT5iPk8877hpTPt8Y9cdSKRw9sU=
github.com/rickb777/plural v1.4.1/go.mod h1:kdmXUpmKBJTS0FtG/TFumd//VBWsNTD7zOw7x4umxNw=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
Expand Down
8 changes: 4 additions & 4 deletions rep.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package date
import "time"

const (
secondsPerDay int64 = 60 * 60 * 24
secondsPerDay = 60 * 60 * 24
)

// encode returns the number of days elapsed from date zero to the date
Expand All @@ -22,17 +22,17 @@ func encode(t time.Time) Date {
_, offset := t.Zone()
secs := t.Unix() + int64(offset)
if secs >= 0 {
return zeroOffset + Date(secs/secondsPerDay)
return ZeroOffset + Date(secs/secondsPerDay)
}

// Unfortunately operator / rounds towards 0, so negative values
// must be handled differently
return zeroOffset - Date((secondsPerDay-1-secs)/secondsPerDay)
return ZeroOffset - Date((secondsPerDay-1-secs)/secondsPerDay)
}

// decode returns the Time value corresponding to 00:00:00 UTC of the date
// represented by d, the number of days elapsed since date zero.
func decode(d Date) time.Time {
secs := int64(d-zeroOffset) * secondsPerDay
secs := int64(d-ZeroOffset) * secondsPerDay
return time.Unix(secs, 0).UTC()
}
12 changes: 6 additions & 6 deletions sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ func TestDate_Scan(t *testing.T) {
//{v: "00000101", expected: 0},
//{v: "00000102", expected: 1},
{v: "0001-01-01", expected: 0},
{v: "19700101", expected: zeroOffset},
{v: "1970-01-01", expected: zeroOffset},
{v: "1971-01-01", expected: 365 + zeroOffset},
{v: "2018-12-31", expected: 17896 + zeroOffset},
{v: "31/12/2018", expected: 17896 + zeroOffset},
{v: []byte("19700101"), expected: zeroOffset},
{v: "19700101", expected: ZeroOffset},
{v: "1970-01-01", expected: ZeroOffset},
{v: "1971-01-01", expected: 365 + ZeroOffset},
{v: "2018-12-31", expected: 17896 + ZeroOffset},
{v: "31/12/2018", expected: 17896 + ZeroOffset},
{v: []byte("19700101"), expected: ZeroOffset},
{v: Date(10000).Midnight(), expected: 10000},
}

Expand Down
Loading

0 comments on commit 8b74c0b

Please sign in to comment.