Skip to main content

rl_lang/interpreter/stdlib/time/
format_time.rs

1//! Unix timestamp -> formatted string conversion.
2//!
3//! `unix_to_parts` decomposes a Unix timestamp into `(year, month, day, hour, minute, second)`
4//! using the Gregorian calendar algorithm (proleptic calendar, UTC only, no DST).
5//!
6//! `apply_pattern` performs simple string substitution on strftime-like tokens:
7//! `%Y` (4-digit year), `%m` (month), `%d` (day), `%H` (hour), `%M` (minute), `%S` (second).
8
9use crate::interpreter::{
10    evaluator::Evaluator,
11    stdlib::common::{verr, vok, vs},
12    values::Value,
13};
14
15pub fn unix_to_parts(timestamp: i64) -> (i32, u32, u32, u32, u32, u32) {
16    let total_seconds = timestamp;
17    let time_of_day = total_seconds % 86400;
18    let hour = time_of_day / 3600;
19    let minute = (time_of_day % 3600) / 60;
20    let second = time_of_day % 60;
21
22    let days_since_epoch = total_seconds / 86400;
23    let days_since_march0 = days_since_epoch + 719468;
24
25    let century = days_since_march0.div_euclid(146097);
26    let day_in_century = days_since_march0.rem_euclid(146097);
27    let year_in_century = (day_in_century - day_in_century / 1460 + day_in_century / 36524
28        - day_in_century / 146096)
29        / 365;
30    let day_in_year =
31        day_in_century - (365 * year_in_century + year_in_century / 4 - year_in_century / 100);
32    let month_index = (5 * day_in_year + 2) / 153;
33    let day = day_in_year - (153 * month_index + 2) / 5 + 1;
34    let month = if month_index < 10 {
35        month_index + 3
36    } else {
37        month_index - 9
38    };
39    let year = year_in_century + century * 400 + if month <= 2 { 1 } else { 0 };
40
41    (
42        year as i32,
43        month as u32,
44        day as u32,
45        hour as u32,
46        minute as u32,
47        second as u32,
48    )
49}
50
51fn apply_pattern(
52    pattern: &str,
53    year: i32,
54    month: u32,
55    day: u32,
56    hour: u32,
57    minute: u32,
58    second: u32,
59) -> String {
60    pattern
61        .replace("%Y", &format!("{:04}", year))
62        .replace("%m", &format!("{:02}", month))
63        .replace("%d", &format!("{:02}", day))
64        .replace("%H", &format!("{:02}", hour))
65        .replace("%M", &format!("{:02}", minute))
66        .replace("%S", &format!("{:02}", second))
67}
68
69pub fn format_time(_: &mut Evaluator, timestamp: i64, pattern: String) -> Value {
70    if timestamp < 0 {
71        return verr!(vs!("timestamp is negative".to_string()));
72    }
73    let (year, month, day, hour, minute, second) = unix_to_parts(timestamp);
74    vok!(vs!(apply_pattern(
75        &pattern, year, month, day, hour, minute, second,
76    )))
77}
78
79pub fn date_str(_: &mut Evaluator, timestamp: i64) -> Value {
80    if timestamp < 0 {
81        return verr!(vs!("timestamp is negative".to_string()));
82    }
83    let (year, month, day, hour, minute, second) = unix_to_parts(timestamp);
84    vok!(vs!(apply_pattern(
85        "%Y-%m-%d", year, month, day, hour, minute, second,
86    )))
87}
88
89pub fn time_str(_: &mut Evaluator, timestamp: i64) -> Value {
90    if timestamp < 0 {
91        return verr!(vs!("timestamp is negative".to_string()));
92    }
93    let (year, month, day, hour, minute, second) = unix_to_parts(timestamp);
94    vok!(vs!(apply_pattern(
95        "%H:%M:%S", year, month, day, hour, minute, second,
96    )))
97}