Add support for RFC 2822 in DateTime (#285)

This commit is contained in:
Ryan 2025-04-23 07:09:11 -04:00 committed by GitHub
parent 27e3efca97
commit dd7f6d613b
Signed by: DevComp
GPG key ID: B5690EEEBB952194
7 changed files with 201 additions and 0 deletions

View file

@ -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<str>) -> DateTimeResult<Self> {
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<String>, Option<String>)| {

View file

@ -22,6 +22,12 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
.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)?)
})?

View file

@ -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",
}

View file

@ -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"
)

View file

@ -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"
)

View file

@ -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"
)

View file

@ -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