Skip to content

Commit

Permalink
version 2 basic implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Rick committed Nov 15, 2023
1 parent 1753b84 commit 94b9241
Show file tree
Hide file tree
Showing 21 changed files with 328 additions and 672 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ It also provides

* `clock.Clock` which expresses a wall-clock style hours-minutes-seconds with millisecond precision.
* `timespan.DateRange` which expresses a period between two dates.
* `timespan.TimeSpan` which expresses a duration of time between two instants.
* `timespan.TimeSpan` which expresses a duration of time between two instants (see RFC5545).
* `view.VDate` which wraps `Date` for use in templates etc.

See [package documentation](https://godoc.org/github.com/rickb777/date) for
Expand All @@ -27,18 +27,21 @@ to the ISO-8601 form (e.g. "PT30S").

## Installation

go get -u github.com/rickb777/date

or

dep ensure -add github.com/rickb777/date
go get github.com/rickb777/date/v2

## Status

This library has been in reliable production use for some time. Versioning follows the well-known semantic version pattern.

### Version 2

Changes since v1:

* `date.Date` is now an integer that holds the number of days since year zero. Previously, it was a struct based on year 1970.
* `date.Date` arithmetic and comparison operations now rely on Go operators; the corresponding methods have been deleted.
* `date.Date` zero value is now year 0 (Gregorian proleptic astronomical) so 1970 will no longer cause issues.
* `date.PeriodOfDays` has been moved to `timespan.PeriodOfDays`
* `date.DateString` has been deleted; the SQL `driver.Valuer` implementation is now pluggable and serves the same purpose more simply.
* The [period.Period](https://pkg.go.dev/github.com/rickb777/period) type has moved.
* The `clock.Clock` type is now nanosecond resolution (formerly millisecond resolution).

Expand Down
162 changes: 40 additions & 122 deletions date.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,107 +12,83 @@ import (
"github.com/rickb777/period"
)

// PeriodOfDays describes a period of time measured in whole days. Negative values
// indicate days earlier than some mark.
type PeriodOfDays int32

// ZeroDays is the named zero value for PeriodOfDays.
const ZeroDays PeriodOfDays = 0

// A Date represents a date under the proleptic Gregorian calendar as
// used by ISO 8601. This calendar uses astronomical year numbering,
// so it includes a year 0 and represents earlier years as negative numbers
// (i.e. year 0 is 1 BC; year -1 is 2 BC, and so on).
//
// A Date value requires 4 bytes of storage and can represent dates from
// Tue, 23 Jun -5,877,641 (5,877,642 BC) to Fri, 11 Jul 5,881,580.
// Dates outside that range will "wrap around".
// On 32-bit architectures, dates range from about year -5 million to +5 million.
// On 64-bit architecturew, the range is huge.
//
// Programs using dates should typically store and pass them as values,
// not pointers. That is, date variables and struct fields should be of
// type date.Date, not *date.Date unless the pointer indicates an optional
// value. A Date value can be used by multiple goroutines simultaneously.
//
// Date values can be compared using the Before, After, and Equal methods
// as well as the == and != operators.
// Date values can be compared using the ==, !=, >, >=, <, and <= operators.
//
// The Sub method subtracts two dates, returning the number of days between
// them. The Add method adds a Date and a number of days, producing a Date.
//
// The zero value of type Date is Thursday, January 1, 1970 (called 'the
// epoch'), based on Unix convention. The IsZero method gives a simple way
// of detecting a date that has not been initialized explicitly, with the
// caveat that this is also a 'normal' date.
// Because a Date is a number of days since Zero, + and - operations
// add or subtract some number of days.
//
// The first official date of the Gregorian calendar was Friday, October 15th
// 1582, quite unrelated to the epoch used here. The Date type does not
// distinguish between official Gregorian dates and earlier proleptic dates,
// which can also be represented when needed.
type Date struct {
day PeriodOfDays // day gives the number of days elapsed since date zero.
}
// 1582, quite unrelated to the Unix epoch or the year 0 used here. The Date
// type does not distinguish between official Gregorian dates and earlier
// proleptic dates, which can also be represented when needed.
type Date int

const (
// Zero is the named zero value for Date and corresponds to Saturday, January 1,
// year 0 in the proleptic Gregorian calendar using astronomical year numbering.
Zero Date = 0
)

// New returns the Date value corresponding to the given year, month, and day.
//
// The month and day may be outside their usual ranges and will be normalized
// during the conversion.
func New(year int, month time.Month, day int) Date {
t := time.Date(year, month, day, 12, 0, 0, 0, time.UTC)
return Date{encode(t)}
return encode(t)
}

// NewAt returns the Date value corresponding to the given time.
// Note that the date is computed relative to the time zone specified by
// the given Time value.
func NewAt(t time.Time) Date {
return Date{encode(t)}
}

// NewOfDays returns the Date value corresponding to the given period since the
// epoch (1st January 1970), which may be negative.
func NewOfDays(p PeriodOfDays) Date {
return Date{p}
}

// Date returns the Date value corresponding to the given period since the
// epoch (1st January 1970), which may be negative.
func (p PeriodOfDays) Date() Date {
return Date{p}
return encode(t)
}

// Today returns today's date according to the current local time.
func Today() Date {
t := time.Now()
return Date{encode(t)}
return encode(time.Now())
}

// TodayUTC returns today's date according to the current UTC time.
func TodayUTC() Date {
t := time.Now().UTC()
return Date{encode(t)}
return encode(time.Now().UTC())
}

// TodayIn returns today's date according to the current time relative to
// the specified location.
func TodayIn(loc *time.Location) Date {
t := time.Now().In(loc)
return Date{encode(t)}
return encode(t)
}

// Min returns the smallest representable date.
// Min returns the smallest representable date, which is nearly 6 million years in the past.
func Min() Date {
return Date{day: PeriodOfDays(math.MinInt32)}
return Date(math.MinInt32 + 1)
}

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

// UTC returns a Time value corresponding to midnight on the given date,
// UTC time. Note that midnight is the beginning of the day rather than the end.
func (d Date) UTC() time.Time {
return decode(d.day)
return decode(d)
}

// Local returns a Time value corresponding to midnight on the given date,
Expand All @@ -125,16 +101,15 @@ func (d Date) Local() time.Time {
// relative to the specified time zone. Note that midnight is the beginning
// of the day rather than the end.
func (d Date) In(loc *time.Location) time.Time {
t := decode(d.day).In(loc)
t := decode(d).In(loc)
_, offset := t.Zone()
return t.Add(time.Duration(-offset) * time.Second)
}

// Date returns the year, month, and day of d.
// The first day of the month is 1.
func (d Date) Date() (year int, month time.Month, day int) {
t := decode(d.day)
return t.Date()
return decode(d).Date()
}

// LastDayOfMonth returns the last day of the month specified by d.
Expand All @@ -147,87 +122,39 @@ func (d Date) LastDayOfMonth() int {
// Day returns the day of the month specified by d.
// The first day of the month is 1.
func (d Date) Day() int {
t := decode(d.day)
return t.Day()
return decode(d).Day()
}

// Month returns the month of the year specified by d.
func (d Date) Month() time.Month {
t := decode(d.day)
return t.Month()
return decode(d).Month()
}

// Year returns the year specified by d.
func (d Date) Year() int {
t := decode(d.day)
return t.Year()
return decode(d).Year()
}

// YearDay returns the day of the year specified by d, in the range [1,365] for
// non-leap years, and [1,366] in leap years.
func (d Date) YearDay() int {
t := decode(d.day)
return t.YearDay()
return decode(d).YearDay()
}

// Weekday returns the day of the week specified by d.
func (d Date) Weekday() time.Weekday {
// Date zero, January 1, 1970, fell on a Thursday
wdayZero := time.Thursday
// Date zero, January 1, 0000, fell on a Saturday
const weekdayZero = time.Saturday
// Taking into account potential for overflow and negative offset
return time.Weekday((int32(wdayZero) + int32(d.day)%7 + 7) % 7)
return time.Weekday((int(weekdayZero) + int(d)%7 + 7) % 7)
}

// ISOWeek returns the ISO 8601 year and week number in which d occurs.
// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to
// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1
// of year n+1.
func (d Date) ISOWeek() (year, week int) {
t := decode(d.day)
return t.ISOWeek()
}

// IsZero reports whether d represents the zero (i.e. uninitialised) date.
// Because Date follows Unix conventions, it is based on 1970-01-01. So be
// careful with this: the corresponding 1970-01-01 date is not itself a 'zero'.
func (d Date) IsZero() bool {
return d.day == 0
}

// Equal reports whether d and u represent the same date.
func (d Date) Equal(u Date) bool {
return d.day == u.day
}

// Before reports whether the date d is before u.
func (d Date) Before(u Date) bool {
return d.day < u.day
}

// After reports whether the date d is after u.
func (d Date) After(u Date) bool {
return d.day > u.day
}

// Min returns the earlier of two dates.
func (d Date) Min(u Date) Date {
if d.day > u.day {
return u
}
return d
}

// Max returns the later of two dates.
func (d Date) Max(u Date) Date {
if d.day < u.day {
return u
}
return d
}

// Add returns the date d plus the given number of days. The parameter may be negative.
func (d Date) Add(days PeriodOfDays) Date {
return Date{d.day + days}
return decode(d).ISOWeek()
}

// AddDate returns the date corresponding to adding the given number of years,
Expand All @@ -242,8 +169,8 @@ func (d Date) Add(days PeriodOfDays) Date {
// the result. For example, adding 0y 1m 3d to September 28 gives October 31 (not
// November 1).
func (d Date) AddDate(years, months, days int) Date {
t := decode(d.day).AddDate(years, months, days)
return Date{encode(t)}
t := decode(d).AddDate(years, months, days)
return encode(t)
}

// AddPeriod returns the date corresponding to adding the given period. If the
Expand All @@ -260,18 +187,9 @@ func (d Date) AddDate(years, months, days int) Date {
//
// See the description for AddDate.
func (d Date) AddPeriod(delta period.Period) Date {
t, _ := delta.AddTo(decode(d.day))
return Date{encode(t)}
}

// Sub returns d-u as the number of days between the two dates.
func (d Date) Sub(u Date) (days PeriodOfDays) {
return d.day - u.day
}

// DaysSinceEpoch returns the number of days since the epoch (1st January 1970), which may be negative.
func (d Date) DaysSinceEpoch() (days PeriodOfDays) {
return d.day
t1 := decode(d)
t2, _ := delta.AddTo(t1)
return encode(t2)
}

// IsLeap simply tests whether a given year is a leap year, using the proleptic Gregorian calendar algorithm.
Expand Down
Loading

0 comments on commit 94b9241

Please sign in to comment.