Skip to content

Instantly share code, notes, and snippets.

@sby1ce
Created September 20, 2025 19:51
Show Gist options
  • Select an option

  • Save sby1ce/bd5cd467982d172dd7d8d52b488a4176 to your computer and use it in GitHub Desktop.

Select an option

Save sby1ce/bd5cd467982d172dd7d8d52b488a4176 to your computer and use it in GitHub Desktop.
Incomplete RFC3339 parsing
use chrono::{format::Parsed, DateTime, NaiveDate, NaiveTime, Utc};
const INVALID: &str = "invalid";
const TOO_SHORT: &str = "too short";
struct Numbers {
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
}
fn parse(s: &str) -> Result<Numbers, &'static str> {
#[inline]
fn get_digit<T: From<u8>>(bytes: &[u8], index: usize) -> Result<T, &'static str> {
match bytes.get(index) {
Some(c) if c.is_ascii_digit() => Ok(T::from(c - b'0')),
_ => Err(INVALID),
}
}
let bytes: &[u8] = s.as_bytes();
// Y4 - M2 - D2 T H2 : m2 : S2 Z
if bytes.len() < 20 {
return Err(TOO_SHORT);
}
let year: i32 = {
let y1: i32 = get_digit(bytes, 0)?;
let y2: i32 = get_digit(bytes, 1)?;
let y3: i32 = get_digit(bytes, 2)?;
let y4: i32 = get_digit(bytes, 3)?;
y1 * 1000 + y2 * 100 + y3 * 10 + y4
};
let month: u32 = {
let m1: u32 = get_digit(bytes, 5)?;
let m2: u32 = get_digit(bytes, 6)?;
m1 * 10 + m2
};
let day: u32 = {
let d1: u32 = get_digit(bytes, 8)?;
let d2: u32 = get_digit(bytes, 9)?;
d1 * 10 + d2
};
let hour: u32 = {
let h1: u32 = get_digit(bytes, 11)?;
let h2: u32 = get_digit(bytes, 12)?;
h1 * 10 + h2
};
let minute: u32 = {
let m1: u32 = get_digit(bytes, 14)?;
let m2: u32 = get_digit(bytes, 15)?;
m1 * 10 + m2
};
let second: u32 = {
let m1: u32 = get_digit(bytes, 17)?;
let m2: u32 = get_digit(bytes, 18)?;
m1 * 10 + m2
};
if bytes.get(4) != Some(&b'-')
|| bytes.get(7) != Some(&b'-')
|| !matches!(bytes.get(10), Some(&b't' | &b'T' | &b' '))
|| bytes.get(13) != Some(&b':')
|| bytes.get(16) != Some(&b':')
{
return Err(INVALID);
}
Ok(Numbers { year, month, day, hour, minute, second })
}
pub fn using_parsed(s: &str) -> Result<DateTime<Utc>, &'static str> {
let Numbers { year, month, day, hour, minute, second } = parse(s)?;
let mut parsed = Parsed::new();
parsed.set_year(year.into()).unwrap();
parsed.set_month(month.into()).unwrap();
parsed.set_day(day.into()).unwrap();
parsed.set_hour(hour.into()).unwrap();
parsed.set_minute(minute.into()).unwrap();
parsed.set_second(second.into()).unwrap();
parsed.set_offset(0).unwrap();
Ok(parsed.to_datetime().unwrap().to_utc())
}
pub fn using_naive(s: &str) -> Result<DateTime<Utc>, &'static str> {
let Numbers { year, month, day, hour, minute, second } = parse(s)?;
let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
let time = NaiveTime::from_hms_opt(hour, minute, second).unwrap();
Ok(date.and_time(time).and_utc())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test() {
let datetime = Utc::now().to_rfc3339();
let parsed = using_parsed(&datetime).unwrap();
let naive = using_naive(&datetime).unwrap();
assert_eq!(parsed, naive);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment