Skip to content

Commit dd7f6d6

Browse files
authored
Add support for RFC 2822 in DateTime (#285)
1 parent 27e3efc commit dd7f6d6

7 files changed

Lines changed: 201 additions & 0 deletions

File tree

crates/lune-std-datetime/src/date_time.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ impl DateTime {
173173
Ok(Self { inner })
174174
}
175175

176+
/**
177+
Parses a time string in the RFC 2822 format, such as
178+
`Tue, 1 Jul 2003 10:52:37 +0200`, into a new `DateTime` struct.
179+
180+
See [`chrono::DateTime::parse_from_rfc2822`] for additional details.
181+
182+
# Errors
183+
184+
Returns an error if the input string is not a valid RFC 2822 date-time.
185+
*/
186+
pub fn from_rfc_2822_date(rfc_date: impl AsRef<str>) -> DateTimeResult<Self> {
187+
let inner = ChronoDateTime::parse_from_rfc2822(rfc_date.as_ref())?.with_timezone(&Utc);
188+
Ok(Self { inner })
189+
}
190+
176191
/**
177192
Extracts individual date & time values from this
178193
`DateTime`, using the current local time zone.
@@ -200,6 +215,16 @@ impl DateTime {
200215
pub fn to_iso_date(self) -> String {
201216
self.inner.to_rfc3339()
202217
}
218+
219+
/**
220+
Formats a time string in the RFC 2822 format, such as `Tue, 1 Jul 2003 10:52:37 +0200`.
221+
222+
See [`chrono::DateTime::to_rfc2822`] for additional details.
223+
*/
224+
#[must_use]
225+
pub fn to_rfc_2822_date(self) -> String {
226+
self.inner.to_rfc2822()
227+
}
203228
}
204229

