Skip to content

Commit

Permalink
std/time: fix absolute-to-unix conversion and improve tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mertcandav committed Nov 20, 2024
1 parent 804e5ce commit 059565f
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 109 deletions.
29 changes: 10 additions & 19 deletions std/time/time.jule
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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))
}
}
164 changes: 74 additions & 90 deletions std/time/unixtime.jule
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,18 +18,18 @@ 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]

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

Expand Down Expand Up @@ -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
}
20 changes: 20 additions & 0 deletions std/time/unixtime_test.jule
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}
}

0 comments on commit 059565f

Please sign in to comment.