From 059565f9e1ca4529779b80df90bdce72c56f0108 Mon Sep 17 00:00:00 2001 From: mertcandav Date: Wed, 20 Nov 2024 16:35:19 +0300 Subject: [PATCH] std/time: fix absolute-to-unix conversion and improve tests --- std/time/time.jule | 29 +++---- std/time/unixtime.jule | 164 ++++++++++++++++-------------------- std/time/unixtime_test.jule | 20 +++++ 3 files changed, 104 insertions(+), 109 deletions(-) diff --git a/std/time/time.jule b/std/time/time.jule index 1c8d32cc..c2e5a5ff 100644 --- a/std/time/time.jule +++ b/std/time/time.jule @@ -4,6 +4,14 @@ use "std/runtime" +// The unsigned zero year for internal Unix time calculations. +// Must be 1 mod 400, and times before it will not compute correctly, +// but otherwise can be changed at will. +const absoluteZeroYear = -292277022399 + +// Offsets to convert between internal and absolute or Unix times. +const absoluteToUnix = -9223372028715321600 + // A Time represents an instant in time with nanosecond precision. // // Zero-value indicates the beginning of Unix time, i.e. zero seconds. @@ -53,24 +61,7 @@ struct AbsTime { impl AbsTime { // Returns absolute time in Unix time. fn Unix(self): i64 { - mut leap := false - mut y := self.Year - unixYearOffset - mut m := self.Month - unixMonthOffset - if m >= 12 || m < 0 { - mut adj := m / 12 - m %= 12 - if m < 0 { - adj-- - m += 12 - } - y += adj - } - mut t := unixYearToSeconds(y, leap) - t += unixMonthToSeconds(m, leap) - t += day * i64(self.Day-1) - t += hour * i64(self.Hour) - t += 60 * i64(self.Minute) - t += i64(self.Second) - ret t + ret absUnix(i64(self.Year), i64(self.Month), i64(self.Day), + i64(self.Hour), i64(self.Minute), i64(self.Second)) } } \ No newline at end of file diff --git a/std/time/unixtime.jule b/std/time/unixtime.jule index 490f3105..eefc0aad 100644 --- a/std/time/unixtime.jule +++ b/std/time/unixtime.jule @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD 3-Clause // license that can be found in the LICENSE file. -const hour = 3600 -const day = hour * 24 +const secPerHour = 3600 +const secPerDay = secPerHour * 24 const unixYearOffset = 1900 // unix-year offset by today const unixMonthOffset = 1 // unix-month offset by today @@ -18,7 +18,7 @@ const daysPer4Y = daysPerY*4 + 1 // 2000-03-01 (mod 400 year, immediately after feb29 const _2000_03_01 = 946684800 -const modApoch = _2000_03_01 + day*(31+29) +const modApoch = _2000_03_01 + secPerDay*(31+29) // Days in month. static mdays: [...]i64 = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29] @@ -26,10 +26,10 @@ static mdays: [...]i64 = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29] // Returns new absolute time by Unix time without nanoseconds. fn UnixAbs(sec: i64): AbsTime { secs := sec - modApoch - mut days := secs / day - mut remSecs := secs % day + mut days := secs / secPerDay + mut remSecs := secs % secPerDay if remSecs < 0 { - remSecs += day + remSecs += secPerDay days-- } @@ -87,98 +87,82 @@ fn UnixAbs(sec: i64): AbsTime { at.WeekDay += 7 } at.YearDay = int(yDay) - at.Hour = int(remSecs / hour) + at.Hour = int(remSecs / secPerHour) at.Minute = int(remSecs / 60 % 60) at.Second = int(remSecs % 60) ret at } -fn unixYearToSeconds(y: int, mut &leap: bool): i64 { - if y-2 <= 136 { - mut leaps := (y - 68) >> 2 - leap = (y-68)&3 == 0 - if leap { - leaps-- - } - ret i64(31536000*(y-70) + day*leaps) - } +fn isLeap(year: i64): bool { + ret year%4 == 0 && (year%100 != 0 || year%400 == 0) +} - mut leaps := int(0) - mut centuries := int(0) - mut cycles := (y - 100) / 400 - mut rem := (y - 100) % 400 - if rem < 0 { - cycles-- - rem += 400 - } - if rem == 0 { - leap = true - centuries = 0 - leaps = 0 - } else { - if rem >= 200 { - if rem >= 300 { - centuries = 3 - rem -= 300 - } else { - centuries = 2 - rem -= 200 - } - } else { - if rem >= 100 { - centuries = 1 - rem -= 100 - } else { - centuries = 0 - } - } - if rem == 0 { - leap = false - leaps = 0 - } else { - leaps = rem / 4 - rem %= 4 - leap = rem == 0 - } - } +// 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. +fn daysSinceEpoch(year: i64): u64 { + mut y := u64(year - absoluteZeroYear) - leaps += 97*cycles + 24*centuries - if leap { - leaps++ - } + // Add in days from 400-year cycles. + mut n := y / 400 + y -= 400 * n + mut d := daysPer400Y * n - ret i64((y-100)*31536000 + leaps*day + 946684800 + day) + // Add in 100-year cycles. + n = y / 100 + y -= 100 * n + d += daysPer100Y * n + + // Add in 4-year cycles. + n = y / 4 + y -= 4 * n + d += daysPer4Y * n + + // Add in non-leap years. + n = y + d += 365 * n + + ret d } -fn unixMonthToSeconds(m: int, leap: bool): i64 { - // Set seconds through month. - mut t := i64(0) - match m { - | 1: - t = 31 * day - | 2: - t = 59 * day - | 3: - t = 90 * day - | 4: - t = 120 * day - | 5: - t = 151 * day - | 6: - t = 181 * day - | 7: - t = 212 * day - | 8: - t = 243 * day - | 9: - t = 273 * day - | 10: - t = 304 * day - | 11: - t = 334 * day - } - if leap && m >= 2 { - t += day - } - ret t +// 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). +static daysBefore: [...]i32 = [ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +] + +// Returns absolute time as Unix time. +fn absUnix(mut year: i64, mut month: i64, mut day: i64, + mut hour: i64, mut minute: i64, mut second: i64): i64 { + // Compute days since the absolute epoch. + mut d := daysSinceEpoch(year) + + // Add in days before this month. + d += u64(daysBefore[month-1]) + if isLeap(year) && month >= 3 { + d++ // February 29 + } + + // Add in days before today. + d += u64(day - 1) + + // Add in time elapsed today. + mut abs := d * secPerDay + abs += u64(hour*secPerHour + minute*60 + second) + + unix := i64(abs) + absoluteToUnix + ret unix } \ No newline at end of file diff --git a/std/time/unixtime_test.jule b/std/time/unixtime_test.jule index b1327dce..53994b70 100644 --- a/std/time/unixtime_test.jule +++ b/std/time/unixtime_test.jule @@ -13,9 +13,18 @@ static unixAbsTests = []unixTest([ {0, {Year: 1970, Month: 1, Day: 1, Hour: 0, Minute: 0, Second: 0}}, {966702998, {Year: 2000, Month: 8, Day: 19, Hour: 16, Minute: 36, Second: 38}}, {77784758457, {Year: 4434, Month: 11, Day: 25, Hour: 13, Minute: 20, Second: 57}}, + {974735338, {Year: 2000, Month: 11, Day: 20, Hour: 15, Minute: 48, Second: 58}}, + {327428778204, {Year: 12345, Month: 10, Day: 21, Hour: 22, Minute: 23, Second: 24}}, {-10, {Year: 1969, Month: 12, Day: 31, Hour: 23, Minute: 59, Second: 50}}, {-9999998088000, {Year: -314918, Month: 9, Day: 4, Hour: 9, Minute: 20, Second: 0}}, {-16295527509, {Year: 1453, Month: 8, Day: 13, Hour: 8, Minute: 34, Second: 51}}, + {-16314912000, {Year: 1453, Month: 1, Day: 1, Hour: 0, Minute: 0, Second: 0}}, + {-31020451200, {Year: 987, Month: 1, Day: 1, Hour: 0, Minute: 0, Second: 0}}, + {-62135596800, {Year: 1, Month: 1, Day: 1, Hour: 0, Minute: 0, Second: 0}}, + {-62198755200, {Year: -1, Month: 1, Day: 1, Hour: 0, Minute: 0, Second: 0}}, + {-62167219200, {Year: 0, Month: 1, Day: 1, Hour: 0, Minute: 0, Second: 0}}, + {-126010455062, {Year: -2024, Month: 11, Day: 20, Hour: 15, Minute: 48, Second: 58}}, + {-125253072662, {Year: -2000, Month: 11, Day: 20, Hour: 15, Minute: 48, Second: 58}}, ]) #test @@ -31,4 +40,15 @@ fn testUnixAbs(t: &testing::T) { t.Errorf("#{} conversion failed", i) } } +} + +#test +fn testAbsUnix(t: &testing::T) { + for i, test in unixAbsTests { + unixtime := test.abs.Unix() + if unixtime != test.sec { + println(unixtime) + t.Errorf("#{} conversion failed", i) + } + } } \ No newline at end of file