# `Calendrical.Lunisolar`

Shared base implementation for Calendrical's lunisolar calendars.

A lunisolar calendar approximates the tropical year using lunar months,
inserting an intercalary (leap) month roughly every three years to keep the
calendar aligned with the seasons. Calendrical implements three lunisolar
calendars on top of this module:

* `Calendrical.Chinese` — observation point Beijing.

* `Calendrical.Korean` (Dangi) — observation point Seoul.

* `Calendrical.LunarJapanese` — observation point Tokyo.

Each implementation is a thin wrapper that supplies its epoch and an
observation-location function (latitude, longitude, altitude, time-zone
offset). All of the astronomical heavy lifting — winter solstice, mean and
true new moon, leap-month detection, sexagesimal cycle calculations — is
delegated to `Astro` and lives in this module.

This module is **not** intended to be called directly from application code.
Use one of the lunisolar calendar modules above and the standard `Date` and
`Calendar` APIs.

## Naming conventions

Several "year" and "month" concepts coexist in a lunisolar calendar. To keep
the code unambiguous, this module uses the following names consistently:

* `year` is the calendar year, counted as the number of years since the
  calendar's epoch.

* `cyclical_year` is the position within the 60-year sexagesimal cycle
  (1..60).

* `cycle` is the number of completed sexagesimal cycles since the epoch.

* `month` is the *ordinal* month of the calendar year — 1..12 in an ordinary
  year and 1..13 in a leap year. This is the value the `Calendar` behaviour
  expects.

* `lunar_month` is the *traditional* month number, 1..12 in all years, with
  leap months represented as `{month, :leap}`. This is the value users see
  in cultural contexts and the form returned by `Calendrical.localize/3`.

## References

* Reingold and Dershowitz, *Calendrical Calculations: The Ultimate Edition*,
  4th ed., chapters on the Chinese, Korean and Japanese calendars.

* The accompanying Common Lisp source distributed with the same book.

# `cycle`

```elixir
@type cycle() :: pos_integer()
```

A sexigesimal cycle number

# `lunar_month`

```elixir
@type lunar_month() :: Calendar.month() | {Calendar.month(), :leap}
```

A lunar month

# `chinese_new_year_for_gregorian_year`

Returns the day of the Lunar New Year in a Gregorian year, at the
calendar's place.

### Arguments

* `gregorian_year` is a Gregorian year.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ISO day number of the new year.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.chinese_new_year_for_gregorian_year(2023, location)
    738907

# `current_major_solar_term`

Returns the last Chinese major solar term (zhongqi) before
`iso_days`.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The major solar term, 1 to 12.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.current_major_solar_term(739_038, location)
    4

# `current_minor_solar_term`

Returns the last minor solar term (jieqi) before `iso_days`.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The minor solar term, 1 to 12.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.current_minor_solar_term(739_038, location)
    4

# `cyclical_date_to_iso_days`

```elixir
@spec cyclical_date_to_iso_days(
  integer(),
  integer(),
  integer(),
  integer(),
  integer(),
  (Astro.Time.time() -&gt;
     {Astro.angle(), Astro.angle(), Astro.meters(), Astro.Time.hours()})
) :: integer()
```

Returns the ISO day number of a lunisolar date whose year is given
as a cycle and the year within the cycle, and whose months are
ordinal.

### Arguments

* `cycle` is the 60-year cycle.

* `cyclical_year` is the year within the cycle, from `1` to `60`.

* `month` is the ordinal month of the year.

* `day` is the day of the month.

* `epoch` is the calendar's epoch in ISO days.

* `location_fun` is a function that takes a moment and returns the
  `{latitude, longitude, altitude, offset}` of the calendar's
  reference location.

### Returns

* The number of days since the ISO epoch, as an integer.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.cyclical_date_to_iso_days(78, 40, 1, 1, epoch, location)
    738907

# `date_from_iso_days`

Returns the lunisolar date of an ISO day number, with ordinal
months that run from 1 to 12, or 1 to 13 in a leap year.

