1defmodule Calendar.NaiveDateTime.Parse do 2 import Calendar.ParseUtil 3 4 @doc """ 5 Parse ASN.1 GeneralizedTime. 6 7 Returns tuple with {:ok, [NaiveDateTime], UTC offset (optional)} 8 9 ## Examples 10 11 iex> "19851106210627.3" |> asn1_generalized 12 {:ok, %NaiveDateTime{year: 1985, month: 11, day: 6, hour: 21, minute: 6, second: 27, microsecond: {300_000, 1}}, nil} 13 iex> "19851106210627.3Z" |> asn1_generalized 14 {:ok, %NaiveDateTime{year: 1985, month: 11, day: 6, hour: 21, minute: 6, second: 27, microsecond: {300_000, 1}}, 0} 15 iex> "19851106210627.3-5000" |> asn1_generalized 16 {:ok, %NaiveDateTime{year: 1985, month: 11, day: 6, hour: 21, minute: 6, second: 27, microsecond: {300_000, 1}}, -180000} 17 """ 18 def asn1_generalized(string) do 19 captured = string |> capture_generalized_time_string 20 if captured do 21 parse_captured_iso8601(captured, captured["z"], captured["offset_hours"], captured["offset_mins"]) 22 else 23 {:bad_format, nil, nil} 24 end 25 end 26 defp capture_generalized_time_string(string) do 27 ~r/(?<year>[\d]{4})(?<month>[\d]{2})(?<day>[\d]{2})(?<hour>[\d]{2})(?<min>[\d]{2})(?<sec>[\d]{2})(\.(?<fraction>[\d]+))?(?<z>[zZ])?((?<offset_sign>[\+\-])(?<offset_hours>[\d]{1,2})(?<offset_mins>[\d]{2}))?/ 28 |> Regex.named_captures(string) 29 end 30 31 @doc """ 32 Parses a "C time" string. 33 34 ## Examples 35 iex> Calendar.NaiveDateTime.Parse.asctime("Wed Apr 9 07:53:03 2003") 36 {:ok, %NaiveDateTime{year: 2003, month: 4, day: 9, hour: 7, minute: 53, second: 3, microsecond: {0, 0}}} 37 iex> asctime("Thu, Apr 10 07:53:03 2003") 38 {:ok, %NaiveDateTime{year: 2003, month: 4, day: 10, hour: 7, minute: 53, second: 3, microsecond: {0, 0}}} 39 """ 40 def asctime(string) do 41 cap = capture_asctime_string(string) 42 month_num = month_number_for_month_name(cap["month"]) 43 Calendar.NaiveDateTime.from_erl({{cap["year"]|>to_int, month_num, cap["day"]|>to_int}, {cap["hour"]|>to_int, cap["min"]|>to_int, cap["sec"]|>to_int}}) 44 end 45 46 @doc """ 47 Like `asctime/1`, but returns the result without tagging it with :ok. 48 49 ## Examples 50 iex> asctime!("Wed Apr 9 07:53:03 2003") 51 %NaiveDateTime{year: 2003, month: 4, day: 9, hour: 7, minute: 53, second: 3, microsecond: {0, 0}} 52 iex> asctime!("Thu, Apr 10 07:53:03 2003") 53 %NaiveDateTime{year: 2003, month: 4, day: 10, hour: 7, minute: 53, second: 3, microsecond: {0, 0}} 54 """ 55 def asctime!(string) do 56 {:ok, result} = asctime(string) 57 result 58 end 59 60 defp capture_asctime_string(string) do 61 ~r/(?<month>[^\d]{3})[\s]+(?<day>[\d]{1,2})[\s]+(?<hour>[\d]{2})[^\d]?(?<min>[\d]{2})[^\d]?(?<sec>[\d]{2})[^\d]?(?<year>[\d]{4})/ 62 |> Regex.named_captures(string) 63 end 64 65 @doc """ 66 Parses an ISO8601 datetime. Returns {:ok, NaiveDateTime struct, UTC offset in secods} 67 In case there is no UTC offset, the third element of the tuple will be nil. 68 69 ## Examples 70 71 # With offset 72 iex> iso8601("1996-12-19T16:39:57-0200") 73 {:ok, %NaiveDateTime{year: 1996, month: 12, day: 19, hour: 16, minute: 39, second: 57, microsecond: {0, 0}}, -7200} 74 75 # Without offset 76 iex> iso8601("1996-12-19T16:39:57") 77 {:ok, %NaiveDateTime{year: 1996, month: 12, day: 19, hour: 16, minute: 39, second: 57, microsecond: {0, 0}}, nil} 78 79 # With fractional seconds 80 iex> iso8601("1996-12-19T16:39:57.123") 81 {:ok, %NaiveDateTime{year: 1996, month: 12, day: 19, hour: 16, minute: 39, second: 57, microsecond: {123000, 3}}, nil} 82 83 # With fractional seconds 84 iex> iso8601("1996-12-19T16:39:57,123") 85 {:ok, %NaiveDateTime{year: 1996, month: 12, day: 19, hour: 16, minute: 39, second: 57, microsecond: {123000, 3}}, nil} 86 87 # With Z denoting 0 offset 88 iex> iso8601("1996-12-19T16:39:57Z") 89 {:ok, %NaiveDateTime{year: 1996, month: 12, day: 19, hour: 16, minute: 39, second: 57, microsecond: {0, 0}}, 0} 90 91 # Invalid date 92 iex> iso8601("1996-13-19T16:39:57Z") 93 {:error, :invalid_datetime, nil} 94 """ 95 def iso8601(string) do 96 captured = capture_iso8601_string(string) 97 if captured do 98 parse_captured_iso8601(captured, captured["z"], captured["offset_hours"], captured["offset_mins"]) 99 else 100 {:bad_format, nil, nil} 101 end 102 end 103 104 defp parse_captured_iso8601(captured, z, _, _) when z != "" do 105 parse_captured_iso8601(captured, "", "00", "00") 106 end 107 defp parse_captured_iso8601(captured, _z, "", "") do 108 {tag, ndt} = Calendar.NaiveDateTime.from_erl(erl_date_time_from_regex_map(captured), parse_fraction(captured["fraction"])) 109 {tag, ndt, nil} 110 end 111 defp parse_captured_iso8601(captured, _z, offset_hours, offset_mins) do 112 {tag, ndt} = Calendar.NaiveDateTime.from_erl(erl_date_time_from_regex_map(captured), parse_fraction(captured["fraction"])) 113 if tag == :ok do 114 {:ok, offset_in_seconds} = offset_from_captured(captured, offset_hours, offset_mins) 115 {tag, ndt, offset_in_seconds} 116 else 117 {tag, ndt, nil} 118 end 119 end 120 121 defp offset_from_captured(captured, offset_hours, offset_mins) do 122 offset_in_secs = hours_mins_to_secs!(offset_hours, offset_mins) 123 offset_in_secs = case captured["offset_sign"] do 124 "-" -> offset_in_secs*-1 125 _ -> offset_in_secs 126 end 127 {:ok, offset_in_secs} 128 end 129 130 defp capture_iso8601_string(string) do 131 ~r/(?<year>[\d]{4})[^\d]?(?<month>[\d]{2})[^\d]?(?<day>[\d]{2})[^\d](?<hour>[\d]{2})[^\d]?(?<min>[\d]{2})[^\d]?(?<sec>[\d]{2})([\.\,](?<fraction>[\d]+))?(?<z>[zZ])?((?<offset_sign>[\+\-])(?<offset_hours>[\d]{1,2}):?(?<offset_mins>[\d]{2}))?/ 132 |> Regex.named_captures(string) 133 end 134 135 defp erl_date_time_from_regex_map(mapped) do 136 erl_date_time_from_strings({{mapped["year"],mapped["month"],mapped["day"]},{mapped["hour"],mapped["min"],mapped["sec"]}}) 137 end 138 139 defp erl_date_time_from_strings({{year, month, date},{hour, min, sec}}) do 140 { {year|>to_int, month|>to_int, date|>to_int}, 141 {hour|>to_int, min|>to_int, sec|>to_int} } 142 end 143 144 defp parse_fraction(""), do: {0, 0} 145 # parse and return microseconds 146 defp parse_fraction(string) do 147 usec = String.slice(string, 0..5) 148 |> String.pad_trailing(6, "0") 149 |> Integer.parse 150 |> elem(0) 151 {usec, String.length(string)} 152 end 153end 154