date

package module
v0.0.0-...-7b83215 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 5 Imported by: 0

README

go-ruby-date/date

date — go-ruby-date

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's date standard library — MRI 4.0.5's Date and DateTime. It is the deterministic, interpreter-independent core of calendar arithmetic, parsing and formatting: construct a date from civil / ordinal / week-date / Julian-Day coordinates, shift it by days / months / years, render it with the full strftime directive set, and read it back with Date.parse / Date.strptimewithout any Ruby runtime, matching MRI byte-for-byte across a broad differential corpus.

It is the date backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-regexp (the Onigmo engine), go-ruby-erb (the ERB compiler) and go-ruby-yaml (the Psych port).

What it is — and isn't. Calendar math, the strftime directive engine and the parse / strptime readers are fully deterministic and need no interpreter, so they live here as pure Go built on the astronomical Julian Day Number — exactly as MRI's date_core.c does. Binding these values to live Ruby Date / DateTime objects is the host's job; this library hands back a small, idiomatic Go *Date the host maps to and from its own objects.

Features

A faithful port of MRI's date, validated against the ruby binary on every supported platform:

  • The Julian Day Number model — arbitrary BC/AD years on the proleptic calendar, with the Julian → Gregorian reform (Date::ITALY default, ENGLAND / GREGORIAN / JULIAN, or any JDN): pre-1582 dates use the Julian calendar, the 1582-10-05…14 gap is invalid, and leap-year rules switch with the calendar.
  • ConstructorsNewDate (Date.new / civil), DateJD (jd), Ordinal, Commercial (ISO week date), NewDateTime, plus the deterministic Today / Now seam (SetToday / SetTodayInstant for tests — never real time in tests).
  • The full strftime directive set%Y %y %C %m %B %b %h %d %e %j %H %k %I %l %M %S %L %N %p %P %A %a %u %w %U %W %V %G %g %s %Q %z %Z %D %F %T %R %r %v %c %x %X %n %t %% — with the - _ 0 ^ # flags, an explicit field width, the : / :: / ::: %z variants, the ignored E / O locale modifiers, and the MRI year-padding quirk (%Y of a negative year is sign-plus-four-digits).
  • Parse (heuristic, multi-format — ISO, slashed/dashed, month-name, RFC-2822, week-date, ordinal; comp two-digit-year expansion) and Strptime (explicit format, including %s / %Q epoch input).
  • Arithmetic+/- days (Plus / Minus / Diff), >>/<< months (PlusMonths, day clamping), PlusYears, Next* / Prev*, and Step / Upto / Downto.
  • AccessorsYear Month Day Wday Yday Cwyear Cweek Cwday Jd Mjd Leap, and for DateTime Hour Min Sec SecFractionNanos Offset.
  • Named formatsIso8601, Rfc3339, Rfc2822, Httpdate, Ctime, Jisx0301 (Japanese eras), plus Cmp / Equal (instant-aware).

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-date/date"
)

func main() {
	d, _ := date.NewDate(2026, 6, 29)
	fmt.Println(d.Strftime("%A, %-d %B %Y"))     // Monday, 29 June 2026
	fmt.Println(d.Iso8601(), d.Jd(), d.Cweek())  // 2026-06-29 2461221 27
	fmt.Println(d.PlusMonths(8).String())        // 2027-02-28  (Jun 29 → Feb clamp)

	dt, _ := date.NewDateTime(2026, 6, 29, 14, 3, 5, 19800) // +05:30
	fmt.Println(dt.Strftime("%Y-%m-%dT%H:%M:%S%:z"))        // 2026-06-29T14:03:05+05:30
	fmt.Println(dt.Strftime("%s"))                          // 1782721985

	p, _ := date.Parse("June 29, 2026", false)
	fmt.Println(p.Iso8601())                                // 2026-06-29

	s, _ := date.Strptime("2026-180", "%Y-%j")
	fmt.Println(s.Iso8601())                                // 2026-06-29
}

Tests & coverage

go test -race -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # total: (statements) 100.0%

The suite has two layers: deterministic, Ruby-free golden tests (with MRI-4.0.5-verified expectations inline) that hold to 100% coverage on the no-Ruby, qemu cross-arch and Windows lanes, and a differential MRI oracle (gated on Ruby ≥ 4.0, $stdout.binmode for exact bytes on Windows) that runs where ruby is present. CI builds and tests on three OSes and all six supported 64-bit architectures (amd64, arm64, riscv64, loong64, ppc64le, s390x).

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-date/date authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package date is a pure-Go (no cgo) reimplementation of Ruby's date standard library — MRI 4.0.5's Date and DateTime — the deterministic, interpreter-independent core of calendar arithmetic, parsing and formatting.

