Current section
Files
Jump to
Current section
Files
src/glrss_parser/rss/date.gleam
import gleam/int
import gleam/option.{type Option}
import gleam/result
import glrss_parser/util
import party as p
pub type RssDate {
RssDate(
day_of_week: Option(DayOfWeek),
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
offset_minutes: Int,
)
}
pub type DayOfWeek {
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
}
pub fn dow_from(str: String) -> Result(DayOfWeek, Nil) {
case str {
"Mon" | "Monday" -> Ok(Monday)
"Tue" | "Tuesday" -> Ok(Tuesday)
"Wed" | "Wednesday" -> Ok(Wednesday)
"Thu" | "Thursday" -> Ok(Thursday)
"Fri" | "Friday" -> Ok(Friday)
"Sat" | "Saturday" -> Ok(Saturday)
"Sun" | "Sunday" -> Ok(Sunday)
_ -> Error(Nil)
}
}
pub fn dow_to_short(from dow: DayOfWeek) -> String {
case dow {
Monday -> "Mon"
Tuesday -> "Tue"
Wednesday -> "Wed"
Thursday -> "Thu"
Friday -> "Fri"
Saturday -> "Sat"
Sunday -> "Sun"
}
}
pub fn empty() -> RssDate {
RssDate(option.None, 0, 0, 0, 0, 0, 0, 0)
}
/// Parses [RFC 822](https://www.rfc-editor.org/rfc/rfc822.html#section-5) dates.
/// Currently it does not use the minute zimezone offset.
pub fn parse(str: String) {
p.go(date_time(), str)
}
// #### SCARY PARSING STUFF #####
// [ day "," ] date time
fn date_time() {
//opt(day() |> t(w1())) |> t(date()) |> w1t(time())
use dow <- p.do(p.perhaps(day()))
use Date(day:, month:, year:) <- p.do(date())
use _ <- p.do(p.whitespace1())
use Time(hour: t_hour, zone: Zone(offset_minutes)) <- p.do(time())
let Hour(hour:, minute:, second:) = t_hour
let day_of_week =
dow
|> option.from_result()
|> option.flatten()
p.return(RssDate(
day_of_week:,
year:,
month:,
day:,
hour:,
minute:,
second:,
offset_minutes:,
))
}
/// day ","
/// day = "Mon" / "Tue" / "Wed" / "Thu"
/// / "Fri" / "Sat" / "Sun"
fn day() {
use str <- p.do(p.many1_concat(p.letter()))
use _ <- p.do(p.seq(p.char(","), p.whitespace1()))
str
|> dow_from()
|> option.from_result()
|> p.return()
}
// date = [d]d MMM [yy]yy
fn date() {
use day <- p.do(p_integer())
use _ <- p.do(p.whitespace1())
use month <- p.do(p_letters() |> p.map(month3_to_int))
use _ <- p.do(p.whitespace1())
use year <- p.do(p_integer())
case day, month, year {
Ok(day), Ok(month), Ok(year) -> p.return(Date(day:, month:, year:))
_, _, _ -> p.fail()
}
}
type Date {
Date(day: Int, month: Int, year: Int)
}
fn month3_to_int(month: String) {
case month {
"Jan" -> Ok(1)
"Feb" -> Ok(2)
"Mar" -> Ok(3)
"Apr" -> Ok(4)
"May" -> Ok(5)
"Jun" -> Ok(6)
"Jul" -> Ok(7)
"Aug" -> Ok(8)
"Sep" -> Ok(9)
"Oct" -> Ok(10)
"Nov" -> Ok(11)
"Dec" -> Ok(12)
_ -> Error(Nil)
}
}
fn time() {
use hour <- p.do(hour())
use _ <- p.do(p.whitespace1())
use zone <- p.do(zone())
p.return(Time(hour:, zone:))
}
type Time {
Time(hour: Hour, zone: Zone)
}
fn hour() {
let colon = p.char(":")
use hour <- p.do(p_integer())
use _ <- p.do(colon)
use minute <- p.do(p_integer())
use second <- p.do(p.perhaps(p.seq(colon, p_integer())))
let second = second |> result.flatten() |> result.unwrap(0)
case hour, minute {
Ok(hour), Ok(minute) -> p.return(Hour(hour:, minute:, second:))
_, _ -> p.fail()
}
}
type Hour {
Hour(hour: Int, minute: Int, second: Int)
}
fn zone() {
p.either(zone_str(), zone_int())
}
fn zone_str() {
let str_or_empty = fn(res) { result.unwrap(res, "") }
use c1 <- p.do(p.letter())
use c2 <- p.do(p.perhaps(p.letter()) |> p.map(str_or_empty))
use c3 <- p.do(p.perhaps(p.letter()) |> p.map(str_or_empty))
let zone = c1 <> c2 <> c3
case zone_str_to_hour(zone) {
Ok(hour) -> p.return(Zone(minutes: hour * 60))
Error(_) -> p.fail()
}
}
fn zone_int() {
let digit = p.digit()
use sign <- p.do(p.either(p.char("+"), p.char("-")))
use hour_tens <- p.do(digit)
use hour_ones <- p.do(digit)
use min_tens <- p.do(digit)
use min_ones <- p.do(digit)
use hour <- util.try_unwrap(int.parse(hour_tens <> hour_ones), p.fail())
use min <- result.try(int.parse(min_tens <> min_ones))
let sign = case sign {
"+" -> 1
"-" -> -1
_ -> panic as "unreachable"
}
Ok(p.return(Zone(minutes: sign * { hour * 60 + min })))
}
type Zone {
Zone(minutes: Int)
}
fn zone_str_to_hour(zone: String) {
case zone {
"Z" | "UT" | "GMT" -> Ok(0)
"A" -> Ok(-1)
"B" -> Ok(-2)
"C" -> Ok(-3)
"D" | "EDT" -> Ok(-4)
"E" | "EST" | "CDT" -> Ok(-5)
"F" | "CST" | "MDT" -> Ok(-6)
"G" | "MST" | "PDT" -> Ok(-7)
"H" | "PST" -> Ok(-8)
"I" -> Ok(-9)
"K" -> Ok(-10)
"L" -> Ok(-11)
"M" -> Ok(-12)
"N" -> Ok(1)
"O" -> Ok(2)
"P" -> Ok(3)
"Q" -> Ok(4)
"R" -> Ok(5)
"S" -> Ok(6)
"T" -> Ok(7)
"U" -> Ok(8)
"V" -> Ok(9)
"W" -> Ok(10)
"X" -> Ok(11)
"Y" -> Ok(12)
_ -> Error(Nil)
}
}
fn p_integer() {
p.digits() |> p.map(int.parse)
}
fn p_letters() {
p.many1_concat(p.letter())
}
// fn offset_to_utc(rss_date: RssDate, zone: Zone) -> RssDate {
// let RssDate(day_of_week:, year:, month:, day:, hour:, minute:, second:) =
// rss_date
// // let #(#(year, month, day), #(hour, minute, second)) =
// // do_offset_to_utc(
// // #(#(year, month, day), #(hour, minute, second)),
// // zone.minutes,
// // )
// let dt
// RssDate(day_of_week:, year:, month:, day:, hour:, minute:, second:)
// }
// @external(erlang, "glrss_parser_ffi", "offset_to_utc")
// @external(javascript, "../../../glrss_parser_ffi.mjs", "offsetToUtc")
// fn do_offset_to_utc(
// date_time: #(#(Int, Int, Int), #(Int, Int, Int)),
// offset_minutes: Int,
// ) -> #(#(Int, Int, Int), #(Int, Int, Int))