/
date_time_parser.ex
211 lines (174 loc) · 7.66 KB
/
date_time_parser.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
defmodule DateTimeParser do
@moduledoc "README.md" |> File.read!() |> String.split("<!-- MDOC -->") |> Enum.fetch!(1)
@external_resource "README.md"
import DateTimeParser.Formatters
alias DateTimeParser.Parser
@type assume_date :: {:assume_date, boolean() | Date.t()}
@type assume_time :: {:assume_time, boolean() | Time.t()}
@type assume_utc :: {:assume_utc, boolean()}
@type use_1904_date_system :: {:use_1904_date_system, boolean()}
@type to_utc :: {:to_utc, boolean()}
@typedoc "List of modules that implement the `DateTimeParser.Parser` behaviour."
@type parsers :: {:parsers, list(atom())}
@typedoc """
Options for `parse_datetime/2`
* `:assume_utc` Default `false`.
Only applicable for strings where parsing could not determine a timezone. Instead of returning a
NaiveDateTime, this option will assume them to be in UTC timezone, and therefore return a
DateTime. If the timezone is determined, then it will continue to be returned in the original
timezone. See `to_utc` option to also convert it to UTC.
* `:to_utc` Default `false`.
If there's a timezone detected in the string, then attempt to convert to UTC timezone. If you
know that your timestamps are in the future and are going to store it for later use, it may be
better to convert to UTC and keep the original timestamp since government organizations may change
timezone rules before the timestamp elapses, therefore making the UTC timestamp wrong or invalid.
[Check out the guide on future timestamps](./future-utc-datetime.html).
* `:assume_time` Default `false`.
If a time cannot be determined, then it will not be assumed by default. If you supply `true`, then
`~T[00:00:00]` will be assumed. You can also supply your own time, and the found tokens will be
merged with it.
* `:use_1904_date_system` Default `false`.
For Serial timestamps, the parser will use the 1900 Date System by default. If you supply `true`, then
the 1904 Date System will be used to parse the timestamp.
* `:parsers` The parsers to use when analyzing the string. When `Parser.Tokenizer`, the appropriate tokenizer
will be used depending on the function used and conditions found in the string. **Order matters**
and determines the order in which parsers are attempted. These are the available built-in parsers:
#{for parser <- DateTimeParser.Parser.builtin_parsers(), do: " * `#{inspect(parser)}`\n"}
This is the default in this order:
#{for parser <- DateTimeParser.Parser.default_parsers(), do: " 1. `#{inspect(parser)}`\n"}
"""
@type parse_datetime_options :: [
assume_utc() | to_utc() | assume_time() | use_1904_date_system() | parsers()
]
@typedoc """
Options for `parse_date/2`
* `:assume_date` Default `false`.
If a date cannot be fully determined, then it will not be assumed by default. If you supply
`true`, then `Date.utc_today()` will be assumed. You can also supply your own date, and the found
tokens will be merged with it.
"""
@type parse_date_options :: [assume_date() | parsers()]
@typedoc """
Options for `parse_time/2`.
See `t:parse_datetime_options/0` for further definition.
"""
@type parse_time_options :: [parsers()]
@typedoc """
Options for `parse/2`.
Combination of `t:parse_date_options/0` and `t:parse_datetime_options/0` and
`t:parse_time_options/0`
"""
@type parse_options :: parse_datetime_options() | parse_date_options() | parse_time_options()
@doc """
Parse a `%DateTime{}`, `%NaiveDateTime{}`, `%Date{}`, or `%Time{}` from a string.
Accepts `t:parse_options/0`
"""
@spec parse(String.t() | nil, parse_options()) ::
{:ok, DateTime.t() | NaiveDateTime.t() | Date.t() | Time.t()} | {:error, String.t()}
def parse(string, opts \\ [])
def parse(string, opts) when is_binary(string) do
with {:error, _} <- parse_datetime(string, opts),
{:error, _} <- parse_date(string, opts) do
parse_time(string, opts)
end
end
def parse(value, _opts), do: {:error, "Could not parse #{inspect(value)}"}
@doc """
Parse a `%DateTime{}`, `%NaiveDateTime{}`, `%Date{}`, or `%Time{}` from a string. Raises a
`DateTimeParser.ParseError` when parsing fails.
Accepts `t:parse_options/0`.
"""
@spec parse!(String.t() | nil, parse_options()) ::
DateTime.t() | NaiveDateTime.t() | Date.t() | Time.t() | no_return()
def parse!(string, opts \\ []) do
case parse(string, opts) do
{:ok, result} -> result
{:error, message} -> raise(__MODULE__.ParseError, message)
end
end
@doc """
Parse a `%DateTime{}` or `%NaiveDateTime{}` from a string.
Accepts options `t:parse_datetime_options/0`
"""
@spec parse_datetime(String.t() | nil, parse_datetime_options()) ::
{:ok, DateTime.t() | NaiveDateTime.t()} | {:error, String.t()}
def parse_datetime(string, opts \\ [])
def parse_datetime(string, opts) when is_binary(string) do
with {:ok, parser} <- string |> clean() |> Parser.build(:datetime, opts),
{:ok, datetime} <- parser.mod.parse(parser) do
{:ok, datetime}
else
_ ->
{:error, "Could not parse #{inspect(string)}"}
end
end
def parse_datetime(value, _opts), do: {:error, "Could not parse #{inspect(value)}"}
@doc """
Parse a `%DateTime{}` or `%NaiveDateTime{}` from a string. Raises a `DateTimeParser.ParseError` when
parsing fails.
Accepts options `t:parse_datetime_options/0`.
"""
@spec parse_datetime!(String.t() | nil, parse_datetime_options()) ::
DateTime.t() | NaiveDateTime.t() | no_return()
def parse_datetime!(string, opts \\ []) do
case parse_datetime(string, opts) do
{:ok, result} -> result
{:error, message} -> raise(__MODULE__.ParseError, message)
end
end
@doc """
Parse `%Time{}` from a string. Accepts options `t:parse_time_options/0`
"""
@spec parse_time(String.t() | nil, parse_time_options()) ::
{:ok, Time.t()} | {:error, String.t()}
def parse_time(string, opts \\ [])
def parse_time(string, opts) when is_binary(string) do
with {:ok, parser} <- string |> clean() |> Parser.build(:time, opts),
{:ok, time} <- parser.mod.parse(parser) do
{:ok, time}
else
_ ->
{:error, "Could not parse #{inspect(string)}"}
end
end
def parse_time(value, _opts), do: {:error, "Could not parse #{inspect(value)}"}
@doc """
Parse `%Time{}` from a string. Raises a `DateTimeParser.ParseError` when parsing fails.
"""
@spec parse_time!(String.t() | nil, parse_time_options()) :: Time.t() | no_return()
def parse_time!(string, opts \\ []) do
case parse_time(string, opts) do
{:ok, result} -> result
{:error, message} -> raise(__MODULE__.ParseError, message)
end
end
@doc """
Parse `%Date{}` from a string.
Accepts options `t:parse_date_options/0`
"""
@spec parse_date(String.t() | nil, parse_date_options()) ::
{:ok, Date.t()} | {:error, String.t()}
def parse_date(string, opts \\ [])
def parse_date(string, opts) when is_binary(string) do
with {:ok, parser} <- string |> clean() |> Parser.build(:date, opts),
{:ok, date} <- parser.mod.parse(parser) do
{:ok, date}
else
_ ->
{:error, "Could not parse #{inspect(string)}"}
end
end
def parse_date(value, _opts), do: {:error, "Could not parse #{inspect(value)}"}
@doc """
Parse a `%Date{}` from a string. Raises a `DateTimeParser.ParseError` when parsing fails.
Accepts options `t:parse_date_options/0`.
"""
@spec parse_date!(String.t() | nil, parse_datetime_options()) ::
Date.t() | no_return()
def parse_date!(string, opts \\ []) do
case parse_date(string, opts) do
{:ok, result} -> result
{:error, message} -> raise(__MODULE__.ParseError, message)
end
end
end