From 6d98312d46c195baebc01e79d2494b9a42e139a5 Mon Sep 17 00:00:00 2001 From: Normandes Date: Thu, 7 Dec 2023 09:02:21 -0300 Subject: [PATCH 1/2] time: improve performance of calendar algorithms and clarify its specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implementation improves the performance of Go's time package using the recent algorithms developed by Neri and Schneider [1]. Their algorithms are faster than alternatives, my benchmarks confirm performance improvements in Go as well. ``` goos: linux goarch: amd64 pkg: time cpu: Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz | /tmp/master.txt | /tmp/nerishcneider.txt | sec/op | sec/op vs base Now-8 69.38n ± 0% 69.33n ± 0% -0.07% (p=0.001 n=20) NowUnixNano-8 70.35n ± 0% 70.08n ± 0% -0.38% (p=0.000 n=20) NowUnixMilli-8 71.47n ± 0% 71.26n ± 0% -0.30% (p=0.000 n=20) NowUnixMicro-8 71.32n ± 0% 71.21n ± 0% -0.15% (p=0.000 n=20) Format-8 441.9n ± 0% 433.8n ± 0% -1.83% (p=0.000 n=20) FormatRFC3339-8 293.9n ± 0% 283.4n ± 0% -3.57% (p=0.000 n=20) FormatRFC3339Nano-8 297.5n ± 0% 286.9n ± 0% -3.55% (p=0.000 n=20) FormatNow-8 242.4n ± 0% 239.7n ± 0% -1.11% (p=0.000 n=20) MarshalJSON-8 144.0n ± 0% 136.2n ± 0% -5.42% (p=0.000 n=20) MarshalText-8 142.0n ± 0% 136.2n ± 0% -4.02% (p=0.000 n=20) Parse-8 241.2n ± 0% 240.9n ± 0% -0.12% (p=0.002 n=20) ParseRFC3339UTC-8 78.52n ± 0% 73.12n ± 0% -6.87% (p=0.000 n=20) ParseRFC3339UTCBytes-8 86.60n ± 0% 81.05n ± 0% -6.42% (p=0.000 n=20) ParseRFC3339TZ-8 291.5n ± 0% 283.1n ± 0% -2.90% (p=0.000 n=20) ParseRFC3339TZBytes-8 343.2n ± 0% 333.2n ± 0% -2.93% (p=0.000 n=20) ParseDuration-8 125.6n ± 0% 125.8n ± 0% +0.16% (p=0.002 n=20) Hour-8 6.458n ± 0% 6.457n ± 0% ~ (p=0.130 n=20) Second-8 6.460n ± 0% 6.456n ± 0% -0.05% (p=0.002 n=20) Year-8 18.68n ± 0% 14.10n ± 0% -24.52% (p=0.000 n=20) Day-8 24.01n ± 0% 17.14n ± 0% -28.61% (p=0.000 n=20) ISOWeek-8 27.55n ± 0% 24.46n ± 0% -11.22% (p=0.000 n=20) GoString-8 157.9n ± 0% 152.9n ± 0% -3.13% (p=0.000 n=20) UnmarshalText-8 290.1n ± 0% 283.4n ± 0% -2.31% (p=0.000 n=20) Geomean 93.84n ± 0% 89.08n ± 0% -5.07% ``` To achieve better performance we should change the supported range of dates: instead of supporting years taking any int value, they should be restricted to a much smaller range which still spans tens of thousands of years. [1] Neri, C, Schneider, L. Euclidean affine functions and their application to calendar algorithms. Softw Pract Exper. 2023; 53(4): 937–970. doi:10.1002/spe.3172 https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 For: golang/go#63844 --- src/time/format.go | 4 +- src/time/format_rfc3339.go | 2 +- src/time/format_test.go | 4 +- src/time/time.go | 281 +++++++++++++++++++++++-------------- src/time/time_test.go | 94 ++++++++++++- src/time/zoneinfo.go | 7 +- 6 files changed, 277 insertions(+), 115 deletions(-) diff --git a/src/time/format.go b/src/time/format.go index 7fbeddb5406574..babaa79caf39c0 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -557,7 +557,7 @@ func (t Time) String() string { // code. func (t Time) GoString() string { abs := t.abs() - year, month, day, _ := absDate(abs, true) + year, month, day := absDate(abs, true) hour, minute, second := absClock(abs) buf := make([]byte, 0, len("time.Date(9999, time.September, 31, 23, 59, 59, 999999999, time.Local)")) @@ -671,7 +671,7 @@ func (t Time) appendFormat(b []byte, layout string) []byte { // Compute year, month, day if needed. if year < 0 && std&stdNeedDate != 0 { - year, month, day, yday = absDate(abs, true) + year, month, day, yday = absDateWithYday(abs, true) yday++ } diff --git a/src/time/format_rfc3339.go b/src/time/format_rfc3339.go index 1151666c3e42f3..8df573d29065c9 100644 --- a/src/time/format_rfc3339.go +++ b/src/time/format_rfc3339.go @@ -19,7 +19,7 @@ func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte { _, offset, abs := t.locabs() // Format date. - year, month, day, _ := absDate(abs, true) + year, month, day := absDate(abs, true) b = appendInt(b, year, 4) b = append(b, '-') b = appendInt(b, int(month), 2) diff --git a/src/time/format_test.go b/src/time/format_test.go index 8a26eaa35bab10..bc6d0d85317722 100644 --- a/src/time/format_test.go +++ b/src/time/format_test.go @@ -223,7 +223,7 @@ func TestFormatSingleDigits(t *testing.T) { func TestFormatShortYear(t *testing.T) { years := []int{ - -100001, -100000, -99999, + -32767, -32766, -32766, -10001, -10000, -9999, -1001, -1000, -999, -101, -100, -99, @@ -233,7 +233,7 @@ func TestFormatShortYear(t *testing.T) { 99, 100, 101, 999, 1000, 1001, 9999, 10000, 10001, - 99999, 100000, 100001, + 32765, 32766, 32767, } for _, y := range years { diff --git a/src/time/time.go b/src/time/time.go index 9d4c6e919e58b1..e9af63ff95104b 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -508,25 +508,25 @@ func (t Time) locabs() (name string, offset int, abs uint64) { // Date returns the year, month, and day in which t occurs. func (t Time) Date() (year int, month Month, day int) { - year, month, day, _ = t.date(true) + year, month, day = t.date(true) return } // Year returns the year in which t occurs. func (t Time) Year() int { - year, _, _, _ := t.date(false) + year, _, _ := t.date(false) return year } // Month returns the month of the year specified by t. func (t Time) Month() Month { - _, month, _, _ := t.date(true) + _, month, _ := t.date(true) return month } // Day returns the day of the month specified by t. func (t Time) Day() int { - _, _, day, _ := t.date(true) + _, _, day := t.date(true) return day } @@ -565,7 +565,7 @@ func (t Time) ISOWeek() (year, week int) { } // find the Thursday of the calendar week abs += uint64(d) * secondsPerDay - year, _, _, yday := absDate(abs, false) + year, _, _, yday := absDateWithYday(abs, false) return year, yday/7 + 1 } @@ -608,8 +608,7 @@ func (t Time) Nanosecond() int { // YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years, // and [1,366] in leap years. func (t Time) YearDay() int { - _, _, _, yday := t.date(false) - return yday + 1 + return absYearDay(t.abs()) + 1 } // A Duration represents the elapsed time between two instants @@ -979,84 +978,150 @@ const ( // date computes the year, day of year, and when full=true, // the month and day in which t occurs. -func (t Time) date(full bool) (year int, month Month, day int, yday int) { +func (t Time) date(full bool) (year int, month Month, day int) { return absDate(t.abs(), full) } -// absDate is like date but operates on an absolute time. -func absDate(abs uint64, full bool) (year int, month Month, day int, yday int) { - // Split into time and day. - d := abs / secondsPerDay - - // Account for 400 year cycles. - n := d / daysPer400Years - y := 400 * n - d -= daysPer400Years * n - - // Cut off 100-year cycles. - // The last cycle has one extra leap year, so on the last day - // of that year, day / daysPer100Years will be 4 instead of 3. - // Cut it back down to 3 by subtracting n>>2. - n = d / daysPer100Years - n -= n >> 2 - y += 100 * n - d -= daysPer100Years * n - - // Cut off 4-year cycles. - // The last cycle has a missing leap year, which does not - // affect the computation. - n = d / daysPer4Years - y += 4 * n - d -= daysPer4Years * n - - // Cut off years within a 4-year cycle. - // The last year is a leap year, so on the last day of that year, - // day / 365 will be 4 instead of 3. Cut it back down to 3 - // by subtracting n>>2. - n = d / 365 - n -= n >> 2 - y += n - d -= 365 * n - - year = int(int64(y) + absoluteZeroYear) - yday = int(d) +// The algorithm is figure 12 of Neri, Schneider, "Euclidean affine functions +// and their application to calendar algorithms". +// https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 +func absDate(abs uint64, full bool) (year int, month Month, day int) { + daysAbs := int64(abs / secondsPerDay) + daysUnix := int32(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay) + + // Shift and correction constants. + s := uint32(3670) + K := uint32(719468 + 146097*s) + L := int(400 * s) + + N := uint32(daysUnix) + K + + // Century + N_1 := 4*N + 3 + C := N_1 / 146097 + + // Year + R := N_1 % 146097 + N_2 := R | 3 + P_2 := 2939745 * uint64(N_2) + Z := uint32(P_2 / 4294967296) + N_Y := uint32(P_2%4294967296) / 2939745 / 4 + + J := 0 + if N_Y >= 306 { + J = 1 + } + + Y := 100*C + Z + year = int(Y) - L + J if !full { return } - day = yday - if isLeap(year) { - // Leap year - switch { - case day > 31+29-1: - // After leap day; pretend it wasn't there. - day-- - case day == 31+29-1: - // Leap day. - month = February - day = 29 - return + // Month and day + N_3 := 2141*N_Y + 197913 + M := N_3 / 65536 + D := N_3 % 65536 / 2141 + + month = Month(M) + if J == 1 { + month = Month(M - 12) + } + day = int(D + 1) + + return +} + +// The algorithm is basicaly figure 12 of Neri, Schneider, "Euclidean affine functions +// and their application to calendar algorithms", adapted to calculate yday. +// https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 +func absDateWithYday(abs uint64, full bool) (year int, month Month, day int, yday int) { + daysAbs := int64(abs / secondsPerDay) + + daysUnix := int32(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay) + + // Shift and correction constants. + s := uint32(3670) // chosen so that 1970 is roughly in the middle of the range + K := uint32(719468 + 146097*s) + L := int(400 * s) + + N := uint32(daysUnix) + K + + // Century + N_1 := 4*N + 3 + C := N_1 / 146097 + + // Year + R := N_1 % 146097 + N_2 := R | 3 + P_2 := 2939745 * uint64(N_2) + Z := uint32(P_2 / 4294967296) + N_Y := uint32(P_2%4294967296) / 2939745 / 4 + + isLeapYear := 0 + if Z != 0 { + if Z%4 == 0 { + isLeapYear = 1 } + } else if C%4 == 0 { + isLeapYear = 1 + } + + J := 0 + if N_Y >= 306 { + J = 1 } - // Estimate month on assumption that every month has 31 days. - // The estimate may be too low by at most one month, so adjust. - month = Month(day / 31) - end := int(daysBefore[month+1]) - var begin int - if day >= end { - month++ - begin = end + yday = int(N_Y) + if J == 1 { + yday = yday - 306 } else { - begin = int(daysBefore[month]) + yday = yday + 31 + 28 + isLeapYear + } + + Y := 100*C + Z + year = int(Y) - L + J + + if !full { + return } - month++ // because January is 1 - day = day - begin + 1 + // Month and day + N_3 := 2141*N_Y + 197913 + M := N_3 / 65536 + D := N_3 % 65536 / 2141 + + month = Month(M) + if J == 1 { + month = Month(M - 12) + } + day = int(D + 1) + return } +func absYearDay(abs uint64) int { + daysAbs := int64(abs / secondsPerDay) + daysUnix := int32(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay) + + // Shift and correction constants. + s := uint32(3670) // chosen so that 1970 is roughly in the middle of the range + K := uint32(719468 - 306 + 146097*s) + N := uint32(daysUnix) + K + + // Century + N_1 := 4*N + 3 + + // Year + R := N_1 % 146097 + N_2 := R | 3 + P_2 := 2939745 * uint64(N_2) + N_Y := uint32(P_2%4294967296) / 2939745 / 4 + + return int(N_Y) +} + // daysBefore[m] counts the number of days in a non-leap year // before month m begins. There is an entry for m=12, counting // the number of days before January of next year (365). @@ -1083,32 +1148,33 @@ func daysIn(m Month, year int) int { return int(daysBefore[m] - daysBefore[m-1]) } -// daysSinceEpoch takes a year and returns the number of days from -// the absolute epoch to the start of that year. -// This is basically (year - zeroYear) * 365, but accounting for leap days. -func daysSinceEpoch(year int) uint64 { - y := uint64(int64(year) - absoluteZeroYear) +// daysSinceEpoch takes a year, month and day and returns the number of days from +// Jan 1 1970 (Unix time) to the given date. +// The algorithm is figure 13 of Neri, Schneider, "Euclidean affine functions +// and their application to calendar algorithms". +// https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 +// It gives correct results if the date is in the range [-1,468,000/Mar/01, 1,471,745/Fev/28]. +func daysSinceEpoch(year int, month Month, day int) int32 { + s := uint32(3670) // chosen so that 1970 is roughly in the middle of the range + K := uint32(719468 + 146097*s) + L := int(400 * s) - // Add in days from 400-year cycles. - n := y / 400 - y -= 400 * n - d := daysPer400Years * n + y := uint32(year + L) + m := uint32(month) + if month < 3 { + y-- + m += 12 + } - // Add in 100-year cycles. - n = y / 100 - y -= 100 * n - d += daysPer100Years * n + d := uint32(day - 1) - // Add in 4-year cycles. - n = y / 4 - y -= 4 * n - d += daysPer4Years * n + c := y / 100 - // Add in non-leap years. - n = y - d += 365 * n + y_star := 1461*y/4 - c + c/4 + m_star := (153*m - 457) / 5 + n := y_star + m_star + d - return d + return int32(n - K) } // Provided by package runtime. @@ -1477,6 +1543,10 @@ func norm(hi, lo, base int) (nhi, nlo int) { // their usual ranges and will be normalized during the conversion. // For example, October 32 converts to November 1. // +// The normalized year must be in [-32767, 32767]. +// For example, for month==13 and year==2020, the Date converts to +// January 2021. +// // A daylight savings time transition skips or repeats times. // For example, in the United States, March 13, 2011 2:15am never occurred, // while November 6, 2011 1:15am occurred twice. In such cases, the @@ -1484,7 +1554,7 @@ func norm(hi, lo, base int) (nhi, nlo int) { // Date returns a time that is correct in one of the two zones involved // in the transition, but it does not guarantee which. // -// Date panics if loc is nil. +// Date panics if loc is nil or if normalized year is out of the range [-32767, 32767]. func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { if loc == nil { panic("time: missing Location in call to Date") @@ -1493,6 +1563,9 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T // Normalize month, overflowing into year. m := int(month) - 1 year, m = norm(year, m, 12) + if year > 32767 || year < -32767 { + panic("time: year is out of range [-32767, 32767]") + } month = Month(m) + 1 // Normalize nsec, sec, min, hour, overflowing into day. @@ -1501,23 +1574,25 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T hour, min = norm(hour, min, 60) day, hour = norm(day, hour, 24) - // Compute days since the absolute epoch. - d := daysSinceEpoch(year) + d := daysSinceEpoch(year, month, day) + unix := int64(int(d)*secondsPerDay + hour*secondsPerHour + min*secondsPerMinute + sec) - // Add in days before this month. - d += uint64(daysBefore[month-1]) - if isLeap(year) && month >= March { - d++ // February 29 - } + // Compute days since the absolute epoch. + // d := daysSinceEpochOriginal(year) + // // Add in days before this month. + // d += uint64(daysBefore[month-1]) + // if isLeap(year) && month >= March { + // d++ // February 29 + // } - // Add in days before today. - d += uint64(day - 1) + // // Add in days before today. + // d += uint64(day - 1) - // Add in time elapsed today. - abs := d * secondsPerDay - abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) + // // Add in time elapsed today. + // abs := d * secondsPerDay + // abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) - unix := int64(abs) + (absoluteToInternal + internalToUnix) + // unix := int64(abs) + (absoluteToInternal + internalToUnix) // Look for zone offset for expected time, so we can adjust to UTC. // The lookup function expects UTC, so first we pass unix in the diff --git a/src/time/time_test.go b/src/time/time_test.go index 86335e3796009d..76ad7ed0b04b16 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -660,6 +660,90 @@ func TestDate(t *testing.T) { } } +func TestAllSupportedDates(t *testing.T) { + dateMax := yearMonthDay{32768, 1, 1} + dateMin := yearMonthDay{-32768, 12, 31} + + checkTimeRange(&dateMax, 1, t) + checkTimeRange(&dateMin, -1, t) +} + +func checkTimeRange(limit *yearMonthDay, direction int, t *testing.T) { + secondsPerDay := int64(24 * 60 * 60) + + want := int64(0) + ymd := yearMonthDay{1970, 1, 1} + + for !ymd.equal(limit) { + d := ymd.date() + + got := d.Unix() / secondsPerDay + if got != want { + fmt.Printf("%d/%d/%d", ymd.year, ymd.month, ymd.day) + t.Fatalf("got %d, want %d", got, want) + } + + ymd.moveDate(direction) + want += int64(direction) + } +} + +type yearMonthDay struct { + year int32 + month uint8 + day uint8 +} + +func (ymd *yearMonthDay) equal(o *yearMonthDay) bool { + return ymd.day == o.day && ymd.month == o.month && ymd.year == o.year +} + +func (ymd *yearMonthDay) date() Time { + return Date(int(ymd.year), Month(ymd.month), int(ymd.day), 0, 0, 0, 0, UTC) +} + +func (ymd *yearMonthDay) moveDate(direction int) { + if direction > 0 { + if ymd.day < lastDayOfMonth(ymd.year, ymd.month) { + ymd.day++ + } else { + ymd.day = 1 + if ymd.month == 12 { + ymd.month = 1 + ymd.year++ + } else { + ymd.month++ + } + } + } else { + if ymd.day > 1 { + ymd.day-- + } else { + if ymd.month == 1 { + ymd.month = 12 + ymd.year-- + } else { + ymd.month-- + } + ymd.day = lastDayOfMonth(ymd.year, ymd.month) + } + } +} + +func lastDayOfMonth(year int32, month uint8) uint8 { + if month == 2 { + isLeapYear := year%4 == 0 && (year%100 != 0 || year%400 == 0) + if isLeapYear { + return 29 + } else { + return 28 + } + } + + m := []uint8{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} + return m[month] +} + // Several ways of getting from // Fri Nov 18 7:56:35 PST 2011 // to @@ -1826,16 +1910,18 @@ func TestZoneBounds(t *testing.T) { } // If the zone begins at the beginning of time, start will be returned as a zero Time. - // Use math.MinInt32 to avoid overflow of int arguments on 32-bit systems. - beginTime := Date(math.MinInt32, January, 1, 0, 0, 0, 0, loc) + // Use January 1st -32767 to avoid overflow of int arguments on 32-bit systems. + minYear := -32767 + beginTime := Date(minYear, January, 1, 0, 0, 0, 0, loc) start, end := beginTime.ZoneBounds() if !start.IsZero() || end.IsZero() { t.Errorf("ZoneBounds of %v expects start is zero Time, got:\n start=%v\n end=%v", beginTime, start, end) } // If the zone goes on forever, end will be returned as a zero Time. - // Use math.MaxInt32 to avoid overflow of int arguments on 32-bit systems. - foreverTime := Date(math.MaxInt32, January, 1, 0, 0, 0, 0, loc) + // Use December 31th 32767 to avoid overflow of int arguments on 32-bit systems. + maxYear := 32767 + foreverTime := Date(maxYear, December, 31, 0, 0, 0, 0, loc) start, end = foreverTime.ZoneBounds() if start.IsZero() || !end.IsZero() { t.Errorf("ZoneBounds of %v expects end is zero Time, got:\n start=%v\n end=%v", foreverTime, start, end) diff --git a/src/time/zoneinfo.go b/src/time/zoneinfo.go index c8d17623029356..4ffec52cb824d3 100644 --- a/src/time/zoneinfo.go +++ b/src/time/zoneinfo.go @@ -331,14 +331,15 @@ func tzset(s string, lastTxSec, sec int64) (name string, offset int, start, end return "", 0, 0, 0, false, false } - year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false) + absTmp := uint64(sec + unixToInternal + internalToAbsolute) + year, _, _, yday := absDateWithYday(absTmp, false) ysec := int64(yday*secondsPerDay) + sec%secondsPerDay // Compute start of year in seconds since Unix epoch. - d := daysSinceEpoch(year) + d := daysSinceEpoch(year, 1, 1) abs := int64(d * secondsPerDay) - abs += absoluteToInternal + internalToUnix + // abs += absoluteToInternal + internalToUnix startSec := int64(tzruleTime(year, startRule, stdOffset)) endSec := int64(tzruleTime(year, endRule, dstOffset)) From 59ed4ddf67fb23ddcf4c6b0ebae55b1340b89621 Mon Sep 17 00:00:00 2001 From: Cassio Neri Date: Fri, 8 Dec 2023 15:11:22 +0000 Subject: [PATCH 2/2] Simplify time package. --- src/time/format.go | 5 +- src/time/format_rfc3339.go | 2 +- src/time/time.go | 160 ++++++++++++------------------------- src/time/zoneinfo.go | 2 +- 4 files changed, 56 insertions(+), 113 deletions(-) diff --git a/src/time/format.go b/src/time/format.go index babaa79caf39c0..f1ad1e0daf14e5 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -557,7 +557,7 @@ func (t Time) String() string { // code. func (t Time) GoString() string { abs := t.abs() - year, month, day := absDate(abs, true) + year, month, day := absDate(abs) hour, minute, second := absClock(abs) buf := make([]byte, 0, len("time.Date(9999, time.September, 31, 23, 59, 59, 999999999, time.Local)")) @@ -671,7 +671,8 @@ func (t Time) appendFormat(b []byte, layout string) []byte { // Compute year, month, day if needed. if year < 0 && std&stdNeedDate != 0 { - year, month, day, yday = absDateWithYday(abs, true) + _, month, day = absDate(abs) + year, yday = absYearDay(abs) yday++ } diff --git a/src/time/format_rfc3339.go b/src/time/format_rfc3339.go index 8df573d29065c9..3189300cf75750 100644 --- a/src/time/format_rfc3339.go +++ b/src/time/format_rfc3339.go @@ -19,7 +19,7 @@ func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte { _, offset, abs := t.locabs() // Format date. - year, month, day := absDate(abs, true) + year, month, day := absDate(abs) b = appendInt(b, year, 4) b = append(b, '-') b = appendInt(b, int(month), 2) diff --git a/src/time/time.go b/src/time/time.go index e9af63ff95104b..a259c99a11ffce 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -508,25 +508,25 @@ func (t Time) locabs() (name string, offset int, abs uint64) { // Date returns the year, month, and day in which t occurs. func (t Time) Date() (year int, month Month, day int) { - year, month, day = t.date(true) + year, month, day = absDate(t.abs()) return } // Year returns the year in which t occurs. func (t Time) Year() int { - year, _, _ := t.date(false) + year, _, _ := t.Date() return year } // Month returns the month of the year specified by t. func (t Time) Month() Month { - _, month, _ := t.date(true) + _, month, _ := t.Date() return month } // Day returns the day of the month specified by t. func (t Time) Day() int { - _, _, day := t.date(true) + _, _, day := t.Date() return day } @@ -565,7 +565,7 @@ func (t Time) ISOWeek() (year, week int) { } // find the Thursday of the calendar week abs += uint64(d) * secondsPerDay - year, _, _, yday := absDateWithYday(abs, false) + year, yday := absYearDay(abs) return year, yday/7 + 1 } @@ -608,7 +608,8 @@ func (t Time) Nanosecond() int { // YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years, // and [1,366] in leap years. func (t Time) YearDay() int { - return absYearDay(t.abs()) + 1 + _, yday := absYearDay(t.abs()) + return yday + 1 } // A Duration represents the elapsed time between two instants @@ -974,27 +975,25 @@ const ( daysPer400Years = 365*400 + 97 daysPer100Years = 365*100 + 24 daysPer4Years = 365*4 + 1 + + // Neri-Schneider shift and correction constants. (ns_s is chosen so that + // 1970 is roughly in the middle of the range of their algorithms validity.) + ns_s = uint32(3670) + ns_K = uint32(719468 + 146097*ns_s) + ns_L = int(400 * ns_s) ) -// date computes the year, day of year, and when full=true, -// the month and day in which t occurs. -func (t Time) date(full bool) (year int, month Month, day int) { - return absDate(t.abs(), full) -} +// Computes the year, month and day given the absolute time. +func absDate(abs uint64) (year int, month Month, day int) { -// The algorithm is figure 12 of Neri, Schneider, "Euclidean affine functions -// and their application to calendar algorithms". -// https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 -func absDate(abs uint64, full bool) (year int, month Month, day int) { daysAbs := int64(abs / secondsPerDay) daysUnix := int32(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay) - // Shift and correction constants. - s := uint32(3670) - K := uint32(719468 + 146097*s) - L := int(400 * s) + // The algorithm is figure 12 of Neri, Schneider, "Euclidean affine functions + // and their application to calendar algorithms". + // https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 - N := uint32(daysUnix) + K + N := uint32(daysUnix) + ns_K // Century N_1 := 4*N + 3 @@ -1005,7 +1004,7 @@ func absDate(abs uint64, full bool) (year int, month Month, day int) { N_2 := R | 3 P_2 := 2939745 * uint64(N_2) Z := uint32(P_2 / 4294967296) - N_Y := uint32(P_2%4294967296) / 2939745 / 4 + N_Y := uint32(P_2%4294967296) / 11758980 J := 0 if N_Y >= 306 { @@ -1013,40 +1012,35 @@ func absDate(abs uint64, full bool) (year int, month Month, day int) { } Y := 100*C + Z - year = int(Y) - L + J - - if !full { - return - } + year = int(Y) - ns_L + J // Month and day N_3 := 2141*N_Y + 197913 - M := N_3 / 65536 - D := N_3 % 65536 / 2141 + M := uint16(N_3 / 65536) + D := uint16(N_3%65536) / 2141 month = Month(M) if J == 1 { - month = Month(M - 12) + month -= 12 } - day = int(D + 1) + day = int(D) + 1 return } -// The algorithm is basicaly figure 12 of Neri, Schneider, "Euclidean affine functions -// and their application to calendar algorithms", adapted to calculate yday. -// https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 -func absDateWithYday(abs uint64, full bool) (year int, month Month, day int, yday int) { - daysAbs := int64(abs / secondsPerDay) +// Computes the year and the year day (in [0, 365]) given the absolute time. +func absYearDay(abs uint64) (year int, yday int) { + daysAbs := int64(abs / secondsPerDay) daysUnix := int32(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay) - // Shift and correction constants. - s := uint32(3670) // chosen so that 1970 is roughly in the middle of the range - K := uint32(719468 + 146097*s) - L := int(400 * s) + // The algorithm is adapted from figure 12 of Neri, Schneider, "Euclidean affine + // functions and their application to calendar algorithms". + // https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 - N := uint32(daysUnix) + K + // The natural epoch for this algorithm is 0001-Jan-01, i.e, 306 days after + // 0000-Mar-01 + N := uint32(daysUnix) + ns_K - 306 // Century N_1 := 4*N + 3 @@ -1055,73 +1049,18 @@ func absDateWithYday(abs uint64, full bool) (year int, month Month, day int, yda // Year R := N_1 % 146097 N_2 := R | 3 + P_2 := 2939745 * uint64(N_2) Z := uint32(P_2 / 4294967296) - N_Y := uint32(P_2%4294967296) / 2939745 / 4 - - isLeapYear := 0 - if Z != 0 { - if Z%4 == 0 { - isLeapYear = 1 - } - } else if C%4 == 0 { - isLeapYear = 1 - } - - J := 0 - if N_Y >= 306 { - J = 1 - } - - yday = int(N_Y) - if J == 1 { - yday = yday - 306 - } else { - yday = yday + 31 + 28 + isLeapYear - } - + N_Y := uint32(P_2%4294967296) / 11758980 Y := 100*C + Z - year = int(Y) - L + J - - if !full { - return - } - // Month and day - N_3 := 2141*N_Y + 197913 - M := N_3 / 65536 - D := N_3 % 65536 / 2141 - - month = Month(M) - if J == 1 { - month = Month(M - 12) - } - day = int(D + 1) + year = int(Y) - ns_L + 1 + yday = int(N_Y) return } -func absYearDay(abs uint64) int { - daysAbs := int64(abs / secondsPerDay) - daysUnix := int32(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay) - - // Shift and correction constants. - s := uint32(3670) // chosen so that 1970 is roughly in the middle of the range - K := uint32(719468 - 306 + 146097*s) - N := uint32(daysUnix) + K - - // Century - N_1 := 4*N + 3 - - // Year - R := N_1 % 146097 - N_2 := R | 3 - P_2 := 2939745 * uint64(N_2) - N_Y := uint32(P_2%4294967296) / 2939745 / 4 - - return int(N_Y) -} - // daysBefore[m] counts the number of days in a non-leap year // before month m begins. There is an entry for m=12, counting // the number of days before January of next year (365). @@ -1150,16 +1089,15 @@ func daysIn(m Month, year int) int { // daysSinceEpoch takes a year, month and day and returns the number of days from // Jan 1 1970 (Unix time) to the given date. -// The algorithm is figure 13 of Neri, Schneider, "Euclidean affine functions -// and their application to calendar algorithms". -// https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 -// It gives correct results if the date is in the range [-1,468,000/Mar/01, 1,471,745/Fev/28]. func daysSinceEpoch(year int, month Month, day int) int32 { - s := uint32(3670) // chosen so that 1970 is roughly in the middle of the range - K := uint32(719468 + 146097*s) - L := int(400 * s) - y := uint32(year + L) + // The algorithm is figure 13 of Neri, Schneider, "Euclidean affine functions + // and their application to calendar algorithms". + // https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172 + // It gives correct results if the date is in the range + // [-1,468,000/Mar/01, 1,471,745/Fev/28]. + + y := uint32(year + ns_L) m := uint32(month) if month < 3 { y-- @@ -1174,7 +1112,7 @@ func daysSinceEpoch(year int, month Month, day int) int32 { m_star := (153*m - 457) / 5 n := y_star + m_star + d - return int32(n - K) + return int32(n - ns_K) } // Provided by package runtime. @@ -1512,7 +1450,11 @@ func (t Time) IsDST() bool { } func isLeap(year int) bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) + d := 3 + if year%25 == 0 { + d = 15 + } + return (year & d) == 0 } // norm returns nhi, nlo such that diff --git a/src/time/zoneinfo.go b/src/time/zoneinfo.go index 4ffec52cb824d3..4473a079f88cbe 100644 --- a/src/time/zoneinfo.go +++ b/src/time/zoneinfo.go @@ -332,7 +332,7 @@ func tzset(s string, lastTxSec, sec int64) (name string, offset int, start, end } absTmp := uint64(sec + unixToInternal + internalToAbsolute) - year, _, _, yday := absDateWithYday(absTmp, false) + year, yday := absYearDay(absTmp) ysec := int64(yday*secondsPerDay) + sec%secondsPerDay