### Arguments

* `iso_days` is the number of days since the ISO epoch.

* `epoch` is the calendar's epoch in ISO days.

* `location_fun` is a function that takes a moment and returns the
  `{latitude, longitude, altitude, offset}` of the calendar's
  reference location.

### Returns

* A `{year, month, day}` tuple, the year counted from the
  calendar's epoch.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.date_from_iso_days(738907, epoch, location)
    {4660, 1, 1}

# `date_to_iso_days`

```elixir
@spec date_to_iso_days(
  integer(),
  integer(),
  integer(),
  integer(),
  (Astro.Time.time() -&gt;
     {Astro.angle(), Astro.angle(), Astro.meters(), Astro.Time.hours()})
) ::
  integer()
```

Returns the ISO day number of a lunisolar date whose months are
ordinal: numbered 1 to 12, or 1 to 13 in a leap year, with the
leap month unmarked.

### Arguments

* `year` is the year counted from the calendar's epoch.

* `month` is the ordinal month of the year.

* `day` is the day of the month.

* `epoch` is the calendar's epoch in ISO days.

* `location_fun` is a function that takes a moment and returns the
  `{latitude, longitude, altitude, offset}` of the calendar's
  reference location.

### Returns

* The number of days since the ISO epoch, as an integer.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.date_to_iso_days(4660, 1, 1, epoch, location)
    738907

# `day_name`

Returns the sexagesimal name of a day.

### Arguments

* `date` is an ISO day number.

### Returns

* `{stem, branch}`.

### Examples

    # 22 January 2023, Chinese New Year, was a 庚辰 day
    iex> Calendrical.Lunisolar.day_name(738_907)
    {7, 5}

# `day_name_on_or_before`

Returns the latest day on or before `date` that has the given
sexagesimal name.

### Arguments

* `name` is a `{stem, branch}` name.

* `date` is an ISO day number.

### Returns

* The ISO day number of that day.

### Examples

    iex> Calendrical.Lunisolar.day_name_on_or_before({1, 1}, 738_907)
    738891

# `december_solstice_on_or_before`

Returns the day of the winter solstice on or before `iso_days`, at
the calendar's place.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ISO day number of the solstice.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.december_solstice_on_or_before(739_038, location)
    738876

# `gregorian_date_for_lunar`

Returns the Gregorian date for the lunar month and day in a
given Gregorian year.

### Arguments

* `gregorian_year` is a Gregorian year.

* `lunar_month` is a month of the lunar year that begins in that
  Gregorian year.

* `lunar_day` is a day of that month.

* `epoch` is the ISO day number of the first day of the calendar's
  year 1, as a calendar's `epoch/0` returns it.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* `{year, month, day}` in the Gregorian calendar.

Raises `Calendrical.InvalidDateError` when the lunar year has no such
month, or the month no such day.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.gregorian_date_for_lunar(2023, 1, 1, epoch, location)
    {2023, 1, 22}

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.gregorian_date_for_lunar(2024, {2, :leap}, 1, epoch, location)
    ** (Calendrical.InvalidDateError) [gregorian_year: 2024, lunar_month: {2, :leap}, lunar_day: 1] is not a date

# `leap_month`

Returns the ordinal number of a year's leap month.

### Arguments

* `year` is a year of the lunisolar calendar.

* `epoch` is the ISO day number of the first day of the calendar's
  year 1, as a calendar's `epoch/0` returns it.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ordinal month that is the leap month, counting it among the
  year's months, or `nil` if the year has none.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.leap_month(4660, epoch, location)
    3
    iex> Calendrical.Lunisolar.leap_month(4661, epoch, location)
    nil

# `leap_month?`

Returns whether the given month of a year is its leap month.

Approximately every three years (7 times in 19 years),
a leap month is added to the Chinese calendar.

To determine when, find the number of new moons between
the 11th month in one year and the 11th month in the
following year.

A leap month is inserted if there are 13 New Moons
from the start of the 11th month in the first year
to the start of the 11th month in the next year.

