From dd1c43c4949aa33b793a265216640914ab03b257 Mon Sep 17 00:00:00 2001 From: Compey Date: Sat, 26 Aug 2023 21:06:44 +0530 Subject: [PATCH] refactor: breakup datetime into multiple modules --- src/lune/builtins/datetime/builder.rs | 206 +++++++++++ src/lune/builtins/datetime/date_time.rs | 241 +++++++++++++ src/lune/builtins/datetime/mod.rs | 455 +----------------------- 3 files changed, 456 insertions(+), 446 deletions(-) create mode 100644 src/lune/builtins/datetime/builder.rs create mode 100644 src/lune/builtins/datetime/date_time.rs diff --git a/src/lune/builtins/datetime/builder.rs b/src/lune/builtins/datetime/builder.rs new file mode 100644 index 0000000..5f963f5 --- /dev/null +++ b/src/lune/builtins/datetime/builder.rs @@ -0,0 +1,206 @@ +use crate::lune::builtins::datetime::date_time::Timezone; +use chrono::prelude::*; +use chrono_locale::LocaleDate; +use mlua::prelude::*; +use once_cell::sync::Lazy; + +#[derive(Copy, Clone)] +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<'lua> FromLua<'lua> for Timezone { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + fn num_to_enum(num: i32) -> LuaResult { + match num { + 1 => Ok(Timezone::Utc), + 2 => Ok(Timezone::Local), + _ => Err(LuaError::external("Invalid enum member!")), + } + } + + match value { + LuaValue::Integer(num) => num_to_enum(num), + LuaValue::Number(num) => num_to_enum(num as i32), + 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, number or string expected", + )), + } + } +} + +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(self, timezone: Timezone, format: Option, locale: Option) -> String + where + T: ToString, + { + let format_lazy: Lazy = Lazy::new(|| { + if let Some(fmt) = format { + fmt.to_string() + } else { + "%Y-%m-%dT%H:%M:%SZ".to_string() + } + }); + + let locale_lazy: Lazy = Lazy::new(|| { + if let Some(locale) = locale { + locale.to_string() + } else { + "en".to_string() + } + }); + + match timezone { + Timezone::Utc => Utc + .with_ymd_and_hms( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + ) + .unwrap() + .formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) + .to_string(), + Timezone::Local => Local + .with_ymd_and_hms( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + ) + .unwrap() + .formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) + .to_string(), + } + } + + fn build(self) -> Self { + self + } +} + +impl LuaUserData for DateTimeBuilder {} + +impl<'lua> FromLua<'lua> for DateTimeBuilder { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + match value { + LuaValue::Table(t) => Ok(Self::default() + .with_year(t.get("year")?) + // FIXME: Months are offset by two, months start on march for some reason... + .with_month( + (match t.get("month")? { + LuaValue::String(str) => Ok(str.to_str()?.parse::().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")?) + // TODO: millisecond support + .build()), + _ => Err(LuaError::external( + "expected type table for DateTimeBuilder", + )), + } + } +} diff --git a/src/lune/builtins/datetime/date_time.rs b/src/lune/builtins/datetime/date_time.rs new file mode 100644 index 0000000..3e95a25 --- /dev/null +++ b/src/lune/builtins/datetime/date_time.rs @@ -0,0 +1,241 @@ +use crate::lune::builtins::datetime::builder::DateTimeBuilder; +use chrono::prelude::*; +use chrono::DateTime as ChronoDateTime; + +/// Possible types of timestamps accepted by `DateTime`. +pub enum TimestampType { + Seconds, + Millis, +} + +/// General timezone types accepted by `DateTime` methods. +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) -> Self { + if let Some(date_time) = date_time { + let utc_time: ChronoDateTime = Utc.from_utc_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(date_time.year, date_time.month, date_time.day) + .expect("invalid date"), + NaiveTime::from_hms_milli_opt( + date_time.hour, + date_time.minute, + date_time.second, + date_time.millisecond, + ) + .expect("invalid time"), + )); + + Self { + unix_timestamp: utc_time.timestamp(), + unix_timestamp_millis: utc_time.timestamp_millis(), + } + } else { + let utc_time = Utc::now(); + + Self { + unix_timestamp: utc_time.timestamp(), + unix_timestamp_millis: utc_time.timestamp_millis(), + } + } + } + + /// 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_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) -> Self { + if let Some(date_time) = date_time { + let local_time: ChronoDateTime = Local + .from_local_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(date_time.year, date_time.month, date_time.day) + .expect("invalid date"), + NaiveTime::from_hms_milli_opt( + date_time.hour, + date_time.minute, + date_time.second, + date_time.millisecond, + ) + .expect("invalid time"), + )) + .unwrap(); + + Self { + unix_timestamp: local_time.timestamp(), + unix_timestamp_millis: local_time.timestamp_millis(), + } + } else { + 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(iso_date: T) -> Option + where + T: ToString, + { + let time = + ChronoDateTime::parse_from_str(iso_date.to_string().as_str(), "%Y-%m-%dT%H:%M:%SZ") + .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(date_time: ChronoDateTime) -> DateTimeBuilder + where + T: TimeZone, + { + let mut date_time_constructor = DateTimeBuilder::default(); + + // Any less tedious way to get Enum member based on index? + // I know there's some crates available with derive macros for this, + // would it be okay if used some of them? + date_time_constructor + .with_year(date_time.year()) + .with_month(match date_time.month() { + 1 => Month::January, + 2 => Month::February, + 3 => Month::March, + 4 => Month::April, + 5 => Month::May, + 6 => Month::June, + 7 => Month::July, + 8 => Month::August, + 9 => Month::September, + 10 => Month::October, + 11 => Month::November, + 12 => Month::December, + _ => panic!("invalid month ordinal"), + }) + .with_day(date_time.day()) + .with_hour(date_time.hour()) + .with_minute(date_time.minute()) + .with_second(date_time.second()); + + 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) -> DateTimeBuilder { + Self::to_datetime_builder(Local.timestamp_opt(self.unix_timestamp, 0).unwrap()) + } + + /// 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) -> DateTimeBuilder { + Self::to_datetime_builder(Utc.timestamp_opt(self.unix_timestamp, 0).unwrap()) + } + + /// Formats a date as a ISO 8601 date-time string. 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) -> 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 **local time** + /// 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(&self, timezone: Timezone, fmt_str: T, locale: T) -> String + where + T: ToString, + { + self.to_universal_time().to_string( + timezone, + Some(fmt_str.to_string()), + Some(locale.to_string()), + ) + } +} diff --git a/src/lune/builtins/datetime/mod.rs b/src/lune/builtins/datetime/mod.rs index a0bbc63..6f04f60 100644 --- a/src/lune/builtins/datetime/mod.rs +++ b/src/lune/builtins/datetime/mod.rs @@ -1,13 +1,15 @@ -use crate::lune::builtins::LuaUserData; -use crate::lune::util::TableBuilder; -use chrono::prelude::*; -use chrono::DateTime as ChronoDateTime; -use chrono_locale::LocaleDate; use mlua::prelude::*; -use once_cell::sync::Lazy; + +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 -// TODO: fromLocalTime, toLocalTime // FIX: DateTime::from_iso_date is broken pub fn create(lua: &'static Lua) -> LuaResult { @@ -63,12 +65,6 @@ pub fn create(lua: &'static Lua) -> LuaResult { .build_readonly() } -/// Possible types of timestamps accepted by `DateTime`. -pub enum TimestampType { - Seconds, - Millis, -} -// get fraction from f32 impl<'lua> FromLua<'lua> for TimestampType { fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { match value { @@ -85,232 +81,6 @@ impl<'lua> FromLua<'lua> for TimestampType { } } -#[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) -> Self { - if let Some(date_time) = date_time { - let utc_time: ChronoDateTime = Utc.from_utc_datetime(&NaiveDateTime::new( - NaiveDate::from_ymd_opt(date_time.year, date_time.month, date_time.day) - .expect("invalid date"), - NaiveTime::from_hms_milli_opt( - date_time.hour, - date_time.minute, - date_time.second, - date_time.millisecond, - ) - .expect("invalid time"), - )); - - Self { - unix_timestamp: utc_time.timestamp(), - unix_timestamp_millis: utc_time.timestamp_millis(), - } - } else { - let utc_time = Utc::now(); - - Self { - unix_timestamp: utc_time.timestamp(), - unix_timestamp_millis: utc_time.timestamp_millis(), - } - } - } - - /// 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_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) -> Self { - if let Some(date_time) = date_time { - let local_time: ChronoDateTime = Local - .from_local_datetime(&NaiveDateTime::new( - NaiveDate::from_ymd_opt(date_time.year, date_time.month, date_time.day) - .expect("invalid date"), - NaiveTime::from_hms_milli_opt( - date_time.hour, - date_time.minute, - date_time.second, - date_time.millisecond, - ) - .expect("invalid time"), - )) - .unwrap(); - - Self { - unix_timestamp: local_time.timestamp(), - unix_timestamp_millis: local_time.timestamp_millis(), - } - } else { - 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(iso_date: T) -> Option - where - T: ToString, - { - let time = - ChronoDateTime::parse_from_str(iso_date.to_string().as_str(), "%Y-%m-%dT%H:%M:%SZ") - .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(date_time: ChronoDateTime) -> DateTimeBuilder - where - T: TimeZone, - { - let mut date_time_constructor = DateTimeBuilder::default(); - - // Any less tedious way to get Enum member based on index? - // I know there's some crates available with derive macros for this, - // would it be okay if used some of them? - date_time_constructor - .with_year(date_time.year()) - .with_month(match date_time.month() { - 1 => Month::January, - 2 => Month::February, - 3 => Month::March, - 4 => Month::April, - 5 => Month::May, - 6 => Month::June, - 7 => Month::July, - 8 => Month::August, - 9 => Month::September, - 10 => Month::October, - 11 => Month::November, - 12 => Month::December, - _ => panic!("invalid month ordinal"), - }) - .with_day(date_time.day()) - .with_hour(date_time.hour()) - .with_minute(date_time.minute()) - .with_second(date_time.second()); - - 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) -> DateTimeBuilder { - Self::to_datetime_builder(Local.timestamp_opt(self.unix_timestamp, 0).unwrap()) - } - - /// 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) -> DateTimeBuilder { - Self::to_datetime_builder(Utc.timestamp_opt(self.unix_timestamp, 0).unwrap()) - } - - /// Formats a date as a ISO 8601 date-time string. 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) -> 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 **local time** - /// 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(&self, timezone: Timezone, fmt_str: T, locale: T) -> String - where - T: ToString, - { - self.to_universal_time().to_string( - timezone, - Some(fmt_str.to_string()), - Some(locale.to_string()), - ) - } -} - 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)); @@ -369,210 +139,3 @@ impl<'lua> FromLua<'lua> for DateTime { } } } - -#[derive(Copy, Clone)] -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, - } - } -} - -/// General timezone types accepted by `DateTime` methods. -pub enum Timezone { - Utc, - Local, -} - -impl<'lua> FromLua<'lua> for Timezone { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - fn num_to_enum(num: i32) -> LuaResult { - match num { - 1 => Ok(Timezone::Utc), - 2 => Ok(Timezone::Local), - _ => Err(LuaError::external("Invalid enum member!")), - } - } - - match value { - LuaValue::Integer(num) => num_to_enum(num), - LuaValue::Number(num) => num_to_enum(num as i32), - 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, number or string expected", - )), - } - } -} - -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. - fn to_string(self, timezone: Timezone, format: Option, locale: Option) -> String - where - T: ToString, - { - let format_lazy: Lazy = Lazy::new(|| { - if let Some(fmt) = format { - fmt.to_string() - } else { - "%Y-%m-%dT%H:%M:%SZ".to_string() - } - }); - - let locale_lazy: Lazy = Lazy::new(|| { - if let Some(locale) = locale { - locale.to_string() - } else { - "en".to_string() - } - }); - - match timezone { - Timezone::Utc => Utc - .with_ymd_and_hms( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - ) - .unwrap() - .formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) - .to_string(), - Timezone::Local => Local - .with_ymd_and_hms( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - ) - .unwrap() - .formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) - .to_string(), - } - } - - fn build(self) -> Self { - self - } -} - -impl LuaUserData for DateTimeBuilder {} - -impl<'lua> FromLua<'lua> for DateTimeBuilder { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - match value { - LuaValue::Table(t) => Ok(Self::default() - .with_year(t.get("year")?) - // FIXME: Months are offset by two, months start on march for some reason... - .with_month( - (match t.get("month")? { - LuaValue::String(str) => Ok(str.to_str()?.parse::().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")?) - // TODO: millisecond support - .build()), - _ => Err(LuaError::external( - "expected type table for DateTimeBuilder", - )), - } - } -}