Quando

A Taskwarrior-style date expression parser for Elixir. Parse expressions like +7d, eom, monday, or P1Y2M3D into DateTime structs using NimbleParsec.

Installation

Add quando to your list of dependencies in mix.exs:

def deps do
  [
    {:quando, "~> 0.1.0"}
  ]
end

Usage

# Parse relative durations
{:ok, dt} = Quando.parse("+7d")      # 7 days from now
{:ok, dt} = Quando.parse("-2w")      # 2 weeks ago
{:ok, dt} = Quando.parse("+3m")      # 3 months from now (m = months)
{:ok, dt} = Quando.parse("30min")    # 30 minutes from now

# Parse chained durations
{:ok, dt} = Quando.parse("+1d+9h")   # Tomorrow at this time + 9 hours

# Parse date synonyms
{:ok, dt} = Quando.parse("today")    # Start of today
{:ok, dt} = Quando.parse("tomorrow") # Start of tomorrow

# Parse period boundaries
{:ok, dt} = Quando.parse("eom")      # End of month
{:ok, dt} = Quando.parse("sow")      # Start of week

# Parse day/month names
{:ok, dt} = Quando.parse("monday")   # Next Monday
{:ok, dt} = Quando.parse("fri")      # Next Friday
{:ok, dt} = Quando.parse("december") # December 1st

# Parse ordinals
{:ok, dt} = Quando.parse("15th")     # 15th of current/next month

# Parse ISO-8601 durations
{:ok, dt} = Quando.parse("P3D")      # 3 days from now
{:ok, dt} = Quando.parse("PT1H30M")  # 1 hour 30 minutes from now

# Bang version raises on error
dt = Quando.parse!("+7d")

# Get the parsed AST without calculation
{:ok, ast} = Quando.parse_ast("+7d")
# => {:ok, {:chained_duration, [{:duration, %{sign: :+, amount: 7, unit: :days}}]}}

Supported Syntax

Duration Offsets

Expression Description
+7d, -3d, 5d Days
+2w, -1w Weeks
+3m, -2m Months (note: m = months)
+1y, -2y Years
+5h, -2h Hours
30min, 45mins Minutes (use min/mins for minutes)
+30s, -15sec Seconds

Chained Durations

Chain multiple durations together:

Expression Description
+1d+9h Tomorrow plus 9 hours
-2w+3d Two weeks ago plus 3 days
+1y+2m+3d 1 year, 2 months, 3 days from now

Date Synonyms

Expression Description
now Current time
today Start of today (00:00:00)
yesterday Start of yesterday
tomorrow Start of tomorrow

Day Names

Full names and abbreviations are supported:

monday, mon, tuesday, tue, wednesday, wed, thursday, thu, friday, fri, saturday, sat, sunday, sun

Returns the next occurrence of the specified day.

Month Names

Full names and abbreviations are supported:

january, jan, february, feb, march, mar, april, apr, may, june, jun, july, jul, august, aug, september, sep, october, oct, november, nov, december, dec

Returns the 1st of the next occurrence of the specified month.

Period Boundaries

Expression Description
sow, socw Start of week
eow, eocw End of week
som, socm Start of month
eom, eocm End of month
soy, socy Start of year
eoy, eocy End of year
soq Start of quarter
eoq End of quarter
eod End of day

Ordinals

Expression Description
1st, 2nd, 3rd Day of month
15th, 31st Day of month

Returns the specified day of the current month if not yet passed, otherwise the next month.

ISO-8601 Durations

Expression Description
P3D 3 days
P2W 2 weeks
P1Y 1 year
P1Y2M3D 1 year, 2 months, 3 days
PT1H 1 hour
PT30M 30 minutes
PT45S 45 seconds
P1Y2M3DT12H40M50S Complex duration

Options

Reference Time

By default, calculations are relative to DateTime.utc_now(). You can specify a custom reference time:

reference = ~U[2026-06-15 10:00:00Z]
{:ok, dt} = Quando.parse("+7d", reference: reference)
# => ~U[2026-06-22 10:00:00Z]

Week Start

By default, weeks start on Monday (1). You can specify a different start day:

# Week starts on Sunday
{:ok, dt} = Quando.parse("sow", week_start: 7)

Key Disambiguation

Following Taskwarrior conventions:

This matches CLI tools where quick typing is important and months are more commonly referenced than minutes for task scheduling.

Reference

License

MIT - see LICENSE