The Chinese calendar uses a solar term system
that has 12 principal terms to indicate when the Sun's
longitude is a multiple of 30 degrees.

Unlike all other months, the leap month does not
contain a principal term (Zhongqi).

### Arguments

* `year` is a year of the lunisolar calendar.

* `month` is an ordinal month of the year, counting the leap month.

* `epoch` is the ISO day number of the first day of the calendar's
  year 1, as a calendar's `epoch/0` returns it.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* `true` if `month` is the year's leap month, otherwise `false`.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.leap_month?(4660, 3, epoch, location)
    true
    iex> Calendrical.Lunisolar.leap_month?(4660, 4, epoch, location)
    false

# `leap_month?`

Returns whether `month` is the leap month of the year given as a
cycle and the year within the cycle.

### Arguments

* `cycle` is the 60-year cycle.

* `cyclical_year` is the year within the cycle, from `1` to `60`.

* `month` is the ordinal month of the year.

* `epoch` is the calendar's epoch in ISO days.

* `location_fun` is a function that takes a moment and returns the
  `{latitude, longitude, altitude, offset}` of the calendar's
  reference location.

### Returns

* `true` if `month` is the year's leap month, otherwise `false`.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.leap_month?(78, 40, 3, epoch, location)
    true

# `leap_year?`

Returns whether the given year is a leap year.

Leap years have 13 months. To determine if a year
is a leap year, calculate the number of new moons
between the 11th month in one year (i.e., the month
containing the Winter Solstice) and the 11th month
in the following year.

If there are 13 new moons from the start of the 11th
month in the first year to the start of the 11th
month in the second year, a leap month must be inserted.

In leap years, at least one month does not contain a
Principal Term. The first such month is the leap month.

The additional complexity is that a leap year is
calculated for the solar year, but the calendar
is managed in lunar years and months. Therefore when
a leap year is detected, the leap month could be in
the current lunar year or the next lunar year.

### Arguments

* `year` is a year of the lunisolar calendar.

* `epoch` is the ISO day number of the first day of the calendar's
  year 1, as a calendar's `epoch/0` returns it.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* `true` if the year has 13 months, otherwise `false`.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.leap_year?(4660, epoch, location)
    true
    iex> Calendrical.Lunisolar.leap_year?(4661, epoch, location)
    false

# `location`

Returns the place a lunisolar calendar is reckoned at on a day, as
the point Astro takes.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, as a calendar's
  `location/1` does.

### Returns

* A `Geo.PointZ` of the longitude, latitude and altitude, with the
  offset from UTC, in days, as its `:offset` property.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> %Geo.PointZ{properties: %{offset: offset}} = Calendrical.Lunisolar.location(738_907, location)
    iex> offset
    0.3333333333333333

# `major_solar_term_on_or_after`

Returns the moment of the first major solar term (zhongqi) on or
after `iso_days`. The major terms begin when the sun's longitude is
a multiple of 30 degrees.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The moment, in fractional ISO days.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.major_solar_term_on_or_after(739_038, location) |> floor()
    739057

# `midnight_in_location`

Returns the universal time of the (clock) midnight that begins
`iso_days` at the calendar's place.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The moment, in fractional ISO days.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.midnight_in_location(738_907, location)
    738906.6666666666

# `minor_solar_term_on_or_after`

Returns the moment of the first minor solar term (jieqi) on or
after `iso_days`. The minor terms begin when the sun's longitude is
an odd multiple of 15 degrees.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The moment, in fractional ISO days.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.minor_solar_term_on_or_after(739_038, location) |> floor()
    739042

# `month_name`

Returns the sexagesimal name of a month of a Chinese year.

### Arguments

* `month` is a month of the year.

* `year` is a year of the Chinese calendar.

### Returns

* `{stem, branch}`.

### Examples

    iex> Calendrical.Lunisolar.month_name(1, 4660)
    {1, 3}

# `name_difference`

Returns the number of names from one Lunar name to the next
occurrence of another.

