1package numfmt23import (4 "math"5 "time"6)78// Dates are serial numbers as in Sheets: whole days since 1899-12-309// (day 0), with the time of day as the fraction. Unlike Excel there is no10// fictitious 1900-02-29, and dates before 1900 are negative.1112var (13 MonthNames = [...]string{"January", "February", "March", "April", "May", "June", "July",14 "August", "September", "October", "November", "December"}15 DayNames = [...]string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}16)1718// epochDays is 1899-12-30 as days since 0000-03-01 (see daysFromCivil).19var epochDays = daysFromCivil(1899, 12, 30)2021// daysFromCivil counts days since 0000-03-01 in the proleptic Gregorian22// calendar (Howard Hinnant's algorithm). Months outside 1-12 normalize.23func daysFromCivil(y, m, d int) int {24 y, m = AddMonths(y, m, 0)25 if m <= 2 {26 y--27 }28 era := floorDivInt(y, 400)29 yoe := y - era*40030 mp := (m + 9) % 1231 doy := (153*mp+2)/5 + d - 132 doe := yoe*365 + yoe/4 - yoe/100 + doy33 return era*146097 + doe34}3536// Civil converts a serial day number to year, month and day.37func Civil(serial int64) (y, m, d int) {38 z := int(serial) + epochDays39 era := floorDivInt(z, 146097)40 doe := z - era*14609741 yoe := (doe - doe/1460 + doe/36524 - doe/146096) / 36542 y = yoe + era*40043 doy := doe - (365*yoe + yoe/4 - yoe/100)44 mp := (5*doy + 2) / 15345 d = doy - (153*mp+2)/5 + 146 m = (mp+2)%12 + 147 if m <= 2 {48 y++49 }50 return y, m, d51}5253// DateSerial returns the serial of a date; month and day overflow into54// the next month or year, as DATE() allows.55func DateSerial(y, m, d int) float64 {56 return float64(daysFromCivil(y, m, 1) + d - 1 - epochDays)57}5859// TimeSerial is the fraction of a day for a time of day.60func TimeSerial(h, m int, s float64) float64 {61 return (float64(h)*3600 + float64(m)*60 + s) / 8640062}6364// AddMonths adds n months to month m of year y, returning a month from65// 1 to 12 and the year it falls in. m itself may be out of range.66func AddMonths(y, m, n int) (int, int) {67 m += n68 y += floorDivInt(m-1, 12)69 m = ((m-1)%12+12)%12 + 170 return y, m71}7273// DaysIn is the number of days in month m of year y.74func DaysIn(y, m int) int {75 return int(DateSerial(y, m+1, 1) - DateSerial(y, m, 1))76}7778// Weekday returns 0 for Sunday through 6 for Saturday. Day 0 was a79// Saturday.80func Weekday(serial int64) int {81 return int(((serial+6)%7 + 7) % 7)82}8384// SerialOf converts a time to a serial in its own time zone.85func SerialOf(t time.Time) float64 {86 h, m, s := t.Clock()87 return DateSerial(t.Year(), int(t.Month()), t.Day()) +88 TimeSerial(h, m, float64(s)+float64(t.Nanosecond())/1e9)89}9091// SplitSerial returns the date and time parts of a serial, rounded to92// the second.93func SplitSerial(v float64) (days int64, secs int) {94 total := int64(math.Round(v * 86400))95 days = floorDiv(total, 86400)96 return days, int(total - days*86400)97}9899func floorDiv(a, b int64) int64 {100 q := a / b101 if (a%b != 0) && ((a < 0) != (b < 0)) {102 q--103 }104 return q105}106107func floorDivInt(a, b int) int { return int(floorDiv(int64(a), int64(b))) }