Skip to content

time: improve performance of calendar algorithms and clarify its spec… #64600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/time/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)"))
Expand Down Expand Up @@ -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 = absDate(abs, true)
_, month, day = absDate(abs)
year, yday = absYearDay(abs)
yday++
}

Expand Down
2 changes: 1 addition & 1 deletion src/time/format_rfc3339.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/time/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
247 changes: 132 additions & 115 deletions src/time/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 := absYearDay(abs)
return year, yday/7 + 1
}

Expand Down Expand Up @@ -608,7 +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)
_, yday := absYearDay(t.abs())
return yday + 1
}

Expand Down Expand Up @@ -975,85 +975,89 @@ 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, yday 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)

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
}
// Computes the year, month and day given the absolute time.
func absDate(abs uint64) (year int, month Month, day int) {

daysAbs := int64(abs / secondsPerDay)
daysUnix := int32(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay)

// 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) + ns_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) / 11758980

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
} else {
begin = int(daysBefore[month])
Y := 100*C + Z
year = int(Y) - ns_L + J

// Month and day
N_3 := 2141*N_Y + 197913
M := uint16(N_3 / 65536)
D := uint16(N_3%65536) / 2141

month = Month(M)
if J == 1 {
month -= 12
}
day = int(D) + 1

return
}

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

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

// 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
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) / 11758980
Y := 100*C + Z

year = int(Y) - ns_L + 1
yday = int(N_Y)

month++ // because January is 1
day = day - begin + 1
return
}

Expand Down Expand Up @@ -1083,32 +1087,32 @@ 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.
func daysSinceEpoch(year int, month Month, day int) int32 {

// 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].

// Add in days from 400-year cycles.
n := y / 400
y -= 400 * n
d := daysPer400Years * n
y := uint32(year + ns_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 - ns_K)
}

// Provided by package runtime.
Expand Down Expand Up @@ -1446,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
Expand Down Expand Up @@ -1477,14 +1485,18 @@ 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
// choice of time zone, and therefore the time, is not well-defined.
// 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")
Expand All @@ -1493,6 +1505,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.
Expand All @@ -1501,23 +1516,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
Expand Down
Loading