205230
impl LuaUserData for DateTime {
@@ -230,6 +255,8 @@ impl LuaUserData for DateTime {
230255
);
231256
// Normal methods
232257
methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_iso_date()));
258+
methods.add_method("toRfc3339", |_, this, ()| Ok(this.to_iso_date()));
259+
methods.add_method("toRfc2822", |_, this, ()| Ok(this.to_rfc_2822_date()));
233260
methods.add_method(
234261
"formatUniversalTime",
235262
|_, this, (format, locale): (Option<String>, Option<String>)| {

crates/lune-std-datetime/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
2222
.with_function("fromIsoDate", |_, iso_date: String| {
2323
Ok(DateTime::from_iso_date(iso_date)?)
2424
})?
25+
.with_function("fromRfc3339", |_, iso_date: String| {
26+
Ok(DateTime::from_iso_date(iso_date)?)
27+
})?
28+
.with_function("fromRfc2822", |_, rfc_date: String| {
29+
Ok(DateTime::from_rfc_2822_date(rfc_date)?)
30+
})?
2531
.with_function("fromLocalTime", |_, values| {
2632
Ok(DateTime::from_local_time(&values)?)
2733
})?

crates/lune/src/tests.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,14 @@ create_tests! {
9292
datetime_format_local_time: "datetime/formatLocalTime",
9393
datetime_format_universal_time: "datetime/formatUniversalTime",
9494
datetime_from_iso_date: "datetime/fromIsoDate",
95+
datetime_from_rfc_2822_date: "datetime/fromRfc2822",
96+
datetime_from_rfc_3339_date: "datetime/fromRfc3339",
9597
datetime_from_local_time: "datetime/fromLocalTime",
9698
datetime_from_universal_time: "datetime/fromUniversalTime",
9799
datetime_from_unix_timestamp: "datetime/fromUnixTimestamp",
98100
datetime_now: "datetime/now",
99101
datetime_to_iso_date: "datetime/toIsoDate",
102+
datetime_to_rfc_2822_date: "datetime/toRfc2822",
100103
datetime_to_local_time: "datetime/toLocalTime",
101104
datetime_to_universal_time: "datetime/toUniversalTime",
102105
}

tests/datetime/fromRfc2822.luau

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
local DateTime = require("@lune/datetime")
2+
3+
assert(
4+
DateTime.fromRfc2822("Fri, 21 Nov 1997 09:55:06 -0600") ~= nil,
5+
"expected DateTime.fromRfcDate() to return DateTime, got nil"
6+
)
7+
8+
assert(
9+
DateTime.fromRfc2822("Tue, 1 Jul 2003 10:52:37 +0200") ~= nil,
10+
"expected DateTime.fromRfcDate() to return DateTime, got nil"
11+
)

tests/datetime/fromRfc3339.luau

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
local DateTime = require("@lune/datetime")
2+
3+
assert(
4+
DateTime.fromRfc3339("2023-08-26T16:56:28Z") ~= nil,
5+
"expected DateTime.fromIsoDate() to return DateTime, got nil"
6+
)
7+
8+
assert(
9+
DateTime.fromRfc3339("1929-12-05T23:18:23Z") ~= nil,
10+
"expected DateTime.fromIsoDate() to return DateTime, got nil"
11+
)

tests/datetime/toRfc2822.luau

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
local DateTime = require("@lune/datetime")
2+
3+
local now = DateTime.now()
4+
local nowRfc = now:toRfc2822()
5+
6+
assert(type(nowRfc) == "string", "toRfcDate should return a string")
7+
assert(
8+
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$"),
9+
"RFC 2822 date string does not match expected format"
10+
)
11+
12+
-- Extract components of the RFC 2822 string
13+
local day, date, month, year, time, timezone =
14+
nowRfc:match("^(%a%a%a), (%d%d?) (%a%a%a) (%d%d%d%d) (%d%d:%d%d:%d%d) ([+-]%d%d%d%d)$")
15+
16+
if not day or not date or not month or not year or not time or not timezone then
17+
error("Failed to extract components from RFC 2822 date string")
18+
end
19+
20+
-- Validate month
21+
local validMonths = {
22+
Jan = true,
23+
Feb = true,
24+
Mar = true,
25+
Apr = true,
26+
May = true,
27+
Jun = true,
28+
Jul = true,
29+
Aug = true,
30+
Sep = true,
31+
Oct = true,
32+
Nov = true,
33+
Dec = true,
34+
}
35+
assert(validMonths[month], "Month must be a valid RFC 2822 month abbreviation")
36+
37+
-- Validate year
38+
assert(string.match(year, "^%d%d%d%d$"), "Year must be a 4-digit number")
39+
40+
-- Validate date
41+
local dayNum = tonumber(date)
42+
assert(dayNum >= 1 and dayNum <= 31, "Date must be between 1 and 31")
43+
44+
-- Validate time
45+
local hour, minute, second = time:match("^(%d%d):(%d%d):(%d%d)$")
46+
if not hour or not minute or not second then
47+
error("Failed to extract time components from RFC 2822 date string")
48+
end
49+
50+
assert(hour and tonumber(hour) >= 0 and tonumber(hour) < 24, "Hour must be between 0 and 23")
51+
assert(
52+
minute and tonumber(minute) >= 0 and tonumber(minute) < 60,
53+
"Minute must be between 0 and 59"
54+
)
55+
assert(
56+
second and tonumber(second) >= 0 and tonumber(second) < 60,
57+
"Second must be between 0 and 59"
58+
)
59+
60+
-- Validate timezone
61+
local tzHour, tzMinute = timezone:match("^([+-]%d%d)(%d%d)$")
62+
if not tzHour or not tzMinute then
63+
error("Failed to extract timezone components from RFC 2822 date string")
64+
end
65+
66+
assert(
67+
tzHour and tonumber(tzHour) >= -14 and tonumber(tzHour) <= 14,
68+
"Timezone hour offset must be between -14 and +14"
69+
)
70+
assert(
71+
tzMinute and tonumber(tzMinute) >= 0 and tonumber(tzMinute) < 60,
72+
"Timezone minute offset must be between 0 and 59"
73+
)

types/datetime.luau

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,24 @@ function DateTime.toIsoDate(self: DateTime): string
189189
return nil :: any
190190
end
191191

192+
--[=[
193+
@within DateTime
194+
@tag Method
195+
196+
Formats this `DateTime` as an RFC 2822 date-time string.
197+
198+
Some examples of RFC 2822 date-time strings are:
199+
200+
- `Fri, 21 Nov 1997 09:55:06 -0600`
201+
- `Tue, 1 Jul 2003 10:52:37 +0200`
202+
- `Mon, 23 Dec 2024 01:58:48 GMT`
203+
204+
@return string -- The RFC 2822 formatted string
205+
]=]
206+
function DateTime.toRfc2822(self: DateTime): string
207+
return nil :: any
208+
end
209+
192210
--[=[
193211
@within DateTime
194212
@tag Method
@@ -255,6 +273,9 @@ export type DateTime = typeof(DateTime)
255273
-- Formats the current moment in time as an ISO 8601 string
256274
print(now:toIsoDate())
257275
276+
-- Formats the current moment in time as an RFC 2822 string
277+
print(now:toRfc2822())
278+
258279
-- Formats the current moment in time, using the local
259280
-- time, the French locale, and the specified time string
260281
print(now:formatLocalTime("%A, %d %B %Y", "fr"))
@@ -395,6 +416,7 @@ end
395416
@tag Constructor
396417
397418
Creates a new `DateTime` from an ISO 8601 date-time string.
419+
This function behaves the same as `fromRfc3339`.
398420
399421
### Errors
400422
@@ -414,4 +436,52 @@ function dateTime.fromIsoDate(isoDate: string): DateTime
414436
return nil :: any
415437
end
416438

439+
--[=[
440+
@within DateTime
441+
@tag Constructor
442+
443+
Creates a new `DateTime` from an RFC 3339 date-time string.
444+
445+
### Errors
446+
447+
This constructor is fallible and may throw an error if the given
448+
string does not strictly follow the RFC 3339 date-time string format.
449+
450+
Some examples of valid RFC 3339 date-time strings are:
451+
452+
- `2020-02-22T18:12:08Z`
453+
- `2000-01-31T12:34:56+05:00`
454+
- `1970-01-01T00:00:00.055Z`
455+
456+
@param rfc3339Date -- An RFC 3339 formatted string
457+
@return DateTime -- The new DateTime object
458+
]=]
459+
function dateTime.fromRfc3339(rfc3339Date: string): DateTime
460+
return nil :: any
461+
end
462+
463+
--[=[
464+
@within DateTime
465+
@tag Constructor
466+
467+
Creates a new `DateTime` from an RFC 2822 date-time string.
468+
469+
### Errors
470+
471+
This constructor is fallible and may throw an error if the given
472+
string does not strictly follow the RFC 2822 date-time string format.
473+
474+
Some examples of valid RFC 2822 date-time strings are:
475+
476+
- `Fri, 21 Nov 1997 09:55:06 -0600`
477+
- `Tue, 1 Jul 2003 10:52:37 +0200`
478+
- `Mon, 23 Dec 2024 01:58:48 GMT`
479+
480+
@param rfc2822Date -- An RFC 2822 formatted string
481+
@return DateTime -- The new DateTime object
482+
]=]
483+
function dateTime.fromRfc2822(rfc2822Date: string): DateTime
484+
return nil :: any
485+
end
486+
417487
return dateTime

0 commit comments

Comments
 (0)