Created
December 9, 2023 00:31
-
-
Save unixdj/e31f75d5b49b5a2b3b4fbbae69368937 to your computer and use it in GitHub Desktop.
Go #63844 absDate and Date using uint64
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2009 The Go Authors. All rights reserved. | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file. | |
package main | |
import ( | |
"fmt" | |
"time" | |
) | |
const ( | |
hasMonotonic = 1 << 63 | |
maxWall = wallToInternal + (1<<33 - 1) // year 2157 | |
minWall = wallToInternal // year 1885 | |
nsecMask = 1<<30 - 1 | |
nsecShift = 30 | |
) | |
const ( | |
// The unsigned zero year for internal calculations. | |
// Must be 1 mod 400, and times before it will not compute correctly, | |
// but otherwise can be changed at will. | |
absoluteZeroYear = -292277022399 | |
// The year of the zero Time. | |
// Assumed by the unixToInternal computation below. | |
internalYear = 1 | |
// Offsets to convert between internal and absolute or Unix times. | |
absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay | |
internalToAbsolute = -absoluteToInternal | |
unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay | |
internalToUnix int64 = -unixToInternal | |
wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay | |
) | |
const ( | |
secondsPerMinute = 60 | |
secondsPerHour = 60 * secondsPerMinute | |
secondsPerDay = 24 * secondsPerHour | |
secondsPerWeek = 7 * secondsPerDay | |
daysPer400Years = 365*400 + 97 | |
daysPer100Years = 365*100 + 24 | |
daysPer4Years = 365*4 + 1 | |
) | |
// 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 time.Month, day int) { | |
// daysAbs := int64(abs / secondsPerDay) | |
// daysUnix := int64(daysAbs - (unixToInternal+internalToAbsolute)/secondsPerDay) | |
daysUnix := abs/secondsPerDay - 59 | |
// N := uint64(daysUnix + K) | |
// Century | |
N_1 := 4*daysUnix + 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 + uint64(Z) | |
year = int(Y) + J + absoluteZeroYear | |
if !full { | |
return | |
} | |
// Month and day | |
N_3 := 2141*N_Y + 197913 | |
M := N_3 / 65536 | |
D := N_3 % 65536 / 2141 | |
month = time.Month(M) | |
if J == 1 { | |
month = time.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 time.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 | |
} | |
yday = int(N_Y) | |
if J == 1 { | |
yday = yday - 306 | |
} else { | |
yday = yday + 31 + 28 + isLeapYear | |
} | |
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 = time.Month(M) | |
if J == 1 { | |
month = time.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) | |
} | |
func norm(hi, lo, base int) (nhi, nlo int) { | |
if lo < 0 { | |
n := (-lo-1)/base + 1 | |
hi -= n | |
lo += n * base | |
} | |
if lo >= base { | |
n := lo / base | |
hi += n | |
lo -= n * base | |
} | |
return hi, lo | |
} | |
func daysSinceEpoch(year int, month time.Month, day int) uint64 { | |
// 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 := uint64(int64(year) - absoluteZeroYear) | |
if month < 3 { | |
y-- | |
month += 12 | |
} | |
c := y / 100 | |
y_star := 1461*y/4 - c + c/4 | |
m_star := (153*uint64(month) - 457) / 5 | |
n := y_star + m_star + uint64(day) | |
return uint64(n + 58) | |
} | |
// Date returns the Time corresponding to | |
// | |
// yyyy-mm-dd hh:mm:ss + nsec nanoseconds | |
// | |
// in the appropriate zone for that time in the given location. | |
// | |
// The month, day, hour, min, sec, and nsec values may be outside | |
// 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 or if normalized year is out of the range [-32767, 32767]. | |
func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) time.Time { | |
if loc == nil { | |
panic("time: missing Location in call to Date") | |
} | |
// 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 = time.Month(m) + 1 | |
// Normalize nsec, sec, min, hour, overflowing into day. | |
sec, nsec = norm(sec, nsec, 1e9) | |
min, sec = norm(min, sec, 60) | |
hour, min = norm(hour, min, 60) | |
day, hour = norm(day, hour, 24) | |
d := daysSinceEpoch(year, month, day) | |
unix := int64(int(d)*secondsPerDay+hour*secondsPerHour+min*secondsPerMinute+sec) + (absoluteToInternal + internalToUnix) | |
// 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 time elapsed today. | |
// abs := d * secondsPerDay | |
// abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) | |
// 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 | |
// hope that it will not be too close to a zone transition, | |
// and then adjust if it is. | |
/* | |
_, offset, start, end, _ := loc.lookup(unix) | |
if offset != 0 { | |
utc := unix - int64(offset) | |
// If utc is valid for the time zone we found, then we have the right offset. | |
// If not, we get the correct offset by looking up utc in the location. | |
if utc < start || utc >= end { | |
_, offset, _, _, _ = loc.lookup(utc) | |
} | |
unix -= int64(offset) | |
} | |
*/ | |
// t := unixTime(unix, int32(nsec)) | |
// t.setLoc(loc) | |
t := time.Unix(unix, int64(nsec)) | |
return t | |
} | |
func main() { | |
a := time.Now().Unix() + unixToInternal + internalToAbsolute | |
fmt.Println(absDate(uint64(a), true)) | |
a = time.Date(-absoluteZeroYear, 5, 23, 0, 0, 0, 0, time.UTC).Unix() + unixToInternal + internalToAbsolute | |
fmt.Println(absDate(uint64(a), true)) | |
t := Date(2023, 5, 23, 0, 0, 0, 0, time.UTC) | |
fmt.Println(t) | |
t = Date(-absoluteZeroYear, 5, 23, 0, 0, 0, 0, time.UTC) | |
fmt.Println(t) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment