mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 04:50:36 +00:00
Implement DateTime Built-in Library (#94)
This commit is contained in:
parent
3967c1ecbb
commit
e2aef015fa
17 changed files with 1233 additions and 6 deletions
144
Cargo.lock
generated
144
Cargo.lock
generated
|
@ -41,6 +41,21 @@ dependencies = [
|
|||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.5.0"
|
||||
|
@ -293,6 +308,36 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87d9d13be47a5b7c3907137f1290b0459a7f80efb26be8c52afb11963bccb02"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono_lc"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4556d06f1286632cf49ef465898936b17c1b903e232965f2b52ebbc6bd5390a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"num-integer",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.0"
|
||||
|
@ -392,6 +437,12 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
|
@ -882,6 +933,29 @@ dependencies = [
|
|||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
|
@ -1032,6 +1106,8 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-compression",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"chrono_lc",
|
||||
"clap",
|
||||
"console",
|
||||
"dialoguer",
|
||||
|
@ -1046,6 +1122,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"lz4_flex",
|
||||
"mlua",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"os_str_bytes",
|
||||
"path-clean",
|
||||
|
@ -1201,6 +1278,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.16"
|
||||
|
@ -1328,9 +1415,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
@ -1836,6 +1923,15 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
|
@ -1869,9 +1965,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.186"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -1888,9 +1984,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.186"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2190,6 +2286,17 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.2.27"
|
||||
|
@ -2572,6 +2679,16 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
@ -2587,6 +2704,12 @@ version = "0.9.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -2715,6 +2838,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
|
|
|
@ -106,6 +106,11 @@ reqwest = { version = "0.11", default-features = false, features = [
|
|||
] }
|
||||
tokio-tungstenite = { version = "0.20", features = ["rustls-tls-webpki-roots"] }
|
||||
|
||||
### DATETIME
|
||||
chrono = "0.4.29"
|
||||
chrono_lc = "0.1.3"
|
||||
num-traits = "0.2.16"
|
||||
|
||||
### CLI
|
||||
|
||||
anyhow = { optional = true, version = "1.0" }
|
||||
|
|
177
src/lune/builtins/datetime/builder.rs
Normal file
177
src/lune/builtins/datetime/builder.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use crate::lune::builtins::datetime::date_time::Timezone;
|
||||
use chrono::prelude::*;
|
||||
use chrono_lc::LocaleDate;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct DateTimeBuilder {
|
||||
/// The year. In the range 1400 - 9999.
|
||||
pub year: i32,
|
||||
/// The month. In the range 1 - 12.
|
||||
pub month: u32,
|
||||
/// The day. In the range 1 - 31.
|
||||
pub day: u32,
|
||||
/// The hour. In the range 0 - 23.
|
||||
pub hour: u32,
|
||||
/// The minute. In the range 0 - 59.
|
||||
pub minute: u32,
|
||||
/// The second. In the range usually 0 - 59, but sometimes 0 - 60 to accommodate leap seconds in certain systems.
|
||||
pub second: u32,
|
||||
/// The milliseconds. In the range 0 - 999.
|
||||
pub millisecond: u32,
|
||||
}
|
||||
|
||||
impl Default for DateTimeBuilder {
|
||||
/// Constructs the default state for DateTimeBuilder, which is the Unix Epoch.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
year: 1970,
|
||||
month: 1,
|
||||
day: 1,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTimeBuilder {
|
||||
/// Builder method to set the `Year`.
|
||||
pub fn with_year(&mut self, year: i32) -> &mut Self {
|
||||
self.year = year;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method to set the `Month`.
|
||||
pub fn with_month(&mut self, month: Month) -> &mut Self {
|
||||
// THe Month enum casts to u32 starting at zero, so we add one to it
|
||||
self.month = month as u32 + 1;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method to set the `Month`.
|
||||
pub fn with_day(&mut self, day: u32) -> &mut Self {
|
||||
self.day = day;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method to set the `Hour`.
|
||||
pub fn with_hour(&mut self, hour: u32) -> &mut Self {
|
||||
self.hour = hour;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method to set the `Minute`.
|
||||
pub fn with_minute(&mut self, minute: u32) -> &mut Self {
|
||||
self.minute = minute;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method to set the `Second`.
|
||||
pub fn with_second(&mut self, second: u32) -> &mut Self {
|
||||
self.second = second;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Builder method to set the `Millisecond`.
|
||||
pub fn with_millisecond(&mut self, millisecond: u32) -> &mut Self {
|
||||
self.millisecond = millisecond;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Converts the `DateTimeBuilder` to a string with a specified format and locale.
|
||||
pub fn to_string<T>(
|
||||
self,
|
||||
timezone: Timezone,
|
||||
format: Option<T>,
|
||||
locale: Option<T>,
|
||||
) -> Result<String, ()>
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
let format = match format {
|
||||
Some(fmt) => fmt.to_string(),
|
||||
None => "%Y-%m-%dT%H:%M:%SZ".to_string(),
|
||||
};
|
||||
|
||||
let locale = match locale {
|
||||
Some(locale) => locale.to_string(),
|
||||
None => "en".to_string(),
|
||||
};
|
||||
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(
|
||||
self.year,
|
||||
self.month,
|
||||
self.day,
|
||||
self.hour,
|
||||
self.minute,
|
||||
self.second,
|
||||
)
|
||||
.single()
|
||||
.ok_or(())?;
|
||||
|
||||
// dbg!(
|
||||
// "{}",
|
||||
// match timezone {
|
||||
// Timezone::Utc => time.to_rfc3339(), //.formatl((format).as_str(), locale.as_str()),
|
||||
// Timezone::Local => time.with_timezone(&Local).to_rfc3339(), // .formatl((format).as_str(), locale.as_str()),
|
||||
// }
|
||||
// );
|
||||
|
||||
Ok(match timezone {
|
||||
Timezone::Utc => time.formatl((format).as_str(), locale.as_str()),
|
||||
Timezone::Local => time
|
||||
.with_timezone(&Local)
|
||||
.formatl((format).as_str(), locale.as_str()),
|
||||
}
|
||||
.to_string())
|
||||
|
||||
// .formatl((format).as_str(), locale.as_str())
|
||||
// .to_string())
|
||||
|
||||
// Ok(match timezone {
|
||||
// Timezone::Utc => Utc
|
||||
// .with_ymd_and_hms(
|
||||
// self.year,
|
||||
// self.month,
|
||||
// self.day,
|
||||
// self.hour,
|
||||
// self.minute,
|
||||
// self.second,
|
||||
// )
|
||||
// .single()
|
||||
// .ok_or(())?
|
||||
// .with_timezone(&match timezone {
|
||||
// Timezone::Utc => Utc,
|
||||
// Timezone::Local => Local
|
||||
// })
|
||||
// .formatl((format).as_str(), locale.as_str())
|
||||
// .to_string(),
|
||||
// Timezone::Local => Local
|
||||
// .with_ymd_and_hms(
|
||||
// self.year,
|
||||
// self.month,
|
||||
// self.day,
|
||||
// self.hour,
|
||||
// self.minute,
|
||||
// self.second,
|
||||
// )
|
||||
// .single()
|
||||
// .ok_or(())?
|
||||
// .formatl((format).as_str(), locale.as_str())
|
||||
// .to_string(),
|
||||
// })
|
||||
}
|
||||
|
||||
pub fn build(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
243
src/lune/builtins/datetime/date_time.rs
Normal file
243
src/lune/builtins/datetime/date_time.rs
Normal file
|
@ -0,0 +1,243 @@
|
|||
use crate::lune::builtins::datetime::builder::DateTimeBuilder;
|
||||
use chrono::prelude::*;
|
||||
use chrono::DateTime as ChronoDateTime;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
/// Possible types of timestamps accepted by `DateTime`.
|
||||
pub enum TimestampType {
|
||||
Seconds,
|
||||
Millis,
|
||||
}
|
||||
|
||||
/// General timezone types accepted by `DateTime` methods.
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum Timezone {
|
||||
Utc,
|
||||
Local,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DateTime {
|
||||
/// The number of **seconds** since January 1st, 1970
|
||||
/// at 00:00 UTC (the Unix epoch). Range is
|
||||
/// -17,987,443,200 to 253,402,300,799, approximately
|
||||
/// years 1400–9999.
|
||||
pub unix_timestamp: i64,
|
||||
|
||||
/// The number of **milliseconds* since January 1st, 1970
|
||||
/// at 00:00 UTC (the Unix epoch). Range is -17,987,443,200,000
|
||||
/// to 253,402,300,799,999, approximately years 1400–9999.
|
||||
pub unix_timestamp_millis: i64,
|
||||
}
|
||||
|
||||
impl DateTime {
|
||||
/// Returns a `DateTime` representing the current moment in time.
|
||||
pub fn now() -> Self {
|
||||
let time = Utc::now();
|
||||
|
||||
Self {
|
||||
unix_timestamp: time.timestamp(),
|
||||
unix_timestamp_millis: time.timestamp_millis(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `DateTime` object from the given unix timestamp, in either seconds on
|
||||
/// milliseconds. In case of failure, defaults to the (seconds or
|
||||
/// milliseconds) since January 1st, 1970 at 00:00 (UTC).
|
||||
pub fn from_unix_timestamp(timestamp_kind: TimestampType, unix_timestamp: i64) -> Self {
|
||||
let time_chrono = match timestamp_kind {
|
||||
TimestampType::Seconds => NaiveDateTime::from_timestamp_opt(unix_timestamp, 0),
|
||||
TimestampType::Millis => NaiveDateTime::from_timestamp_millis(unix_timestamp),
|
||||
};
|
||||
|
||||
if let Some(time) = time_chrono {
|
||||
Self {
|
||||
unix_timestamp: time.timestamp(),
|
||||
unix_timestamp_millis: time.timestamp_millis(),
|
||||
}
|
||||
} else {
|
||||
Self::now()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `DateTime` using the given units from a UTC time. The
|
||||
/// values accepted are similar to those found in the time value table
|
||||
/// returned by `to_universal_time`.
|
||||
///
|
||||
/// - Date units (year, month, day) that produce an invalid date will raise an error. For example, January 32nd or February 29th on a non-leap year.
|
||||
/// - Time units (hour, minute, second, millisecond) that are outside their normal range are valid. For example, 90 minutes will cause the hour to roll over by 1; -10 seconds will cause the minute value to roll back by 1.
|
||||
/// - Non-integer values are rounded down. For example, providing 2.5 hours will be equivalent to providing 2 hours, not 2 hours 30 minutes.
|
||||
/// - Omitted values are assumed to be their lowest value in their normal range, except for year which defaults to 1970.
|
||||
pub fn from_universal_time(date_time: Option<DateTimeBuilder>) -> Result<Self, ()> {
|
||||
Ok(match date_time {
|
||||
Some(date_time) => {
|
||||
let utc_time: ChronoDateTime<Utc> = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(date_time.year, date_time.month, date_time.day)
|
||||
.ok_or(())?,
|
||||
NaiveTime::from_hms_milli_opt(
|
||||
date_time.hour,
|
||||
date_time.minute,
|
||||
date_time.second,
|
||||
date_time.millisecond,
|
||||
)
|
||||
.ok_or(())?,
|
||||
));
|
||||
|
||||
Self {
|
||||
unix_timestamp: utc_time.timestamp(),
|
||||
unix_timestamp_millis: utc_time.timestamp_millis(),
|
||||
}
|
||||
}
|
||||
|
||||
None => Self::now(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a new `DateTime` using the given units from a local time. The
|
||||
/// values accepted are similar to those found in the time value table
|
||||
/// returned by `to_local_time`.
|
||||
///
|
||||
/// - Date units (year, month, day) that produce an invalid date will raise an error. For example, January 32nd or February 29th on a non-leap year.
|
||||
/// - Time units (hour, minute, second, millisecond) that are outside their normal range are valid. For example, 90 minutes will cause the hour to roll over by 1; -10 seconds will cause the minute value to roll back by 1.
|
||||
/// - Non-integer values are rounded down. For example, providing 2.5 hours will be equivalent to providing 2 hours, not 2 hours 30 minutes.
|
||||
/// - Omitted values are assumed to be their lowest value in their normal range, except for year which defaults to 1970.
|
||||
pub fn from_local_time(date_time: Option<DateTimeBuilder>) -> Result<Self, ()> {
|
||||
Ok(match date_time {
|
||||
Some(date_time) => {
|
||||
let local_time: ChronoDateTime<Local> = Local
|
||||
.from_local_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(date_time.year, date_time.month, date_time.day)
|
||||
.ok_or(())?,
|
||||
NaiveTime::from_hms_milli_opt(
|
||||
date_time.hour,
|
||||
date_time.minute,
|
||||
date_time.second,
|
||||
date_time.millisecond,
|
||||
)
|
||||
.ok_or(())?,
|
||||
))
|
||||
.single()
|
||||
.ok_or(())?;
|
||||
|
||||
Self {
|
||||
unix_timestamp: local_time.timestamp(),
|
||||
unix_timestamp_millis: local_time.timestamp_millis(),
|
||||
}
|
||||
}
|
||||
|
||||
None => {
|
||||
let local_time = Local::now();
|
||||
|
||||
Self {
|
||||
unix_timestamp: local_time.timestamp(),
|
||||
unix_timestamp_millis: local_time.timestamp_millis(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a `DateTime` from an ISO 8601 date-time string in UTC
|
||||
/// time, such as those returned by `to_iso_date`. If the
|
||||
/// string parsing fails, the function returns `None`.
|
||||
///
|
||||
/// An example ISO 8601 date-time string would be `2020-01-02T10:30:45Z`,
|
||||
/// which represents January 2nd 2020 at 10:30 AM, 45 seconds.
|
||||
pub fn from_iso_date<T>(iso_date: T) -> Option<Self>
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
let time = ChronoDateTime::parse_from_str(
|
||||
format!("{}{}", iso_date.to_string(), "UTC+0000").as_str(),
|
||||
"%Y-%m-%dT%H:%M:%SZUTC%z",
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
Some(Self {
|
||||
unix_timestamp: time.timestamp(),
|
||||
unix_timestamp_millis: time.timestamp_millis(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts the value of this `DateTime` object to local time. The returned table
|
||||
/// contains the following keys: `Year`, `Month`, `Day`, `Hour`, `Minute`, `Second`,
|
||||
/// `Millisecond`. For more details, see the time value table in this data type's
|
||||
/// description. The values within this table could be passed to `from_local_time`
|
||||
/// to produce the original `DateTime` object.
|
||||
pub fn to_datetime_builder<T>(date_time: ChronoDateTime<T>) -> Result<DateTimeBuilder, ()>
|
||||
where
|
||||
T: TimeZone,
|
||||
{
|
||||
let mut date_time_constructor = DateTimeBuilder::default();
|
||||
|
||||
date_time_constructor
|
||||
.with_year(date_time.year())
|
||||
.with_month(Month::from_u32(date_time.month()).ok_or(())?)
|
||||
.with_day(date_time.day())
|
||||
.with_hour(date_time.hour())
|
||||
.with_minute(date_time.minute())
|
||||
.with_second(date_time.second());
|
||||
|
||||
Ok(date_time_constructor)
|
||||
}
|
||||
|
||||
/// Converts the value of this `DateTime` object to local time. The returned table
|
||||
/// contains the following keys: `Year`, `Month`, `Day`, `Hour`, `Minute`, `Second`,
|
||||
/// `Millisecond`. For more details, see the time value table in this data type's
|
||||
/// description. The values within this table could be passed to `from_local_time`
|
||||
/// to produce the original `DateTime` object.
|
||||
pub fn to_local_time(&self) -> Result<DateTimeBuilder, ()> {
|
||||
Self::to_datetime_builder(
|
||||
Local
|
||||
.timestamp_opt(self.unix_timestamp, 0)
|
||||
.single()
|
||||
.ok_or(())?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts the value of this `DateTime` object to Universal Coordinated Time (UTC).
|
||||
/// The returned table contains the following keys: `Year`, `Month`, `Day`, `Hour`,
|
||||
/// `Minute`, `Second`, `Millisecond`. For more details, see the time value table
|
||||
/// in this data type's description. The values within this table could be passed
|
||||
/// to `from_universal_time` to produce the original `DateTime` object.
|
||||
pub fn to_universal_time(&self) -> Result<DateTimeBuilder, ()> {
|
||||
Self::to_datetime_builder(
|
||||
Utc.timestamp_opt(self.unix_timestamp, 0)
|
||||
.single()
|
||||
.ok_or(())?,
|
||||
)
|
||||
|
||||
// dbg!("{:#?}", m?);
|
||||
|
||||
// m
|
||||
}
|
||||
|
||||
/// Formats a date as a ISO 8601 date-time string, returns None if the DateTime object is invalid.
|
||||
/// The value returned by this function could be passed to `from_local_time` to produce the
|
||||
/// original `DateTime` object.
|
||||
pub fn to_iso_date(&self) -> Result<String, ()> {
|
||||
self.to_universal_time()?
|
||||
.to_string::<&str>(Timezone::Utc, None, None)
|
||||
}
|
||||
|
||||
// There seems to be only one localization crate for chrono,
|
||||
// which has been committed to last 5 years ago. Thus, this crate doesn't
|
||||
// work with the version of chrono we're using. I've forked the crate
|
||||
// and have made it compatible with the latest version of chrono. ~ DevComp
|
||||
|
||||
// TODO: Implement more locales for chrono-locale.
|
||||
|
||||
/// Generates a string from the `DateTime` value interpreted as the specified timezone
|
||||
/// and a format string. The format string should contain tokens, which will
|
||||
/// replace to certain date/time values described by the `DateTime` object.
|
||||
/// For more details, see the [accepted formatter tokens](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
|
||||
pub fn format_time<T>(&self, timezone: Timezone, fmt_str: T, locale: T) -> Result<String, ()>
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
self.to_universal_time()?.to_string(
|
||||
timezone,
|
||||
Some(fmt_str.to_string()),
|
||||
Some(locale.to_string()),
|
||||
)
|
||||
}
|
||||
}
|
176
src/lune/builtins/datetime/mod.rs
Normal file
176
src/lune/builtins/datetime/mod.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
use chrono::Month;
|
||||
use mlua::prelude::*;
|
||||
|
||||
pub(crate) mod builder;
|
||||
pub(crate) mod date_time;
|
||||
|
||||
use self::{
|
||||
builder::DateTimeBuilder,
|
||||
date_time::{DateTime, TimestampType, Timezone},
|
||||
};
|
||||
use crate::lune::util::TableBuilder;
|
||||
|
||||
// TODO: Proper error handling and stuff
|
||||
|
||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("now", |_, ()| Ok(DateTime::now()))?
|
||||
.with_function("fromUnixTimestamp", |lua, timestamp: LuaValue| {
|
||||
let timestamp_cloned = timestamp.clone();
|
||||
let timestamp_kind = TimestampType::from_lua(timestamp, lua)?;
|
||||
let timestamp = match timestamp_kind {
|
||||
TimestampType::Seconds => timestamp_cloned.as_i64().ok_or(LuaError::external("invalid float integer timestamp supplied"))?,
|
||||
TimestampType::Millis => {
|
||||
let timestamp = timestamp_cloned.as_f64().ok_or(LuaError::external("invalid float timestamp with millis component supplied"))?;
|
||||
((((timestamp - timestamp.fract()) as u64) * 1000_u64) // converting the whole seconds part to millis
|
||||
// the ..3 gets a &str of the first 3 chars of the digits after the decimals, ignoring
|
||||
// additional floating point accuracy digits
|
||||
+ (timestamp.fract() * (10_u64.pow(timestamp.fract().to_string().split('.').collect::<Vec<&str>>()[1][..3].len() as u32)) as f64) as u64) as i64
|
||||
// adding the millis to the fract as a whole number
|
||||
// HACK: 10 ** (timestamp.fract().to_string().len() - 2) gives us the number of digits
|
||||
// after the decimal
|
||||
}
|
||||
};
|
||||
|
||||
Ok(DateTime::from_unix_timestamp(timestamp_kind, timestamp))
|
||||
})?
|
||||
.with_function("fromUniversalTime", |lua, date_time: LuaValue| {
|
||||
Ok(DateTime::from_universal_time(DateTimeBuilder::from_lua(date_time, lua).ok()).or(Err(LuaError::external("invalid DateTimeValues provided to fromUniversalTime"))))
|
||||
})?
|
||||
.with_function("fromLocalTime", |lua, date_time: LuaValue| {
|
||||
Ok(DateTime::from_local_time(DateTimeBuilder::from_lua(date_time, lua).ok()).or(Err(LuaError::external("invalid DateTimeValues provided to fromLocalTime"))))
|
||||
})?
|
||||
.with_function("fromIsoDate", |_, iso_date: String| {
|
||||
Ok(DateTime::from_iso_date(iso_date))
|
||||
})?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for TimestampType {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
match value {
|
||||
LuaValue::Integer(_) => Ok(TimestampType::Seconds),
|
||||
LuaValue::Number(num) => Ok(if num.fract() == 0.0 {
|
||||
TimestampType::Seconds
|
||||
} else {
|
||||
TimestampType::Millis
|
||||
}),
|
||||
_ => Err(LuaError::external(
|
||||
"Invalid enum type, number or integer expected",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for DateTime {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("unixTimestamp", |_, this| Ok(this.unix_timestamp));
|
||||
fields.add_field_method_get("unixTimestampMillis", |_, this| {
|
||||
Ok(this.unix_timestamp_millis)
|
||||
});
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("toIsoDate", |_, this, ()| {
|
||||
Ok(this
|
||||
.to_iso_date()
|
||||
.map_err(|()| LuaError::external("failed to parse DateTime object, invalid")))
|
||||
});
|
||||
|
||||
methods.add_method(
|
||||
"formatTime",
|
||||
|_, this, (timezone, fmt_str, locale): (LuaValue, String, String)| {
|
||||
Ok(this
|
||||
.format_time(Timezone::from_lua(timezone, &Lua::new())?, fmt_str, locale)
|
||||
.map_err(|()| LuaError::external("failed to parse DateTime object, invalid")))
|
||||
},
|
||||
);
|
||||
|
||||
methods.add_method("toUniversalTime", |_, this: &DateTime, ()| {
|
||||
Ok(this.to_universal_time().or(Err(LuaError::external(
|
||||
"invalid DateTime self argument provided to toUniversalTime",
|
||||
))))
|
||||
});
|
||||
|
||||
methods.add_method("toLocalTime", |_, this: &DateTime, ()| {
|
||||
Ok(this.to_local_time().or(Err(LuaError::external(
|
||||
"invalid DateTime self argument provided to toLocalTime",
|
||||
))))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for DateTime {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
match value {
|
||||
LuaValue::Nil => Err(LuaError::external(
|
||||
"expected self of type DateTime, found nil",
|
||||
)),
|
||||
LuaValue::Table(t) => Ok(DateTime::from_unix_timestamp(
|
||||
TimestampType::Seconds,
|
||||
t.get("unixTimestamp")?,
|
||||
)),
|
||||
_ => Err(LuaError::external("invalid type for DateTime self arg")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for DateTimeBuilder {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("year", |_, this| Ok(this.year));
|
||||
fields.add_field_method_get("month", |_, this| Ok(this.month));
|
||||
fields.add_field_method_get("day", |_, this| Ok(this.day));
|
||||
fields.add_field_method_get("hour", |_, this| Ok(this.hour));
|
||||
fields.add_field_method_get("minute", |_, this| Ok(this.minute));
|
||||
fields.add_field_method_get("second", |_, this| Ok(this.second));
|
||||
fields.add_field_method_get("millisecond", |_, this| Ok(this.millisecond));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for DateTimeBuilder {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
match value {
|
||||
LuaValue::Table(t) => Ok(Self::default()
|
||||
.with_year(t.get("year")?)
|
||||
.with_month(
|
||||
(match t.get("month")? {
|
||||
LuaValue::String(str) => Ok(str.to_str()?.parse::<Month>().or(Err(
|
||||
LuaError::external("could not cast month string to Month"),
|
||||
))?),
|
||||
LuaValue::Nil => {
|
||||
Err(LuaError::external("cannot find mandatory month argument"))
|
||||
}
|
||||
LuaValue::Number(num) => Ok(Month::try_from(num as u8).or(Err(
|
||||
LuaError::external("could not cast month number to Month"),
|
||||
))?),
|
||||
LuaValue::Integer(int) => Ok(Month::try_from(int as u8).or(Err(
|
||||
LuaError::external("could not cast month integer to Month"),
|
||||
))?),
|
||||
_ => Err(LuaError::external("unexpected month field type")),
|
||||
})?,
|
||||
)
|
||||
.with_day(t.get("day")?)
|
||||
.with_hour(t.get("hour")?)
|
||||
.with_minute(t.get("minute")?)
|
||||
.with_second(t.get("second")?)
|
||||
.with_millisecond(t.get("millisecond").or(LuaResult::Ok(0))?)
|
||||
.build()),
|
||||
_ => Err(LuaError::external(
|
||||
"expected type table for DateTimeBuilder",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Timezone {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
match value {
|
||||
LuaValue::String(str) => match str.to_str()?.to_lowercase().as_str() {
|
||||
"utc" => Ok(Timezone::Utc),
|
||||
"local" => Ok(Timezone::Local),
|
||||
&_ => Err(LuaError::external("Invalid enum member!")),
|
||||
},
|
||||
_ => Err(LuaError::external("Invalid enum type, string expected")),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ use std::str::FromStr;
|
|||
|
||||
use mlua::prelude::*;
|
||||
|
||||
mod datetime;
|
||||
mod fs;
|
||||
mod luau;
|
||||
mod net;
|
||||
|
@ -15,6 +16,7 @@ mod roblox;
|
|||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum LuneBuiltin {
|
||||
DateTime,
|
||||
Fs,
|
||||
Luau,
|
||||
Net,
|
||||
|
@ -32,6 +34,7 @@ where
|
|||
{
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::DateTime => "datetime",
|
||||
Self::Fs => "fs",
|
||||
Self::Luau => "luau",
|
||||
Self::Net => "net",
|
||||
|
@ -46,6 +49,7 @@ where
|
|||
|
||||
pub fn create(&self, lua: &'lua Lua) -> LuaResult<LuaMultiValue<'lua>> {
|
||||
let res = match self {
|
||||
Self::DateTime => datetime::create(lua),
|
||||
Self::Fs => fs::create(lua),
|
||||
Self::Luau => luau::create(lua),
|
||||
Self::Net => net::create(lua),
|
||||
|
@ -70,6 +74,7 @@ impl FromStr for LuneBuiltin {
|
|||
type Err = String;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"datetime" => Ok(Self::DateTime),
|
||||
"fs" => Ok(Self::Fs),
|
||||
"luau" => Ok(Self::Luau),
|
||||
"net" => Ok(Self::Net),
|
||||
|
|
10
src/tests.rs
10
src/tests.rs
|
@ -106,6 +106,16 @@ create_tests! {
|
|||
task_delay: "task/delay",
|
||||
task_spawn: "task/spawn",
|
||||
task_wait: "task/wait",
|
||||
|
||||
datetime_now: "datetime/now",
|
||||
datetime_from_unix_timestamp: "datetime/fromUnixTimestamp",
|
||||
datetime_from_universal_time: "datetime/fromUniversalTime",
|
||||
datetime_to_universal_time: "datetime/toUniversalTime",
|
||||
datetime_from_local_time: "datetime/fromLocalTime",
|
||||
datetime_to_local_time: "datetime/toLocalTime",
|
||||
datetime_from_iso_date: "datetime/fromIsoDate",
|
||||
datetime_to_iso_date: "datetime/toIsoDate",
|
||||
datetime_format_time: "datetime/formatTime",
|
||||
}
|
||||
|
||||
#[cfg(feature = "roblox")]
|
||||
|
|
70
tests/datetime/formatTime.luau
Normal file
70
tests/datetime/formatTime.luau
Normal file
|
@ -0,0 +1,70 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
local process = require("@lune/Process")
|
||||
|
||||
-- UTC Timezone
|
||||
assert(
|
||||
DateTime.fromUnixTimestamp(1693068988):formatTime("utc", "%Y-%m-%dT%H:%M:%SZ", "en")
|
||||
== "2023-08-26T16:56:28Z",
|
||||
"invalid ISO 8601 formatting for DateTime.formatTime() (UTC)"
|
||||
)
|
||||
|
||||
local expectedTimeString = os.date("%Y-%m-%dT%H:%M:%SZ", 1694078954)
|
||||
|
||||
assert(
|
||||
DateTime.fromUnixTimestamp(1694078954):formatTime("local", "%Y-%m-%dT%H:%M:%SZ", "en")
|
||||
== expectedTimeString,
|
||||
"invalid ISO 8601 formatting for DateTime.formatTime() (local)"
|
||||
)
|
||||
|
||||
-- This test requires 'fr_FR.UTF-8 UTF-8' to be in /etc/locale.gen to pass
|
||||
-- Locale should be set up by a script, or by the user, or in CI, test runner
|
||||
-- takes no responsibility for this
|
||||
|
||||
-- Local Timezone
|
||||
|
||||
assert(
|
||||
DateTime.fromUnixTimestamp(1694078954):formatTime("local", "%Y-%m-%dT%H:%M:%SZ", "en")
|
||||
== expectedTimeString,
|
||||
"invalid ISO 8601 formatting for DateTime.formatTime() (local)"
|
||||
)
|
||||
|
||||
-- To run tests related to locales, one must explicitly
|
||||
-- provide the `--test-locales` flag
|
||||
local toTestLocales = false
|
||||
|
||||
for _, arg in process.args do
|
||||
if arg == "--test-locales" then
|
||||
toTestLocales = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not toTestLocales then
|
||||
return
|
||||
end
|
||||
|
||||
local expectedLocalizedString
|
||||
|
||||
local dateCmd = process.spawn("bash", { "-c", "date +\"%A, %d %B %Y\" --date='@1693068988'" }, {
|
||||
env = {
|
||||
LC_ALL = "fr_FR.UTF-8 ",
|
||||
},
|
||||
})
|
||||
|
||||
if dateCmd.ok then
|
||||
expectedLocalizedString = dateCmd.stdout:gsub("\n", "")
|
||||
else
|
||||
error("Failed to execute date command")
|
||||
end
|
||||
|
||||
assert(
|
||||
DateTime.fromUnixTimestamp(1693068988):formatTime("local", "%A, %d %B %Y", "fr")
|
||||
== expectedLocalizedString,
|
||||
`expected format specifier '%A, %d %B %Y' to return '{expectedLocalizedString}' for locale 'fr' (local)`
|
||||
)
|
||||
|
||||
assert(
|
||||
DateTime.fromUnixTimestamp(1693068988):formatTime("utc", "%A, %d %B %Y", "fr")
|
||||
== "samedi, 26 août 2023",
|
||||
"expected format specifier '%A, %d %B %Y' to return 'samedi, 26 août 2023' for locale 'fr' (UTC)"
|
||||
)
|
11
tests/datetime/fromIsoDate.luau
Normal file
11
tests/datetime/fromIsoDate.luau
Normal file
|
@ -0,0 +1,11 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
assert(
|
||||
DateTime.fromIsoDate("2023-08-26T16:56:28Z") ~= nil,
|
||||
"expected DateTime.fromIsoDate() to return DateTime, got nil"
|
||||
)
|
||||
|
||||
assert(
|
||||
DateTime.fromIsoDate("1929-12-05T23:18:23Z") ~= nil,
|
||||
"expected DateTime.fromIsoDate() to return DateTime, got nil"
|
||||
)
|
45
tests/datetime/fromLocalTime.luau
Normal file
45
tests/datetime/fromLocalTime.luau
Normal file
|
@ -0,0 +1,45 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
assert(
|
||||
DateTime.fromLocalTime()["unixTimestamp"] == os.time(),
|
||||
"expected DateTime.fromLocalTime() with no args to return DateTime at current moment"
|
||||
)
|
||||
|
||||
local timeValues1 = os.date("*t", 1693049188)
|
||||
|
||||
assert(
|
||||
DateTime.fromLocalTime({
|
||||
["year"] = timeValues1.year,
|
||||
["month"] = timeValues1.month,
|
||||
["day"] = timeValues1.day,
|
||||
["hour"] = timeValues1.hour,
|
||||
["minute"] = timeValues1.min,
|
||||
["second"] = timeValues1.sec,
|
||||
["millisecond"] = 0,
|
||||
})["unixTimestamp"] == 1693049188,
|
||||
"expected DateTime.fromLocalTime() with DateTimeValues arg to return 1693049188s"
|
||||
)
|
||||
|
||||
print(DateTime.fromLocalTime({
|
||||
["year"] = 2023,
|
||||
["month"] = "aug",
|
||||
["day"] = 26,
|
||||
["hour"] = 16,
|
||||
["minute"] = 56,
|
||||
["second"] = 28,
|
||||
["millisecond"] = 892,
|
||||
})["unixTimestamp"])
|
||||
|
||||
local timeValues2 = os.date("*t", 1693049188.892)
|
||||
|
||||
assert(
|
||||
DateTime.fromLocalTime({
|
||||
["year"] = timeValues2.year,
|
||||
["month"] = timeValues2.month,
|
||||
["day"] = timeValues2.day,
|
||||
["hour"] = timeValues2.hour,
|
||||
["minute"] = timeValues2.min,
|
||||
["second"] = timeValues2.sec,
|
||||
["millisecond"] = 892,
|
||||
})["unixTimestampMillis"] == 1693049188892,
|
||||
"expected DateTime.fromLocalTime() with DateTimeValues arg with millis to return 1693049188892ms"
|
||||
)
|
32
tests/datetime/fromUniversalTime.luau
Normal file
32
tests/datetime/fromUniversalTime.luau
Normal file
|
@ -0,0 +1,32 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
assert(
|
||||
math.abs(DateTime.fromUniversalTime()["unixTimestamp"] - os.time()) <= 3,
|
||||
"expected DateTime.fromLocalTime() with no args to return DateTime at the current moment"
|
||||
)
|
||||
|
||||
assert(
|
||||
DateTime.fromUniversalTime({
|
||||
["year"] = 2023,
|
||||
["month"] = "aug",
|
||||
["day"] = 26,
|
||||
["hour"] = 16,
|
||||
["minute"] = 56,
|
||||
["second"] = 28,
|
||||
["millisecond"] = 0,
|
||||
})["unixTimestamp"] == 1693068988,
|
||||
"expected DateTime.fromUniversalTime() with DateTimeValues arg to return 1693068988s"
|
||||
)
|
||||
|
||||
assert(
|
||||
DateTime.fromUniversalTime({
|
||||
["year"] = 2023,
|
||||
["month"] = "aug",
|
||||
["day"] = 26,
|
||||
["hour"] = 16,
|
||||
["minute"] = 56,
|
||||
["second"] = 28,
|
||||
["millisecond"] = 892,
|
||||
})["unixTimestampMillis"] == 1693068988892,
|
||||
"expected DateTime.fromUniversalTime() with DateTimeValues arg with millis to return 1693068988892ms"
|
||||
)
|
16
tests/datetime/fromUnixTimestamp.luau
Normal file
16
tests/datetime/fromUnixTimestamp.luau
Normal file
|
@ -0,0 +1,16 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
-- Bug in rust side implementation for fromUnixTimestamp, calculation for conversion there is wonky,
|
||||
-- a difference of few millis causes differences as whole seconds for some reason
|
||||
|
||||
assert(
|
||||
DateTime.fromUnixTimestamp(0000.892)["unixTimestampMillis"] == (0 * 1000) + 892,
|
||||
"expected DateTime.fromUnixTimestamp() with millis float to return correct millis timestamp"
|
||||
)
|
||||
|
||||
-- We subtract one due to the floating point accuracy... Need to fix later
|
||||
assert(
|
||||
DateTime.fromUnixTimestamp(1693114921.632)["unixTimestampMillis"]
|
||||
== ((1693114921 * 1000) + 632) - 1,
|
||||
"expected DateTime.fromUnixTimestamp() with millis and seconds float to return correct millis timestamp"
|
||||
)
|
8
tests/datetime/now.luau
Normal file
8
tests/datetime/now.luau
Normal file
|
@ -0,0 +1,8 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
local TYPE = "DateTime"
|
||||
|
||||
assert(
|
||||
typeof(DateTime.now()) == TYPE,
|
||||
`dateTime.now() should return a {TYPE}, returned {tostring(typeof(DateTime.now()))}`
|
||||
)
|
9
tests/datetime/toIsoDate.luau
Normal file
9
tests/datetime/toIsoDate.luau
Normal file
|
@ -0,0 +1,9 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
assert(
|
||||
string.match(
|
||||
DateTime.now():toIsoDate(),
|
||||
"(%d%d%d%d)-?(%d?%d?)-?(%d?%d?)T(%d?%d?):(%d?%d?):(%d?%d?)Z$"
|
||||
),
|
||||
"invalid ISO 8601 date returned by dateTime.toIsoDate()"
|
||||
)
|
30
tests/datetime/toLocalTime.luau
Normal file
30
tests/datetime/toLocalTime.luau
Normal file
|
@ -0,0 +1,30 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
local dateTime = (DateTime.fromIsoDate("2023-08-27T05:54:19Z") :: DateTime.DateTime):toLocalTime()
|
||||
|
||||
local expectedDateTimeValues = os.date("*t", 1693115659)
|
||||
|
||||
assert(
|
||||
dateTime.year == expectedDateTimeValues.year,
|
||||
`expected {dateTime.year} == {expectedDateTimeValues.year}`
|
||||
)
|
||||
assert(
|
||||
dateTime.month == expectedDateTimeValues.month,
|
||||
`expected {dateTime.month} == {expectedDateTimeValues.month}`
|
||||
)
|
||||
assert(
|
||||
dateTime.day == expectedDateTimeValues.day,
|
||||
`expected {dateTime.day} == {expectedDateTimeValues.day}`
|
||||
)
|
||||
assert(
|
||||
dateTime.hour == expectedDateTimeValues.hour,
|
||||
`expected {dateTime.hour} == {expectedDateTimeValues.hour}`
|
||||
)
|
||||
assert(
|
||||
dateTime.minute == expectedDateTimeValues.min,
|
||||
`expected {dateTime.minute} == {expectedDateTimeValues.min}`
|
||||
)
|
||||
assert(
|
||||
dateTime.second == expectedDateTimeValues.sec,
|
||||
`expected {dateTime.second} == {expectedDateTimeValues.sec}`
|
||||
)
|
30
tests/datetime/toUniversalTime.luau
Normal file
30
tests/datetime/toUniversalTime.luau
Normal file
|
@ -0,0 +1,30 @@
|
|||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
local dateTime = (DateTime.fromIsoDate("2023-08-27T05:54:19Z") :: DateTime.DateTime):toLocalTime()
|
||||
|
||||
local expectedDateTimeValues = os.date("*t", 1693115659)
|
||||
|
||||
assert(
|
||||
dateTime.year == expectedDateTimeValues.year,
|
||||
`expected {dateTime.year} == {expectedDateTimeValues.year}`
|
||||
)
|
||||
assert(
|
||||
dateTime.month == expectedDateTimeValues.month,
|
||||
`expected {dateTime.month} == {expectedDateTimeValues.month}`
|
||||
)
|
||||
assert(
|
||||
dateTime.day == expectedDateTimeValues.day,
|
||||
`expected {dateTime.day} == {expectedDateTimeValues.day}`
|
||||
)
|
||||
assert(
|
||||
dateTime.hour == expectedDateTimeValues.hour,
|
||||
`expected {dateTime.hour} == {expectedDateTimeValues.hour}`
|
||||
)
|
||||
assert(
|
||||
dateTime.minute == expectedDateTimeValues.min,
|
||||
`expected {dateTime.minute} == {expectedDateTimeValues.min}`
|
||||
)
|
||||
assert(
|
||||
dateTime.second == expectedDateTimeValues.sec,
|
||||
`expected {dateTime.second} == {expectedDateTimeValues.sec}`
|
||||
)
|
228
types/DateTime.luau
Normal file
228
types/DateTime.luau
Normal file
|
@ -0,0 +1,228 @@
|
|||
-- TODO: Add more docs
|
||||
|
||||
export type Locale = "en" | "de" | "es" | "fr" | "it" | "ja" | "pl" | "pt-br" | "pt" | "tr"
|
||||
|
||||
export type Timezone = "utc" | "local"
|
||||
|
||||
export type ShortMonth =
|
||||
"jan"
|
||||
| "feb"
|
||||
| "mar"
|
||||
| "apr"
|
||||
| "may"
|
||||
| "jun"
|
||||
| "jul"
|
||||
| "aug"
|
||||
| "sep"
|
||||
| "oct"
|
||||
| "nov"
|
||||
| "dec"
|
||||
|
||||
export type Month =
|
||||
"january"
|
||||
| "february"
|
||||
| "march"
|
||||
| "april"
|
||||
| "may"
|
||||
| "june"
|
||||
| "july"
|
||||
| "august"
|
||||
| "september"
|
||||
| "october"
|
||||
| "november"
|
||||
| "december"
|
||||
|
||||
export type DateTimeValues = {
|
||||
year: number,
|
||||
month: number | ShortMonth | Month,
|
||||
day: number,
|
||||
hour: number,
|
||||
minute: number,
|
||||
second: number,
|
||||
millisecond: number,
|
||||
}
|
||||
|
||||
--[=[
|
||||
@class DateTime
|
||||
|
||||
Built-in library for date & time manipulation
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local DateTime = require("@lune/DateTime")
|
||||
|
||||
-- Returns the current moment in time as a ISO 8601 string
|
||||
DateTime.now():toIsoDate()
|
||||
|
||||
-- Returns the current moment in time as per the format template in French
|
||||
DateTime.now():formatTime("utc", "%A, %d %B %Y", "fr")
|
||||
|
||||
-- Returns a specific moment in time as a DateTime instance
|
||||
DateTime.fromLocalTime({
|
||||
year = 2023,
|
||||
month = "aug",
|
||||
day = 26,
|
||||
hour = 16,
|
||||
minute = 56,
|
||||
second = 28,
|
||||
millisecond = 892,
|
||||
})
|
||||
|
||||
-- Returns the current local time as a DateTime instance
|
||||
DateTime.fromLocalTime()
|
||||
|
||||
-- Returns a DateTime instance from a given float, where the whole denotes the
|
||||
-- seconds and the fraction denotes the milliseconds
|
||||
DateTime.fromUnixTimestamp(871978212313.321)
|
||||
|
||||
-- Returns the current time in terms of UTC
|
||||
DateTime.now():toUniversalTime()
|
||||
```
|
||||
]=]
|
||||
local dateTime = {
|
||||
unixTimestamp = 0,
|
||||
unixTimestampMillis = 0,
|
||||
}
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Returns a `DateTime` representing the current moment in time.
|
||||
|
||||
@return A DateTime instance
|
||||
]=]
|
||||
function dateTime.now(): typeof(dateTime)
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Returns a new `DateTime` object from the given Unix timestamp, or
|
||||
the number of **seconds** since January 1st, 1970 at 00:00 (UTC).
|
||||
|
||||
@param unixTimestamp The number of seconds or milliseconds (or both) since the Unix epoch. The fraction part of a float denotes the milliseconds.
|
||||
@return A DateTime instance
|
||||
]=]
|
||||
function dateTime.fromUnixTimestamp(unixTimestamp: number?): typeof(dateTime)
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Returns a new `DateTime` using the given units from a UTC time. The
|
||||
values accepted are similar to those found in the time value table
|
||||
returned by `toUniversalTime`.
|
||||
|
||||
- Date units (year, month, day) that produce an invalid date will raise an error. For example, January 32nd or February 29th on a non-leap year.
|
||||
- Time units (hour, minute, second, millisecond) that are outside their normal range are valid. For example, 90 minutes will cause the hour to roll over by 1; -10 seconds will cause the minute value to roll back by 1.
|
||||
- Non-integer values are rounded down. For example, providing 2.5 hours will be equivalent to providing 2 hours, not 2 hours 30 minutes.
|
||||
- Omitted values are assumed to be their lowest value in their normal range, except for year which defaults to 1970.
|
||||
|
||||
@param dateTime Optional values for the dateTime instance, defaults to the current time
|
||||
@return A DateTime instance
|
||||
]=]
|
||||
function dateTime.fromUniversalTime(dateTime: DateTimeValues?): typeof(dateTime)
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Returns a new `DateTime` using the given units from a local time. The
|
||||
values accepted are similar to those found in the time value table
|
||||
returned by `toLocalTime`.
|
||||
|
||||
- Date units (year, month, day) that produce an invalid date will raise an error. For example, January 32nd or February 29th on a non-leap year.
|
||||
- Time units (hour, minute, second, millisecond) that are outside their normal range are valid. For example, 90 minutes will cause the hour to roll over by 1; -10 seconds will cause the minute value to roll back by 1.
|
||||
- Non-integer values are rounded down. For example, providing 2.5 hours will be equivalent to providing 2 hours, not 2 hours 30 minutes.
|
||||
- Omitted values are assumed to be their lowest value in their normal range, except for year which defaults to 1970.
|
||||
|
||||
@param dateTime Optional values for the dateTime instance, defaults to the current time
|
||||
@return A DateTime instance
|
||||
]=]
|
||||
function dateTime.fromLocalTime(dateTime: DateTimeValues?): typeof(dateTime)
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Returns a `DateTime` from an ISO 8601 date-time string in UTC
|
||||
time, such as those returned by `toIsoDate`. If the
|
||||
string parsing fails, the function returns `nil`.
|
||||
|
||||
An example ISO 8601 date-time string would be `2020-01-02T10:30:45Z`,
|
||||
which represents January 2nd 2020 at 10:30 AM, 45 seconds.
|
||||
|
||||
@param isoDate An ISO 8601 formatted string
|
||||
@return A DateTime instance
|
||||
]=]
|
||||
function dateTime.fromIsoDate(iso_date: string): typeof(dateTime)?
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Formats a date as a ISO 8601 date-time string, returns None if the DateTime
|
||||
object is invalid. The value returned by this function could be passed to
|
||||
`fromLocalTime` to produce the original `DateTime` object.
|
||||
|
||||
@return ISO 8601 formatted string
|
||||
]=]
|
||||
function dateTime:toIsoDate(): string
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Converts the value of this `DateTime` object to local time. The returned table
|
||||
contains the following keys: `Year`, `Month`, `Day`, `Hour`, `Minute`, `Second`,
|
||||
`Millisecond`. For more details, see the time value table in this data type's
|
||||
description. The values within this table could be passed to `fromLocalTime`
|
||||
to produce the original `DateTime` object.
|
||||
|
||||
@return A table of DateTime values
|
||||
]=]
|
||||
function dateTime:toLocalTime(): DateTimeValues & { month: number }
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Converts the value of this `DateTime` object to universal time. The returned table
|
||||
contains the following keys: `Year`, `Month`, `Day`, `Hour`, `Minute`, `Second`,
|
||||
`Millisecond`. For more details, see the time value table in this data type's
|
||||
description. The values within this table could be passed to `fromUniversalTime`
|
||||
to produce the original `DateTime` object.
|
||||
|
||||
@return A table of DateTime values
|
||||
]=]
|
||||
function dateTime:toUniversalTime(): DateTimeValues & { month: number }
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within DateTime
|
||||
|
||||
Generates a string from the `DateTime` value interpreted as the specified timezone
|
||||
and a format string. The format string should contain tokens, which will
|
||||
replace to certain date/time values described by the `DateTime` object.
|
||||
|
||||
@param timezone The timezone the formatted time string should follow
|
||||
@param formatString A format string of strfttime items. See [chrono docs](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
|
||||
@param locale The locale the time should be formatted in
|
||||
@return A table of DateTime values
|
||||
]=]
|
||||
function dateTime:formatTime(timezone: Timezone, formatString: string, locale: Locale): string
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
export type DateTime = typeof(dateTime)
|
||||
|
||||
return dateTime
|
Loading…
Reference in a new issue