quando/date.go
Oliver Jakoubek f47897f3fd Change Now() and DefaultClock to use UTC instead of local time
Changed quando.Now() and DefaultClock.Now() to return dates in UTC
timezone instead of local server timezone. This aligns with industry
standards for date/time libraries and prevents server-timezone-dependent
behavior.

Changes:
- date.go: Now() uses time.Now().UTC() instead of time.Now()
- Updated documentation comments to reflect UTC default
- date_test.go: TestNow() now verifies UTC location
- clock_test.go: TestDefaultClock_Now() now verifies UTC location
- parse_test.go: TestParseRelative() verifies all results are UTC
- parse.go: Updated comment from "local timezone" to "UTC timezone"

Users needing local time can use:
- quando.From(time.Now()) for explicit local time
- quando.Now().In("Europe/Berlin") to convert to specific timezone

Closes: quando-67n
2026-02-12 16:39:01 +01:00

190 lines
5.3 KiB
Go

package quando
import (
"fmt"
"time"
)
// Lang represents a language for internationalization (i18n) in formatting.
//
// Currently supports 17 languages: EN, DE, ES, FR, IT, PT, NL, PL, RU, TR, VI,
// JA, KO, ZhCN, ZhTW, HI, TH.
//
// Language affects:
// - Format(Long): month and weekday names
// - FormatLayout: custom layouts with month/weekday names
// - Duration.Human(): time unit names
//
// Language does NOT affect:
// - ISO, EU, US, RFC2822 formats (always language-independent)
// - Numeric outputs (WeekNumber, Quarter, DayOfYear)
//
// See i18n.go for translation data and helper methods.
type Lang string
const (
// EN represents English language.
EN Lang = "en"
// DE represents German (Deutsch) language.
DE Lang = "de"
// ES represents Spanish (Español) language.
ES Lang = "es"
// FR represents French (Français) language.
FR Lang = "fr"
// IT represents Italian (Italiano) language.
IT Lang = "it"
// PT represents Portuguese (Português) language.
PT Lang = "pt"
// NL represents Dutch (Nederlands) language.
NL Lang = "nl"
// PL represents Polish (Polski) language.
PL Lang = "pl"
// RU represents Russian (Русский) language.
RU Lang = "ru"
// TR represents Turkish (Türkçe) language.
TR Lang = "tr"
// VI represents Vietnamese (Tiếng Việt) language.
VI Lang = "vi"
// JA represents Japanese (日本語) language.
JA Lang = "ja"
// KO represents Korean (한국어) language.
KO Lang = "ko"
// ZhCN represents Chinese Simplified (简体中文) language.
ZhCN Lang = "zh-cn"
// ZhTW represents Chinese Traditional (繁體中文) language.
ZhTW Lang = "zh-tw"
// HI represents Hindi (हिन्दी) language.
HI Lang = "hi"
// TH represents Thai (ไทย) language.
TH Lang = "th"
)
// Date wraps time.Time and provides a fluent API for date operations.
// All operations return new Date instances, making Date immutable and thread-safe.
//
// The Date type supports the full range of Go's time.Time (approximately
// year 0001 to year 9999, with extensions beyond that range).
type Date struct {
t time.Time
lang Lang
}
// Now returns a Date representing the current moment in time.
// The Date uses the UTC timezone by default.
//
// Example:
//
// now := quando.Now()
func Now() Date {
return Date{
t: time.Now().UTC(),
lang: EN, // Default language
}
}
// From converts a time.Time to a Date.
// This is the primary way to create a Date from an existing time value.
//
// Example:
//
// t := time.Date(2026, 2, 9, 12, 0, 0, 0, time.UTC)
// date := quando.From(t)
func From(t time.Time) Date {
return Date{
t: t,
lang: EN, // Default language
}
}
// FromUnix creates a Date from a Unix timestamp (seconds since January 1, 1970 UTC).
// Supports negative timestamps for dates before 1970.
//
// Example:
//
// date := quando.FromUnix(1707480000) // Feb 9, 2024
// past := quando.FromUnix(-946684800) // Jan 1, 1940
func FromUnix(sec int64) Date {
return Date{
t: time.Unix(sec, 0),
lang: EN, // Default language
}
}
// Time returns the underlying time.Time value.
// Use this to convert back to standard library time when needed.
//
// Example:
//
// date := quando.Now()
// t := date.Time()
func (d Date) Time() time.Time {
return d.t
}
// Unix returns the Unix timestamp (seconds since January 1, 1970 UTC).
// The value may be negative for dates before 1970.
//
// Example:
//
// date := quando.Now()
// timestamp := date.Unix()
func (d Date) Unix() int64 {
return d.t.Unix()
}
// WithLang returns a new Date with the specified language for formatting.
// This does not modify the date or time, only the language used for formatting operations.
//
// Example:
//
// date := quando.Now().WithLang(quando.DE)
func (d Date) WithLang(lang Lang) Date {
return Date{
t: d.t,
lang: lang,
}
}
// In converts the date to the specified IANA timezone.
// Returns error for invalid timezone names. Never panics.
//
// The method uses the IANA Timezone Database (e.g., "America/New_York", "Europe/Berlin", "UTC").
// Daylight Saving Time (DST) transitions are handled automatically by the timezone database.
//
// When combined with arithmetic operations, DST-safe behavior is preserved:
// Add(1, Days) means "same wall clock time on next calendar day", not "24 hours later".
//
// Example:
//
// utc := quando.From(time.Date(2026, 6, 15, 12, 0, 0, 0, time.UTC))
// berlin, err := utc.In("Europe/Berlin")
// // berlin is 2026-06-15 14:00:00 CEST (UTC+2 in summer)
//
// For a list of valid timezone names, see: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
func (d Date) In(location string) (Date, error) {
// Validate input
if location == "" {
return Date{}, fmt.Errorf("timezone location is empty: %w", ErrInvalidTimezone)
}
// Load timezone from IANA database
loc, err := time.LoadLocation(location)
if err != nil {
return Date{}, fmt.Errorf("loading timezone %q: %w", location, ErrInvalidTimezone)
}
// Convert time to new timezone
converted := d.t.In(loc)
// Return new Date with converted time, preserving language
return Date{
t: converted,
lang: d.lang,
}, nil
}
// String returns the ISO 8601 representation of the date (YYYY-MM-DD HH:MM:SS).
// This method is called automatically by fmt.Println and similar functions.
func (d Date) String() string {
return d.t.Format("2006-01-02 15:04:05")
}