dnscrypt 0.1.0

A pure-Rust DNSCrypt v2 client library — sync and async support.
Documentation
//! Miscellaneous utility functions.

/// Returns `true` if `year` is a leap year in the proleptic Gregorian calendar.
fn is_leap_year(year: u64) -> bool {
    year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400))
}

/// Parse an RFC 7231 / HTTP `Date` header value into a Unix timestamp (seconds).
///
/// Handles the "preferred" format mandated by HTTP/1.1:
/// `<day-name>, <DD> <month> <YYYY> <HH>:<MM>:<SS> GMT`
///
/// Example: `"Mon, 01 Jun 2026 12:41:36 GMT"`
///
/// Returns `None` if the string does not match this format exactly.
///
/// # Note on efficiency
///
/// This implementation iterates over years from 1970 to compute days —
/// perfectly fine for one-off header parsing and avoids pulling in a date
/// library.
pub fn parse_http_date(date_str: &str) -> Option<u64> {
    let mut parts = date_str.split_whitespace();
    let _day_name = parts.next()?; // "Mon,"
    let day: u64 = parts.next()?.parse().ok()?; // "01"
    let month_str = parts.next()?; // "Jun"
    let year: u64 = parts.next()?.parse().ok()?; // "2026"
    let time_str = parts.next()?; // "12:41:36"

    let month: u64 = match month_str {
        "Jan" => 1,
        "Feb" => 2,
        "Mar" => 3,
        "Apr" => 4,
        "May" => 5,
        "Jun" => 6,
        "Jul" => 7,
        "Aug" => 8,
        "Sep" => 9,
        "Oct" => 10,
        "Nov" => 11,
        "Dec" => 12,
        _ => return None,
    };

    let mut t = time_str.split(':');
    let hour: u64 = t.next()?.parse().ok()?;
    let minute: u64 = t.next()?.parse().ok()?;
    let second: u64 = t.next()?.parse().ok()?;

    // Sum days from 1970-01-01.
    let mut days: u64 = (1970..year)
        .map(|y| if is_leap_year(y) { 366 } else { 365 })
        .sum();

    let month_days = if is_leap_year(year) {
        [0u64, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        [0u64, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };
    days += month_days[1..month as usize].iter().sum::<u64>();
    days += day - 1;

    Some(days * 86400 + hour * 3600 + minute * 60 + second)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_http_date() {
        // 1970-01-01 00:00:00 GMT = Unix epoch 0
        assert_eq!(parse_http_date("Thu, 01 Jan 1970 00:00:00 GMT"), Some(0));
        // 2026-06-01 12:41:36 GMT
        assert!(parse_http_date("Mon, 01 Jun 2026 12:41:36 GMT").is_some());
        // Garbage
        assert!(parse_http_date("not a date").is_none());
    }

    #[test]
    fn test_leap_year() {
        assert!(is_leap_year(2000));
        assert!(is_leap_year(2024));
        assert!(!is_leap_year(1900));
        assert!(!is_leap_year(2023));
    }
}