Dayoff.Parser (Dayoff v0.2.1)

Copy Markdown View Source

Turns a rule string like "4th thursday in November" or "substitutes 12-25 if saturday then previous friday" into tokens.

A port of the reference implementation's Parser.js: an ordered list of anchored regular expressions is tried at the head of the remaining string, the first match wins and is consumed, spaces are skipped, and a pass that consumes nothing makes the rule unparseable. Two token reorderings follow, see reorder/1. The grammar is documented in the "Rules" guide.

Tokens are maps of three kinds:

  • %{fn: ...} produce candidate dates for a year (:gregorian, :julian, :easter, :equinox, :hebrew, :islamic, :jalaali, :chinese, :korean, :vietnamese, :bengali_revised)

  • %{rule: ...} move, filter or annotate those dates

  • %{modifier: ...} change how the following rules behave (:substitutes, :and, :if, :then, :if_equal)

    iex> Dayoff.Parser.parse!("12-25 and if sunday then next monday") [ %{fn: :gregorian, year: nil, month: 12, day: 25}, %{modifier: :and}, %{rule: :date_if_then, if: [:sunday], direction: :next, then: :monday, rules: []} ]

Summary

Types

A parsed token, see the module documentation.

Functions

Parses a rule string, {:error, :unparseable} when part of it matches no production.

The two reorderings the reference parser applies after tokenizing.

Types

token()

@type token() :: %{optional(atom()) => term()}

A parsed token, see the module documentation.

Functions

parse(rule)

@spec parse(String.t()) :: {:ok, [token()]} | {:error, :unparseable}

Parses a rule string, {:error, :unparseable} when part of it matches no production.

parse!(rule)

@spec parse!(String.t()) :: [token()]

Like parse/1 but raises ArgumentError.

reorder(tokens)

@spec reorder([token()]) :: [token()]

The two reorderings the reference parser applies after tokenizing.

Buffered date_dir tokens come out reversed after the next other token, so friday after 4th thursday in November evaluates November 1st, then the 4th Thursday, then the Friday after it. Modifiers written before the first fn token are rotated behind it, so substitutes 01-01 if ... applies the modifier after the date exists.

iex> Dayoff.Parser.parse!("friday after 4th thursday in November") |> Enum.map(&Map.take(&1, [:fn, :rule, :count, :weekday]))
[%{fn: :gregorian}, %{rule: :date_dir, count: 4, weekday: :thursday}, %{rule: :date_dir, count: 1, weekday: :friday}]

iex> Dayoff.Parser.parse!("substitutes 01-01 if Sunday then next Monday") |> Enum.map(&Map.take(&1, [:fn, :rule, :modifier]))
[%{fn: :gregorian}, %{modifier: :substitutes}, %{rule: :date_if_then}]