157 lines
3.9 KiB
Go
157 lines
3.9 KiB
Go
|
|
package quando
|
||
|
|
|
||
|
|
import "time"
|
||
|
|
|
||
|
|
// Duration represents the difference between two dates.
|
||
|
|
// It provides methods to extract the duration in various units.
|
||
|
|
//
|
||
|
|
// Integer methods (Seconds, Minutes, Hours, Days, Weeks, Months, Years)
|
||
|
|
// return rounded-down values. For more precise calculations involving
|
||
|
|
// months and years, use MonthsFloat() and YearsFloat().
|
||
|
|
//
|
||
|
|
// Durations can be negative if the start date is after the end date.
|
||
|
|
type Duration struct {
|
||
|
|
start time.Time
|
||
|
|
end time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
// Diff calculates the duration between two dates.
|
||
|
|
// If a is before b, the duration is positive. If a is after b, the duration is negative.
|
||
|
|
//
|
||
|
|
// Example:
|
||
|
|
//
|
||
|
|
// start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||
|
|
// end := time.Date(2026, 12, 31, 0, 0, 0, 0, time.UTC)
|
||
|
|
// duration := quando.Diff(start, end)
|
||
|
|
// months := duration.Months() // 11 months
|
||
|
|
func Diff(a, b time.Time) Duration {
|
||
|
|
return Duration{
|
||
|
|
start: a,
|
||
|
|
end: b,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Seconds returns the number of seconds in the duration (rounded down).
|
||
|
|
func (d Duration) Seconds() int64 {
|
||
|
|
return int64(d.end.Sub(d.start).Seconds())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Minutes returns the number of minutes in the duration (rounded down).
|
||
|
|
func (d Duration) Minutes() int64 {
|
||
|
|
return int64(d.end.Sub(d.start).Minutes())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Hours returns the number of hours in the duration (rounded down).
|
||
|
|
func (d Duration) Hours() int64 {
|
||
|
|
return int64(d.end.Sub(d.start).Hours())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Days returns the number of days in the duration (rounded down).
|
||
|
|
// This calculates calendar days, not 24-hour periods.
|
||
|
|
func (d Duration) Days() int {
|
||
|
|
start := d.start
|
||
|
|
end := d.end
|
||
|
|
negative := false
|
||
|
|
|
||
|
|
// Handle negative duration
|
||
|
|
if end.Before(start) {
|
||
|
|
start, end = end, start
|
||
|
|
negative = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate days by counting calendar days
|
||
|
|
days := 0
|
||
|
|
for start.Before(end) {
|
||
|
|
start = start.AddDate(0, 0, 1)
|
||
|
|
days++
|
||
|
|
}
|
||
|
|
|
||
|
|
if negative {
|
||
|
|
return -days
|
||
|
|
}
|
||
|
|
return days
|
||
|
|
}
|
||
|
|
|
||
|
|
// Weeks returns the number of weeks in the duration (rounded down).
|
||
|
|
func (d Duration) Weeks() int {
|
||
|
|
return d.Days() / 7
|
||
|
|
}
|
||
|
|
|
||
|
|
// Months returns the number of months in the duration (rounded down).
|
||
|
|
// This handles month-end dates and leap years correctly.
|
||
|
|
func (d Duration) Months() int {
|
||
|
|
start := d.start
|
||
|
|
end := d.end
|
||
|
|
negative := false
|
||
|
|
|
||
|
|
// Handle negative duration
|
||
|
|
if end.Before(start) {
|
||
|
|
start, end = end, start
|
||
|
|
negative = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate months based on year and month difference
|
||
|
|
years := end.Year() - start.Year()
|
||
|
|
months := int(end.Month()) - int(start.Month())
|
||
|
|
totalMonths := years*12 + months
|
||
|
|
|
||
|
|
// Adjust if end day is before start day (not a full month yet)
|
||
|
|
if end.Day() < start.Day() {
|
||
|
|
totalMonths--
|
||
|
|
}
|
||
|
|
|
||
|
|
if negative {
|
||
|
|
return -totalMonths
|
||
|
|
}
|
||
|
|
return totalMonths
|
||
|
|
}
|
||
|
|
|
||
|
|
// Years returns the number of years in the duration (rounded down).
|
||
|
|
func (d Duration) Years() int {
|
||
|
|
return d.Months() / 12
|
||
|
|
}
|
||
|
|
|
||
|
|
// MonthsFloat returns the precise number of months in the duration as a float64.
|
||
|
|
// This provides more accurate calculations than the integer Months() method.
|
||
|
|
func (d Duration) MonthsFloat() float64 {
|
||
|
|
start := d.start
|
||
|
|
end := d.end
|
||
|
|
negative := false
|
||
|
|
|
||
|
|
// Handle negative duration
|
||
|
|
if end.Before(start) {
|
||
|
|
start, end = end, start
|
||
|
|
negative = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get integer months first
|
||
|
|
fullMonths := float64(d.Months())
|
||
|
|
if negative {
|
||
|
|
fullMonths = -fullMonths
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate fractional month based on days
|
||
|
|
// Create a date at the same day in the month after start + full months
|
||
|
|
baseDate := start.AddDate(0, int(fullMonths), 0)
|
||
|
|
|
||
|
|
// Days remaining after full months
|
||
|
|
daysRemaining := end.Sub(baseDate).Hours() / 24
|
||
|
|
|
||
|
|
// Days in the month we're currently in
|
||
|
|
nextMonth := baseDate.AddDate(0, 1, 0)
|
||
|
|
daysInMonth := nextMonth.Sub(baseDate).Hours() / 24
|
||
|
|
|
||
|
|
fractionalMonth := daysRemaining / daysInMonth
|
||
|
|
|
||
|
|
result := fullMonths + fractionalMonth
|
||
|
|
if negative {
|
||
|
|
return -result
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// YearsFloat returns the precise number of years in the duration as a float64.
|
||
|
|
// This provides more accurate calculations than the integer Years() method.
|
||
|
|
func (d Duration) YearsFloat() float64 {
|
||
|
|
return d.MonthsFloat() / 12.0
|
||
|
|
}
|