use std::fmt;
use std::time::Duration;
const SECONDS_IN_DAY: u64 = 86400;
fn is_leap_year(year: i64) -> bool {
if year % 4 != 0 {
false
} else if year % 100 != 0 {
true
} else if year % 400 != 0 {
false
} else {
true
}
}
fn days_in_year(year: i64) -> u32 {
if is_leap_year(year) {
366
} else {
365
}
}
const CYCLE_LEAP_YEARS: u32 = 400 / 4 - 400 / 100 + 400 / 400;
const CYCLE_DAYS: u32 = 400 * 365 + CYCLE_LEAP_YEARS;
const CYCLE_SECONDS: u64 = CYCLE_DAYS as u64 * SECONDS_IN_DAY;
const YEARS_1970_2000_SECONDS: u64 = 946684800;
const YEARS_1600_1970_SECONDS: u64 = CYCLE_SECONDS - YEARS_1970_2000_SECONDS;
#[cfg_attr(rustfmt, rustfmt_skip)]
static YEAR_DELTAS: [u8; 401] = [
0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10,
10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15,
15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20,
20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29,
29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34,
34, 35, 35, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39,
39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43, 44, 44, 44,
44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53,
53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58,
58, 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63,
63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 68,
68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77,
77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82,
82, 83, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87,
87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92,
92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 97, 97, 97, 97,
];
pub struct TmUtc {
year: i64,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
nanos: u32,
}
#[derive(Debug, thiserror::Error)]
pub enum Rfc3339ParseError {
#[error("Unexpected EOF")]
UnexpectedEof,
#[error("Trailing characters")]
TrailngCharacters,
#[error("Expecting digits")]
ExpectingDigits,
#[error("Expecting character: {:?}", .0)]
ExpectingChar(char),
#[error("Expecting timezone")]
ExpectingTimezone,
#[error("No digits after dot")]
NoDigitsAfterDot,
#[error("Date-time field is out of range")]
DateTimeFieldOutOfRange,
#[error("Expecting date-time separator")]
ExpectingDateTimeSeparator,
}
pub type Rfc3339ParseResult<A> = Result<A, Rfc3339ParseError>;
impl TmUtc {
fn day_of_cycle_to_year_day_of_year(day_of_cycle: u32) -> (i64, u32) {
debug_assert!(day_of_cycle < CYCLE_DAYS);
let mut year_mod_400 = (day_of_cycle / 365) as i64;
let mut day_or_year = (day_of_cycle % 365) as u32;
let delta = YEAR_DELTAS[year_mod_400 as usize] as u32;
if day_or_year < delta {
year_mod_400 -= 1;
day_or_year += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32;
} else {
day_or_year -= delta;
}
(year_mod_400, day_or_year)
}
fn year_day_of_year_to_day_of_cycle(year_mod_400: u32, day_of_year: u32) -> u32 {
debug_assert!(year_mod_400 < 400);
debug_assert!(day_of_year < days_in_year(year_mod_400 as i64));
year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + day_of_year
}
fn second_of_day_to_h_m_s(seconds: u32) -> (u32, u32, u32) {
debug_assert!(seconds < 86400);
let hour = seconds / 3600;
let minute = seconds % 3600 / 60;
let second = seconds % 60;
(hour, minute, second)
}
fn h_m_s_to_second_of_day(hour: u32, minute: u32, second: u32) -> u32 {
debug_assert!(hour < 24);
debug_assert!(minute < 60);
debug_assert!(second < 60);
hour * 3600 + minute * 60 + second
}
fn days_in_months(year: i64) -> &'static [u32] {
if is_leap_year(year) {
&[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
} else {
&[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
}
}
fn day_of_year_to_month_day(year: i64, day_of_year: u32) -> (u32, u32) {
debug_assert!(day_of_year < days_in_year(year));
let days_in_months = TmUtc::days_in_months(year);
let mut rem_days = day_of_year;
let mut month = 1;
while rem_days >= days_in_months[month - 1] {
rem_days -= days_in_months[month - 1];
month += 1;
}
debug_assert!(rem_days + 1 <= days_in_months[month - 1]);
(month as u32, rem_days + 1)
}
fn month_day_to_day_of_year(year: i64, month: u32, day: u32) -> u32 {
debug_assert!(month >= 1);
debug_assert!(month <= 12);
debug_assert!(day >= 1);
let days_in_months = TmUtc::days_in_months(year);
let mut day_of_year = 0;
for next_month in 1..month {
day_of_year += days_in_months[next_month as usize - 1];
}
debug_assert!(day <= days_in_months[month as usize - 1]);
day_of_year + day - 1
}
fn from_cycle_start_add_duration(mut cycle_start: i64, add: Duration) -> TmUtc {
debug_assert!(cycle_start % 400 == 0);
let days = add.as_secs() / SECONDS_IN_DAY;
let duration_of_day = add - Duration::from_secs(days * SECONDS_IN_DAY);
let cycles = days / CYCLE_DAYS as u64;
cycle_start += cycles as i64 * 400;
let day_of_cycle = days % CYCLE_DAYS as u64;
let (year_mod_400, day_of_year) =
TmUtc::day_of_cycle_to_year_day_of_year(day_of_cycle as u32);
let (year,) = (cycle_start + year_mod_400,);
let (month, day) = TmUtc::day_of_year_to_month_day(year, day_of_year);
let (hour, minute, second) =
TmUtc::second_of_day_to_h_m_s(duration_of_day.as_secs() as u32);
TmUtc {
year,
month,
day,
hour,
minute,
second,
nanos: duration_of_day.subsec_nanos(),
}
}
pub fn from_protobuf_timestamp(seconds: i64, nanos: u32) -> TmUtc {
assert!(nanos <= 999_999_999);
let (mut year, mut seconds) = if seconds >= 0 {
(1970, seconds as u64)
} else {
let minus_seconds = if seconds == i64::MIN {
i64::MIN as u64
} else {
-seconds as u64
};
let cycles = (minus_seconds + CYCLE_SECONDS) / CYCLE_SECONDS;
(
1970 - 400 * cycles as i64,
cycles * CYCLE_SECONDS - minus_seconds,
)
};
year -= 370;
seconds += YEARS_1600_1970_SECONDS;
TmUtc::from_cycle_start_add_duration(year, Duration::new(seconds, nanos))
}
pub fn to_protobuf_timestamp(&self) -> (i64, u32) {
assert!(self.year >= 0);
assert!(self.year <= 9999);
let year_mod_400 = ((self.year % 400 + 400) % 400) as u32;
let cycle_start = self.year - year_mod_400 as i64;
let day_of_year = TmUtc::month_day_to_day_of_year(self.year, self.month, self.day);
let day_of_cycle = TmUtc::year_day_of_year_to_day_of_cycle(year_mod_400, day_of_year);
let second_of_day = TmUtc::h_m_s_to_second_of_day(self.hour, self.minute, self.second);
let second_of_cycle = day_of_cycle as u64 * SECONDS_IN_DAY + second_of_day as u64;
let epoch_seconds = (cycle_start - 1600) / 400 * CYCLE_SECONDS as i64
- YEARS_1600_1970_SECONDS as i64
+ second_of_cycle as i64;
(epoch_seconds, self.nanos)
}
pub fn parse_rfc_3339(s: &str) -> Rfc3339ParseResult<(i64, u32)> {
struct Parser<'a> {
s: &'a [u8],
pos: usize,
}
impl<'a> Parser<'a> {
fn next_number(&mut self, len: usize) -> Rfc3339ParseResult<u32> {
let end_pos = self.pos + len;
if end_pos > self.s.len() {
return Err(Rfc3339ParseError::UnexpectedEof);
}
let mut r = 0;
for i in 0..len {
let c = self.s[self.pos + i];
if c >= b'0' && c <= b'9' {
r = r * 10 + (c - b'0') as u32;
} else {
return Err(Rfc3339ParseError::ExpectingDigits);
}
}
self.pos += len;
Ok(r)
}
fn lookahead_char(&self) -> Rfc3339ParseResult<u8> {
if self.pos == self.s.len() {
return Err(Rfc3339ParseError::UnexpectedEof);
}
Ok(self.s[self.pos])
}
fn next_char(&mut self, expect: u8) -> Rfc3339ParseResult<()> {
assert!(expect < 0x80);
let c = self.lookahead_char()?;
if c != expect {
return Err(Rfc3339ParseError::ExpectingChar(expect as char));
}
self.pos += 1;
Ok(())
}
}
let mut parser = Parser {
s: s.as_bytes(),
pos: 0,
};
let year = parser.next_number(4)? as i64;
parser.next_char(b'-')?;
let month = parser.next_number(2)?;
parser.next_char(b'-')?;
let day = parser.next_number(2)?;
if month < 1 || month > 12 {
return Err(Rfc3339ParseError::DateTimeFieldOutOfRange);
}
if day < 1 || day > TmUtc::days_in_months(year as i64)[month as usize - 1] {
return Err(Rfc3339ParseError::DateTimeFieldOutOfRange);
}
match parser.lookahead_char()? {
b'T' | b't' | b' ' => parser.pos += 1,
_ => return Err(Rfc3339ParseError::ExpectingDateTimeSeparator),
}
let hour = parser.next_number(2)?;
parser.next_char(b':')?;
let minute = parser.next_number(2)?;
parser.next_char(b':')?;
let second = parser.next_number(2)?;
if hour > 23 || minute > 59 || second > 60 {
return Err(Rfc3339ParseError::DateTimeFieldOutOfRange);
}
let second = if second == 60 { 59 } else { second };
let nanos = if parser.lookahead_char()? == b'.' {
parser.pos += 1;
let mut digits = 0;
let mut nanos = 0;
while parser.lookahead_char()? >= b'0' && parser.lookahead_char()? <= b'9' {
let digit = (parser.lookahead_char()? - b'0') as u32;
parser.pos += 1;
if digits == 9 {
continue;
}
digits += 1;
nanos = nanos * 10 + digit;
}
if digits == 0 {
return Err(Rfc3339ParseError::NoDigitsAfterDot);
}
for _ in digits..9 {
nanos *= 10;
}
nanos
} else {
0
};
let offset_seconds = if parser.lookahead_char()? == b'Z' || parser.lookahead_char()? == b'z'
{
parser.pos += 1;
0
} else {
let sign = if parser.lookahead_char()? == b'+' {
1
} else if parser.lookahead_char()? == b'-' {
-1
} else {
return Err(Rfc3339ParseError::ExpectingTimezone);
};
parser.pos += 1;
let hour_offset = parser.next_number(2)?;
parser.next_char(b':')?;
let minute_offset = parser.next_number(2)?;
(hour_offset * 3600 + 60 * minute_offset) as i64 * sign
};
if parser.pos != parser.s.len() {
return Err(Rfc3339ParseError::TrailngCharacters);
}
let (seconds, nanos) = TmUtc {
year,
month,
day,
hour,
minute,
second,
nanos,
}
.to_protobuf_timestamp();
Ok((seconds - offset_seconds, nanos))
}
}
impl fmt::Display for TmUtc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.year > 9999 {
write!(f, "+{}", self.year)?;
} else if self.year < 0 {
write!(f, "{:05}", self.year)?;
} else {
write!(f, "{:04}", self.year)?;
}
write!(
f,
"-{:02}-{:02}T{:02}:{:02}:{:02}",
self.month, self.day, self.hour, self.minute, self.second
)?;
let subsec_digits = f.precision().unwrap_or(9);
if subsec_digits != 0 {
let mut subsec_digits = subsec_digits;
let width = if subsec_digits > 9 { 9 } else { subsec_digits };
let mut subsec = self.nanos;
for _ in width..9 {
subsec /= 10;
}
write!(f, ".{:0width$}", subsec, width = width as usize)?;
for _ in 9..subsec_digits {
write!(f, "0")?;
subsec_digits -= 1;
}
}
write!(f, "Z")
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_fmt() {
fn test_impl(expected: &str, secs: i64, nanos: u32, subsec_digits: u32) {
let tm_utc = TmUtc::from_protobuf_timestamp(secs, nanos);
assert_eq!(
expected,
format!("{:.prec$}", tm_utc, prec = subsec_digits as usize)
);
}
test_impl("1970-01-01T00:00:00Z", 0, 0, 0);
test_impl("2018-08-29T23:26:19Z", 1535585179, 0, 0);
test_impl("2018-08-29T23:26:19.123Z", 1535585179, 123456789, 3);
test_impl("1646-04-01T03:45:44Z", -10216613656, 0, 0);
test_impl("1970-01-01T00:00:00.000000001000Z", 0, 1, 12);
test_impl("5138-11-16T09:46:40Z", 100000000000, 0, 0);
test_impl("+33658-09-27T01:46:41Z", 1000000000001, 0, 0);
test_impl("0000-12-31T00:00:00Z", -62135683200, 0, 0);
test_impl("-0003-10-30T14:13:20Z", -62235683200, 0, 0);
test_impl("+2147485547-12-31T23:59:59Z", 67768036191676799, 0, 0);
test_impl("1969-12-31T23:59:59Z", -1, 0, 0);
test_impl("1969-12-31T23:59:00Z", -60, 0, 0);
test_impl("1969-12-31T23:59:58.900Z", -2, 900_000_000, 3);
test_impl("1966-10-31T14:13:20Z", -100000000, 0, 0);
test_impl("-29719-04-05T22:13:19Z", -1000000000001, 0, 0);
test_impl("-2147481748-01-01T00:00:00Z", -67768040609740800, 0, 0);
}
#[test]
fn test_parse_fmt() {
fn test_impl(s: &str, width: usize) {
let (seconds, nanos) = TmUtc::parse_rfc_3339(s).unwrap();
let formatted = format!(
"{:.width$}",
TmUtc::from_protobuf_timestamp(seconds, nanos),
width = width
);
assert_eq!(formatted, s);
}
test_impl("1970-01-01T00:00:00Z", 0);
test_impl("1970-01-01T00:00:00.000Z", 3);
test_impl("1970-01-01T00:00:00.000000000Z", 9);
test_impl("1970-01-02T00:00:00Z", 0);
test_impl("1970-03-01T00:00:00Z", 0);
test_impl("1974-01-01T00:00:00Z", 0);
test_impl("2018-01-01T00:00:00Z", 0);
test_impl("2018-09-02T05:49:10.123456789Z", 9);
test_impl("0001-01-01T00:00:00.000000000Z", 9);
test_impl("9999-12-31T23:59:59.999999999Z", 9);
}
#[test]
fn test_parse_alt() {
fn test_impl(alt: &str, parse: &str) {
let reference = TmUtc::parse_rfc_3339(alt).unwrap();
let parsed = TmUtc::parse_rfc_3339(parse).unwrap();
assert_eq!(reference, parsed, "{} - {}", alt, parse);
}
test_impl("1970-01-01 00:00:00Z", "1970-01-01T00:00:00Z");
test_impl("1970-01-01 00:00:00Z", "1970-01-01t00:00:00Z");
test_impl("1970-01-01 00:00:00Z", "1970-01-01 00:00:00z");
test_impl("2016-12-31 23:59:59Z", "2016-12-31 23:59:60Z");
test_impl("1970-01-01 00:00:00Z", "1970-01-01T03:00:00+03:00");
test_impl("1970-01-01 00:00:00Z", "1969-12-31 22:15:00-01:45");
}
#[test]
fn test_parse_incorrect_inputs() {
fn test_impl(s: &str) {
assert!(TmUtc::parse_rfc_3339(s).is_err(), "{}", s);
}
test_impl("1970-01-01T00:00:61Z");
test_impl("1970-01-01T00:60:61Z");
test_impl("1970-01-01T24:00:61Z");
test_impl("1970-01-01T00:00:00.Z");
test_impl("1970-01-32T00:00:00Z");
test_impl("1970-02-29T00:00:00Z");
test_impl("1980-02-30T00:00:00Z");
test_impl("1980-13-01T00:00:00Z");
test_impl("1970-01-01T00:00:00");
test_impl("1970-01-01T00:00Z");
}
#[test]
fn test_fmt_max_duration() {
assert_eq!(
"-292277022657-01-27T08:29:52.000000000Z",
format!("{}", TmUtc::from_protobuf_timestamp(i64::MIN, 0))
);
assert_eq!(
"+292277026596-12-04T15:30:07.999999999Z",
format!("{}", TmUtc::from_protobuf_timestamp(i64::MAX, 999_999_999))
);
}
}