Skip to content

Commit cdb5004

Browse files
committed
add datetime function
1 parent ecd96c4 commit cdb5004

File tree

10 files changed

+184
-5
lines changed

10 files changed

+184
-5
lines changed

Cargo.lock

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ serde_json = "1"
3535
serde_tuple = "0.5.0"
3636
term_size = "=1.0.0-beta.2"
3737
thread_local = "1"
38+
time = "0.3.36"
3839
tinyvec = {version = "1", features = ["alloc", "serde"]}
3940
toml = "0.8.10"
4041
unicode-segmentation = "1.10"

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This version is not yet released. If you are reading this on the website, then t
1616
- For example, `X__1` will format to `X₁`
1717
- Add new [Scoped Modules](https://uiua.org/tutorial/modules#scoped-modules)
1818
- These allow you to create a module without creating a file
19+
- Add the [`datetime`](https://uiua.org/docs/datetime) function, which splits a time into its date and time components
1920
- [`un °`](https://uiua.org/docs/un) [`shape △`](https://uiua.org/docs/shape) now generates an array with the given shape and incrementing elements
2021
- [`un °`](https://uiua.org/docs/un) [`pick ⊡`](https://uiua.org/docs/pick) is now equivalent to [`range ⇡`](https://uiua.org/docs/range) [`shape △`](https://uiua.org/docs/shape) [`duplicate .`](https://uiua.org/docs/duplicate)
2122
- [`keep ▽`](https://uiua.org/docs/keep) will now cycle counts if the counts array is shorter than the counted array

site/primitives.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,12 @@
500500
"class": "Encoding",
501501
"description": "Encode an array into a CSV string"
502502
},
503+
"datetime": {
504+
"args": 1,
505+
"outputs": 1,
506+
"class": "Misc",
507+
"description": "Get the date and time information from a time"
508+
},
503509
"deal": {
504510
"args": 2,
505511
"outputs": 1,

site/src/backend.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ impl SysBackend for WebBackend {
338338
self.play_audio(bytes)
339339
}
340340
fn now(&self) -> f64 {
341-
*START_TIME.get().unwrap() + instant::now() / 1000.0
341+
*START_TIME.get_or_init(|| instant::now() / 1000.0) + instant::now() / 1000.0
342342
}
343343
fn set_clipboard(&self, contents: &str) -> Result<(), String> {
344344
_ = window()

src/algorithm/invert.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ fn prim_inverse(prim: Primitive, span: usize) -> Option<Instr> {
112112
Csv => Instr::ImplPrim(UnCsv, span),
113113
Xlsx => Instr::ImplPrim(UnXlsx, span),
114114
Fft => Instr::ImplPrim(UnFft, span),
115+
DateTime => Instr::ImplPrim(UnDatetime, span),
115116
_ => return None,
116117
})
117118
}
@@ -141,6 +142,7 @@ fn impl_prim_inverse(prim: ImplPrimitive, span: usize) -> Option<Instr> {
141142
UnCsv => Instr::Prim(Csv, span),
142143
UnXlsx => Instr::Prim(Xlsx, span),
143144
UnFft => Instr::Prim(Fft, span),
145+
UnDatetime => Instr::Prim(DateTime, span),
144146
TraceN(n, inverse) => Instr::ImplPrim(TraceN(n, !inverse), span),
145147
_ => return None,
146148
})

src/algorithm/monadic.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ use std::{
88
iter::{self, repeat},
99
mem::size_of,
1010
ptr, slice,
11+
time::Duration,
1112
};
1213

1314
use ecow::{eco_vec, EcoVec};
1415
use rayon::prelude::*;
16+
use time::{Date, Month, OffsetDateTime, Time};
1517

1618
use crate::{
1719
array::*,
@@ -1979,4 +1981,132 @@ impl Value {
19791981
}
19801982
s
19811983
}
1984+
/// Get the `datetime` of a value
1985+
pub fn datetime(&self, env: &Uiua) -> UiuaResult<Array<f64>> {
1986+
let mut arr = match self {
1987+
Value::Num(arr) => arr.clone(),
1988+
Value::Byte(arr) => arr.convert_ref(),
1989+
value => return Err(env.error(format!("Cannot get datetime of {}", value.type_name()))),
1990+
};
1991+
let mut new_data = eco_vec![0.0; arr.data.len() * 6];
1992+
let slice = new_data.make_mut();
1993+
for (i, &n) in arr.data.iter().enumerate() {
1994+
let dur = Duration::try_from_secs_f64(n.abs())
1995+
.map_err(|_| env.error(format!("{n} is not a valid time")))?;
1996+
let dt = if n >= 0.0 {
1997+
OffsetDateTime::UNIX_EPOCH + dur
1998+
} else {
1999+
OffsetDateTime::UNIX_EPOCH - dur
2000+
};
2001+
slice[i * 6] = dt.year() as f64;
2002+
slice[i * 6 + 1] = dt.month() as u8 as f64;
2003+
slice[i * 6 + 2] = dt.day() as f64;
2004+
slice[i * 6 + 3] = dt.hour() as f64;
2005+
slice[i * 6 + 4] = dt.minute() as f64;
2006+
slice[i * 6 + 5] = dt.second() as f64;
2007+
}
2008+
arr.data = new_data.into();
2009+
arr.shape.push(6);
2010+
arr.validate_shape();
2011+
Ok(arr)
2012+
}
2013+
pub(crate) fn undatetime(&self, env: &Uiua) -> UiuaResult<Array<f64>> {
2014+
let mut arr = match self {
2015+
Value::Num(arr) => arr.clone(),
2016+
Value::Byte(arr) => arr.convert_ref(),
2017+
value => {
2018+
return Err(env.error(format!("Cannot decode datetime from {}", value.type_name())))
2019+
}
2020+
};
2021+
let convert = |chunk: &[f64]| -> UiuaResult<f64> {
2022+
let mut year = chunk.first().copied().unwrap_or(0.0);
2023+
let mut month = chunk.get(1).copied().unwrap_or(0.0) - 1.0;
2024+
let mut day = chunk.get(2).copied().unwrap_or(0.0) - 1.0;
2025+
let mut hour = chunk.get(3).copied().unwrap_or(0.0);
2026+
let mut minute = chunk.get(4).copied().unwrap_or(0.0);
2027+
let mut second = chunk.get(5).copied().unwrap_or(0.0);
2028+
let mut frac = chunk.get(6).copied().unwrap_or(0.0);
2029+
frac += second.fract();
2030+
if frac >= 1.0 {
2031+
second += frac.floor();
2032+
frac = 1.0 - frac.fract();
2033+
} else if frac < 0.0 {
2034+
second += frac.ceil();
2035+
frac = -frac.fract();
2036+
}
2037+
if second >= 60.0 {
2038+
minute += (second / 60.0).floor();
2039+
second %= 60.0;
2040+
} else if second < 0.0 {
2041+
minute += (second / 60.0).ceil();
2042+
second = 60.0 + (second % 60.0);
2043+
}
2044+
if minute >= 60.0 {
2045+
hour += (minute / 60.0).floor();
2046+
minute %= 60.0;
2047+
} else if minute < 0.0 {
2048+
hour += (minute / 60.0).ceil();
2049+
minute = 60.0 + (minute % 60.0);
2050+
}
2051+
if hour >= 24.0 {
2052+
day += (hour / 24.0).floor();
2053+
hour %= 24.0;
2054+
} else if hour < 0.0 {
2055+
day += (hour / 24.0).ceil();
2056+
hour = 24.0 + (hour % 24.0);
2057+
}
2058+
let day_delta = if day >= 28.0 {
2059+
let delta = day - 27.0;
2060+
day -= delta;
2061+
delta
2062+
} else if day < 0.0 {
2063+
let delta = day;
2064+
day = 0.0;
2065+
delta
2066+
} else {
2067+
day.fract()
2068+
};
2069+
if month >= 12.0 {
2070+
year += (month / 12.0).floor();
2071+
month %= 12.0;
2072+
} else if month < 1.0 {
2073+
year += (month / 12.0).ceil();
2074+
month = 12.0 - (month % 12.0);
2075+
}
2076+
let year = year as i32;
2077+
let month = month as u8 + 1;
2078+
let day = day as u8 + 1;
2079+
let hour = hour as u8;
2080+
let minute = minute as u8;
2081+
let second = second as u8;
2082+
let mut dt = OffsetDateTime::new_utc(
2083+
Date::from_calendar_date(year, Month::January.nth_next(month - 1), day).map_err(
2084+
|e| env.error(format!("Invalid date {year:04}-{month:02}-{day:02}: {e}")),
2085+
)?,
2086+
Time::from_hms(hour, minute, second).map_err(|e| {
2087+
env.error(format!(
2088+
"Invalid time {hour:02}:{minute:02}:{second:02}: {e}"
2089+
))
2090+
})?,
2091+
);
2092+
if day_delta > 0.0 {
2093+
dt += Duration::from_secs_f64(day_delta * 86400.0);
2094+
} else if day_delta < 0.0 {
2095+
dt -= Duration::from_secs_f64(day_delta.abs() * 86400.0);
2096+
}
2097+
Ok(dt.unix_timestamp() as f64 + frac)
2098+
};
2099+
let &[.., n] = &*arr.shape else {
2100+
return Err(env.error("Cannot decode datetime from scalar"));
2101+
};
2102+
let mut new_data = eco_vec![0.0; arr.data.len() / n];
2103+
let slice = new_data.make_mut();
2104+
for (i, chunk) in arr.data.chunks_exact(n).enumerate() {
2105+
slice[i] = convert(chunk)?;
2106+
}
2107+
arr.shape.pop();
2108+
arr.data = new_data.into();
2109+
arr.validate_shape();
2110+
Ok(arr)
2111+
}
19822112
}

src/primitive/defs.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,10 +2308,40 @@ primitive!(
23082308
(1, Type, Misc, "type"),
23092309
/// Get the current time in seconds
23102310
///
2311+
/// Time is expressed in seconds since the Unix epoch.
23112312
/// ex: now
23122313
/// [under][now] can be used to time a function.
23132314
/// ex: ⍜now(5&sl1)
23142315
(0, Now, Misc, "now", Impure),
2316+
/// Get the date and time information from a time
2317+
///
2318+
/// You can use [now] to get the current time in seconds since the Unix epoch.
2319+
/// [datetime] turns a time into an array with 6 numbers:
2320+
/// - Year
2321+
/// - Month (1-12)
2322+
/// - Day (1-31)
2323+
/// - Hour (0-23)
2324+
/// - Minute (0-59)
2325+
/// - Second (0-59)
2326+
///
2327+
/// ex: datetime now
2328+
/// The time is always in UTC.
2329+
/// [datetime] is semi-pervasive.
2330+
/// ex: datetime [1e8 1e9 1e10]
2331+
/// You can format the time like this:
2332+
/// ex: datetime now # Time
2333+
/// : ⍚(⬚@0↙¯⊙°⋕) [4....2] # Pad
2334+
/// : °[°$"_-_-_ _:_:_"] # Format
2335+
///
2336+
/// You can use [un][datetime] to convert an array back into a time.
2337+
/// An array with fewer than 6 numbers will be padded with zeros.
2338+
/// ex: °datetime [2023 2 28 1 2 3]
2339+
/// ex: °datetime [2014_4_1 2022_3_31]
2340+
/// Invalid numbers in the datetime will be normalized.
2341+
/// ex: ⍜°datetime∘ [2023 2 29]
2342+
/// ex: ⍜°datetime∘ [1917 5 0]
2343+
/// ex: ⍜°datetime∘ [1996 12 ¯100]
2344+
(1, DateTime, Misc, "datetime"),
23152345
/// The number of radians in a quarter circle
23162346
///
23172347
/// Equivalent to `divide``2``pi` or `divide``4``tau`
@@ -2785,6 +2815,7 @@ impl_primitive!(
27852815
(1, UnCsv),
27862816
(1, UnXlsx),
27872817
(1, UnFft),
2818+
(1, UnDatetime),
27882819
(2(0), MatchPattern),
27892820
// Unders
27902821
(1, UndoFix),

src/primitive/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ impl fmt::Display for ImplPrimitive {
176176
UnCsv => write!(f, "{Un}{Csv}"),
177177
UnXlsx => write!(f, "{Un}{Xlsx}"),
178178
UnFft => write!(f, "{Un}{Fft}"),
179+
UnDatetime => write!(f, "{Un}{DateTime}"),
179180
UndoTake => write!(f, "{Under}{Take}"),
180181
UndoDrop => write!(f, "{Under}{Drop}"),
181182
UndoSelect => write!(f, "{Under}{Select}"),
@@ -787,6 +788,7 @@ impl Primitive {
787788
env.try_recv(id)?;
788789
}
789790
Primitive::Now => env.push(env.rt.backend.now()),
791+
Primitive::DateTime => env.monadic_ref_env(Value::datetime)?,
790792
Primitive::SetInverse => {
791793
let f = env.pop_function()?;
792794
let _inv = env.pop_function()?;
@@ -1031,6 +1033,7 @@ impl ImplPrimitive {
10311033
env.push(val);
10321034
}
10331035
ImplPrimitive::UnFft => algorithm::unfft(env)?,
1036+
ImplPrimitive::UnDatetime => env.monadic_ref_env(Value::undatetime)?,
10341037
ImplPrimitive::UndoInsert => {
10351038
let key = env.pop(1)?;
10361039
let _value = env.pop(2)?;

tests/units.ua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,10 @@ B ← |1 (⨬(+⊃(B-1|B-2)|1)<2.)
573573
⍤⟜≍: [{"1" "2" ""} {"3" "" ""} {"4" "5" "6"}] °csv "1,2\n3\n4,5,6\n"
574574
⍤⟜≍: "1,2\n3\n4,5,6\n" csv {1_2 3 4_5_6}
575575

576+
# Datetime
577+
⍤⟜≍: [2023 2 28 1 2 3] ⍜°datetime∘ [2023 2 28 1 2 3]
578+
⍤⟜≍: [2023 3 1 1 2 3] ⍜°datetime∘ [2023 2 29 1 2 3]
579+
576580
# On
577581
⍤⟜≍: [1 1 3] [⟜⊙⋅⊙◌ 1 2 3 4]
578582
⍤⟜≍: [1 1 2 2 4] [⟜⊙⟜⊙⋅∘ 1 2 3 4]

0 commit comments

Comments
 (0)