Packages
timex
3.0.8
3.7.13
3.7.12
3.7.11
3.7.9
3.7.8
3.7.7
3.7.6
3.7.5
3.7.3
3.7.2
3.7.1
3.7.0
3.6.4
3.6.3
3.6.2
retired
3.6.1
3.6.0
3.5.0
3.4.2
3.4.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.25
retired
3.1.24
3.1.23
3.1.22
3.1.21
3.1.20
3.1.19
3.1.18
3.1.17
3.1.16
3.1.15
3.1.13
3.1.12
3.1.11
3.1.10
3.1.9
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.2.1
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.0
1.0.2
1.0.1
1.0.0
1.0.0-rc4
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
1.0.0-pre
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.2
0.10.1
0.10.0
0.9.0
0.8.0
0.7.1
0.6.0
0.5.0
0.4.8
0.4.7
0.4.6
Timex is a rich, comprehensive Date/Time library for Elixir projects, with full timezone support via the :tzdata package. If you need to manipulate dates, times, datetimes, timestamps, etc., then Timex is for you!
Current section
Files
Jump to
Current section
Files
lib/parse/datetime/tokenizers/strftime.ex
defmodule Timex.Parse.DateTime.Tokenizers.Strftime do
@moduledoc """
Implements the parser for strftime-style datetime format strings.
"""
import Combine.Parsers.Base
import Combine.Parsers.Text
use Timex.Parse.DateTime.Tokenizer
@doc """
Tokenizes the given format string and returns an error or a list of directives.
"""
@spec tokenize(String.t) :: [Directive.t] | {:error, term}
def tokenize(<<>>), do: {:error, "Format string cannot be empty."}
def tokenize(str) do
case Combine.parse(str, strftime_format_parser()) do
results when is_list(results) ->
directives = results |> List.flatten |> Enum.filter(fn x -> x !== nil end)
case Enum.any?(directives, fn %Directive{type: type} -> type != :literal end) do
false -> {:error, "Invalid format string, must contain at least one directive."}
true -> {:ok, directives}
end
{:error, _} = err -> err
end
end
@doc """
Applies a given token + value to the DateTime represented by the current input string.
"""
@spec apply(DateTime.t, atom, term) :: {:ok, DateTime.t} | {:error, term} | :unrecognized
def apply(_, _, _), do: :unrecognized
defp flags(), do: map(one_of(char(), ["-", "0", "_"]), &map_flag/1)
defp min_width(), do: integer()
defp modifiers(), do: map(one_of(char(), ["E", "O"]), &map_modifier/1)
defp directives() do
choice([
one_of(char(), [
# Years/Centuries
"Y", "y", "C", "G", "g",
# Months
"m", "B", "b", "h",
# Days, Days of Week
"d", "e", "j", "u", "w", "A", "a",
# Weeks
"V", "W", "U",
# Time
"H", "k", "I", "l", "M", "S", "s", "P", "p", "f", "L",
# Timezones
"Z", "z",
# Compound
"D", "F", "R", "r", "T", "v"
]),
string(":z"),
string("::z")
])
end
defp strftime_format_parser() do
many1(choice([
# %<flag><width><modifier><directive>
pair_right(char("%"), pipe([option(flags()), option(min_width()), option(modifiers()), directives()], &coalesce_token/1)),
map(none_of(char(), ["%"]), &map_literal/1),
map(pair_left(char("%"), char("%")), &map_literal/1)
])) |> eof
end
defp coalesce_token([flags, width, modifiers, directive]) do
flags = flags || []
width = width || -1
modifiers = modifiers || []
map_directive(directive, [flags: flags, min_width: width, modifiers: modifiers])
end
defp map_directive(directive, opts) do
case directive do
# Years/Centuries
"Y" -> force_width(4, :year4, directive, opts)
"y" -> force_width(2, :year2, directive, opts)
"C" -> force_width(2, :century, directive, opts)
"G" -> force_width(4, :iso_year4, directive, opts)
"g" -> force_width(2, :iso_year2, directive, opts)
# Months
"m" -> force_width(2, :month, directive, opts)
"B" -> Directive.get(:mfull, directive, opts)
"b" -> Directive.get(:mshort, directive, opts)
"h" -> Directive.get(:mshort, directive, opts)
# Days
"d" -> force_width(2, :day, directive, opts)
"e" -> force_width(2, :day, directive, Keyword.merge(opts, flags: Keyword.merge([padding: :spaces], get_in(opts, [:flags]))))
"j" -> force_width(3, :oday, directive, opts)
# Weeks
"V" -> force_width(2, :iso_weeknum, directive, opts)
"W" -> force_width(2, :week_mon, directive, opts)
"U" -> force_width(2, :week_sun, directive, opts)
"u" -> Directive.get(:wday_mon, directive, opts)
"w" -> Directive.get(:wday_sun, directive, opts)
"a" -> Directive.get(:wdshort, directive, opts)
"A" -> Directive.get(:wdfull, directive, opts)
# Hours
"H" -> force_width(2, :hour24, directive, opts)
"k" -> force_width(2, :hour24, directive, Keyword.merge(opts, flags: Keyword.merge([padding: :spaces], get_in(opts, [:flags]))))
"I" -> force_width(2, :hour12, directive, opts)
"l" -> force_width(2, :hour12, directive, Keyword.merge(opts, flags: Keyword.merge([padding: :spaces], get_in(opts, [:flags]))))
"M" -> force_width(2, :min, directive, opts)
"S" -> force_width(2, :sec, directive, opts)
"s" -> Directive.get(:sec_epoch, directive, opts)
"P" -> Directive.get(:am, directive, opts)
"p" -> Directive.get(:AM, directive, opts)
"f" -> Directive.get(:us, directive, opts)
"L" -> force_width(3, :ms, directive, opts)
# Timezones
"Z" -> Directive.get(:zname, directive, opts)
"z" -> Directive.get(:zoffs, directive, opts)
":z" -> Directive.get(:zoffs_colon, directive, opts)
"::z" -> Directive.get(:zoffs_sec, directive, opts)
# Preformatted Directives
"D" -> Directive.get(:slashed, directive, opts)
"F" -> Directive.get(:iso_date, directive, opts)
"R" -> Directive.get(:strftime_iso_clock, directive, opts)
"r" -> Directive.get(:strftime_kitchen, directive, opts)
"T" -> Directive.get(:strftime_iso_clock_full, directive, opts)
"v" -> Directive.get(:strftime_iso_shortdate, directive, opts)
end
end
defp force_width(size, type, directive, opts) do
flags = Keyword.merge([padding: :zeroes], get_in(opts, [:flags]))
mods = get_in(opts, [:modifiers])
Directive.get(type, directive, [flags: flags, modifiers: mods, width: [min: size, max: size]])
end
defp map_literal([]), do: nil
defp map_literal(literals)
when is_list(literals), do: Enum.map(literals, &map_literal/1)
defp map_literal(literal), do: %Directive{type: :literal, value: literal, parser: char(literal)}
defp map_flag(flag) do
case flag do
"_" -> [padding: :spaces]
"-" -> [padding: :none]
"0" -> [padding: :zeroes]
"^" -> [transform: &String.upcase/1]
"#" -> [transform: &swap_case/1]
_ -> []
end
end
defp swap_case(<<char::utf8, _::binary>> = str)
when char in ?a..?z, do: String.upcase(str)
defp swap_case(<<char::utf8, _::binary>> = str)
when char in ?A..?Z, do: String.downcase(str)
defp swap_case(str), do: str
defp map_modifier(modifier) do
case modifier do
"E" -> [:locale_dependent_numerics]
"O" -> [:alternative_numerics]
_ -> []
end
end
end