use alloc::string::String;
use core::ops::Not;
use crate::calendar::Iso;
use crate::error::ErrorKind;
use crate::fmt::rw::{self, ReadDigits};
use crate::fmt::{format, parse};
use crate::{
Calendar, Date, DateTime, Moment, Month, Offset, PlainDateTime, PlainTime, Year, YearMonth,
};
impl Moment {
pub fn format_rfc3339(self) -> String {
format(self, Self::write_rfc3339, 32)
}
#[inline]
fn write_rfc3339(self, output: &mut String) {
self.on(Iso).write_rfc3339(output);
}
}
impl Moment {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
Ok(DateTime::read_rfc3339(input)?.into())
}
}
impl<C: Calendar> Year<C> {
pub fn format_rfc3339(self) -> String {
format(self, Self::write_rfc3339, 4)
}
#[inline]
fn write_rfc3339(self, output: &mut String) {
let y = self.number();
if (0..=9999).contains(&y) {
rw::write_two_digit_i32(output, (y / 100) as u8);
rw::write_two_digit_i32(output, (y % 100) as u8);
} else if y.is_negative() {
output.push('-');
rw::write_i32(output, None, -y);
} else {
output.push('+');
rw::write_i32(output, None, y);
}
}
}
impl Year<Iso> {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[allow(unsafe_code)]
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
let negative = rw::read_sign_is_negative(input, false)?;
let (mut number, _) = rw::read_i32_in_range(
input,
negative.not().then_some(ReadDigits::AtLeast(4)),
Iso::MIN_YEAR.number(),
Iso::MAX_YEAR.number(),
)?;
if negative {
number = -number;
}
Ok(unsafe { Self::unchecked_of(Iso, number) })
}
}
impl<C: Calendar> Month<C> {
pub fn format_rfc3339(self) -> String {
format(self, Self::write_rfc3339, 2)
}
#[inline]
fn write_rfc3339(self, output: &mut String) {
rw::write_two_digit_i32(output, self.number());
}
}
impl Month<Iso> {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[allow(unsafe_code)]
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
let (number, _) = rw::read_i32_in_range(input, 2, 1, 12)?;
Ok(unsafe { Self::unchecked_of(Iso, number as u8) })
}
}
impl<C: Calendar> YearMonth<C> {
pub fn format_rfc3339(self) -> String {
format(self, Self::write_rfc3339, 7)
}
#[inline]
fn write_rfc3339(self, output: &mut String) {
self.year().write_rfc3339(output);
output.push('-');
self.month().write_rfc3339(output);
}
}
impl YearMonth<Iso> {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
let year = Year::read_rfc3339(input)?;
rw::read_ensure_u8(input, b'-')?;
let month = Month::read_rfc3339(input)?;
Ok(Self {
calendar: Iso,
year: year.number(),
month: month.number(),
})
}
}
impl<C: Calendar> Date<C> {
pub fn format_rfc3339(self) -> String {
format(self, Self::write_rfc3339, 10)
}
#[inline]
fn write_rfc3339(self, output: &mut String) {
let (year, month, day) = Iso.date_components(self.as_days_since_ako_epoch());
year.write_rfc3339(output);
output.push('-');
month.write_rfc3339(output);
output.push('-');
rw::write_two_digit_i32(output, day);
}
}
impl Date<Iso> {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
let year_month = YearMonth::read_rfc3339(input)?;
rw::read_ensure_u8(input, b'-')?;
let days = year_month.days() as i32;
let (day, _) = rw::read_i32_in_range(input, 2, 1, days)?;
Self::iso(year_month.year, year_month.month, day as u8)
}
}
impl PlainTime {
pub fn format_rfc3339(self) -> String {
format(self, Self::write_rfc3339, 20)
}
#[inline]
fn write_rfc3339(self, output: &mut String) {
let (hour, minute, second, nsec) = self.components();
rw::write_two_digit_i32(output, hour);
output.push(':');
rw::write_two_digit_i32(output, minute);
output.push(':');
rw::write_two_digit_i32(output, second);
if nsec > 0 {
output.push('.');
rw::write_i32(output, 9, nsec as i32);
}
}
}
impl PlainTime {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
let (hour, _) = rw::read_i32_in_range(input, 2, 0, 23)?;
rw::read_ensure_u8(input, b':')?;
let (minute, _) = rw::read_i32_in_range(input, 2, 0, 59)?;
rw::read_ensure_u8(input, b':')?;
let second = match rw::read_i32(input, 2)? {
Some((num, _)) if (0..=59).contains(&num) => num,
Some((num, _)) if hour == 23 && minute == 23 && num == 60 => {
59
}
_ => {
return Err(ErrorKind::OutOfRange.into());
}
};
let nanosecond = if input.first().is_some_and(|byte| *byte == b'.') {
*input = &input[1..];
let (fs, len) = rw::read_i32_in_range(input, ReadDigits::AtLeast(1), 0, 999_999_999)?;
if len == 3 {
(fs as u32) * 1_000_000
} else if len <= 6 {
(fs as u32) * 1_000
} else {
fs as u32
}
} else {
0
};
Ok(Self::of_nanosecond(
hour as u8,
minute as u8,
second as u8,
nanosecond,
))
}
}
impl<C: Calendar> PlainDateTime<C> {
pub fn format_rfc3339(self) -> String {
format(self, Self::write_rfc3339, 20)
}
#[inline]
fn write_rfc3339(self, output: &mut String) {
self.date.write_rfc3339(output);
output.push('T');
self.time.write_rfc3339(output);
}
}
impl PlainDateTime<Iso> {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
let date = Date::read_rfc3339(input)?;
rw::read_ensure_one_of_u8(input, b"Tt ")?;
let time = PlainTime::read_rfc3339(input)?;
Ok(Self { date, time })
}
}
impl Offset {
pub fn format_rfc3339(&self) -> String {
format(self, Self::write_rfc3339, 6)
}
#[inline]
fn write_rfc3339(&self, output: &mut String) {
if self.is_utc() {
output.push('Z');
} else {
let (hours, minutes, seconds) = self.components();
output.push(if hours.is_negative() { '-' } else { '+' });
rw::write_two_digit_i32(output, hours.unsigned_abs());
output.push(':');
rw::write_two_digit_i32(output, minutes.unsigned_abs());
if seconds != 0 {
output.push(':');
rw::write_two_digit_i32(output, seconds.unsigned_abs());
}
}
}
}
impl Offset {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
if input
.first()
.copied()
.is_some_and(|byte| byte == b'Z' || byte == b'z')
{
*input = &input[1..];
return Ok(Self::UTC);
}
let negative = rw::read_sign_is_negative(input, true)?;
let (mut hour, _) = rw::read_i32_in_range(input, 2, 0, 23)?;
rw::read_ensure_u8(input, b':')?;
let (mut minute, _) = rw::read_i32_in_range(input, 2, 0, 59)?;
let mut second = if !input.is_empty() {
rw::read_ensure_u8(input, b':')?;
let (second, _) = rw::read_i32_in_range(input, 2, 0, 59)?;
second
} else {
0
};
if negative {
hour = -hour;
minute = -minute;
second = -second;
}
Self::of(hour as i8, minute as i8, second as i8)
}
}
impl<C: Calendar> DateTime<C> {
pub fn format_rfc3339(&self) -> String {
format(self, Self::write_rfc3339, 20)
}
#[inline]
fn write_rfc3339(&self, output: &mut String) {
let (date, time, offset) = self.components();
date.write_rfc3339(output);
output.push('T');
time.write_rfc3339(output);
offset.write_rfc3339(output);
}
}
impl DateTime<Iso> {
pub fn parse_rfc3339<S: AsRef<str>>(s: S) -> crate::Result<Self> {
parse(s.as_ref(), Self::read_rfc3339)
}
#[inline]
fn read_rfc3339(input: &mut &[u8]) -> crate::Result<Self> {
let dt = PlainDateTime::read_rfc3339(input)?;
let offset = Offset::read_rfc3339(input)?;
Ok(dt.assume_offset(offset))
}
}
#[cfg(test)]
mod tests {
use test_case::test_case;
use crate::{Moment, Year};
#[test_case(1, "1970-01-01T00:00:01Z")]
#[test_case(1726509578, "2024-09-16T17:59:38Z")]
#[test_case(-2186632822, "1900-09-16T17:59:38Z")]
#[test_case(-68487746422, "-201-09-16T17:59:38Z")]
#[test_case(16842189578, "2503-09-16T17:59:38Z")]
fn expect_rfc3339_moment(unix: i64, text: &'static str) -> crate::Result<()> {
let m = Moment::from_unix_seconds(unix);
let output = m.format_rfc3339();
let parsed = Moment::parse_rfc3339(text)?;
assert_eq!(output, text);
assert_eq!(unix, parsed.to_unix_seconds());
Ok(())
}
#[test_case(1970, "1970")]
#[test_case(2024, "2024")]
#[test_case(1900, "1900")]
#[test_case(-201, "-201")]
#[test_case(2503, "2503")]
fn expect_rfc3339_year(year: i32, text: &'static str) -> crate::Result<()> {
let y = Year::iso(year)?;
let output = y.format_rfc3339();
let parsed = Year::parse_rfc3339(text)?;
assert_eq!(output, text);
assert_eq!(year, parsed.number());
Ok(())
}
}