mirror of
https://github.com/lune-org/lune.git
synced 2025-04-07 12:00:56 +01:00
Refactor DateTime builtin to be a wrapper around chrono, improve error handling, misc smaller changes and consistency improvements
This commit is contained in:
parent
7a05563f0f
commit
fad48b9603
19 changed files with 900 additions and 895 deletions
|
@ -26,6 +26,7 @@ cli = [
|
||||||
"dep:clap",
|
"dep:clap",
|
||||||
"dep:include_dir",
|
"dep:include_dir",
|
||||||
"dep:regex",
|
"dep:regex",
|
||||||
|
"dep:rustyline",
|
||||||
]
|
]
|
||||||
roblox = [
|
roblox = [
|
||||||
"dep:glam",
|
"dep:glam",
|
||||||
|
@ -116,7 +117,6 @@ num-traits = "0.2"
|
||||||
anyhow = { optional = true, version = "1.0" }
|
anyhow = { optional = true, version = "1.0" }
|
||||||
env_logger = { optional = true, version = "0.10" }
|
env_logger = { optional = true, version = "0.10" }
|
||||||
itertools = { optional = true, version = "0.11" }
|
itertools = { optional = true, version = "0.11" }
|
||||||
|
|
||||||
clap = { optional = true, version = "4.1", features = ["derive"] }
|
clap = { optional = true, version = "4.1", features = ["derive"] }
|
||||||
include_dir = { optional = true, version = "0.7", features = ["glob"] }
|
include_dir = { optional = true, version = "0.7", features = ["glob"] }
|
||||||
regex = { optional = true, version = "1.7", default-features = false, features = [
|
regex = { optional = true, version = "1.7", default-features = false, features = [
|
||||||
|
|
|
@ -1,177 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,243 +0,0 @@
|
||||||
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()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
32
src/lune/builtins/datetime/error.rs
Normal file
32
src/lune/builtins/datetime/error.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type DateTimeResult<T, E = DateTimeError> = Result<T, E>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum DateTimeError {
|
||||||
|
#[error("invalid date")]
|
||||||
|
InvalidDate,
|
||||||
|
#[error("invalid time")]
|
||||||
|
InvalidTime,
|
||||||
|
#[error("ambiguous date or time")]
|
||||||
|
Ambiguous,
|
||||||
|
#[error("date or time is outside allowed range")]
|
||||||
|
OutOfRangeUnspecified,
|
||||||
|
#[error("{name} must be within range {min} -> {max}, got {value}")]
|
||||||
|
OutOfRange {
|
||||||
|
name: &'static str,
|
||||||
|
value: String,
|
||||||
|
min: String,
|
||||||
|
max: String,
|
||||||
|
},
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseError(#[from] chrono::ParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DateTimeError> for LuaError {
|
||||||
|
fn from(value: DateTimeError) -> Self {
|
||||||
|
LuaError::runtime(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,176 +1,251 @@
|
||||||
use chrono::Month;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
pub(crate) mod builder;
|
use chrono::prelude::*;
|
||||||
pub(crate) mod date_time;
|
use chrono::DateTime as ChronoDateTime;
|
||||||
|
use chrono_lc::LocaleDate;
|
||||||
|
|
||||||
use self::{
|
|
||||||
builder::DateTimeBuilder,
|
|
||||||
date_time::{DateTime, TimestampType, Timezone},
|
|
||||||
};
|
|
||||||
use crate::lune::util::TableBuilder;
|
use crate::lune::util::TableBuilder;
|
||||||
|
|
||||||
// TODO: Proper error handling and stuff
|
mod error;
|
||||||
|
mod values;
|
||||||
|
|
||||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
use error::*;
|
||||||
|
use values::*;
|
||||||
|
|
||||||
|
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
TableBuilder::new(lua)?
|
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| {
|
.with_function("fromIsoDate", |_, iso_date: String| {
|
||||||
Ok(DateTime::from_iso_date(iso_date))
|
Ok(DateTime::from_iso_date(iso_date)?)
|
||||||
})?
|
})?
|
||||||
|
.with_function("fromLocalTime", |_, values| {
|
||||||
|
Ok(DateTime::from_local_time(&values)?)
|
||||||
|
})?
|
||||||
|
.with_function("fromUniversalTime", |_, values| {
|
||||||
|
Ok(DateTime::from_universal_time(&values)?)
|
||||||
|
})?
|
||||||
|
.with_function("fromUnixTimestamp", |_, timestamp| {
|
||||||
|
Ok(DateTime::from_unix_timestamp_float(timestamp)?)
|
||||||
|
})?
|
||||||
|
.with_function("now", |_, ()| Ok(DateTime::now()))?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for TimestampType {
|
const DEFAULT_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
const DEFAULT_LOCALE: &str = "en";
|
||||||
match value {
|
|
||||||
LuaValue::Integer(_) => Ok(TimestampType::Seconds),
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
LuaValue::Number(num) => Ok(if num.fract() == 0.0 {
|
pub struct DateTime {
|
||||||
TimestampType::Seconds
|
// NOTE: We store this as the UTC time zone since it is the most commonly
|
||||||
} else {
|
// used and getting the generics right for TimeZone is somewhat tricky,
|
||||||
TimestampType::Millis
|
// but none of the method implementations below should rely on this tz
|
||||||
}),
|
inner: ChronoDateTime<Utc>,
|
||||||
_ => Err(LuaError::external(
|
|
||||||
"Invalid enum type, number or integer expected",
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DateTime {
|
||||||
|
/**
|
||||||
|
Creates a new `DateTime` struct representing the current moment in time.
|
||||||
|
|
||||||
|
See [`chrono::DateTime::now`] for additional details.
|
||||||
|
*/
|
||||||
|
pub fn now() -> Self {
|
||||||
|
Self { inner: Utc::now() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Creates a new `DateTime` struct from the given `unix_timestamp`,
|
||||||
|
which is a float of seconds passed since the UNIX epoch.
|
||||||
|
|
||||||
|
This is somewhat unconventional, but fits our Luau interface and dynamic types quite well.
|
||||||
|
To use this method the same way you would use a more traditional `from_unix_timestamp`
|
||||||
|
that takes a `u64` of seconds or similar type, casting the value is sufficient:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
DateTime::from_unix_timestamp_float(123456789u64 as f64)
|
||||||
|
```
|
||||||
|
|
||||||
|
See [`chrono::DateTime::from_timestamp`] for additional details.
|
||||||
|
*/
|
||||||
|
pub fn from_unix_timestamp_float(unix_timestamp: f64) -> DateTimeResult<Self> {
|
||||||
|
let whole = unix_timestamp.trunc() as i64;
|
||||||
|
let fract = unix_timestamp.fract();
|
||||||
|
let nanos = (fract * 1_000_000_000f64)
|
||||||
|
.round()
|
||||||
|
.clamp(u32::MIN as f64, u32::MAX as f64) as u32;
|
||||||
|
let inner = ChronoDateTime::<Utc>::from_timestamp(whole, nanos)
|
||||||
|
.ok_or(DateTimeError::OutOfRangeUnspecified)?;
|
||||||
|
Ok(Self { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Transforms individual date & time values into a new
|
||||||
|
`DateTime` struct, using the universal (UTC) time zone.
|
||||||
|
|
||||||
|
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
|
||||||
|
for additional details and cases where this constructor may return an error.
|
||||||
|
*/
|
||||||
|
pub fn from_universal_time(values: &DateTimeValues) -> DateTimeResult<Self> {
|
||||||
|
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
|
||||||
|
.ok_or(DateTimeError::InvalidDate)?;
|
||||||
|
|
||||||
|
let time = NaiveTime::from_hms_milli_opt(
|
||||||
|
values.hour,
|
||||||
|
values.minute,
|
||||||
|
values.second,
|
||||||
|
values.millisecond,
|
||||||
|
)
|
||||||
|
.ok_or(DateTimeError::InvalidTime)?;
|
||||||
|
|
||||||
|
let inner = Utc.from_utc_datetime(&NaiveDateTime::new(date, time));
|
||||||
|
|
||||||
|
Ok(Self { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Transforms individual date & time values into a new
|
||||||
|
`DateTime` struct, using the current local time zone.
|
||||||
|
|
||||||
|
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
|
||||||
|
for additional details and cases where this constructor may return an error.
|
||||||
|
*/
|
||||||
|
pub fn from_local_time(values: &DateTimeValues) -> DateTimeResult<Self> {
|
||||||
|
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
|
||||||
|
.ok_or(DateTimeError::InvalidDate)?;
|
||||||
|
|
||||||
|
let time = NaiveTime::from_hms_milli_opt(
|
||||||
|
values.hour,
|
||||||
|
values.minute,
|
||||||
|
values.second,
|
||||||
|
values.millisecond,
|
||||||
|
)
|
||||||
|
.ok_or(DateTimeError::InvalidTime)?;
|
||||||
|
|
||||||
|
let inner = Local
|
||||||
|
.from_local_datetime(&NaiveDateTime::new(date, time))
|
||||||
|
.single()
|
||||||
|
.ok_or(DateTimeError::Ambiguous)?
|
||||||
|
.with_timezone(&Utc);
|
||||||
|
|
||||||
|
Ok(Self { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Formats the `DateTime` using the universal (UTC) time
|
||||||
|
zone, the given format string, and the given locale.
|
||||||
|
|
||||||
|
`format` and `locale` default to `"%Y-%m-%d %H:%M:%S"` and `"en"` respectively.
|
||||||
|
|
||||||
|
See [`chrono_lc::DateTime::formatl`] for additional details.
|
||||||
|
*/
|
||||||
|
pub fn format_string_local(&self, format: Option<&str>, locale: Option<&str>) -> String {
|
||||||
|
self.inner
|
||||||
|
.with_timezone(&Local)
|
||||||
|
.formatl(
|
||||||
|
format.unwrap_or(DEFAULT_FORMAT),
|
||||||
|
locale.unwrap_or(DEFAULT_LOCALE),
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Formats the `DateTime` using the universal (UTC) time
|
||||||
|
zone, the given format string, and the given locale.
|
||||||
|
|
||||||
|
`format` and `locale` default to `"%Y-%m-%d %H:%M:%S"` and `"en"` respectively.
|
||||||
|
|
||||||
|
See [`chrono_lc::DateTime::formatl`] for additional details.
|
||||||
|
*/
|
||||||
|
pub fn format_string_universal(&self, format: Option<&str>, locale: Option<&str>) -> String {
|
||||||
|
self.inner
|
||||||
|
.with_timezone(&Utc)
|
||||||
|
.formatl(
|
||||||
|
format.unwrap_or(DEFAULT_FORMAT),
|
||||||
|
locale.unwrap_or(DEFAULT_LOCALE),
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Parses a time string in the ISO 8601 format, such as
|
||||||
|
`1996-12-19T16:39:57-08:00`, into a new `DateTime` struct.
|
||||||
|
|
||||||
|
See [`chrono::DateTime::parse_from_rfc3339`] for additional details.
|
||||||
|
*/
|
||||||
|
pub fn from_iso_date(iso_date: impl AsRef<str>) -> DateTimeResult<Self> {
|
||||||
|
let inner = ChronoDateTime::parse_from_rfc3339(iso_date.as_ref())?.with_timezone(&Utc);
|
||||||
|
Ok(Self { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extracts individual date & time values from this
|
||||||
|
`DateTime`, using the current local time zone.
|
||||||
|
*/
|
||||||
|
pub fn to_local_time(self) -> DateTimeValues {
|
||||||
|
DateTimeValues::from(self.inner.with_timezone(&Local))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extracts individual date & time values from this
|
||||||
|
`DateTime`, using the universal (UTC) time zone.
|
||||||
|
*/
|
||||||
|
pub fn to_universal_time(self) -> DateTimeValues {
|
||||||
|
DateTimeValues::from(self.inner.with_timezone(&Utc))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Formats a time string in the ISO 8601 format, such as `1996-12-19T16:39:57-08:00`.
|
||||||
|
|
||||||
|
See [`chrono::DateTime::to_rfc3339`] for additional details.
|
||||||
|
*/
|
||||||
|
pub fn to_iso_date(self) -> String {
|
||||||
|
self.inner.to_rfc3339()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for DateTime {
|
impl LuaUserData for DateTime {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
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("unixTimestamp", |_, this| Ok(this.inner.timestamp()));
|
||||||
fields.add_field_method_get("unixTimestampMillis", |_, this| {
|
fields.add_field_method_get("unixTimestampMillis", |_, this| {
|
||||||
Ok(this.unix_timestamp_millis)
|
Ok(this.inner.timestamp_millis())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_method("toIsoDate", |_, this, ()| {
|
// Metamethods to compare DateTime as instants in time
|
||||||
Ok(this
|
methods.add_meta_method(
|
||||||
.to_iso_date()
|
LuaMetaMethod::Eq,
|
||||||
.map_err(|()| LuaError::external("failed to parse DateTime object, invalid")))
|
|_, this: &Self, other: LuaUserDataRef<Self>| Ok(this.eq(&other)),
|
||||||
});
|
);
|
||||||
|
methods.add_meta_method(
|
||||||
methods.add_method(
|
LuaMetaMethod::Lt,
|
||||||
"formatTime",
|
|_, this: &Self, other: LuaUserDataRef<Self>| {
|
||||||
|_, this, (timezone, fmt_str, locale): (LuaValue, String, String)| {
|
Ok(matches!(this.cmp(&other), Ordering::Less))
|
||||||
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_meta_method(
|
||||||
methods.add_method("toUniversalTime", |_, this: &DateTime, ()| {
|
LuaMetaMethod::Le,
|
||||||
Ok(this.to_universal_time().or(Err(LuaError::external(
|
|_, this: &Self, other: LuaUserDataRef<Self>| {
|
||||||
"invalid DateTime self argument provided to toUniversalTime",
|
Ok(matches!(this.cmp(&other), Ordering::Less | Ordering::Equal))
|
||||||
))))
|
|
||||||
});
|
|
||||||
|
|
||||||
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")),
|
);
|
||||||
}
|
// Normal methods
|
||||||
|
methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_iso_date()));
|
||||||
|
methods.add_method(
|
||||||
|
"formatUniversalTime",
|
||||||
|
|_, this, (format, locale): (Option<String>, Option<String>)| {
|
||||||
|
Ok(this.format_string_universal(format.as_deref(), locale.as_deref()))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
methods.add_method(
|
||||||
|
"formatLocalTime",
|
||||||
|
|_, this, (format, locale): (Option<String>, Option<String>)| {
|
||||||
|
Ok(this.format_string_local(format.as_deref(), locale.as_deref()))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
methods.add_method("toUniversalTime", |_, this: &Self, ()| {
|
||||||
|
Ok(this.to_universal_time())
|
||||||
|
});
|
||||||
|
methods.add_method("toLocalTime", |_, this: &Self, ()| Ok(this.to_local_time()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
170
src/lune/builtins/datetime/values.rs
Normal file
170
src/lune/builtins/datetime/values.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use chrono::prelude::*;
|
||||||
|
|
||||||
|
use crate::lune::util::TableBuilder;
|
||||||
|
|
||||||
|
use super::error::{DateTimeError, DateTimeResult};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct DateTimeValues {
|
||||||
|
pub year: i32,
|
||||||
|
pub month: u32,
|
||||||
|
pub day: u32,
|
||||||
|
pub hour: u32,
|
||||||
|
pub minute: u32,
|
||||||
|
pub second: u32,
|
||||||
|
pub millisecond: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DateTimeValues {
|
||||||
|
/**
|
||||||
|
Verifies that all of the date & time values are within allowed ranges:
|
||||||
|
|
||||||
|
| Name | Range |
|
||||||
|
|---------------|----------------|
|
||||||
|
| `year` | `1400 -> 9999` |
|
||||||
|
| `month` | `1 -> 12` |
|
||||||
|
| `day` | `1 -> 31` |
|
||||||
|
| `hour` | `0 -> 23` |
|
||||||
|
| `minute` | `0 -> 59` |
|
||||||
|
| `second` | `0 -> 60` |
|
||||||
|
| `millisecond` | `0 -> 999` |
|
||||||
|
*/
|
||||||
|
pub fn verify(self) -> DateTimeResult<Self> {
|
||||||
|
verify_in_range("year", self.year, 1400, 9999)?;
|
||||||
|
verify_in_range("month", self.month, 1, 12)?;
|
||||||
|
verify_in_range("day", self.day, 1, 31)?;
|
||||||
|
verify_in_range("hour", self.hour, 0, 23)?;
|
||||||
|
verify_in_range("minute", self.minute, 0, 59)?;
|
||||||
|
verify_in_range("second", self.second, 0, 60)?;
|
||||||
|
verify_in_range("millisecond", self.millisecond, 0, 999)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_in_range<T>(name: &'static str, value: T, min: T, max: T) -> DateTimeResult<T>
|
||||||
|
where
|
||||||
|
T: PartialOrd + std::fmt::Display,
|
||||||
|
{
|
||||||
|
assert!(max > min);
|
||||||
|
if value < min || value > max {
|
||||||
|
Err(DateTimeError::OutOfRange {
|
||||||
|
name,
|
||||||
|
min: min.to_string(),
|
||||||
|
max: max.to_string(),
|
||||||
|
value: value.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Conversion methods between DateTimeValues and plain lua tables
|
||||||
|
|
||||||
|
Note that the IntoLua implementation here uses a read-only table,
|
||||||
|
since we generally want to convert into lua when we know we have
|
||||||
|
a fixed point in time, and we guarantee that it doesn't change
|
||||||
|
*/
|
||||||
|
|
||||||
|
impl FromLua<'_> for DateTimeValues {
|
||||||
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
|
if !value.is_table() {
|
||||||
|
return Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "DateTimeValues",
|
||||||
|
message: Some("value must be a table".to_string()),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = value.as_table().unwrap();
|
||||||
|
let values = Self {
|
||||||
|
year: value.get("year")?,
|
||||||
|
month: value.get("month")?,
|
||||||
|
day: value.get("day")?,
|
||||||
|
hour: value.get("hour")?,
|
||||||
|
minute: value.get("minute")?,
|
||||||
|
second: value.get("second")?,
|
||||||
|
millisecond: value.get("millisecond").unwrap_or(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
match values.verify() {
|
||||||
|
Ok(dt) => Ok(dt),
|
||||||
|
Err(e) => Err(LuaError::FromLuaConversionError {
|
||||||
|
from: "table",
|
||||||
|
to: "DateTimeValues",
|
||||||
|
message: Some(e.to_string()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for DateTimeValues {
|
||||||
|
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
|
let tab = TableBuilder::new(lua)?
|
||||||
|
.with_value("year", self.year)?
|
||||||
|
.with_values(vec![
|
||||||
|
("month", self.month),
|
||||||
|
("day", self.day),
|
||||||
|
("hour", self.hour),
|
||||||
|
("minute", self.minute),
|
||||||
|
("second", self.second),
|
||||||
|
("millisecond", self.millisecond),
|
||||||
|
])?
|
||||||
|
.build_readonly()?;
|
||||||
|
Ok(LuaValue::Table(tab))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Conversion methods between chrono's timezone-aware DateTime to
|
||||||
|
and from our non-timezone-aware DateTimeValues values struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
impl<T: TimeZone> From<DateTime<T>> for DateTimeValues {
|
||||||
|
fn from(value: DateTime<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
year: value.year(),
|
||||||
|
month: value.month(),
|
||||||
|
day: value.day(),
|
||||||
|
hour: value.hour(),
|
||||||
|
minute: value.minute(),
|
||||||
|
second: value.second(),
|
||||||
|
millisecond: value.timestamp_subsec_millis(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DateTimeValues> for DateTime<Utc> {
|
||||||
|
type Error = DateTimeError;
|
||||||
|
fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {
|
||||||
|
Utc.with_ymd_and_hms(
|
||||||
|
value.year,
|
||||||
|
value.month,
|
||||||
|
value.day,
|
||||||
|
value.hour,
|
||||||
|
value.minute,
|
||||||
|
value.second,
|
||||||
|
)
|
||||||
|
.single()
|
||||||
|
.ok_or(DateTimeError::Ambiguous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DateTimeValues> for DateTime<Local> {
|
||||||
|
type Error = DateTimeError;
|
||||||
|
fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {
|
||||||
|
Local
|
||||||
|
.with_ymd_and_hms(
|
||||||
|
value.year,
|
||||||
|
value.month,
|
||||||
|
value.day,
|
||||||
|
value.hour,
|
||||||
|
value.minute,
|
||||||
|
value.second,
|
||||||
|
)
|
||||||
|
.single()
|
||||||
|
.ok_or(DateTimeError::Ambiguous)
|
||||||
|
}
|
||||||
|
}
|
21
src/tests.rs
21
src/tests.rs
|
@ -38,6 +38,17 @@ macro_rules! create_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
create_tests! {
|
create_tests! {
|
||||||
|
datetime_format_local_time: "datetime/formatLocalTime",
|
||||||
|
datetime_format_universal_time: "datetime/formatUniversalTime",
|
||||||
|
datetime_from_iso_date: "datetime/fromIsoDate",
|
||||||
|
datetime_from_local_time: "datetime/fromLocalTime",
|
||||||
|
datetime_from_universal_time: "datetime/fromUniversalTime",
|
||||||
|
datetime_from_unix_timestamp: "datetime/fromUnixTimestamp",
|
||||||
|
datetime_now: "datetime/now",
|
||||||
|
datetime_to_iso_date: "datetime/toIsoDate",
|
||||||
|
datetime_to_local_time: "datetime/toLocalTime",
|
||||||
|
datetime_to_universal_time: "datetime/toUniversalTime",
|
||||||
|
|
||||||
fs_files: "fs/files",
|
fs_files: "fs/files",
|
||||||
fs_copy: "fs/copy",
|
fs_copy: "fs/copy",
|
||||||
fs_dirs: "fs/dirs",
|
fs_dirs: "fs/dirs",
|
||||||
|
@ -106,16 +117,6 @@ create_tests! {
|
||||||
task_delay: "task/delay",
|
task_delay: "task/delay",
|
||||||
task_spawn: "task/spawn",
|
task_spawn: "task/spawn",
|
||||||
task_wait: "task/wait",
|
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")]
|
#[cfg(feature = "roblox")]
|
||||||
|
|
53
tests/datetime/formatLocalTime.luau
Normal file
53
tests/datetime/formatLocalTime.luau
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
local DateTime = require("@lune/datetime")
|
||||||
|
local process = require("@lune/process")
|
||||||
|
|
||||||
|
local expectedTimeString = os.date("%Y-%m-%dT%H:%M:%S", 1694078954)
|
||||||
|
|
||||||
|
assert(
|
||||||
|
DateTime.fromUnixTimestamp(1694078954):formatLocalTime("%Y-%m-%dT%H:%M:%S", "en")
|
||||||
|
== expectedTimeString,
|
||||||
|
"invalid ISO 8601 formatting for DateTime.formatLocalTime()"
|
||||||
|
)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
The rest of 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
|
||||||
|
|
||||||
|
To run tests related to locales, one must
|
||||||
|
explicitly provide the `--test-locales` flag
|
||||||
|
]]
|
||||||
|
local runLocaleTests = false
|
||||||
|
|
||||||
|
for _, arg in process.args do
|
||||||
|
if arg == "--test-locales" then
|
||||||
|
runLocaleTests = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not runLocaleTests then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local dateCmd = process.spawn("bash", { "-c", "date +\"%A, %d %B %Y\" --date='@1693068988'" }, {
|
||||||
|
env = {
|
||||||
|
LC_ALL = "fr_FR.UTF-8 ",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert(dateCmd.ok, "Failed to execute date command")
|
||||||
|
|
||||||
|
local expectedLocalizedString = string.gsub(dateCmd.stdout, "\n", "")
|
||||||
|
|
||||||
|
assert(
|
||||||
|
DateTime.fromUnixTimestamp(1693068988):formatLocalTime("%A, %d %B %Y", "fr")
|
||||||
|
== expectedLocalizedString,
|
||||||
|
`expected format specifier '%A, %d %B %Y' to return '{expectedLocalizedString}' for locale 'fr' (local)`
|
||||||
|
)
|
||||||
|
|
||||||
|
assert(
|
||||||
|
DateTime.fromUnixTimestamp(1693068988):formatUniversalTime("%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)"
|
||||||
|
)
|
|
@ -1,70 +0,0 @@
|
||||||
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)"
|
|
||||||
)
|
|
15
tests/datetime/formatUniversalTime.luau
Normal file
15
tests/datetime/formatUniversalTime.luau
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
local DateTime = require("@lune/datetime")
|
||||||
|
|
||||||
|
assert(
|
||||||
|
DateTime.fromUnixTimestamp(1693068988):formatUniversalTime("%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):formatLocalTime("%Y-%m-%dT%H:%M:%SZ", "en")
|
||||||
|
== expectedTimeString,
|
||||||
|
"invalid ISO 8601 formatting for DateTime.formatTime()"
|
||||||
|
)
|
|
@ -1,4 +1,4 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
local DateTime = require("@lune/datetime")
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
DateTime.fromIsoDate("2023-08-26T16:56:28Z") ~= nil,
|
DateTime.fromIsoDate("2023-08-26T16:56:28Z") ~= nil,
|
||||||
|
|
|
@ -1,45 +1,41 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
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)
|
local timeValues1 = os.date("*t", 1693049188)
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
DateTime.fromLocalTime({
|
DateTime.fromLocalTime({
|
||||||
["year"] = timeValues1.year,
|
year = timeValues1.year,
|
||||||
["month"] = timeValues1.month,
|
month = timeValues1.month,
|
||||||
["day"] = timeValues1.day,
|
day = timeValues1.day,
|
||||||
["hour"] = timeValues1.hour,
|
hour = timeValues1.hour,
|
||||||
["minute"] = timeValues1.min,
|
minute = timeValues1.min,
|
||||||
["second"] = timeValues1.sec,
|
second = timeValues1.sec,
|
||||||
["millisecond"] = 0,
|
millisecond = 0,
|
||||||
})["unixTimestamp"] == 1693049188,
|
}).unixTimestamp == 1693049188,
|
||||||
"expected DateTime.fromLocalTime() with DateTimeValues arg to return 1693049188s"
|
"expected DateTime.fromLocalTime() with DateTimeValues arg to return 1693049188s"
|
||||||
)
|
)
|
||||||
|
|
||||||
print(DateTime.fromLocalTime({
|
print(DateTime.fromLocalTime({
|
||||||
["year"] = 2023,
|
year = 2023,
|
||||||
["month"] = "aug",
|
month = 8,
|
||||||
["day"] = 26,
|
day = 26,
|
||||||
["hour"] = 16,
|
hour = 16,
|
||||||
["minute"] = 56,
|
minute = 56,
|
||||||
["second"] = 28,
|
second = 28,
|
||||||
["millisecond"] = 892,
|
millisecond = 892,
|
||||||
})["unixTimestamp"])
|
}).unixTimestamp)
|
||||||
|
|
||||||
local timeValues2 = os.date("*t", 1693049188.892)
|
local timeValues2 = os.date("*t", 1693049188.892)
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
DateTime.fromLocalTime({
|
DateTime.fromLocalTime({
|
||||||
["year"] = timeValues2.year,
|
year = timeValues2.year,
|
||||||
["month"] = timeValues2.month,
|
month = timeValues2.month,
|
||||||
["day"] = timeValues2.day,
|
day = timeValues2.day,
|
||||||
["hour"] = timeValues2.hour,
|
hour = timeValues2.hour,
|
||||||
["minute"] = timeValues2.min,
|
minute = timeValues2.min,
|
||||||
["second"] = timeValues2.sec,
|
second = timeValues2.sec,
|
||||||
["millisecond"] = 892,
|
millisecond = 892,
|
||||||
})["unixTimestampMillis"] == 1693049188892,
|
}).unixTimestampMillis == 1693049188892,
|
||||||
"expected DateTime.fromLocalTime() with DateTimeValues arg with millis to return 1693049188892ms"
|
"expected DateTime.fromLocalTime() with DateTimeValues arg with millis to return 1693049188892ms"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,32 +1,27 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
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(
|
assert(
|
||||||
DateTime.fromUniversalTime({
|
DateTime.fromUniversalTime({
|
||||||
["year"] = 2023,
|
year = 2023,
|
||||||
["month"] = "aug",
|
month = 8,
|
||||||
["day"] = 26,
|
day = 26,
|
||||||
["hour"] = 16,
|
hour = 16,
|
||||||
["minute"] = 56,
|
minute = 56,
|
||||||
["second"] = 28,
|
second = 28,
|
||||||
["millisecond"] = 0,
|
millisecond = 0,
|
||||||
})["unixTimestamp"] == 1693068988,
|
}).unixTimestamp == 1693068988,
|
||||||
"expected DateTime.fromUniversalTime() with DateTimeValues arg to return 1693068988s"
|
"expected DateTime.fromUniversalTime() with DateTimeValues arg to return 1693068988s"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
DateTime.fromUniversalTime({
|
DateTime.fromUniversalTime({
|
||||||
["year"] = 2023,
|
year = 2023,
|
||||||
["month"] = "aug",
|
month = 8,
|
||||||
["day"] = 26,
|
day = 26,
|
||||||
["hour"] = 16,
|
hour = 16,
|
||||||
["minute"] = 56,
|
minute = 56,
|
||||||
["second"] = 28,
|
second = 28,
|
||||||
["millisecond"] = 892,
|
millisecond = 892,
|
||||||
})["unixTimestampMillis"] == 1693068988892,
|
}).unixTimestampMillis == 1693068988892,
|
||||||
"expected DateTime.fromUniversalTime() with DateTimeValues arg with millis to return 1693068988892ms"
|
"expected DateTime.fromUniversalTime() with DateTimeValues arg with millis to return 1693068988892ms"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
local DateTime = require("@lune/datetime")
|
||||||
|
|
||||||
-- Bug in rust side implementation for fromUnixTimestamp, calculation for conversion there is wonky,
|
-- 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
|
-- a difference of few millis causes differences as whole seconds for some reason
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
DateTime.fromUnixTimestamp(0000.892)["unixTimestampMillis"] == (0 * 1000) + 892,
|
DateTime.fromUnixTimestamp(0000.892).unixTimestampMillis == (0 * 1000) + 892,
|
||||||
"expected DateTime.fromUnixTimestamp() with millis float to return correct millis timestamp"
|
"expected DateTime.fromUnixTimestamp() with millis float to return correct millis timestamp"
|
||||||
)
|
)
|
||||||
|
|
||||||
-- We subtract one due to the floating point accuracy... Need to fix later
|
-- We subtract one due to the floating point accuracy... Need to fix later
|
||||||
assert(
|
assert(
|
||||||
DateTime.fromUnixTimestamp(1693114921.632)["unixTimestampMillis"]
|
DateTime.fromUnixTimestamp(1693114921.632).unixTimestampMillis
|
||||||
== ((1693114921 * 1000) + 632) - 1,
|
== ((1693114921 * 1000) + 632) - 1,
|
||||||
"expected DateTime.fromUnixTimestamp() with millis and seconds float to return correct millis timestamp"
|
"expected DateTime.fromUnixTimestamp() with millis and seconds float to return correct millis timestamp"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
local DateTime = require("@lune/datetime")
|
||||||
|
|
||||||
local TYPE = "DateTime"
|
local TYPE = "DateTime"
|
||||||
|
|
||||||
assert(
|
local now = DateTime.now()
|
||||||
typeof(DateTime.now()) == TYPE,
|
|
||||||
`dateTime.now() should return a {TYPE}, returned {tostring(typeof(DateTime.now()))}`
|
assert(typeof(now) == TYPE, `dateTime.now() should return a {TYPE}, returned {typeof(now)}`)
|
||||||
)
|
|
||||||
|
|
|
@ -1,9 +1,72 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
local DateTime = require("@lune/datetime")
|
||||||
|
|
||||||
|
local now = DateTime.now()
|
||||||
|
local nowIso = now:toIsoDate()
|
||||||
|
|
||||||
|
-- Make sure we have separator characters, T to separate date & time, + or Z to separate timezone
|
||||||
|
|
||||||
|
local dateTimeSplitIdx = string.find(nowIso, "T")
|
||||||
|
local timezoneSplitIdx = string.find(nowIso, "+")
|
||||||
|
local timezoneZeroedIdx = string.find(nowIso, "Z")
|
||||||
|
|
||||||
|
assert(dateTimeSplitIdx ~= nil, "Missing date & time separator 'T' in iso 8601 string")
|
||||||
assert(
|
assert(
|
||||||
string.match(
|
timezoneSplitIdx ~= nil or timezoneZeroedIdx ~= nil,
|
||||||
DateTime.now():toIsoDate(),
|
"Missing timezone separator '+' or 'Z' in iso date string"
|
||||||
"(%d%d%d%d)-?(%d?%d?)-?(%d?%d?)T(%d?%d?):(%d?%d?):(%d?%d?)Z$"
|
|
||||||
),
|
|
||||||
"invalid ISO 8601 date returned by dateTime.toIsoDate()"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- Split date (before T) by dashes, split time (after T, before + or Z)
|
||||||
|
-- by colons, we should then get 3 substrings for each of date & time
|
||||||
|
|
||||||
|
local dateParts = string.split(string.sub(nowIso, 1, dateTimeSplitIdx - 1), "-")
|
||||||
|
local timeParts = string.split(
|
||||||
|
string.sub(
|
||||||
|
nowIso,
|
||||||
|
dateTimeSplitIdx + 1,
|
||||||
|
((timezoneSplitIdx or timezoneZeroedIdx) :: number) - 1
|
||||||
|
),
|
||||||
|
":"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert(#dateParts == 3, "Date partial of iso 8601 should consist of 3 substrings, separated by '-'")
|
||||||
|
assert(#timeParts == 3, "Time partial of iso 8601 should consist of 3 substrings, separated by ':'")
|
||||||
|
|
||||||
|
-- date should be in format YYYY:MM::DD
|
||||||
|
-- time should be in format HH:MM:SS with optional fraction for seconds
|
||||||
|
|
||||||
|
assert(string.match(dateParts[1], "^%d%d%d%d$"), "Date partial should have 4 digits for year")
|
||||||
|
assert(string.match(dateParts[2], "^%d%d$"), "Date partial should have 2 digits for month")
|
||||||
|
assert(string.match(dateParts[3], "^%d%d$"), "Date partial should have 2 digits for day")
|
||||||
|
|
||||||
|
assert(string.match(timeParts[1], "^%d%d$"), "Time partial should have 2 digits for hour")
|
||||||
|
assert(string.match(timeParts[2], "^%d%d$"), "Time partial should have 2 digits for minute")
|
||||||
|
assert(
|
||||||
|
string.match(timeParts[3], "^%d%d%.?%d*$") and tonumber(timeParts[3]) ~= nil,
|
||||||
|
"Time partial should have minimum 2 digits with optional fraction for seconds"
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Timezone specifier is either 'Z' for zeroed out timezone (no offset),
|
||||||
|
-- in which case we don't need to check anything other than it being the
|
||||||
|
-- last character, or it can be a timezone offset in the format HH::MM
|
||||||
|
|
||||||
|
if timezoneZeroedIdx ~= nil then
|
||||||
|
-- No timezone offset
|
||||||
|
assert(
|
||||||
|
timezoneZeroedIdx == #nowIso,
|
||||||
|
"Timezone specifier 'Z' must be at the last character in iso 8601 string"
|
||||||
|
)
|
||||||
|
elseif timezoneSplitIdx ~= nil then
|
||||||
|
-- Timezone offset
|
||||||
|
local timezoneParts = string.split(string.sub(nowIso, timezoneSplitIdx + 1), ":")
|
||||||
|
assert(#timezoneParts == 2, "Timezone partial should consist of 2 substings, separated by ':'")
|
||||||
|
assert(
|
||||||
|
string.match(timezoneParts[1], "^%d%d$"),
|
||||||
|
"Timezone partial should have 2 digits for hour"
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
string.match(timezoneParts[2], "^%d%d$"),
|
||||||
|
"Timezone partial should have 2 digits for minute"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
error("unreachable")
|
||||||
|
end
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
local DateTime = require("@lune/datetime")
|
||||||
|
|
||||||
local dateTime = (DateTime.fromIsoDate("2023-08-27T05:54:19Z") :: DateTime.DateTime):toLocalTime()
|
local values = DateTime.fromIsoDate("2023-08-27T05:54:19Z"):toLocalTime()
|
||||||
|
|
||||||
local expectedDateTimeValues = os.date("*t", 1693115659)
|
local expectedDateTimeValues = os.date("*t", 1693115659)
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
dateTime.year == expectedDateTimeValues.year,
|
values.year == expectedDateTimeValues.year,
|
||||||
`expected {dateTime.year} == {expectedDateTimeValues.year}`
|
`expected {values.year} == {expectedDateTimeValues.year}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.month == expectedDateTimeValues.month,
|
values.month == expectedDateTimeValues.month,
|
||||||
`expected {dateTime.month} == {expectedDateTimeValues.month}`
|
`expected {values.month} == {expectedDateTimeValues.month}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.day == expectedDateTimeValues.day,
|
values.day == expectedDateTimeValues.day,
|
||||||
`expected {dateTime.day} == {expectedDateTimeValues.day}`
|
`expected {values.day} == {expectedDateTimeValues.day}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.hour == expectedDateTimeValues.hour,
|
values.hour == expectedDateTimeValues.hour,
|
||||||
`expected {dateTime.hour} == {expectedDateTimeValues.hour}`
|
`expected {values.hour} == {expectedDateTimeValues.hour}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.minute == expectedDateTimeValues.min,
|
values.minute == expectedDateTimeValues.min,
|
||||||
`expected {dateTime.minute} == {expectedDateTimeValues.min}`
|
`expected {values.minute} == {expectedDateTimeValues.min}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.second == expectedDateTimeValues.sec,
|
values.second == expectedDateTimeValues.sec,
|
||||||
`expected {dateTime.second} == {expectedDateTimeValues.sec}`
|
`expected {values.second} == {expectedDateTimeValues.sec}`
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
local DateTime = require("@lune/DateTime")
|
local DateTime = require("@lune/DateTime")
|
||||||
|
|
||||||
local dateTime = (DateTime.fromIsoDate("2023-08-27T05:54:19Z") :: DateTime.DateTime):toLocalTime()
|
local values = DateTime.fromIsoDate("2023-08-27T05:54:19Z"):toLocalTime()
|
||||||
|
|
||||||
local expectedDateTimeValues = os.date("*t", 1693115659)
|
local expectedDateTimeValues = os.date("*t", 1693115659)
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
dateTime.year == expectedDateTimeValues.year,
|
values.year == expectedDateTimeValues.year,
|
||||||
`expected {dateTime.year} == {expectedDateTimeValues.year}`
|
`expected {values.year} == {expectedDateTimeValues.year}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.month == expectedDateTimeValues.month,
|
values.month == expectedDateTimeValues.month,
|
||||||
`expected {dateTime.month} == {expectedDateTimeValues.month}`
|
`expected {values.month} == {expectedDateTimeValues.month}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.day == expectedDateTimeValues.day,
|
values.day == expectedDateTimeValues.day,
|
||||||
`expected {dateTime.day} == {expectedDateTimeValues.day}`
|
`expected {values.day} == {expectedDateTimeValues.day}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.hour == expectedDateTimeValues.hour,
|
values.hour == expectedDateTimeValues.hour,
|
||||||
`expected {dateTime.hour} == {expectedDateTimeValues.hour}`
|
`expected {values.hour} == {expectedDateTimeValues.hour}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.minute == expectedDateTimeValues.min,
|
values.minute == expectedDateTimeValues.min,
|
||||||
`expected {dateTime.minute} == {expectedDateTimeValues.min}`
|
`expected {values.minute} == {expectedDateTimeValues.min}`
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
dateTime.second == expectedDateTimeValues.sec,
|
values.second == expectedDateTimeValues.sec,
|
||||||
`expected {dateTime.second} == {expectedDateTimeValues.sec}`
|
`expected {values.second} == {expectedDateTimeValues.sec}`
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,67 +1,184 @@
|
||||||
-- TODO: Add more docs
|
|
||||||
|
|
||||||
export type Locale = "en" | "de" | "es" | "fr" | "it" | "ja" | "pl" | "pt-br" | "pt" | "tr"
|
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 = {
|
export type DateTimeValues = {
|
||||||
|
--- Year(s), in the range 1400 -> 9999
|
||||||
year: number,
|
year: number,
|
||||||
month: number | ShortMonth | Month,
|
--- Month(s), in the range 1 -> 12
|
||||||
|
month: number,
|
||||||
|
--- Day(s), in the range 1 -> 31
|
||||||
day: number,
|
day: number,
|
||||||
|
--- Hour(s), in the range 0 -> 23
|
||||||
hour: number,
|
hour: number,
|
||||||
|
--- Minute(s), in the range 0 -> 59
|
||||||
minute: number,
|
minute: number,
|
||||||
|
--- Second(s), in the range 0 -> 60, where 60 is a leap second
|
||||||
second: number,
|
second: number,
|
||||||
millisecond: number,
|
--- Millisecond(s), in the range 0 -> 999
|
||||||
|
millisecond: number?,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local DateTime = {
|
||||||
|
--- Number of seconds passed since the UNIX epoch.
|
||||||
|
unixTimestamp = 0,
|
||||||
|
--- Number of milliseconds passed since the UNIX epoch.
|
||||||
|
unixTimestampMillis = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
|
||||||
|
Formats this `DateTime` as an ISO 8601 date-time string.
|
||||||
|
|
||||||
|
Some examples of ISO 8601 date-time strings:
|
||||||
|
|
||||||
|
- `2020-02-22T18:12:08Z`
|
||||||
|
- `2000-01-31T12:34:56+05:00`
|
||||||
|
- `1970-01-01T00:00:00.055Z`
|
||||||
|
|
||||||
|
@return string -- The ISO 8601 formatted string
|
||||||
|
]=]
|
||||||
|
function DateTime.toIsoDate(self: DateTime): string
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
|
||||||
|
Extracts local time values from this `DateTime`.
|
||||||
|
|
||||||
|
The returned table contains the following values:
|
||||||
|
|
||||||
|
| Key | Type | Range |
|
||||||
|
|---------------|----------|----------------|
|
||||||
|
| `year` | `number` | `1400 -> 9999` |
|
||||||
|
| `month` | `number` | `1 -> 12` |
|
||||||
|
| `day` | `number` | `1 -> 31` |
|
||||||
|
| `hour` | `number` | `0 -> 23` |
|
||||||
|
| `minute` | `number` | `0 -> 59` |
|
||||||
|
| `second` | `number` | `0 -> 60` |
|
||||||
|
| `millisecond` | `number` | `0 -> 999` |
|
||||||
|
|
||||||
|
@return DateTimeValues -- A table of DateTime values
|
||||||
|
]=]
|
||||||
|
function DateTime.toLocalTime(self: DateTime): DateTimeValues
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
|
||||||
|
Extracts UTC (universal) time values from this `DateTime`.
|
||||||
|
|
||||||
|
The returned table contains the following values:
|
||||||
|
|
||||||
|
| Key | Type | Range |
|
||||||
|
|---------------|----------|----------------|
|
||||||
|
| `year` | `number` | `1400 -> 9999` |
|
||||||
|
| `month` | `number` | `1 -> 12` |
|
||||||
|
| `day` | `number` | `1 -> 31` |
|
||||||
|
| `hour` | `number` | `0 -> 23` |
|
||||||
|
| `minute` | `number` | `0 -> 59` |
|
||||||
|
| `second` | `number` | `0 -> 60` |
|
||||||
|
| `millisecond` | `number` | `0 -> 999` |
|
||||||
|
|
||||||
|
@return DateTimeValues -- A table of DateTime values
|
||||||
|
]=]
|
||||||
|
function DateTime.toUniversalTime(self: DateTime): DateTimeValues
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
|
||||||
|
Formats this `DateTime` using the given `formatString` and `locale`, as local time.
|
||||||
|
|
||||||
|
The given `formatString` is parsed using a `strftime`/`strptime`-inspired
|
||||||
|
date and time formatting syntax, allowing tokens such as the following:
|
||||||
|
|
||||||
|
| Token | Example | Description |
|
||||||
|
|-------|----------|---------------|
|
||||||
|
| `%Y` | `1998` | Year number |
|
||||||
|
| `%m` | `04` | Month number |
|
||||||
|
| `%d` | `29` | Day number |
|
||||||
|
| `%A` | `Monday` | Weekday name |
|
||||||
|
| `%M` | `59` | Minute number |
|
||||||
|
| `%S` | `10` | Second number |
|
||||||
|
|
||||||
|
For a full reference of all available tokens, see the
|
||||||
|
[chrono documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
|
||||||
|
|
||||||
|
If not provided, `formatString` and `locale` will default
|
||||||
|
to `"%Y-%m-%d %H:%M:%S"` and `"en"` (english) respectively.
|
||||||
|
|
||||||
|
@param formatString -- A string containing formatting tokens
|
||||||
|
@param locale -- The locale the time should be formatted in
|
||||||
|
@return string -- The formatting string
|
||||||
|
]=]
|
||||||
|
function DateTime.formatLocalTime(self: DateTime, formatString: string?, locale: Locale?): string
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
|
||||||
|
Formats this `DateTime` using the given `formatString` and `locale`, as UTC (universal) time.
|
||||||
|
|
||||||
|
The given `formatString` is parsed using a `strftime`/`strptime`-inspired
|
||||||
|
date and time formatting syntax, allowing tokens such as the following:
|
||||||
|
|
||||||
|
| Token | Example | Description |
|
||||||
|
|-------|----------|---------------|
|
||||||
|
| `%Y` | `1998` | Year number |
|
||||||
|
| `%m` | `04` | Month number |
|
||||||
|
| `%d` | `29` | Day number |
|
||||||
|
| `%A` | `Monday` | Weekday name |
|
||||||
|
| `%M` | `59` | Minute number |
|
||||||
|
| `%S` | `10` | Second number |
|
||||||
|
|
||||||
|
For a full reference of all available tokens, see the
|
||||||
|
[chrono documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
|
||||||
|
|
||||||
|
If not provided, `formatString` and `locale` will default
|
||||||
|
to `"%Y-%m-%d %H:%M:%S"` and `"en"` (english) respectively.
|
||||||
|
|
||||||
|
@param formatString -- A string containing formatting tokens
|
||||||
|
@param locale -- The locale the time should be formatted in
|
||||||
|
@return string -- The formatting string
|
||||||
|
]=]
|
||||||
|
function DateTime.formatUniversalTime(
|
||||||
|
self: DateTime,
|
||||||
|
formatString: string?,
|
||||||
|
locale: Locale?
|
||||||
|
): string
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
export type DateTime = typeof(DateTime)
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@class DateTime
|
@class DateTime
|
||||||
|
|
||||||
Built-in library for date & time manipulation
|
Built-in library for date & time
|
||||||
|
|
||||||
### Example usage
|
### Example usage
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local DateTime = require("@lune/DateTime")
|
local DateTime = require("@lune/datetime")
|
||||||
|
|
||||||
-- Returns the current moment in time as a ISO 8601 string
|
-- Creates a DateTime for the current exact moment in time
|
||||||
DateTime.now():toIsoDate()
|
local now = DateTime.now()
|
||||||
|
|
||||||
-- Returns the current moment in time as per the format template in French
|
-- Formats the current moment in time as an ISO 8601 string
|
||||||
DateTime.now():formatTime("utc", "%A, %d %B %Y", "fr")
|
print(now:toIsoDate())
|
||||||
|
|
||||||
|
-- Formats the current moment in time, using the local
|
||||||
|
-- time, the Frech locale, and the specified time string
|
||||||
|
print(now:formatLocalTime("%A, %d %B %Y", "fr"))
|
||||||
|
|
||||||
-- Returns a specific moment in time as a DateTime instance
|
-- Returns a specific moment in time as a DateTime instance
|
||||||
DateTime.fromLocalTime({
|
local someDayInTheFuture = DateTime.fromLocalTime({
|
||||||
year = 2023,
|
year = 3033,
|
||||||
month = "aug",
|
month = 8,
|
||||||
day = 26,
|
day = 26,
|
||||||
hour = 16,
|
hour = 16,
|
||||||
minute = 56,
|
minute = 56,
|
||||||
|
@ -69,160 +186,139 @@ export type DateTimeValues = {
|
||||||
millisecond = 892,
|
millisecond = 892,
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Returns the current local time as a DateTime instance
|
-- Extracts the current local date & time as separate values (same values as above table)
|
||||||
DateTime.fromLocalTime()
|
print(now:toLocalTime())
|
||||||
|
|
||||||
-- Returns a DateTime instance from a given float, where the whole denotes the
|
-- Returns a DateTime instance from a given float, where the whole
|
||||||
-- seconds and the fraction denotes the milliseconds
|
-- denotes the seconds and the fraction denotes the milliseconds
|
||||||
|
-- Note that the fraction for millis here is completely optional
|
||||||
DateTime.fromUnixTimestamp(871978212313.321)
|
DateTime.fromUnixTimestamp(871978212313.321)
|
||||||
|
|
||||||
-- Returns the current time in terms of UTC
|
-- Extracts the current universal (UTC) date & time as separate values
|
||||||
DateTime.now():toUniversalTime()
|
print(now:toUniversalTime())
|
||||||
```
|
```
|
||||||
]=]
|
]=]
|
||||||
local dateTime = {
|
local dateTime = {}
|
||||||
unixTimestamp = 0,
|
|
||||||
unixTimestampMillis = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@within DateTime
|
@within DateTime
|
||||||
|
|
||||||
Returns a `DateTime` representing the current moment in time.
|
Returns a `DateTime` representing the current moment in time.
|
||||||
|
|
||||||
@return A DateTime instance
|
@return DateTime -- The new DateTime object
|
||||||
]=]
|
]=]
|
||||||
function dateTime.now(): typeof(dateTime)
|
function dateTime.now(): DateTime
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@within DateTime
|
@within DateTime
|
||||||
|
|
||||||
Returns a new `DateTime` object from the given Unix timestamp, or
|
Creates a new `DateTime` from the given UNIX timestamp.
|
||||||
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.
|
This timestamp may contain both a whole and fractional part -
|
||||||
@return A DateTime instance
|
where the fractional part denotes milliseconds / nanoseconds.
|
||||||
|
|
||||||
|
Example usage of fractions:
|
||||||
|
|
||||||
|
- `DateTime.fromUnixTimestamp(123456789.001)` - one millisecond
|
||||||
|
- `DateTime.fromUnixTimestamp(123456789.000000001)` - one nanosecond
|
||||||
|
|
||||||
|
Note that the fractional part has limited precision down to exactly
|
||||||
|
one nanosecond, any fraction that is more precise will get truncated.
|
||||||
|
|
||||||
|
@param unixTimestamp -- Seconds passed since the UNIX epoch
|
||||||
|
@return DateTime -- The new DateTime object
|
||||||
]=]
|
]=]
|
||||||
function dateTime.fromUnixTimestamp(unixTimestamp: number?): typeof(dateTime)
|
function dateTime.fromUnixTimestamp(unixTimestamp: number): DateTime
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@within DateTime
|
@within DateTime
|
||||||
|
|
||||||
Returns a new `DateTime` using the given units from a UTC time. The
|
Creates a new `DateTime` from the given date & time values table, in universal (UTC) time.
|
||||||
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.
|
The given table must contains the following values:
|
||||||
- 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
|
| Key | Type | Range |
|
||||||
@return A DateTime instance
|
|----------|----------|----------------|
|
||||||
|
| `year` | `number` | `1400 -> 9999` |
|
||||||
|
| `month` | `number` | `1 -> 12` |
|
||||||
|
| `day` | `number` | `1 -> 31` |
|
||||||
|
| `hour` | `number` | `0 -> 23` |
|
||||||
|
| `minute` | `number` | `0 -> 59` |
|
||||||
|
| `second` | `number` | `0 -> 60` |
|
||||||
|
|
||||||
|
An additional `millisecond` value may also be included,
|
||||||
|
and should be within the range `0 -> 999`, but is optional.
|
||||||
|
|
||||||
|
This constructor is fallible and may throw an error in the following situations:
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
- Non-integer values are rounded down. For example, providing 2.5 hours
|
||||||
|
is equivalent to providing 2 hours, not 2 hours and 30 minutes.
|
||||||
|
|
||||||
|
@param dateTime -- Table containing date & time values
|
||||||
|
@return DateTime -- The new DateTime object
|
||||||
]=]
|
]=]
|
||||||
function dateTime.fromUniversalTime(dateTime: DateTimeValues?): typeof(dateTime)
|
function dateTime.fromUniversalTime(values: DateTimeValues): DateTime
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@within DateTime
|
@within DateTime
|
||||||
|
|
||||||
Returns a new `DateTime` using the given units from a local time. The
|
Creates a new `DateTime` from the given date & time values table, in local time.
|
||||||
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.
|
The given table must contains the following values:
|
||||||
- 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
|
| Key | Type | Range |
|
||||||
@return A DateTime instance
|
|----------|----------|----------------|
|
||||||
|
| `year` | `number` | `1400 -> 9999` |
|
||||||
|
| `month` | `number` | `1 -> 12` |
|
||||||
|
| `day` | `number` | `1 -> 31` |
|
||||||
|
| `hour` | `number` | `0 -> 23` |
|
||||||
|
| `minute` | `number` | `0 -> 59` |
|
||||||
|
| `second` | `number` | `0 -> 60` |
|
||||||
|
|
||||||
|
An additional `millisecond` value may also be included,
|
||||||
|
and should be within the range `0 -> 999`, but is optional.
|
||||||
|
|
||||||
|
This constructor is fallible and may throw an error in the following situations:
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
- Non-integer values are rounded down. For example, providing 2.5 hours
|
||||||
|
is equivalent to providing 2 hours, not 2 hours and 30 minutes.
|
||||||
|
|
||||||
|
@param dateTime -- Table containing date & time values
|
||||||
|
@return DateTime -- The new DateTime object
|
||||||
]=]
|
]=]
|
||||||
function dateTime.fromLocalTime(dateTime: DateTimeValues?): typeof(dateTime)
|
function dateTime.fromLocalTime(values: DateTimeValues): DateTime
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@within DateTime
|
@within DateTime
|
||||||
|
|
||||||
Returns a `DateTime` from an ISO 8601 date-time string in UTC
|
Creates a new `DateTime` from an ISO 8601 date-time string.
|
||||||
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`,
|
This constructor is fallible and may throw an error if the given
|
||||||
which represents January 2nd 2020 at 10:30 AM, 45 seconds.
|
string does not strictly follow the ISO 8601 date-time string format.
|
||||||
|
|
||||||
@param isoDate An ISO 8601 formatted string
|
Some examples of valid ISO 8601 date-time strings:
|
||||||
@return A DateTime instance
|
|
||||||
|
- `2020-02-22T18:12:08Z`
|
||||||
|
- `2000-01-31T12:34:56+05:00`
|
||||||
|
- `1970-01-01T00:00:00.055Z`
|
||||||
|
|
||||||
|
@param isoDate -- An ISO 8601 formatted string
|
||||||
|
@return DateTime -- The new DateTime object
|
||||||
]=]
|
]=]
|
||||||
function dateTime.fromIsoDate(iso_date: string): typeof(dateTime)?
|
function dateTime.fromIsoDate(isoDate: string): DateTime
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
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
|
return dateTime
|
||||||
|
|
Loading…
Add table
Reference in a new issue