diff --git a/Cargo.lock b/Cargo.lock index 7d4e84f..10c8d8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,8 +326,8 @@ dependencies = [ [[package]] name = "chrono_locale" -version = "0.1.1" -source = "git+https://github.com/0x5eal/chrono-locale.git?rev=2a7ebcc#2a7ebcc019d133bc17833d66731092dee60dc3b7" +version = "0.1.2" +source = "git+https://github.com/0x5eal/chrono-locale.git?tag=v0.1.2#b47f2afe0661a6b15ae788af250ea8afa9e33a4d" dependencies = [ "chrono", "lazy_static", diff --git a/src/lune/builtins/date_time.rs b/src/lune/builtins/date_time.rs index 6c37288..3541664 100644 --- a/src/lune/builtins/date_time.rs +++ b/src/lune/builtins/date_time.rs @@ -5,18 +5,27 @@ use once_cell::sync::Lazy; // TODO: Proper error handling and stuff +/// Possible types of timestamps accepted by `DateTime`. pub enum TimestampType { Seconds, Millis, } 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 + /// Returns a `DateTime` representing the current moment in time. pub fn now() -> Self { let time = Utc::now(); @@ -26,9 +35,9 @@ impl DateTime { } } - /// Returns a new DateTime object from the given unix timestamp, in either seconds on + /// 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) + /// 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), @@ -45,7 +54,15 @@ impl DateTime { } } - pub fn from_universal_time(date_time: Option) -> Self { + /// 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) @@ -73,7 +90,15 @@ impl DateTime { } } - pub fn from_local_time(date_time: Option) -> Self { + /// 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( @@ -103,25 +128,36 @@ impl DateTime { } } - pub fn from_iso_date(iso_date: T) -> Self + /// 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") - .expect("invalid ISO 8601 string"); + .ok()?; - Self { + Some(Self { unix_timestamp: time.timestamp(), unix_timestamp_millis: time.timestamp_millis(), - } + }) } - fn to_datetime_constructor(date_time: ChronoDateTime) -> DateTimeConstructor + /// 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 = DateTimeConstructor::default(); + 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, @@ -151,25 +187,43 @@ impl DateTime { date_time_constructor } - pub fn to_local_time(&self) -> DateTimeConstructor { - Self::to_datetime_constructor(Local.timestamp_opt(self.unix_timestamp, 0).unwrap()) + /// 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()) } - pub fn to_universal_time(&self) -> DateTimeConstructor { - Self::to_datetime_constructor(Utc.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) + .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. + // 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, @@ -182,18 +236,25 @@ impl DateTime { } } -pub struct DateTimeConstructor { +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 DateTimeConstructor { - /// Constructs the default state for DateTimeConstructor, which is the Unix Epoch. +impl Default for DateTimeBuilder { + /// Constructs the default state for DateTimeBuilder, which is the Unix Epoch. fn default() -> Self { Self { year: 1970, @@ -207,54 +268,63 @@ impl Default for DateTimeConstructor { } } +/// General timezone types accepted by `DateTime` methods. pub enum Timezone { - UTC, + Utc, Local, } -impl DateTimeConstructor { +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 { self.month = month as u32; 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, @@ -276,7 +346,7 @@ impl DateTimeConstructor { }); match timezone { - Timezone::UTC => Utc + Timezone::Utc => Utc .with_ymd_and_hms( self.year, self.month,