Skip to content

gomooth/chronos

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chronos

toolkit for time handling operations

English | 简体中文

Static Badge Go Report Card MIT license

Install

Go version 1.21+

go get -u github.com/gomooth/chronos

or

import "github.com/gomooth/chronos"

Usage

Yesterday, Tomorrow

// 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())

Time Parsing

2.1 Special Expressions

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("明天")    // tomorrow

2.2 Timestamps

For 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

2.3 Standard Formats

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:002006-01-02T15:04:05Z

  • RFC822 - 02 Jan 06 15:04 MST02 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 MSTMon, 02 Jan 2006 15:04:05 -0700

  • RFC3339 - 2006-01-02T15:04:05Z07:002006-01-02T15:04:05.999999999Z07:00

  • RFC7231 - Mon, 02 Jan 2006 15:04:05 MST

  • Stamp - Jan _2 15:04:05Jan _2 15:04:05.000Jan _2 15:04:05.000000Jan _2 15:04:05.000000000

  • DateTime - 2006-01-02 15:04:05

  • Date - 2006-01-022006/01/02

  • Time - 15:04:053:04PM

For non-standard custom formats, use chronos.ParseWithLayout(layout)

input := "22/09/2023"
at, err := chronos.Parse(input, chronos.ParseWithLayout("02/01/2006"))

2.4 Custom Timezone

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))

2.5 Natural Language Expressions

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 and or 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, second
    • minute, hour
    • day, week, month, year
  • Supported directions:

    • ago/before - past
    • later/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)

Time Comparison

3.1 Extremes

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)

3.2 Difference

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()

3.3 Calendar Difference

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

Time Boundaries

// 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)

Tools

5.1 Leap Year Check

chronos.IsLeap(at)

5.2 Days in Month

chronos.DaysInMonth(at)

5.3 AddDate

// 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))

5.4 Format

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)

5.5 Period

// 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 Periods

Natural Language (Chinese)

Chinese 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 (一~九万九千九百九十九、两)

About

A simple and friendly time toolkit for golang

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages