# `GanttEx.Dates`
[🔗](https://github.com/rocket4ce/gantt_ex/blob/v0.1.0/lib/gantt_ex/dates.ex#L1)

Date arithmetic used across GanttEx.

GanttEx works with `NaiveDateTime` values internally, the same way the
original DHTMLX Gantt works with local `Date` objects: a Gantt chart shows
wall-clock time, so time zones are left to the host application.

Durations are always expressed as an integer amount of a *unit*:

  * `:minute`, `:hour`, `:day` and `:week` have a fixed length
  * `:month`, `:quarter` and `:year` follow the calendar (a month can have
    28 to 31 days)

## Examples

    iex> GanttEx.Dates.add(~N[2025-01-30 00:00:00], 1, :month)
    ~N[2025-02-28 00:00:00]

    iex> GanttEx.Dates.diff(~N[2025-01-01 00:00:00], ~N[2025-01-04 00:00:00], :day)
    3

    iex> GanttEx.Dates.floor(~N[2025-05-14 13:45:00], :week)
    ~N[2025-05-12 00:00:00]

# `unit`

```elixir
@type unit() :: :minute | :hour | :day | :week | :month | :quarter | :year
```

A duration unit.

# `add`

```elixir
@spec add(NaiveDateTime.t(), number(), unit()) :: NaiveDateTime.t()
```

Adds `amount` units to `datetime`. Negative amounts move backwards.

Calendar units clamp the day when the target month is shorter.

## Examples

    iex> GanttEx.Dates.add(~N[2025-01-01 00:00:00], 36, :hour)
    ~N[2025-01-02 12:00:00]

    iex> GanttEx.Dates.add(~N[2025-01-31 00:00:00], 1, :quarter)
    ~N[2025-04-30 00:00:00]

    iex> GanttEx.Dates.add(~N[2025-01-10 00:00:00], -1, :week)
    ~N[2025-01-03 00:00:00]

# `cast`

```elixir
@spec cast(term()) :: {:ok, NaiveDateTime.t() | nil} | :error
```

Casts a date-like value into a `NaiveDateTime` truncated to seconds.

Accepts `NaiveDateTime`, `DateTime` (converted to its wall-clock time),
`Date` (midnight), ISO 8601 strings (`"2025-01-31"`, `"2025-01-31 10:30"`,
`"2025-01-31T10:30:00"`) and the DHTMLX default format (`"31-01-2025"`,
`"31-01-2025 10:30"`). `nil` is returned as `{:ok, nil}`.

## Examples

    iex> GanttEx.Dates.cast(~D[2025-03-01])
    {:ok, ~N[2025-03-01 00:00:00]}

    iex> GanttEx.Dates.cast("2025-03-01 08:30")
    {:ok, ~N[2025-03-01 08:30:00]}

    iex> GanttEx.Dates.cast("01-03-2025")
    {:ok, ~N[2025-03-01 00:00:00]}

    iex> GanttEx.Dates.cast(nil)
    {:ok, nil}

    iex> GanttEx.Dates.cast("yesterday")
    :error

# `cast!`

```elixir
@spec cast!(term()) :: NaiveDateTime.t() | nil
```

Like `cast/1` but raises `ArgumentError` on invalid input.

    iex> GanttEx.Dates.cast!("2025-01-01")
    ~N[2025-01-01 00:00:00]

# `cast_unit`

```elixir
@spec cast_unit(term()) :: {:ok, unit()} | :error
```

Casts a unit given as atom or string.

    iex> GanttEx.Dates.cast_unit("hour")
    {:ok, :hour}

    iex> GanttEx.Dates.cast_unit(:day)
    {:ok, :day}

    iex> GanttEx.Dates.cast_unit("decade")
    :error

# `ceil`

```elixir
@spec ceil(NaiveDateTime.t(), unit(), keyword()) :: NaiveDateTime.t()
```

Rounds `datetime` up to the start of the next `unit`, unless it is already
aligned.

## Examples

    iex> GanttEx.Dates.ceil(~N[2025-05-14 13:45:10], :day)
    ~N[2025-05-15 00:00:00]

    iex> GanttEx.Dates.ceil(~N[2025-05-01 00:00:00], :month)
    ~N[2025-05-01 00:00:00]

# `diff`

```elixir
@spec diff(NaiveDateTime.t(), NaiveDateTime.t(), unit()) :: integer()
```

Returns the duration between `from` and `to` in `unit`, rounded to the
nearest integer. The result is negative when `to` is before `from`.

## Examples

    iex> GanttEx.Dates.diff(~N[2025-01-01 00:00:00], ~N[2025-01-01 18:00:00], :day)
    1

    iex> GanttEx.Dates.diff(~N[2025-01-15 00:00:00], ~N[2025-03-15 00:00:00], :month)
    2

    iex> GanttEx.Dates.diff(~N[2025-01-01 00:00:00], ~N[2024-12-31 00:00:00], :hour)
    -24

# `diff_float`

```elixir
@spec diff_float(NaiveDateTime.t(), NaiveDateTime.t(), unit()) :: float()
```

Like `diff/3` but returns the exact (fractional) amount.

    iex> GanttEx.Dates.diff_float(~N[2025-01-01 00:00:00], ~N[2025-01-01 12:00:00], :day)
    0.5

# `floor`

```elixir
@spec floor(NaiveDateTime.t(), unit(), keyword()) :: NaiveDateTime.t()
```

Rounds `datetime` down to the start of `unit`.

Weeks start on Monday by default; pass `week_start: 7` to start on Sunday
(ISO day numbers, `1` = Monday ... `7` = Sunday).

## Examples

    iex> GanttEx.Dates.floor(~N[2025-05-14 13:45:10], :hour)
    ~N[2025-05-14 13:00:00]

    iex> GanttEx.Dates.floor(~N[2025-05-14 13:45:10], :quarter)
    ~N[2025-04-01 00:00:00]

    iex> GanttEx.Dates.floor(~N[2025-05-14 13:45:10], :week, week_start: 7)
    ~N[2025-05-11 00:00:00]

# `from_seconds`

```elixir
@spec from_seconds(number()) :: NaiveDateTime.t()
```

Converts gregorian seconds back into a `NaiveDateTime`.

    iex> GanttEx.Dates.from_seconds(60)
    ~N[0000-01-01 00:01:00]

# `iso_week`

```elixir
@spec iso_week(NaiveDateTime.t() | Date.t()) :: 1..53
```

Returns the ISO 8601 week number of `datetime`.

    iex> GanttEx.Dates.iso_week(~N[2025-01-01 00:00:00])
    1

    iex> GanttEx.Dates.iso_week(~N[2024-12-30 00:00:00])
    1

# `max`

```elixir
@spec max(NaiveDateTime.t() | nil, NaiveDateTime.t() | nil) :: NaiveDateTime.t() | nil
```

Returns the latest of two datetimes (ignoring `nil`).

    iex> GanttEx.Dates.max(~N[2025-01-02 00:00:00], ~N[2025-01-01 00:00:00])
    ~N[2025-01-02 00:00:00]

# `min`

```elixir
@spec min(NaiveDateTime.t() | nil, NaiveDateTime.t() | nil) :: NaiveDateTime.t() | nil
```

Returns the earliest of two datetimes (ignoring `nil`).

    iex> GanttEx.Dates.min(~N[2025-01-02 00:00:00], ~N[2025-01-01 00:00:00])
    ~N[2025-01-01 00:00:00]

    iex> GanttEx.Dates.min(nil, ~N[2025-01-01 00:00:00])
    ~N[2025-01-01 00:00:00]

# `round`

```elixir
@spec round(NaiveDateTime.t(), unit(), keyword()) :: NaiveDateTime.t()
```

Rounds `datetime` to the nearest boundary of `unit`.

    iex> GanttEx.Dates.round(~N[2025-05-14 13:00:00], :day)
    ~N[2025-05-15 00:00:00]

    iex> GanttEx.Dates.round(~N[2025-05-14 11:59:00], :day)
    ~N[2025-05-14 00:00:00]

# `to_seconds`

```elixir
@spec to_seconds(NaiveDateTime.t()) :: integer()
```

Converts a `NaiveDateTime` into gregorian seconds (useful for fast math).

    iex> GanttEx.Dates.to_seconds(~N[0000-01-01 00:01:00])
    60

# `unit?`

```elixir
@spec unit?(term()) :: boolean()
```

Returns `true` if `unit` is a supported duration unit.

    iex> GanttEx.Dates.unit?(:day)
    true

    iex> GanttEx.Dates.unit?(:fortnight)
    false

# `unit_seconds`

```elixir
@spec unit_seconds(unit()) :: pos_integer() | nil
```

Returns the length in seconds of a fixed-length unit.

    iex> GanttEx.Dates.unit_seconds(:hour)
    3600

    iex> GanttEx.Dates.unit_seconds(:month)
    nil

# `units`

```elixir
@spec units() :: [unit()]
```

Returns all supported units, from the smallest to the largest.

    iex> GanttEx.Dates.units()
    [:minute, :hour, :day, :week, :month, :quarter, :year]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
