diff --git a/crates/lune-std-datetime/src/date_time.rs b/crates/lune-std-datetime/src/date_time.rs index fbf3910..c72bbc4 100644 --- a/crates/lune-std-datetime/src/date_time.rs +++ b/crates/lune-std-datetime/src/date_time.rs @@ -173,6 +173,21 @@ impl DateTime { Ok(Self { inner }) } + /** + Parses a time string in the RFC 2822 format, such as + `Tue, 1 Jul 2003 10:52:37 +0200`, into a new `DateTime` struct. + + See [`chrono::DateTime::parse_from_rfc2822`] for additional details. + + # Errors + + Returns an error if the input string is not a valid RFC 2822 date-time. + */ + pub fn from_rfc_2822_date(rfc_date: impl AsRef) -> DateTimeResult { + let inner = ChronoDateTime::parse_from_rfc2822(rfc_date.as_ref())?.with_timezone(&Utc); + Ok(Self { inner }) + } + /** Extracts individual date & time values from this `DateTime`, using the current local time zone. @@ -200,6 +215,16 @@ impl DateTime { pub fn to_iso_date(self) -> String { self.inner.to_rfc3339() } + + /** + Formats a time string in the RFC 2822 format, such as `Tue, 1 Jul 2003 10:52:37 +0200`. + + See [`chrono::DateTime::to_rfc2822`] for additional details. + */ + #[must_use] + pub fn to_rfc_2822_date(self) -> String { + self.inner.to_rfc2822() + } } impl LuaUserData for DateTime { @@ -230,6 +255,8 @@ impl LuaUserData for DateTime { ); // Normal methods methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_iso_date())); + methods.add_method("toRfc3339", |_, this, ()| Ok(this.to_iso_date())); + methods.add_method("toRfc2822", |_, this, ()| Ok(this.to_rfc_2822_date())); methods.add_method( "formatUniversalTime", |_, this, (format, locale): (Option, Option)| { diff --git a/crates/lune-std-datetime/src/lib.rs b/crates/lune-std-datetime/src/lib.rs index f53ddf3..cf01719 100644 --- a/crates/lune-std-datetime/src/lib.rs +++ b/crates/lune-std-datetime/src/lib.rs @@ -22,6 +22,12 @@ pub fn module(lua: &Lua) -> LuaResult { .with_function("fromIsoDate", |_, iso_date: String| { Ok(DateTime::from_iso_date(iso_date)?) })? + .with_function("fromRfc3339", |_, iso_date: String| { + Ok(DateTime::from_iso_date(iso_date)?) + })? + .with_function("fromRfc2822", |_, rfc_date: String| { + Ok(DateTime::from_rfc_2822_date(rfc_date)?) + })? .with_function("fromLocalTime", |_, values| { Ok(DateTime::from_local_time(&values)?) })? diff --git a/crates/lune/src/tests.rs b/crates/lune/src/tests.rs index 04e3cca..8ef206d 100644 --- a/crates/lune/src/tests.rs +++ b/crates/lune/src/tests.rs @@ -92,11 +92,14 @@ create_tests! { datetime_format_local_time: "datetime/formatLocalTime", datetime_format_universal_time: "datetime/formatUniversalTime", datetime_from_iso_date: "datetime/fromIsoDate", + datetime_from_rfc_2822_date: "datetime/fromRfc2822", + datetime_from_rfc_3339_date: "datetime/fromRfc3339", 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_rfc_2822_date: "datetime/toRfc2822", datetime_to_local_time: "datetime/toLocalTime", datetime_to_universal_time: "datetime/toUniversalTime", } diff --git a/tests/datetime/fromRfc2822.luau b/tests/datetime/fromRfc2822.luau new file mode 100644 index 0000000..9ca30c6 --- /dev/null +++ b/tests/datetime/fromRfc2822.luau @@ -0,0 +1,11 @@ +local DateTime = require("@lune/datetime") + +assert( + DateTime.fromRfc2822("Fri, 21 Nov 1997 09:55:06 -0600") ~= nil, + "expected DateTime.fromRfcDate() to return DateTime, got nil" +) + +assert( + DateTime.fromRfc2822("Tue, 1 Jul 2003 10:52:37 +0200") ~= nil, + "expected DateTime.fromRfcDate() to return DateTime, got nil" +) diff --git a/tests/datetime/fromRfc3339.luau b/tests/datetime/fromRfc3339.luau new file mode 100644 index 0000000..0bf2187 --- /dev/null +++ b/tests/datetime/fromRfc3339.luau @@ -0,0 +1,11 @@ +local DateTime = require("@lune/datetime") + +assert( + DateTime.fromRfc3339("2023-08-26T16:56:28Z") ~= nil, + "expected DateTime.fromIsoDate() to return DateTime, got nil" +) + +assert( + DateTime.fromRfc3339("1929-12-05T23:18:23Z") ~= nil, + "expected DateTime.fromIsoDate() to return DateTime, got nil" +) diff --git a/tests/datetime/toRfc2822.luau b/tests/datetime/toRfc2822.luau new file mode 100644 index 0000000..73e6a2a --- /dev/null +++ b/tests/datetime/toRfc2822.luau @@ -0,0 +1,73 @@ +local DateTime = require("@lune/datetime") + +local now = DateTime.now() +local nowRfc = now:toRfc2822() + +assert(type(nowRfc) == "string", "toRfcDate should return a string") +assert( + string.match(nowRfc, "^%a%a%a, %d%d? %a%a%a %d%d%d%d %d%d:%d%d:%d%d [+-]%d%d%d%d$"), + "RFC 2822 date string does not match expected format" +) + +-- Extract components of the RFC 2822 string +local day, date, month, year, time, timezone = + nowRfc:match("^(%a%a%a), (%d%d?) (%a%a%a) (%d%d%d%d) (%d%d:%d%d:%d%d) ([+-]%d%d%d%d)$") + +if not day or not date or not month or not year or not time or not timezone then + error("Failed to extract components from RFC 2822 date string") +end + +-- Validate month +local validMonths = { + Jan = true, + Feb = true, + Mar = true, + Apr = true, + May = true, + Jun = true, + Jul = true, + Aug = true, + Sep = true, + Oct = true, + Nov = true, + Dec = true, +} +assert(validMonths[month], "Month must be a valid RFC 2822 month abbreviation") + +-- Validate year +assert(string.match(year, "^%d%d%d%d$"), "Year must be a 4-digit number") + +-- Validate date +local dayNum = tonumber(date) +assert(dayNum >= 1 and dayNum <= 31, "Date must be between 1 and 31") + +-- Validate time +local hour, minute, second = time:match("^(%d%d):(%d%d):(%d%d)$") +if not hour or not minute or not second then + error("Failed to extract time components from RFC 2822 date string") +end + +assert(hour and tonumber(hour) >= 0 and tonumber(hour) < 24, "Hour must be between 0 and 23") +assert( + minute and tonumber(minute) >= 0 and tonumber(minute) < 60, + "Minute must be between 0 and 59" +) +assert( + second and tonumber(second) >= 0 and tonumber(second) < 60, + "Second must be between 0 and 59" +) + +-- Validate timezone +local tzHour, tzMinute = timezone:match("^([+-]%d%d)(%d%d)$") +if not tzHour or not tzMinute then + error("Failed to extract timezone components from RFC 2822 date string") +end + +assert( + tzHour and tonumber(tzHour) >= -14 and tonumber(tzHour) <= 14, + "Timezone hour offset must be between -14 and +14" +) +assert( + tzMinute and tonumber(tzMinute) >= 0 and tonumber(tzMinute) < 60, + "Timezone minute offset must be between 0 and 59" +) diff --git a/types/datetime.luau b/types/datetime.luau index 04b42aa..0df3785 100644 --- a/types/datetime.luau +++ b/types/datetime.luau @@ -189,6 +189,24 @@ function DateTime.toIsoDate(self: DateTime): string return nil :: any end +--[=[ + @within DateTime + @tag Method + + Formats this `DateTime` as an RFC 2822 date-time string. + + Some examples of RFC 2822 date-time strings are: + + - `Fri, 21 Nov 1997 09:55:06 -0600` + - `Tue, 1 Jul 2003 10:52:37 +0200` + - `Mon, 23 Dec 2024 01:58:48 GMT` + + @return string -- The RFC 2822 formatted string +]=] +function DateTime.toRfc2822(self: DateTime): string + return nil :: any +end + --[=[ @within DateTime @tag Method @@ -255,6 +273,9 @@ export type DateTime = typeof(DateTime) -- Formats the current moment in time as an ISO 8601 string print(now:toIsoDate()) + -- Formats the current moment in time as an RFC 2822 string + print(now:toRfc2822()) + -- Formats the current moment in time, using the local -- time, the French locale, and the specified time string print(now:formatLocalTime("%A, %d %B %Y", "fr")) @@ -395,6 +416,7 @@ end @tag Constructor Creates a new `DateTime` from an ISO 8601 date-time string. + This function behaves the same as `fromRfc3339`. ### Errors @@ -414,4 +436,52 @@ function dateTime.fromIsoDate(isoDate: string): DateTime return nil :: any end +--[=[ + @within DateTime + @tag Constructor + + Creates a new `DateTime` from an RFC 3339 date-time string. + + ### Errors + + This constructor is fallible and may throw an error if the given + string does not strictly follow the RFC 3339 date-time string format. + + Some examples of valid RFC 3339 date-time strings are: + + - `2020-02-22T18:12:08Z` + - `2000-01-31T12:34:56+05:00` + - `1970-01-01T00:00:00.055Z` + + @param rfc3339Date -- An RFC 3339 formatted string + @return DateTime -- The new DateTime object +]=] +function dateTime.fromRfc3339(rfc3339Date: string): DateTime + return nil :: any +end + +--[=[ + @within DateTime + @tag Constructor + + Creates a new `DateTime` from an RFC 2822 date-time string. + + ### Errors + + This constructor is fallible and may throw an error if the given + string does not strictly follow the RFC 2822 date-time string format. + + Some examples of valid RFC 2822 date-time strings are: + + - `Fri, 21 Nov 1997 09:55:06 -0600` + - `Tue, 1 Jul 2003 10:52:37 +0200` + - `Mon, 23 Dec 2024 01:58:48 GMT` + + @param rfc2822Date -- An RFC 2822 formatted string + @return DateTime -- The new DateTime object +]=] +function dateTime.fromRfc2822(rfc2822Date: string): DateTime + return nil :: any +end + return dateTime