date — deterministic calendar date
github.com/go-composites/date (Go package Date, imported from the src/
sub-directory) is a composite over a calendar date — a year, month and day
with no time-of-day component (Ruby's Date as the reference). It is the
pure-calendar complement to time: where Time is an instant
(and Duration a span), Date carries only date semantics. It is
interface-first, its fallible constructors (FromYMD, Parse, AddDays) return
a result so failures are values rather than panics, and it ships a
Null-Object variant.
Deterministic by design — there is no Today()
Date is built only from explicit values (FromYMD, Parse); there is
deliberately no Today() (nor Now()). This keeps behaviour
reproducible across architectures and test runs.
Backed by stdlib time, but date-only
Internally a Date is a Go time.Time pinned to midnight UTC. That
backing is used only for calendar validation (FromYMD round-trips
through time.Date, so Feb 30 or month 13 is rejected) and day
arithmetic (AddDays/DaysBetween); the time-of-day is never exposed, so
the surface is purely calendar-based.
API
import Date "github.com/go-composites/date/src"
type Interface interface {
Year() int
Month() int
Day() int
Weekday() string
ToGoString() string
Before(Interface) bool
After(Interface) bool
Equal(Interface) bool
AddDays(n int) Result.Interface
DaysBetween(Interface) int
IsNull() bool
}
func FromYMD(year, month, day int) Result.Interface
func Parse(value string) Result.Interface
func Null() Interface
FromYMD(year, month, day)returns aResult; on success its payload is theDate, otherwise it carries anError.New("invalid calendar date")— a date that normalises to different components (e.g.Feb 30, month13) is rejected. It never panics and never returnsnil.Parse(value)builds aDatefrom an ISO"YYYY-MM-DD"value; it returns aResultwhose payload is theDate, or anErrorwhen the value does not match the ISO layout.Null()returns the Null-Object date (see below).
| Member | Behaviour |
|---|---|
Year() |
Returns the calendar year. |
Month() |
Returns the calendar month (1–12). |
Day() |
Returns the day of the month (1–31). |
Weekday() |
Returns the English name of the day of the week (e.g. "Monday"). |
ToGoString() |
Returns the ISO "YYYY-MM-DD" representation. |
Before(other) / After(other) |
Report whether the receiver falls strictly before / after other. |
Equal(other) |
Reports whether the receiver and other denote the same calendar date. |
AddDays(n) |
Returns a Result whose payload is a new Date n days later (n may be negative). It never panics and never returns nil. |
DaysBetween(other) |
Returns the signed number of days from the receiver to other (positive when other is later, negative when earlier). |
IsNull() |
Returns false for a real date, true for the Null() one. |
Usage
r := Date.FromYMD(2024, 2, 29) // valid: 2024 is a leap year
if !r.HasError() {
d := r.Payload().(Date.Interface)
fmt.Println(d.ToGoString()) // 2024-02-29
fmt.Println(d.Weekday()) // Thursday
}
// An impossible calendar date is a value, not a panic:
if bad := Date.FromYMD(2023, 2, 29); bad.HasError() {
fmt.Println(bad.Error().Message()) // invalid calendar date
}
if p := Date.Parse("2026-06-21"); !p.HasError() {
start := p.Payload().(Date.Interface)
if shifted := start.AddDays(10); !shifted.HasError() {
end := shifted.Payload().(Date.Interface)
fmt.Println(end.ToGoString()) // 2026-07-01
fmt.Println(start.Before(end)) // true
fmt.Println(start.DaysBetween(end)) // 10
}
}
The Null-Object Date
Date.Null() denotes the empty date: Year()/Month()/Day() are 0,
Weekday() and ToGoString() return "", its comparisons are false,
Equal is true only against another null, AddDays yields a Result
wrapping another null Date, DaysBetween is 0, and IsNull() returns
true.
Dependencies
date depends on result and error (and
transitively on null).