toolkit for time handling operations
English | 简体中文
Go version 1.21+
go get -u github.com/gomooth/chronos
or
import "github.com/gomooth/chronos"// Current time yesterday
chronos.Yesterday(time.Now())
// Current time tomorrow
chronos.Tomorrow(time.Now())
// Strict variants: return error on zero-time input
t, err := chronos.StrictYesterday(time.Now())
t, err := chronos.StrictTomorrow(time.Now())
// Must variants: panic on zero-time input
t := chronos.MustYesterday(time.Now())
t := chronos.MustTomorrow(time.Now())No ParseWithNaturalLanguage flag required — these always work:
// Current moment
at, err := chronos.Parse("now")
// Today (same as "now")
at, err := chronos.Parse("today")
// Current time yesterday
at, err := chronos.Parse("yesterday")
// Current time tomorrow
at, err := chronos.Parse("tomorrow")
// Chinese equivalents
at, err := chronos.Parse("现在") // now
at, err := chronos.Parse("今天") // today
at, err := chronos.Parse("昨天") // yesterday
at, err := chronos.Parse("明天") // tomorrowFor numeric parsing, it automatically distinguishes between seconds, milliseconds, microseconds, and nanoseconds based on the number of digits.
// seconds
at, err := chronos.Parse(1672643045)
// milliseconds
at, err := chronos.Parse(1672643045123)
// microseconds
at, err := chronos.Parse(1672643045123456)
// nanoseconds
at, err := chronos.Parse(1672643045123456789)To override auto-detection and explicitly specify precision, use chronos.ParseWithPrecision(p):
// Force millisecond precision
at, err := chronos.Parse(1672643045123, chronos.ParseWithPrecision(chronos.PrecisionMillisecond))Available precision values: PrecisionSecond, PrecisionMillisecond, PrecisionMicrosecond, PrecisionNanosecond
at, err := chronos.Parse("2023-04-22T18:22:15Z")
at, err := chronos.Parse("2023-04-22 18:22:15")
at, err := chronos.Parse("2023-04-22")Supported default time formats include:
-
Unix-Mon Jan _2 15:04:05 MST 2006 -
Cookie-Monday, 02-Jan-2006 15:04:05 MST -
Ruby-Mon Jan 02 15:04:05 -0700 2006 -
ANSIC-Mon Jan _2 15:04:05 2006 -
ISO8601-2006-01-02T15:04:05-07:00,2006-01-02T15:04:05Z -
RFC822-02 Jan 06 15:04 MST,02 Jan 06 15:04 -0700 -
RFC850-Monday, 02-Jan-06 15:04:05 MST -
RFC1036-Mon, 02 Jan 06 15:04:05 -0700 -
RFC1123-Mon, 02 Jan 2006 15:04:05 MST,Mon, 02 Jan 2006 15:04:05 -0700 -
RFC3339-2006-01-02T15:04:05Z07:00,2006-01-02T15:04:05.999999999Z07:00 -
RFC7231-Mon, 02 Jan 2006 15:04:05 MST -
Stamp-Jan _2 15:04:05,Jan _2 15:04:05.000,Jan _2 15:04:05.000000,Jan _2 15:04:05.000000000 -
DateTime-2006-01-02 15:04:05 -
Date-2006-01-02,2006/01/02 -
Time-15:04:05,3:04PM
For non-standard custom formats, use chronos.ParseWithLayout(layout)
input := "22/09/2023"
at, err := chronos.Parse(input, chronos.ParseWithLayout("02/01/2006"))By default, parsing uses local timezone. Set timezone with chronos.ParseWithLocation(loc)
input := "2023-09-22"
loc := time.FixedZone("TEST", 3600)
at, err := chronos.Parse(input, chronos.ParseWithLocation(loc))Natural language parsing is disabled by default. Enable with chronos.ParseWithNaturalLanguage(true).
at, err := chronos.Parse(
"an hour ago",
chronos.ParseWithNaturalLanguage(true), // Enable natural language parsing
)By default, natural language parsing uses current time as base. Set base time with chronos.ParseWithBaseTime(at) (also affects "now"/"yesterday"/"tomorrow" special expressions)
// Custom base time
base := time.Date(2023, 5, 15, 12, 0, 0, 0, time.UTC)
at, err := chronos.Parse(
"an hour ago",
chronos.ParseWithNaturalLanguage(true),
chronos.ParseWithBaseTime(base), // Set base time
)- Compound expressions (connected by
andor comma):
at, err := chronos.Parse(
"2 days and 3 hours ago",
chronos.ParseWithNaturalLanguage(true),
)- Mixed-direction expressions (each fragment has its own direction):
at, err := chronos.Parse(
"2 days ago and 3 hours later",
chronos.ParseWithNaturalLanguage(true),
)-
Supported time units:
nanosecond,microsecond,millisecond,secondminute,hourday,week,month,year
-
Supported directions:
ago/before- pastlater/after- future
-
Quantity representation:
- Can use numbers (e.g. 2 hours ago)
- Can use a or an (e.g. a hour ago, an hour ago)
Get maximum/minimum of multiple times. Supports time.Time, *time.Time, but not mixed comparisons. nil *time.Time values are skipped; if all values are nil, returns the zero value of T.
// Maximum
chronos.Max(time1, time2, time3)
// Minimum
chronos.Min(time1, time2, time3)Compare two times, returns difference in nanoseconds as DiffValue.
// Diff - permissive: zero-time inputs silently return 0
diff := chronos.Diff(t2, t1)
// StrictDiff - returns error on zero-time input
diff, err := chronos.StrictDiff(t2, t1)
// MustDiff - panics on zero-time input
diff := chronos.MustDiff(t2, t1)
// Convert difference units
diff.Nanoseconds()
diff.Microseconds()
diff.Milliseconds()
diff.Seconds()
diff.Minutes()
diff.Hours()
// For calendar-aware day/week/month/year differences, use CalendarDiff instead
// Display difference in human-friendly string
diff.String()Unlike Diff (which is based on nanosecond duration), CalendarDiff computes the difference by calendar position, correctly handling variable-length months and leap years.
// CalendarDiff - permissive: zero-time inputs silently return zero value
cd := chronos.CalendarDiff(t2, t1)
// StrictCalendarDiff - returns error on zero-time input
cd, err := chronos.StrictCalendarDiff(t2, t1)
// MustCalendarDiff - panics on zero-time input
cd := chronos.MustCalendarDiff(t2, t1)
// Access fields
cd.Years // calendar years
cd.Months // calendar months (after years)
cd.Days // calendar days (after months)
cd.Hours // hours (after days)
cd.Minutes // minutes (after hours)
cd.Seconds // seconds (after minutes)
cd.Nanos // nanoseconds (after seconds)
// Direction checks
cd.IsPositive() // t1 is after t2
cd.IsNegative() // t1 is before t2
// Get absolute value
cd.Abs()
// Human-readable string (e.g. "1y 2mo 3d 4h 5m 6s")
cd.String()Example:
t1 := time.Date(2024, 3, 15, 10, 30, 0, 0, time.UTC)
t2 := time.Date(2023, 1, 10, 8, 0, 0, 0, time.UTC)
cd := chronos.CalendarDiff(t1, t2)
// cd.Years=1, cd.Months=2, cd.Days=5, cd.Hours=2, cd.Minutes=30// Start/end of minute for given time
chronos.StartOfMinute(at)
chronos.EndOfMinute(at)
// Start/end of hour for given time
chronos.StartOfHour(at)
chronos.EndOfHour(at)
// Start/end of day for given time
chronos.StartOfDay(at)
chronos.EndOfDay(at)
// Start/end of week for given time
// Defaults to Monday as week start day, customizable with chronos.WithWeekStartDay()
chronos.StartOfWeek(at)
chronos.EndOfWeek(at)
// Set Sunday as week start day
chronos.StartOfWeek(at, chronos.WithWeekStartDay(time.Sunday))
chronos.EndOfWeek(at, chronos.WithWeekStartDay(time.Sunday))
// Start/end of month for given time
chronos.StartOfMonth(at)
chronos.EndOfMonth(at)
// Start/end of quarter for given time
chronos.StartOfQuarter(at)
chronos.EndOfQuarter(at)
// Start/end of year for given time
chronos.StartOfYear(at)
chronos.EndOfYear(at)chronos.IsLeap(at)chronos.DaysInMonth(at)// Add years, months, days to a time (default: Go native overflow behavior)
chronos.AddDate(at, 1, 2, 3)
// With clamp: Jan 31 + 1 month = Feb 28 (clamped to month end)
chronos.AddDate(at, 0, 1, 0, chronos.WithClamp(true))at := time.Date(2023, 5, 15, 14, 30, 45, 0, time.UTC)
// Custom layout
chronos.Format(at, "2006/01/02") // "2023/05/15"
// Preset formats
chronos.FormatDateTime(at) // "2023-05-15 14:30:45"
chronos.FormatDate(at) // "2023-05-15"
chronos.FormatTime(at) // "14:30:45"
chronos.FormatRFC3339(at) // "2023-05-15T14:30:45Z"
chronos.FormatISO8601(at) // "2023-05-15T14:30:45Z" (RFC 3339 Nano, a strict subset of ISO 8601)
// ISO 8601 with subsecond precision control
chronos.FormatISO8601WithOptions(at) // same as FormatISO8601
chronos.FormatISO8601WithOptions(at, chronos.WithSubsecondPrecision(0)) // no subsecond: "2023-05-15T14:30:45Z"
chronos.FormatISO8601WithOptions(at, chronos.WithSubsecondPrecision(3)) // millisecond: "2023-05-15T14:30:45.000Z"
chronos.FormatISO8601WithOptions(at, chronos.WithSubsecondPrecision(6)) // microsecond: "2023-05-15T14:30:45.000000Z"
chronos.FormatISO8601WithOptions(at, chronos.WithSubsecondPrecision(9)) // nanosecond: "2023-05-15T14:30:45.000000000Z"
chronos.FormatISO8601WithOptions(at, chronos.WithSubsecondPrecision(-1))// full precision (default)// NewPeriod - permissive: silently swaps if start > end
p := chronos.NewPeriod(startTime, endTime)
// StrictNewPeriod - returns error if start > end
p, err := chronos.StrictNewPeriod(startTime, endTime)
// MustNewPeriod - panics if start > end
p := chronos.MustNewPeriod(startTime, endTime)
p.Contains(at) // Check if time is within range (inclusive)
p.Overlaps(other) // Check if two ranges overlap (boundary contact counts)
p.OverlapsStrict(other) // Check if two ranges strictly overlap (boundary contact excluded)
p.Duration() // Get the duration of the range
p.StrictDuration() // Get the duration, returns error if invalid (start > end)
p.IsZero() // Check if the range is zero-valued
p.IsValid() // Check if the range is valid (start <= end)
// Set operations
p.Intersection(other) // Intersection of two ranges (zero Period if no overlap)
p.Gap(other) // Gap between two ranges (zero Period if adjacent/overlapping)
p.IsAdjacent(other) // Check if two ranges are adjacent (endpoints touch)
p.Union(other) // Union: merges if overlapping/adjacent, otherwise returns two PeriodsChinese natural language expressions are also supported alongside English:
at, err := chronos.Parse(
"1小时前",
chronos.ParseWithNaturalLanguage(true),
)
// Compound Chinese expressions:
// "2天3小时前", "1周2天后"
// Extended Chinese numerals (十一~九万九千九百九十九):
// "十一小时前", "二十天前", "三十五分钟后", "一万小时前"
// Mixed-direction Chinese expressions:
// "2天前和3小时后", "1周后和2天前"
// Supported Chinese expressions:
// "现在" (now), "今天" (today), "昨天" (yesterday), "明天" (tomorrow)
// "<n>纳秒/微秒/毫秒/秒/分钟/小时/天/周/个月(月)/年 前/后"
// <n> supports Arabic digits and Chinese numerals (一~九万九千九百九十九、两)