The calendar core is built from scratch on the astronomical Julian Day Number (JDN): every Date is, fundamentally, an integer day count, so arbitrary BC/AD years, the proleptic Gregorian calendar and day/month arithmetic all reduce to integer math. No use is made of Go's time package for the calendar (only the Today seam defaults to it); the JDN conversions follow the same formulae MRI's date_core.c uses, so results match MRI byte-for-byte.

A DateTime extends a Date with a time-of-day (hour/minute/second, a fractional nanosecond part) and a UTC offset in seconds, mirroring MRI's split between Date (a calendar value pinned to midnight) and DateTime (an instant).

Index

Constants

View Source
const (
	ITALY     = 2299161  // 1582-10-15, MRI's default reform point
	ENGLAND   = 2361222  // 1752-09-14
	Gregorian = -1 << 62 // proleptic Gregorian everywhere (Date::GREGORIAN, -∞)
	Julian    = 1 << 62  // proleptic Julian everywhere (Date::JULIAN, +∞)
)

Calendar-reform sentinels for the start parameter (Date::ITALY / ENGLAND / GREGORIAN / JULIAN in MRI). ITALY is the default — the date the Gregorian calendar was first adopted (1582-10-15 = JD 2_299_161).

Variables

View Source
var ErrInvalidDate = errors.New("invalid date")

ErrInvalidDate is returned by the constructors when the requested calendar / week-date / ordinal coordinates do not denote a real date.

Functions

func SetToday

func SetToday(y, m, d int) func()

SetToday overrides the clock Today consults and returns a function that restores the previous one — the test seam that keeps "today" deterministic.

func SetTodayInstant

func SetTodayInstant(y, m, d, h, min, s, ns int) func()

SetTodayInstant is SetToday with a full wall-clock instant, so tests that exercise Now's time-of-day are deterministic too. It returns the restore function.

Types

type Date

type Date struct {
	// contains filtered or unexported fields
}

Date is an MRI-compatible Ruby Date / DateTime value. It is an astronomical Julian Day Number (jd) plus, for a DateTime, a time-of-day in nanoseconds since local midnight (nsec, 0 for a plain Date) and a UTC offset in seconds (offset). The isDateTime flag distinguishes a Date (which formats and inspects as a bare calendar date) from a DateTime (which carries the wall clock).

func Commercial

func Commercial(cwyear, cweek, cwday int) (*Date, error)

Commercial constructs Date.commercial(cwyear, cweek, cwday) — an ISO week date (cwday 1 = Monday .. 7 = Sunday). Out-of-range coordinates yield ErrInvalidDate.

func DateJD

func DateJD(jd int) *Date

DateJD constructs Date.jd(jd) — the date with the given astronomical Julian Day Number, under the default ITALY reform.

func NewDate

func NewDate(y, m, d int) (*Date, error)

NewDate constructs Date.new(y, m, d) / Date.civil(y, m, d) under the default ITALY reform. A negative month or day counts from the end (month -1 = December, day -1 = last of the month), as in MRI. An out-of-range coordinate, or one falling in the 1582 reform gap, yields ErrInvalidDate.

func NewDateStart

func NewDateStart(y, m, d, start int) (*Date, error)

