defmodule Moment.Parser do def parse!(moment_str, format_str) do parse!(moment_str, format_str, Moment.now()) end def parse!(moment_str, format_str, default) do case parse(moment_str, format_str, default) do {:ok, moment} -> moment {:error, reason} -> raise reason end end def parse(moment_str, format_str) do parse(moment_str, format_str, Moment.now()) end def parse(moment_str, format_str, default) do case build_regex(format_str) do {:error, reason} -> {:error, reason} {:ok, regex} -> parse_with_regex(moment_str, regex, default) end end defp parse_with_regex(moment_str, regex, default) do case Regex.named_captures(regex, moment_str) do nil -> {:error, "regex and string do not match"} map -> {:ok, %Moment{year: get_year(map, default.year), month: get_month(map, default.month), day: get_day(map, default.day), hour: get_hour(map, 0), minute: get_minute(map, 0), second: get_second(map, 0), nanosecond: get_nanosecond(map, 0), offset: get_offset(map, default.offset)}} end end # Private # ------- defp build_regex!(format_str) do Regex.compile!(build_regex(format_str, "")) end defp build_regex(format_str) do Regex.compile(build_regex(format_str, "")) end defp build_regex("YYYY" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{4})") end defp build_regex("YY" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{2})") end defp build_regex("MM" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{2})") end defp build_regex("M" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{1,2})") end defp build_regex("DD" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?
\d{2})") end defp build_regex("D" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{1,2})") end defp build_regex("HH" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{2})") end defp build_regex("H" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{1,2})") end defp build_regex("mm" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{2})") end defp build_regex("m" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{1,2})") end defp build_regex("ss" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{2})") end defp build_regex("s" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{1,2})") end defp build_regex("SSSSSSSSS" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{9})") end defp build_regex("SSSSSS" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{6})") end defp build_regex("SSS" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{3})") end defp build_regex("SS" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{2})") end defp build_regex("S" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?\d{1})") end defp build_regex("ZZ" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?[+-]\d{4})") end defp build_regex("Z" <> rest, regex_str) do build_regex(rest, regex_str <> ~S"(?[+-]\d{2}\:\d{2})") end defp build_regex(<< h, rest :: binary >>, regex_str) do build_regex(rest, regex_str <> "\\" <> <>) end defp build_regex("", regex_str) do regex_str end # Map Analysis # ------------ defp get_year(map, default) do cond do Dict.has_key?(map, "YYYY") -> {year, _} = Integer.parse(Dict.get(map, "YYYY")) year Dict.has_key?(map, "YY") -> {yy, _} = Integer.parse(Dict.get(map, "YY")) if yy > 68, do: yy + 1900, else: yy + 2000 true -> default end end defp get_month(map, default) do cond do Dict.has_key?(map, "MM") -> {month, _} = Integer.parse(Dict.get(map, "MM")) month Dict.has_key?(map, "M") -> {month, _} = Integer.parse(Dict.get(map, "M")) month true -> default end end defp get_day(map, default) do cond do Dict.has_key?(map, "DD") -> {day, _} = Integer.parse(Dict.get(map, "DD")) day Dict.has_key?(map, "D") -> {day, _} = Integer.parse(Dict.get(map, "D")) day true -> default end end defp get_hour(map, default) do cond do Dict.has_key?(map, "HH") -> {hour, _} = Integer.parse(Dict.get(map, "HH")) hour Dict.has_key?(map, "H") -> {hour, _} = Integer.parse(Dict.get(map, "H")) hour true -> default end end defp get_minute(map, default) do cond do Dict.has_key?(map, "mm") -> {minute, _} = Integer.parse(Dict.get(map, "mm")) minute Dict.has_key?(map, "m") -> {minute, _} = Integer.parse(Dict.get(map, "m")) minute true -> default end end defp get_second(map, default) do cond do Dict.has_key?(map, "ss") -> {second, _} = Integer.parse(Dict.get(map, "ss")) second Dict.has_key?(map, "s") -> {second, _} = Integer.parse(Dict.get(map, "s")) second true -> default end end defp get_nanosecond(map, default) do cond do Dict.has_key?(map, "SSSSSSSSS") -> {nanosecond, _} = Integer.parse(Dict.get(map, "SSSSSSSSS")) nanosecond Dict.has_key?(map, "SSSSSS") -> {microsecond, _} = Integer.parse(Dict.get(map, "SSSSSS")) microsecond * 1_000 Dict.has_key?(map, "SSS") -> {millisecond, _} = Integer.parse(Dict.get(map, "SSS")) millisecond * 1_000_000 Dict.has_key?(map, "SS") -> {centisecond, _} = Integer.parse(Dict.get(map, "SS")) centisecond * 10_000_000 Dict.has_key?(map, "S") -> {decisecond, _} = Integer.parse(Dict.get(map, "S")) decisecond * 100_000_000 true -> default end end defp get_offset(map, default) do cond do Dict.has_key?(map, "Z") -> offset = Dict.get(map, "Z") parse_offset(String.slice(offset, 0..0), String.slice(offset, 1..2), String.slice(offset, 4..5)) Dict.has_key?(map, "ZZ") -> offset = Dict.get(map, "ZZ") parse_offset(String.slice(offset, 0..0), String.slice(offset, 1..2), String.slice(offset, 3..4)) true -> default end end defp parse_offset("+", hour_str, minute_str) do {h, _} = Integer.parse(hour_str) {m, _} = Integer.parse(minute_str) (h * 60 + m) end defp parse_offset("-", hour_str, minute_str) do {h, _} = Integer.parse(hour_str) {m, _} = Integer.parse(minute_str) -(h * 60 + m) end end