From 4853b84ec7ad300c79b8d3e7bdd859521ed42fbc Mon Sep 17 00:00:00 2001 From: mertcandav Date: Tue, 19 Nov 2024 20:11:22 +0300 Subject: [PATCH] std/time: reimplement the Time and AbsTime --- std/encoding/csv/reader.jule | 2 +- std/jule/sema/comptime.jule | 2 +- std/runtime/time_unix.jule | 19 +++ std/runtime/time_windows.jule | 34 ++++ std/time/time.jule | 307 ++++++---------------------------- std/time/unixtime.jule | 184 ++++++++++++++++++++ std/time/unixtime_test.jule | 34 ++++ 7 files changed, 320 insertions(+), 262 deletions(-) create mode 100644 std/runtime/time_windows.jule create mode 100644 std/time/unixtime.jule create mode 100644 std/time/unixtime_test.jule diff --git a/std/encoding/csv/reader.jule b/std/encoding/csv/reader.jule index 7de1abbb..b2508779 100644 --- a/std/encoding/csv/reader.jule +++ b/std/encoding/csv/reader.jule @@ -83,8 +83,8 @@ // {`Multi-line // field`, `comma is ,`} -use "std/bytes" use "std/bufio" +use "std/bytes" use "std/io" use "std/unicode" use "std/unicode/utf8" diff --git a/std/jule/sema/comptime.jule b/std/jule/sema/comptime.jule index 71a4e862..2a5d7560 100644 --- a/std/jule/sema/comptime.jule +++ b/std/jule/sema/comptime.jule @@ -1250,7 +1250,7 @@ impl comptimeValue { e.pushErr(arg.Token, build::LogMsg.ExprNotConst) ret nil } - log := checkDataForIntegerIndexing(e.s, d, arg.Token,e.getOwnerRefers()) + log := checkDataForIntegerIndexing(e.s, d, arg.Token, e.getOwnerRefers()) if log != build::LogMsg.Empty { e.pushErr(arg.Token, log) ret nil diff --git a/std/runtime/time_unix.jule b/std/runtime/time_unix.jule index 26f3a8be..3c844026 100644 --- a/std/runtime/time_unix.jule +++ b/std/runtime/time_unix.jule @@ -4,7 +4,26 @@ cpp use "" +cpp type clockid_t: int + +cpp unsafe fn clock_gettime(id: cpp.clockid_t, mut tp: *cpp.timespec): int + cpp struct timespec { tv_sec: i64 tv_nsec: i64 +} + +// Returns system-wide realtime clock in seconds and nanoseconds. +// The nanoseconds is not a second yet, always in range (0, 999999999). +fn timeNow(): (sec: i64, nsec: i64) { + // Identifier for system-wide realtime clock. + // All possible platforms use 0 for this one, as fas as tested. + const CLOCK_REALTIME = 0 + + mut ts := cpp.timespec{} + if unsafe { cpp.clock_gettime(cpp.clockid_t(CLOCK_REALTIME), &ts) } == -1 { + panic("runtime: timeNow failed") + } + sec, nsec = ts.tv_sec, ts.tv_nsec + ret } \ No newline at end of file diff --git a/std/runtime/time_windows.jule b/std/runtime/time_windows.jule new file mode 100644 index 00000000..019c0ea6 --- /dev/null +++ b/std/runtime/time_windows.jule @@ -0,0 +1,34 @@ +// Copyright 2024 The Jule Programming Language. +// Use of this source code is governed by a BSD 3-Clause +// license that can be found in the LICENSE file. + +cpp fn GetSystemTimeAsFileTime(mut ft: *cpp.FILETIME) + +#typedef +cpp struct FILETIME { + dwLowDateTime: _DWORD + dwHighDateTime: _DWORD +} + +#typedef +cpp struct LARGE_INTEGER { + QuadPart: u64 +} + +// Returns system-wide realtime clock in seconds and nanoseconds. +// The nanoseconds is not a second yet, always in range (0, 999999999). +fn timeNow(): (sec: i64, nsec: i64) { + mut ft := cpp.FILETIME{} + cpp.GetSystemTimeAsFileTime(&ft) // Returns ticks in UTC + // Get the number of seconds since January 1, 1970 12:00am UTC + // FILETIME is in 100-nanosecond intervals since 1601-01-01 + mut t := cpp.LARGE_INTEGER{} + t.QuadPart = u64(ft.dwHighDateTime) + t.QuadPart <<= 32 + t.QuadPart |= u64(ft.dwLowDateTime) + t.QuadPart -= 116444736000000000 // Convert from 1601 to 1970 + t.QuadPart = t.QuadPart / 10 // Convert from 100-ns intervals to nanoseconds + sec = i64(t.QuadPart / 1000000) + nsec = i64(t.QuadPart % 1000000) + ret +} \ No newline at end of file diff --git a/std/time/time.jule b/std/time/time.jule index 5f02face..1c8d32cc 100644 --- a/std/time/time.jule +++ b/std/time/time.jule @@ -2,57 +2,57 @@ // Use of this source code is governed by a BSD 3-Clause // license that can be found in the LICENSE file. -cpp use "" - -cpp type time_t: u64 - -cpp unsafe fn time(t: *cpp.time_t): cpp.time_t - -const hour = 3600 -const day = hour * 24 - -// unix-year offset by today -const unixYearOffset = 1900 - -// unix-month offset by today -const unixMonthOffset = 1 - -const nsecPerMsec = 1000000 -const nsecPerSec = nsecPerMsec * msecPerSec -const msecPerSec = 1000 -const daysPerY = 365 -const daysPer400Y = daysPerY*400 + 97 -const daysPer100Y = daysPerY*100 + 24 -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) +use "std/runtime" + +// A Time represents an instant in time with nanosecond precision. +// +// Zero-value indicates the beginning of Unix time, i.e. zero seconds. +// This means the date January 1, 1970. Implementation can also handle +// the Unix time in the negative plane. For example, -10 seconds should be +// equivalent to Wed Dec 31 1969 23:59:50 UTC+0000. +struct Time { + sec: i64 + nsec: i64 +} -// Days in month. -static mdays: [...]int = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29] +impl Time { + // Returns time in Unix time, nanoseconds will be ignored. + fn Unix(self): i64 { + ret self.sec + } +} -// Type of unix-time seconds. -type UnixTime: u64 +// Returns the current system time with UTC local. +fn Now(): Time { + sec, nsec := runtime::timeNow() + ret Time{sec: sec, nsec: nsec} +} -// Type of abstract time data. -type TimeData: u64 +// Returns new time by Unix time with nanoseconds. +// It is not valid to pass nanoseconds outside the range (0, 999999999). +// Any invalid range will cause panic. +fn Unix(sec: i64, nsec: i64): Time { + if nsec < 0 || nsec >= 1e9 { + panic("std/time: Unix: invalid nanoseconds range") + } + ret Time{sec: sec, nsec: nsec} +} -// Abstract time. +// Absolute time. struct AbsTime { - Day: TimeData - WeekDay: TimeData - YearDay: TimeData - Month: TimeData - Year: TimeData - Second: TimeData - Minute: TimeData - Hour: TimeData + Day: int + WeekDay: int + YearDay: int + Month: int + Year: int + Second: int + Minute: int + Hour: int } impl AbsTime { - // Returns abstract time as unix-time seconds. - fn Unix(self): UnixTime { + // Returns absolute time in Unix time. + fn Unix(self): i64 { mut leap := false mut y := self.Year - unixYearOffset mut m := self.Month - unixMonthOffset @@ -67,223 +67,10 @@ impl AbsTime { } mut t := unixYearToSeconds(y, leap) t += unixMonthToSeconds(m, leap) - t += day * UnixTime(self.Day-1) - t += hour * UnixTime(self.Hour) - t += 60 * UnixTime(self.Minute) - t += UnixTime(self.Second) - ret t - } -} - -// Timestamp. -// If you are going to use this structure to process data -// of a time, you can obtain an 'AbsTime' instance by using -// the `abs()` method to be more efficient. Each function of -// this structure, such as `day` or `year`, uses an `abs()` -// call in the background. Back-to-back calls may cause you -// to make the same calculation over and over again. -struct Time { - sec: UnixTime -} - -impl Time { - // Returns new time instance from unix-time. - static fn Unix(sec: UnixTime): Time { - ret Time{ - sec: sec, - } - } - - // Returns time instance of the moment. - static fn Now(): Time { - ret Time.Unix(unix()) - } -} - -impl Time { - // Returns time as unix-time. - fn Unix(self): UnixTime { ret self.sec } - - // Returns day of month. - fn Day(self): TimeData { ret self.Abs().Day } - - // Returns month. - fn Month(self): TimeData { ret self.Abs().Month } - - // Returns year. - fn Year(self): TimeData { ret self.Abs().Year } - - // Returns second. - fn Second(self): TimeData { ret self.Abs().Second } - - // Returns minute. - fn Minute(self): TimeData { ret self.Abs().Minute } - - // Returns hour. - fn Hour(self): TimeData { ret self.Abs().Hour } - - // Returns time as abstract time. - fn Abs(self): AbsTime { - secs := UnixTime(self.sec) - modApoch - mut days := secs / day - mut remSecs := secs % day - if remSecs < 0 { - remSecs += day - days-- - } - - mut qcCycles := days / daysPer400Y - mut remDays := days % daysPer400Y - if remDays < 0 { - remDays += daysPer400Y - qcCycles-- - } - - mut cCycles := remDays / daysPer100Y - if cCycles == 4 { - cCycles-- - } - remDays -= cCycles * daysPer100Y - - mut qCycles := remDays / daysPer4Y - if qCycles == 25 { - qCycles-- - } - remDays -= qCycles * daysPer4Y - - mut remYears := remDays / daysPerY - if remYears == 4 { - remYears-- - } - remDays -= remYears * daysPerY - - mut leap := UnixTime(0) - if remYears == 0 && (qCycles > 0 || cCycles == 0) { - leap = 1 - } - mut yDay := remDays + 31 + 28 + leap - if yDay >= daysPerY+leap { - yDay -= daysPerY + leap - } - - mut months := u64(0) - for UnixTime(mdays[months]) <= remDays; months++ { - remDays -= UnixTime(mdays[months]) - } - - mut t := AbsTime{} - t.Year = TimeData(remYears + 4*qCycles + 100*cCycles + 400*qcCycles + 100) - t.Month = TimeData(months + 2) - if t.Month >= 12 { - t.Month -= 12 - t.Year++ - } - t.Month += unixMonthOffset - t.Year += unixYearOffset - t.Day = TimeData(remDays + 1) - t.WeekDay = TimeData((3 + days) % 7) - if t.WeekDay < 0 { - t.WeekDay += 7 - } - t.YearDay = TimeData(yDay) - t.Hour = TimeData(remSecs / hour) - t.Minute = TimeData(remSecs / 60 % 60) - t.Second = TimeData(remSecs % 60) + t += day * i64(self.Day-1) + t += hour * i64(self.Hour) + t += 60 * i64(self.Minute) + t += i64(self.Second) ret t } -} - -// Returns current unix time UTC. -fn unix(): UnixTime { - ret unsafe { UnixTime(cpp.time(nil)) } -} - -fn unixYearToSeconds(y: TimeData, mut &leap: bool): UnixTime { - if y-2 <= 136 { - mut leaps := (y - 68) >> 2 - leap = (y-68)&3 == 0 - if leap { - leaps-- - } - ret UnixTime(31536000*(y-70) + day*leaps) - } - - mut leaps := TimeData(0) - mut centuries := TimeData(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 - } - } - - leaps += 97*cycles + 24*centuries - if leap { - leaps++ - } - - ret UnixTime((y-100)*31536000 + leaps*day + 946684800 + day) -} - -fn unixMonthToSeconds(m: TimeData, leap: bool): UnixTime { - // Set seconds through month. - mut t := UnixTime(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 } \ No newline at end of file diff --git a/std/time/unixtime.jule b/std/time/unixtime.jule new file mode 100644 index 00000000..490f3105 --- /dev/null +++ b/std/time/unixtime.jule @@ -0,0 +1,184 @@ +// Copyright 2024 The Jule Programming Language. +// 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 unixYearOffset = 1900 // unix-year offset by today +const unixMonthOffset = 1 // unix-month offset by today + +const nsecPerMsec = 1000000 +const nsecPerSec = nsecPerMsec * msecPerSec +const msecPerSec = 1000 +const daysPerY = 365 +const daysPer400Y = daysPerY*400 + 97 +const daysPer100Y = daysPerY*100 + 24 +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) + +// 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 + if remSecs < 0 { + remSecs += day + days-- + } + + mut qcCycles := days / daysPer400Y + mut remDays := days % daysPer400Y + if remDays < 0 { + remDays += daysPer400Y + qcCycles-- + } + + mut cCycles := remDays / daysPer100Y + if cCycles == 4 { + cCycles-- + } + remDays -= cCycles * daysPer100Y + + mut qCycles := remDays / daysPer4Y + if qCycles == 25 { + qCycles-- + } + remDays -= qCycles * daysPer4Y + + mut remYears := remDays / daysPerY + if remYears == 4 { + remYears-- + } + remDays -= remYears * daysPerY + + mut leap := i64(0) + if remYears == 0 && (qCycles > 0 || cCycles == 0) { + leap = 1 + } + mut yDay := remDays + 31 + 28 + leap + if yDay >= daysPerY+leap { + yDay -= daysPerY + leap + } + + mut months := u64(0) + for mdays[months] <= remDays; months++ { + remDays -= mdays[months] + } + + mut at := AbsTime{} + at.Year = int(remYears + 4*qCycles + 100*cCycles + 400*qcCycles + 100) + at.Month = int(months + 2) + if at.Month >= 12 { + at.Month -= 12 + at.Year++ + } + at.Month += unixMonthOffset + at.Year += unixYearOffset + at.Day = int(remDays + 1) + at.WeekDay = int((3 + days) % 7) + if at.WeekDay <= 0 { + at.WeekDay += 7 + } + at.YearDay = int(yDay) + at.Hour = int(remSecs / hour) + 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) + } + + 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 + } + } + + leaps += 97*cycles + 24*centuries + if leap { + leaps++ + } + + ret i64((y-100)*31536000 + leaps*day + 946684800 + day) +} + +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 +} \ No newline at end of file diff --git a/std/time/unixtime_test.jule b/std/time/unixtime_test.jule new file mode 100644 index 00000000..b1327dce --- /dev/null +++ b/std/time/unixtime_test.jule @@ -0,0 +1,34 @@ +// Copyright 2024 The Jule Programming Language. +// Use of this source code is governed by a BSD 3-Clause +// license that can be found in the LICENSE file. + +use "std/testing" + +struct unixTest { + sec: i64 + abs: AbsTime +} + +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}}, + {-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}}, +]) + +#test +fn testUnixAbs(t: &testing::T) { + for i, test in unixAbsTests { + abs := UnixAbs(test.sec) + if abs.Year != test.abs.Year || + abs.Month != test.abs.Month || + abs.Day != test.abs.Day || + abs.Hour != test.abs.Hour || + abs.Minute != test.abs.Minute || + abs.Second != test.abs.Second { + t.Errorf("#{} conversion failed", i) + } + } +} \ No newline at end of file