NewDateStart is NewDate with an explicit calendar-reform point (Date.new's optional `start` — ITALY / ENGLAND / GREGORIAN / JULIAN, or any JDN).

func NewDateTime

func NewDateTime(y, m, d, h, min, s, offsetSec int) (*Date, error)

NewDateTime constructs DateTime.new(y, m, d, h, min, s, offset). The offset is in seconds east of UTC. An invalid calendar date or out-of-range clock field yields ErrInvalidDate.

func Now

func Now() *Date

Now returns the current local instant as a DateTime in UTC. Like Today it is the sole wall-clock consumer for DateTime and is driven by the same seam.

func Ordinal

func Ordinal(y, yday int) (*Date, error)

Ordinal constructs Date.ordinal(y, yday) — the yday-th day of year y (negative yday counts from the end). An out-of-range yday yields ErrInvalidDate.

func Parse

func Parse(s string, comp bool) (*Date, error)

Parse reads s heuristically (Date.parse) and returns a plain Date — any time-of-day in s is recognised (so the string still parses) but dropped, as MRI's Date.parse yields a Date. comp controls two-digit-year expansion for the slashed/dashed numeric forms. Use ParseDateTime to retain the clock.

func ParseDateTime

func ParseDateTime(s string, comp bool) (*Date, error)

ParseDateTime reads s heuristically (DateTime.parse), retaining any time-of-day and zone it finds.

func Strptime

func Strptime(s, format string) (*Date, error)

Strptime parses s against the explicit Ruby strptime format (Date.strptime / DateTime.strptime). An unmatched format or out-of-range value is an error.

func Today

func Today() *Date

Today returns the current local date as a plain Date. It is the only place the calendar core consults the wall clock; tests inject a deterministic clock via SetToday so they never depend on real time.

func (*Date) Asctime

func (d *Date) Asctime() string

Asctime is the MRI alias for Ctime.

func (*Date) Cmp

func (d *Date) Cmp(o *Date) int

Cmp orders d against o: -1 if earlier, 0 if equal, +1 if later (Date#<=>), comparing by jd and, for DateTimes, by the instant (jd-day plus the offset-adjusted time-of-day).

func (*Date) Ctime

func (d *Date) Ctime() string

Ctime renders Date#ctime / #asctime — "Day Mon DD HH:MM:SS YYYY".

func (*Date) Cwday

func (d *Date) Cwday() int

Cwday returns the ISO commercial day-of-week, 1 = Monday .. 7 = Sunday (Date#cwday).

func (*Date) Cweek

func (d *Date) Cweek() int

Cweek returns the ISO commercial week number, 1 .. 53 (Date#cweek).

func (*Date) Cwyear

func (d *Date) Cwyear() int

Cwyear returns the ISO commercial week-numbering year (Date#cwyear), which can differ from the calendar year at the year boundary.

func (*Date) Day

func (d *Date) Day() int

Day returns the day-of-month, 1 .. 31 (Date#day / #mday).

func (*Date) Diff

func (d *Date) Diff(o *Date) int

Diff returns the whole number of days from o to d (d - o), positive when d is later — MRI's Date#- between two dates yields a Rational that, for whole midnights, reduces to this integer.

func (*Date) Downto

func (d *Date) Downto(limit *Date, fn func(*Date)) int

Downto calls fn for each date from d down to limit inclusive (Date#downto).

func (*Date) Equal

func (d *Date) Equal(o *Date) bool

Equal reports whether d and o denote the same instant (Date#==).

func (*Date) Hour

func (d *Date) Hour() int

Hour returns the hour-of-day, 0 .. 23 (DateTime#hour).

func (*Date) Httpdate

func (d *Date) Httpdate() string

Httpdate renders Date#httpdate — the RFC 1123 / HTTP form in GMT, so the instant is shifted to UTC first (which can move it to an adjacent day).

func (*Date) IsDateTime

func (d *Date) IsDateTime() bool

IsDateTime reports whether d carries a time-of-day (a DateTime) rather than being a bare calendar Date.

func (*Date) Iso8601

func (d *Date) Iso8601() string

Iso8601 renders Date#iso8601: identical to to_s for both Date and DateTime.

func (*Date) Jd

func (d *Date) Jd() int

Jd returns the astronomical Julian Day Number (Date#jd).

func (*Date) Jisx0301

func (d *Date) Jisx0301() string

Jisx0301 renders Date#jisx0301 — the Japanese-era calendar form "E YY.MM.DD" (e.g. "R08.06.29"), with the time appended for a DateTime. A pre-Meiji date falls back to ISO-8601.

func (*Date) Leap

func (d *Date) Leap() bool

Leap reports whether d's year is a leap year (Date#leap?), under its reform.

func (*Date) Min

func (d *Date) Min() int

Min returns the minute-of-hour, 0 .. 59 (DateTime#minute / #min).

func (*Date) Minus

func (d *Date) Minus(n int) *Date

Minus returns d shifted back by n days (Date#- / Date#prev_day(n)).

func (*Date) Mjd

func (d *Date) Mjd() int

Mjd returns the Modified Julian Day Number (Date#mjd) — jd minus 2_400_001.

func (*Date) Month

func (d *Date) Month() int

Month returns the month, 1 = January .. 12 = December (Date#month / #mon).

func (*Date) NextDay

func (d *Date) NextDay(n int) *Date

NextDay returns d + n days (default n = 1).

func (*Date) NextMonth

func (d *Date) NextMonth(n int) *Date

NextMonth returns d shifted forward n months (default n = 1).

func (*Date) NextYear

func (d *Date) NextYear(n int) *Date

NextYear returns d shifted forward n years (default n = 1).

func (*Date) NsecOfDay

func (d *Date) NsecOfDay() int64

NsecOfDay returns the nanoseconds since local midnight (internal/whole-day).

func (*Date) Offset

func (d *Date) Offset() int

Offset returns the UTC offset in seconds east of Greenwich (DateTime#offset is this over 86_400 as a day fraction; callers that need the fraction divide).

func (*Date) Plus

func (d *Date) Plus(n int) *Date

Plus returns d shifted forward by n days (Date#+ / Date#next_day(n)).

func (*Date) PlusMonths

func (d *Date) PlusMonths(n int) *Date

PlusMonths returns d shifted by n calendar months (Date#>> for n>0, Date#<< for n<0), with MRI's day-of-month clamping (Jan 31 >> 1 → Feb 28).

func (*Date) PlusYears

func (d *Date) PlusYears(n int) *Date

PlusYears returns d shifted by n years (Date#next_year(n) / #prev_year), a 12n-month shift with the same clamping (Feb 29 → Feb 28 off a leap year).

func (*Date) PrevDay

func (d *Date) PrevDay(n int) *Date

PrevDay returns d - n days (default n = 1).

func (*Date) PrevMonth

func (d *Date) PrevMonth(n int) *Date

PrevMonth returns d shifted back n months (default n = 1).

func (*Date) PrevYear

func (d *Date) PrevYear(n int) *Date

PrevYear returns d shifted back n years (default n = 1).

func (*Date) Rfc2822

func (d *Date) Rfc2822() string

Rfc2822 renders Date#rfc2822 / #rfc822 — "Day, D Mon YYYY HH:MM:SS ±HHMM" (the day-of-month is not zero-padded, matching MRI).

func (*Date) Rfc3339

func (d *Date) Rfc3339() string

Rfc3339 renders Date#rfc3339 / DateTime#rfc3339 — always the full timestamp form, so a plain Date appears at midnight UTC.

func (*Date) Sec

func (d *Date) Sec() int

Sec returns the second-of-minute, 0 .. 59 (DateTime#second / #sec).

func (*Date) SecFractionNanos

func (d *Date) SecFractionNanos() int64

SecFractionNanos returns the sub-second part in nanoseconds, 0 .. 999_999_999 (the numerator MRI's DateTime#sec_fraction reports as a Rational over 1e9).

func (*Date) Step

func (d *Date) Step(limit *Date, step int, fn func(*Date)) int

Step calls fn for each date from d to limit inclusive, stepping by step days (which may be negative), mirroring Date#step. It returns the number of steps taken. A zero step would not terminate and so takes none.

func (*Date) Strftime

func (d *Date) Strftime(format string) string

Strftime formats d per the Ruby format string (Date#strftime / DateTime#strftime).

func (*Date) String

func (d *Date) String() string

String renders Date#to_s: a plain Date as "YYYY-MM-DD" and a DateTime as the ISO-8601 instant "YYYY-MM-DDTHH:MM:SS±HH:MM".

func (*Date) Succ

func (d *Date) Succ() *Date

Succ / Next return the following day (Date#succ / #next).

func (*Date) ToDate

func (d *Date) ToDate() *Date

ToDate is the public form of dateOnly: the calendar Date underlying a DateTime (DateTime#to_date), with the clock dropped.

func (*Date) Upto

func (d *Date) Upto(limit *Date, fn func(*Date)) int

Upto calls fn for each date from d to limit inclusive, one day at a time (Date#upto); it is Step with a step of 1.

func (*Date) Wday

func (d *Date) Wday() int

Wday returns the day-of-week, 0 = Sunday .. 6 = Saturday (Date#wday).

func (*Date) Yday

func (d *Date) Yday() int

Yday returns the 1-based day-of-year, 1 .. 366 (Date#yday).

func (*Date) Year

func (d *Date) Year() int

Year returns the calendar year (negative for BC), under d's reform.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL