///|
/// HTTP date helpers (RFC 7231 IMF-fixdate), used by `Last-Modified`,
/// `If-Modified-Since` and `If-Unmodified-Since` handling.
///|
let http_day_names : FixedArray[String] = [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
]
///|
let http_month_names : FixedArray[String] = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
]
///|
fn floor_div(a : Int64, b : Int64) -> Int64 {
let quotient = a / b
if a % b != 0 && (a < 0) != (b < 0) {
quotient - 1
} else {
quotient
}
}
///|
/// Days since 1970-01-01 to `(year, month, day)`, by Howard Hinnant's
/// `civil_from_days`.
fn civil_from_days(days : Int64) -> (Int64, Int64, Int64) {
let shifted = days + 719468L
let era = floor_div(
if shifted >= 0L {
shifted
} else {
shifted - 146096L
},
146097L,
)
let doe = shifted - era * 146097L
let yoe = (doe - doe / 1460L + doe / 36524L - doe / 146096L) / 365L
let year = yoe + era * 400L
let doy = doe - (365L * yoe + yoe / 4L - yoe / 100L)
let mp = (5L * doy + 2L) / 153L
let day = doy - (153L * mp + 2L) / 5L + 1L
let month = mp + (if mp < 10L { 3L } else { -9L })
(year + (if month <= 2L { 1L } else { 0L }), month, day)
}
///|
/// `(year, month, day)` to days since 1970-01-01, by Hinnant's
/// `days_from_civil`.
fn days_from_civil(year : Int64, month : Int64, day : Int64) -> Int64 {
let y = year - (if month <= 2L { 1L } else { 0L })
let era = floor_div(if y >= 0L { y } else { y - 399L }, 400L)
let yoe = y - era * 400L
let mp = if month > 2L { month - 3L } else { month + 9L }
let doy = (153L * mp + 2L) / 5L + day - 1L
let doe = yoe * 365L + yoe / 4L - yoe / 100L + doy
era * 146097L + doe - 719468L
}
///|
fn two_digits(value : Int64) -> String {
if value < 10L {
"0" + value.to_string()
} else {
value.to_string()
}
}
///|
/// Formats seconds since the Unix epoch as an IMF-fixdate, e.g.
/// `Sun, 06 Nov 1994 08:49:37 GMT`.
pub fn format_http_date(epoch_seconds : Int64) -> String {
let days = floor_div(epoch_seconds, 86400L)
let seconds = epoch_seconds - days * 86400L
let (year, month, day) = civil_from_days(days)
let weekday = ((days + 4L) % 7L + 7L) % 7L
let hour = seconds / 3600L
let minute = seconds % 3600L / 60L
let second = seconds % 60L
http_day_names[weekday.to_int()] +
", " +
two_digits(day) +
" " +
http_month_names[(month - 1).to_int()] +
" " +
year.to_string() +
" " +
two_digits(hour) +
":" +
two_digits(minute) +
":" +
two_digits(second) +
" GMT"
}
///|
fn parse_digits(text : String) -> Int64? {
if text == "" {
return None
}
let mut value : Int64 = 0
for ch in text {
let digit = ch.to_int() - 48
if digit < 0 || digit > 9 {
return None
}
value = value * 10 + digit.to_int64()
}
Some(value)
}
///|
/// Parses an IMF-fixdate into seconds since the epoch. `None` for any other
/// format, matching how Express falls back to ignoring the header.
pub fn parse_http_date(value : String) -> Int64? {
let tokens : Array[String] = []
for raw in value.split(" ") {
let token = raw.to_owned().trim().to_owned()
if token != "" {
tokens.push(token)
}
}
if tokens.length() < 5 {
return None
}
let day = match parse_digits(tokens[1]) {
Some(value) => value
None => return None
}
let month = month_index(tokens[2])
if month == 0 {
return None
}
let year = match parse_digits(tokens[3]) {
Some(value) => value
None => return None
}
let clock = tokens[4].split(":").to_array()
if clock.length() < 3 {
return None
}
let hour = match parse_digits(clock[0].to_owned()) {
Some(value) => value
None => return None
}
let minute = match parse_digits(clock[1].to_owned()) {
Some(value) => value
None => return None
}
let second = match parse_digits(clock[2].to_owned()) {
Some(value) => value
None => return None
}
let days = days_from_civil(year, month, day)
Some(days * 86400L + hour * 3600L + minute * 60L + second)
}
///|
fn month_index(name : String) -> Int64 {
let mut index = 0
while index < 12 {
if http_month_names[index].to_lower() == name.to_lower() {
return (index + 1).to_int64()
}
index = index + 1
}
0
}