A Time composite (plus a Duration subpackage) for Composition-Oriented
Programming. Time wraps a Go time.Time (Ruby's Time as the reference)
and exposes its fallible operations as Result values — so a malformed
input (the canonical example being a bad Parse) is a value, never a panic
and never nil.
Time is deterministic by construction: there is no Now(). Instances are
built only from explicit values (FromUnix, Parse), which keeps behaviour
reproducible across architectures and test runs.
parsed := Time.Parse(time.RFC3339, value)
if parsed.HasError() {
fmt.Println(parsed.Error().Message())
} else {
fmt.Println(parsed.Payload().(Time.Interface).ToGoString())
}Time and Duration follow the org's Null-Object / never-nil invariant
(enforced by the nonnil CI analyzer): Time.Null() and Duration.Null()
satisfy the same Interface and report IsNull() == true.
export GOPRIVATE=github.com/go-composites GOPROXY=direct GOSUMDB=off
go get github.com/go-composites/time@main[!NOTE] main.go
package main
import (
"fmt"
"time"
Duration "github.com/go-composites/time/src/duration"
Time "github.com/go-composites/time/src"
)
func main() {
epoch := Time.FromUnix(0)
later := Time.FromUnix(3600)
// Comparisons are plain Go bools.
fmt.Println(epoch.Before(later)) // true
// Sub yields the Duration between two Times.
fmt.Println(later.Sub(epoch).ToGoString()) // 1h0m0s
// Add returns a Result — shifting an instant by a Duration.
shifted := epoch.Add(Duration.FromSeconds(90))
fmt.Println(shifted.Payload().(Time.Interface).ToGoString())
// Parsing is fallible: a malformed value is a value, not a panic.
bad := Time.Parse(time.RFC3339, "not-a-time")
fmt.Println("has error:", bad.HasError()) // true
}$ task buildConstructors
FromUnix(sec int64) Interface— an instant from a Unix timestamp (UTC).Parse(layout, value string) Result.Interface— aResultwhose payload is aTime, or anError.New(...)whenvaluedoes not matchlayout.Null() Interface— the Null-ObjectTime(IsNull() == true).
Conversions
ToUnix() int64,Format(layout string) string,ToGoString() string(RFC3339).
Time zones (the IANA tz database is embedded via _ "time/tzdata", so zone
lookups work on every architecture and inside toolchain-less CI containers
without a system tzdata install)
In(name string) Result.Interface— aResultwhose payload is a newTimedenoting the same instant in the IANA locationname(e.g."Europe/Paris"), or anError.New(...)whennameis an unknown zone. Because only the location changes,Before/After/Equalare preserved.Zone() string— the zone abbreviation in effect at the instant (e.g."UTC","CET").UTC() Interface— the same instant with its location set to UTC.
Comparisons (each returns bool)
Before(other)/After(other)/Equal(other).
Arithmetic
Add(Duration.Interface) Result.Interface— aResultwhose payload is a newTimeshifted forward by the duration.Sub(other Interface) Duration.Interface— theDurationbetween two instants.
Null-Object
IsNull() bool.
Constructors
FromSeconds(sec int64) Interface.Parse(s string) Result.Interface— aResultwhose payload is aDuration, or anError.New(...)whensis malformed (seetime.ParseDuration).Null() Interface— the Null-ObjectDuration(IsNull() == true).
Methods
ToSeconds() int64,ToGoString() string.Add(other) Interface/Sub(other) Interface— arithmetic.Equal(other) bool,IsNull() bool.
BSD-3-Clause — see LICENSE.