### Arguments

* `c_name1` is a `{stem, branch}` name.

* `c_name2` is a `{stem, branch}` name.

### Returns

* The number of names, 1 to 60.

### Examples

    iex> Calendrical.Lunisolar.name_difference({1, 1}, {10, 4})
    39

# `new`

Creates a date in the lunisolar calendar as an ISO day number.

### Arguments

* `year` is a year of the lunisolar calendar.

* `lunar_month` is a month of the year, as a positive integer or as
  `{month, :leap}` for the leap month.

* `day` is a day of the month.

* `epoch` is the ISO day number of the first day of the calendar's
  year 1, as a calendar's `epoch/0` returns it.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ISO day number of the date.

* `{:error, %Calendrical.InvalidDateError{}}` if the year has no such
  month or the month no such day.

### Examples

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.new(4660, 1, 1, epoch, location)
    738907

    iex> epoch = Calendrical.Chinese.epoch()
    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.new(4660, 1, 31, epoch, location)
    {:error, %Calendrical.InvalidDateError{value: {4660, 1, 31}}}

# `new_moon_before`

Returns the day of the first new moon before `iso_days`, at the
calendar's place.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ISO day number of the new moon.

Raises `Calendrical.UnsupportedDateRangeError` for a day outside
Gregorian years -20,005 to 20,005.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.new_moon_before(739_038, location)
    739024

# `new_moon_on_or_after`

Returns the day of the first new moon on or after `iso_days`, at
the calendar's place.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ISO day number of the new moon.

Raises `Calendrical.UnsupportedDateRangeError` for a day outside
Gregorian years -20,005 to 20,005.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.new_moon_on_or_after(739_038, location)
    739054

# `new_year_in_sui`

Returns the day of the Lunar New Year in the sui (the period from
one winter solstice to the next) that contains `iso_days`.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ISO day number of the new year.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.new_year_in_sui(739_038, location)
    738907

# `new_year_on_or_before`

Returns the day of the Lunar New Year on or before `iso_days`, at
the calendar's place.

### Arguments

* `iso_days` is an ISO day number.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* The ISO day number of the new year.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.new_year_on_or_before(739_038, location)
    738907

# `no_major_solar_term?`

Returns whether the lunar month starting on `iso_days` has no
major solar term, as only a leap month has none.

### Arguments

* `iso_days` is the ISO day number of the first day of a lunar month.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* `true` if the month has no major solar term, otherwise `false`.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> Calendrical.Lunisolar.no_major_solar_term?(Date.to_gregorian_days(~D[2023-03-22]), location)
    true
    iex> Calendrical.Lunisolar.no_major_solar_term?(Date.to_gregorian_days(~D[2023-04-20]), location)
    false

# `prior_leap_month?`

Returns whether there is a leap month from the lunar month starting
on `m_prime` up to and including the lunar month starting on `m`.

### Arguments

* `m_prime` is the ISO day number of the first day of the earlier
  lunar month.

* `m` is the ISO day number of the first day of the later lunar
  month.

* `location_fun` is a 1-arity function of the ISO day number that
  returns `{latitude, longitude, altitude, offset}`, the place the
  calendar is reckoned at, as a calendar's `location/1` does.

### Returns

* `true` if a leap month falls in that span, otherwise `false`.

### Examples

    iex> location = &Calendrical.Chinese.location/1
    iex> new_year = Date.to_gregorian_days(~D[2023-01-22])
    iex> Calendrical.Lunisolar.prior_leap_month?(new_year, Date.to_gregorian_days(~D[2023-05-19]), location)
    true
    iex> Calendrical.Lunisolar.prior_leap_month?(new_year, Date.to_gregorian_days(~D[2023-02-20]), location)
    false

# `solar_longitude_on_or_after`

```elixir
@spec solar_longitude_on_or_after(Astro.angle(), number(), (Astro.Time.time() -&gt;
                                                        {Astro.angle(),
                                                         Astro.angle(),
                                                         Astro.meters(),
                                                         Astro.Time.hours()})) ::
  Astro.Time.time()
```

