# `Tempo.Holidays.Build`
[🔗](https://github.com/elixir-tempo/tempo_holidays/blob/v0.1.0/lib/tempo/holidays/build.ex#L1)

Build-time generation of the compiled holiday data.

Downloads a pinned [date-holidays](https://github.com/commenthol/date-holidays)
bundle and compiles every territory to one nested `priv/holidays/<CC>.etf` —
the country's holidays plus each state's and region's *own* holidays and the
rule keys they override, which `Tempo.Holidays.Data` merges at load time (so
the country's holidays are not duplicated into every state). The version is
pinned exactly (not a range), so a build is reproducible: the same input
yields the same output, and the data need not be vendored in git.

`ensure_built/0` runs from the `:holidays` Mix compiler and is a no-op once
the current pinned data is in place, in the current data format — so warm
builds, and every consumer build (where the etf ships in the package), do no
network I/O, while a change to the serialised structs regenerates it.
`build/1` forces a fresh download and is what `mix tempo.holidays.update` runs.

Each rule is read with `Tempo.DateRule` as it is built, to leave out one it
does not read, and reading a rule can work out a date: an equinox needs
Astro's ephemeris, and a date of another calendar needs Calendrical. So
`build/1` starts Tempo's applications (`:ex_tempo`, which starts Astro
before Calendrical) before it reads, as the `:holidays` compiler runs
before anything is started.

This module is Mix-only: it runs at the maintainer's build time, never at a
consumer's runtime.

# `error`

```elixir
@type error() ::
  {:invalid_version, term()}
  | {:directory, Path.t(), File.posix()}
  | {:download_failed, String.t(), term()}
  | {atom(), term()}
```

Why the data could not be built: a version that is not three numbers, a
directory that could not be made, a bundle that could not be downloaded,
or an application that could not be started.

# `build`

```elixir
@spec build(keyword()) :: {:ok, non_neg_integer(), [String.t()]} | {:error, error()}
```

Downloads a date-holidays release's bundle of holiday data and writes every
territory's data from it, whatever is already on disk.

### Arguments

* `options` is a keyword list of options.

### Options

* `:version` is the date-holidays release, three numbers such as
  `"3.38.0"`. The default is `pinned_version/0`.

* `:directory` is where the data is written. The default is
  `default_directory/0`.

* `:language` is the ISO 639-1 code of the holidays' names. The default is
  `"en"`.

### Returns

* `{:ok, territory_count, unsupported}`, where `unsupported` lists the
  rules of the release `Tempo.DateRule` does not read, whose holidays are
  left out of the data, or

* `{:error, reason}`: `{:invalid_version, version}`, `{:directory,
  directory, posix}`, `{:download_failed, url, reason}`, or an application
  that cannot be started. `message/1` says each in words.

# `default_directory`

```elixir
@spec default_directory() :: String.t()
```

The directory the package's own data is written to: its `priv/holidays`.

### Returns

* The directory, a path.

### Examples

    iex> Path.basename(Tempo.Holidays.Build.default_directory())
    "holidays"

# `ensure_built`

```elixir
@spec ensure_built() :: :noop | :ok | {:error, error()}
```

Builds the package's own data unless it is already built from the pinned
release, as the `:holidays` compiler asks at each compilation.

### Returns

* `:noop` where the data is in place: on a warm build, and on every build
  of an application where the data ships in the package, so neither
  touches the network, or

* `:ok` where it was built, or

* `{:error, reason}` as `build/1` returns it.

# `holidays_from_bundle`

```elixir
@spec holidays_from_bundle(map(), String.t(), String.t()) :: [
  Tempo.Holidays.Holiday.t()
]
```

Compile one territory's holidays from an already-decoded bundle.

Pure — no network or filesystem — so the compile step is testable on its
own. Returns `[]` for a territory the bundle does not carry.

### Arguments

* `bundle` is the decoded `holidays.json` map.

* `code` is the territory's CLDR/IANA code as a string, such as `"US"`.

* `language` is the ISO 639-1 code for name selection.

### Returns

* A list of `t:Tempo.Holidays.Holiday.t/0`.

# `message`

```elixir
@spec message(error()) :: String.t()
```

An error `build/1` returns, in words.

### Arguments

* `reason` is the reason of an `{:error, reason}` from `build/1` or
  `ensure_built/0`.

### Returns

* The message, a string.

### Examples

    iex> Tempo.Holidays.Build.message({:directory, "/x", :enotdir})
    "could not make /x: not a directory"

# `pinned_version`

```elixir
@spec pinned_version() :: String.t()
```

The date-holidays release the package's own data is built from.

### Returns

* The release, a version string.

### Examples

    iex> Tempo.Holidays.Build.pinned_version()
    "3.38.0"

# `source_url`

```elixir
@spec source_url() :: String.t()
```

The URL of the bundle of holiday data of the pinned date-holidays release.

### Returns

* The URL, a string.

### Examples

    iex> Tempo.Holidays.Build.source_url()
    "https://cdn.jsdelivr.net/npm/date-holidays@3.38.0/data/holidays.json"

# `source_url`

```elixir
@spec source_url(String.t()) :: String.t()
```

The URL of the bundle of holiday data of a date-holidays release.

### Arguments

* `version` is a date-holidays release, such as `"3.37.0"`.

### Returns

* The URL, a string.

### Examples

    iex> Tempo.Holidays.Build.source_url("3.37.0")
    "https://cdn.jsdelivr.net/npm/date-holidays@3.37.0/data/holidays.json"

---

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