forked from nulang-org/nulang
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatetime.nula
More file actions
114 lines (99 loc) · 3.84 KB
/
Copy pathdatetime.nula
File metadata and controls
114 lines (99 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Nulang standard library — DateTime type and operations.
// Import: `import stdlib::datetime`
/// A point in time represented as calendar fields.
pub type DateTime = {
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
}
// ── Helpers ────────────────────────────────────────────────────────────────
/// True when y is a leap year (Gregorian calendar).
fn is_leap(y: Int) -> Bool {
(y % 4 == 0 && y % 100 != 0) || y % 400 == 0
}
/// Number of days in the given year.
fn days_in_year(y: Int) -> Int {
if is_leap(y) then 366 else 365
}
/// Days per month (non-leap index 0 = January).
fn month_days(month: Int, leap: Bool) -> Int {
if month == 1 then 31
else if month == 2 then { if leap then 29 else 28 }
else if month == 3 then 31
else if month == 4 then 30
else if month == 5 then 31
else if month == 6 then 30
else if month == 7 then 31
else if month == 8 then 31
else if month == 9 then 30
else if month == 10 then 31
else if month == 11 then 30
else if month == 12 then 31
else 0
}
// ── Conversion ──────────────────────────────────────────────────────────────
/// Convert a Unix timestamp (seconds since epoch) to a DateTime.
///
/// Handles timestamps before 1970 (negative values) via floor division.
/// The conversion is accurate for all dates in the Gregorian calendar.
/// Leap seconds are not accounted for.
fn unix_to_datetime(ts: Int) -> DateTime {
// Seconds within the day.
let sec_of_day = ts % 86400;
var sec = sec_of_day;
// Handle negative remainders for timestamps before epoch.
if sec < 0 then { sec = sec + 86400 } else {};
let hour = sec / 3600;
let minute = (sec % 3600) / 60;
let second = sec % 60;
// Days since epoch (floor division for negative timestamps).
var days = ts / 86400;
if ts < 0 && ts % 86400 != 0 then { days = days - 1 } else {};
// Walk forward/backward through years.
var year = 1970;
if days >= 0 then {
while days >= days_in_year(year) {
days = days - days_in_year(year);
year = year + 1
}
} else {
while days < 0 {
year = year - 1;
days = days + days_in_year(year)
}
};
// days is now day-of-year (0-indexed).
let leap = is_leap(year);
var month = 1;
while days >= month_days(month, leap) {
days = days - month_days(month, leap);
month = month + 1
};
{ year: year, month: month, day: days + 1, hour: hour, minute: minute, second: second }
}
// ── Public API ─────────────────────────────────────────────────────────────
/// Return the current system time as a DateTime.
///
/// Calls `perform Time.now()` to obtain a Unix timestamp (seconds since
/// 1970-01-01 00:00:00 UTC) and delegates to `unix_to_datetime`.
pub fn now() -> DateTime {
let ts = perform Time.now();
unix_to_datetime(ts)
}
/// Create a DateTime from individual fields.
pub fn new(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> DateTime {
{ year: year, month: month, day: day, hour: hour, minute: minute, second: second }
}
/// Check if a DateTime represents a valid calendar date.
/// Does basic range validation on each field.
pub fn is_valid(dt: DateTime) -> Bool {
dt.year >= 0 &&
dt.month >= 1 && dt.month <= 12 &&
dt.day >= 1 && dt.day <= 31 &&
dt.hour >= 0 && dt.hour <= 23 &&
dt.minute >= 0 && dt.minute <= 59 &&
dt.second >= 0 && dt.second <= 59
}