Return moment at `location` of the first date on or after
`iso_days` when the solar longitude
will be `lambda` degrees.

### Arguments

* `lambda` is the solar longitude in degrees.

* `iso_days` is the moment, in days since the ISO epoch, from which
  to search.

* `location_fun` is a function that takes a moment and returns the
  `{latitude, longitude, altitude, offset}` of the calendar's
  reference location.

### Returns

* The moment, in days since the ISO epoch at the location's standard
  time, when the sun reaches `lambda` degrees.

### Examples

    # 大寒 (sun at 300 degrees) falls on 2026-01-20 Beijing time
    iex> iso_days = Calendar.ISO.date_to_iso_days(2026, 1, 1)
    iex> location = &Calendrical.Chinese.location/1
    iex> moment = Calendrical.Lunisolar.solar_longitude_on_or_after(300, iso_days, location)
    iex> Calendar.ISO.date_from_iso_days(floor(moment))
    {2026, 1, 20}

# `solar_term`

The proleptic-Gregorian date on which the sun reaches the `index`-th solar
term (jié-qì) during Gregorian `year`, observed at `location_fun`.

The 24 solar terms are numbered from `lichun` (立春, index 1) at 315° solar
ecliptic longitude, then every 15° (`qingming` = 5 at 15°, `dongzhi` = 22 at
270°, …). The day is taken in the observer's local standard time, so the
meridian — supplied by the calendar's `location/1` — decides the civil date.

### Arguments

* `index` is the 1-based solar-term number, `1..24`.

* `year` is a proleptic-Gregorian year.

* `location_fun` is the observer's location function, taken from the lunisolar
  calendar (e.g. `&Calendrical.Chinese.location/1`).

### Returns

* `{:ok, t:Date.t/0}` in `Calendrical.Gregorian`.

* `{:error, %Calendrical.InvalidSolarTermError{}}` when `index` is not
  in `1..24`.

### Examples

    iex> Calendrical.Lunisolar.solar_term(5, 2025, &Calendrical.Chinese.location/1)
    {:ok, ~D[2025-04-04 Calendrical.Gregorian]}

# `solar_term_name`

```elixir
@spec solar_term_name(integer()) :: {:ok, String.t()} | {:error, Exception.t()}
```

The name of the solar term (jié-qì) at a 1-based index.

The 24 solar terms are numbered from `lichun` (立春, index 1) at 315° solar
ecliptic longitude, then every 15° — the same order `solar_term/3` computes a
term's date for, so an index that dates a term also names it.

### Arguments

* `index` is the 1-based solar-term number, `1..24`.

### Returns

* `{:ok, name}` with the term's romanised (pinyin) name, such as `"qingming"`.

* `{:error, %Calendrical.InvalidSolarTermError{}}` when `index` is not
  in `1..24`.

### Examples

    iex> Calendrical.Lunisolar.solar_term_name(1)
    {:ok, "lichun"}

    iex> Calendrical.Lunisolar.solar_term_name(5)
    {:ok, "qingming"}

    iex> Calendrical.Lunisolar.solar_term_name(22)
    {:ok, "dongzhi"}

    iex> Calendrical.Lunisolar.solar_term_name(0)
    {:error, %Calendrical.InvalidSolarTermError{index: 0}}

# `stem_and_branch`

Returns the name of a year in the Lunar sexagesimal cycle, as its
celestial stem and terrestrial branch.

### Arguments

* `year` is a position in the sexagesimal cycle, a lunisolar date as
  `{year, month, day}` or a traditional date as
  `{cycle, cyclical_year, month, leap_month?, day}`.

### Returns

* `{stem, branch}`, the stem 1 to 10 and the branch 1 to 12.

### Examples

    iex> Calendrical.Lunisolar.stem_and_branch(40)
    {10, 4}
    iex> Calendrical.Lunisolar.stem_and_branch({4660, 1, 1})
    {10, 4}

---

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