Skip to content

Instantly share code, notes, and snippets.

@qcam
Last active January 4, 2024 10:37
Show Gist options
  • Save qcam/967fb541efc2dcc6073cf838c48abb9c to your computer and use it in GitHub Desktop.
Save qcam/967fb541efc2dcc6073cf838c48abb9c to your computer and use it in GitHub Desktop.
nimble_parsec example with RFC822 datetime parser (https://www.ietf.org/rfc/rfc822.txt)
defmodule RFC822 do
import NimbleParsec
day =
choice([
string("Mon"),
string("Tue"),
string("Wed"),
string("Thu"),
string("Fri"),
string("Sat"),
string("Sun")
])
|> string(",")
|> string(" ")
date =
integer(min: 1, max: 2)
|> ignore(string(" "))
|> choice([
string("Jan"),
string("Feb"),
string("Mar"),
string("Apr"),
string("May"),
string("Jun"),
string("Jul"),
string("Aug"),
string("Sep"),
string("Oct"),
string("Nov"),
string("Dec")
])
|> ignore(string(" "))
|> integer(2)
time =
integer(2)
|> ignore(string(":"))
|> integer(2)
|> ignore(string(":"))
|> optional(integer(2))
timezone =
choice([
string("EST"),
string("EDT"),
])
defparsec :rfc822,
optional(ignore(day))
|> concat(date)
|> ignore(string(" "))
|> concat(time)
|> ignore(string(" "))
|> concat(timezone)
def parse(binary) do
case rfc822(binary) do
{:ok, result, _, _, _, _} ->
# FIXME: irreponsible pattern matching.
[day, month, year, hour, minute, second, zone] = result
{:ok, naive_datetime} =
NaiveDateTime.new(year(year), month(month), day, hour, minute, second)
{
:ok,
naive_datetime
|> NaiveDateTime.add(zone_to_offset(zone), :second)
|> DateTime.from_naive!("Etc/UTC")
}
{:error, _, rest, _, _, _} ->
{:error, :invalid}
end
end
defp year(year), do: 2000 + year
defp month("Jan"), do: 1
defp month("Feb"), do: 2
defp month("Mar"), do: 3
defp month("Apr"), do: 4
defp month("May"), do: 5
defp month("Jun"), do: 6
defp month("Jul"), do: 7
defp month("Aug"), do: 8
defp month("Sep"), do: 9
defp month("Oct"), do: 10
defp month("Nov"), do: 11
defp month("Dec"), do: 12
defp zone_to_offset("EDT"), do: -4 * 60 * 60
defp zone_to_offset("EST"), do: -5 * 60 * 60
defp zone_to_offset("CST"), do: -6 * 60 * 60
# More zones to come
end
defmodule RFC822Test do
use ExUnit.Case
test "parses the string" do
string = "Wed, 02 Oct 02 08:00:00 EST"
assert {:ok, datetime} = RFC822.parse(string)
assert datetime.year == 2002
assert datetime.month == 10
assert datetime.day == 2
assert datetime.hour == 3
assert datetime.minute == 0
assert datetime.second == 0
string = "02 Oct 19 08:00:00 EST"
assert {:ok, datetime} = RFC822.parse(string)
assert datetime.year == 2019
assert datetime.month == 10
assert datetime.day == 2
assert datetime.hour == 3
assert datetime.minute == 0
assert datetime.second == 0
string = "Wednesday, 02 Oct 19 08:00:00 EST"
assert {:error, invalid} = RFC822.parse(string)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment