Calendrical. Lunisolar
(Calendrical v2.2.0)
Copy Markdown
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:
yearis the calendar year, counted as the number of years since the calendar's epoch.cyclical_yearis the position within the 60-year sexagesimal cycle (1..60).cycleis the number of completed sexagesimal cycles since the epoch.monthis the ordinal month of the calendar year — 1..12 in an ordinary year and 1..13 in a leap year. This is the value theCalendarbehaviour expects.lunar_monthis 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 byCalendrical.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.
Summary
Functions
Returns the day of the Lunar New Year in a Gregorian year, at the calendar's place.
Returns the last Chinese major solar term (zhongqi) before
iso_days.
Returns the last minor solar term (jieqi) before iso_days.
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.
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.
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.
Returns the sexagesimal name of a day.
Returns the latest day on or before date that has the given
sexagesimal name.
Returns the day of the winter solstice on or before iso_days, at
the calendar's place.
Returns the Gregorian date for the lunar month and day in a given Gregorian year.
Returns the ordinal number of a year's leap month.
Returns whether the given month of a year is its leap month.
Returns whether month is the leap month of the year given as a
cycle and the year within the cycle.
Returns whether the given year is a leap year.
Returns the place a lunisolar calendar is reckoned at on a day, as the point Astro takes.
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.
Returns the universal time of the (clock) midnight that begins
iso_days at the calendar's place.
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.
Returns the sexagesimal name of a month of a Chinese year.
Returns the number of names from one Lunar name to the next occurrence of another.
Creates a date in the lunisolar calendar as an ISO day number.
Returns the day of the first new moon before iso_days, at the
calendar's place.
Returns the day of the first new moon on or after iso_days, at
the calendar's place.
Returns the day of the Lunar New Year in the sui (the period from
one winter solstice to the next) that contains iso_days.
Returns the day of the Lunar New Year on or before iso_days, at
the calendar's place.
Returns whether the lunar month starting on iso_days has no
major solar term, as only a leap month has none.
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.
Return moment at location of the first date on or after
iso_days when the solar longitude
will be lambda degrees.
The proleptic-Gregorian date on which the sun reaches the index-th solar
term (jié-qì) during Gregorian year, observed at location_fun.
The name of the solar term (jié-qì) at a 1-based index.
Returns the name of a year in the Lunar sexagesimal cycle, as its celestial stem and terrestrial branch.
Types
@type cycle() :: pos_integer()
A sexigesimal cycle number
@type lunar_month() :: Calendar.month() | {Calendar.month(), :leap}
A lunar month
Functions
Returns the day of the Lunar New Year in a Gregorian year, at the calendar's place.
Arguments
gregorian_yearis a Gregorian year.location_funis 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'slocation/1does.
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
Returns the last Chinese major solar term (zhongqi) before
iso_days.
Arguments
iso_daysis an ISO day number.location_funis 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'slocation/1does.
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
Returns the last minor solar term (jieqi) before iso_days.
Arguments
iso_daysis an ISO day number.location_funis 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'slocation/1does.
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
@spec cyclical_date_to_iso_days( integer(), integer(), integer(), integer(), integer(), (Astro.Time.time() -> {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
cycleis the 60-year cycle.cyclical_yearis the year within the cycle, from1to60.monthis the ordinal month of the year.dayis the day of the month.epochis the calendar's epoch in ISO days.location_funis 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
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_daysis the number of days since the ISO epoch.epochis the calendar's epoch in ISO days.location_funis 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}
@spec date_to_iso_days( integer(), integer(), integer(), integer(), (Astro.Time.time() -> {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
yearis the year counted from the calendar's epoch.monthis the ordinal month of the year.dayis the day of the month.epochis the calendar's epoch in ISO days.location_funis 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
Returns the sexagesimal name of a day.
Arguments
dateis 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}
Returns the latest day on or before date that has the given
sexagesimal name.
Arguments
nameis a{stem, branch}name.dateis 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
Returns the day of the winter solstice on or before iso_days, at
the calendar's place.
Arguments
iso_daysis an ISO day number.location_funis 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'slocation/1does.
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
Returns the Gregorian date for the lunar month and day in a given Gregorian year.
Arguments
gregorian_yearis a Gregorian year.lunar_monthis a month of the lunar year that begins in that Gregorian year.lunar_dayis a day of that month.epochis the ISO day number of the first day of the calendar's year 1, as a calendar'sepoch/0returns it.location_funis 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'slocation/1does.
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
Returns the ordinal number of a year's leap month.
Arguments
yearis a year of the lunisolar calendar.epochis the ISO day number of the first day of the calendar's year 1, as a calendar'sepoch/0returns it.location_funis 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'slocation/1does.
Returns
- The ordinal month that is the leap month, counting it among the
year's months, or
nilif 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
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
yearis a year of the lunisolar calendar.monthis an ordinal month of the year, counting the leap month.epochis the ISO day number of the first day of the calendar's year 1, as a calendar'sepoch/0returns it.location_funis 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'slocation/1does.
Returns
trueifmonthis the year's leap month, otherwisefalse.
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
Returns whether month is the leap month of the year given as a
cycle and the year within the cycle.
Arguments
cycleis the 60-year cycle.cyclical_yearis the year within the cycle, from1to60.monthis the ordinal month of the year.epochis the calendar's epoch in ISO days.location_funis a function that takes a moment and returns the{latitude, longitude, altitude, offset}of the calendar's reference location.
Returns
trueifmonthis the year's leap month, otherwisefalse.
Examples
iex> epoch = Calendrical.Chinese.epoch()
iex> location = &Calendrical.Chinese.location/1
iex> Calendrical.Lunisolar.leap_month?(78, 40, 3, epoch, location)
true
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
yearis a year of the lunisolar calendar.epochis the ISO day number of the first day of the calendar's year 1, as a calendar'sepoch/0returns it.location_funis 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'slocation/1does.
Returns
trueif the year has 13 months, otherwisefalse.
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
Returns the place a lunisolar calendar is reckoned at on a day, as the point Astro takes.
Arguments
iso_daysis an ISO day number.location_funis a 1-arity function of the ISO day number that returns{latitude, longitude, altitude, offset}, as a calendar'slocation/1does.
Returns
- A
Geo.PointZof the longitude, latitude and altitude, with the offset from UTC, in days, as its:offsetproperty.
Examples
iex> location = &Calendrical.Chinese.location/1
iex> %Geo.PointZ{properties: %{offset: offset}} = Calendrical.Lunisolar.location(738_907, location)
iex> offset
0.3333333333333333
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_daysis an ISO day number.location_funis 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'slocation/1does.
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
Returns the universal time of the (clock) midnight that begins
iso_days at the calendar's place.
Arguments
iso_daysis an ISO day number.location_funis 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'slocation/1does.
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
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_daysis an ISO day number.location_funis 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'slocation/1does.
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
Returns the sexagesimal name of a month of a Chinese year.
Arguments
monthis a month of the year.yearis a year of the Chinese calendar.
Returns
{stem, branch}.
Examples
iex> Calendrical.Lunisolar.month_name(1, 4660)
{1, 3}
Returns the number of names from one Lunar name to the next occurrence of another.
Arguments
c_name1is a{stem, branch}name.c_name2is a{stem, branch}name.
Returns
- The number of names, 1 to 60.
Examples
iex> Calendrical.Lunisolar.name_difference({1, 1}, {10, 4})
39
Creates a date in the lunisolar calendar as an ISO day number.
Arguments
yearis a year of the lunisolar calendar.lunar_monthis a month of the year, as a positive integer or as{month, :leap}for the leap month.dayis a day of the month.epochis the ISO day number of the first day of the calendar's year 1, as a calendar'sepoch/0returns it.location_funis 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'slocation/1does.
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}}}
Returns the day of the first new moon before iso_days, at the
calendar's place.
Arguments
iso_daysis an ISO day number.location_funis 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'slocation/1does.
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
Returns the day of the first new moon on or after iso_days, at
the calendar's place.
Arguments
iso_daysis an ISO day number.location_funis 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'slocation/1does.
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
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_daysis an ISO day number.location_funis 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'slocation/1does.
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
Returns the day of the Lunar New Year on or before iso_days, at
the calendar's place.
Arguments
iso_daysis an ISO day number.location_funis 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'slocation/1does.
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
Returns whether the lunar month starting on iso_days has no
major solar term, as only a leap month has none.
Arguments
iso_daysis the ISO day number of the first day of a lunar month.location_funis 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'slocation/1does.
Returns
trueif the month has no major solar term, otherwisefalse.
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
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_primeis the ISO day number of the first day of the earlier lunar month.mis the ISO day number of the first day of the later lunar month.location_funis 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'slocation/1does.
Returns
trueif a leap month falls in that span, otherwisefalse.
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
@spec solar_longitude_on_or_after(Astro.angle(), number(), (Astro.Time.time() -> {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
lambdais the solar longitude in degrees.iso_daysis the moment, in days since the ISO epoch, from which to search.location_funis 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
lambdadegrees.
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}
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
indexis the 1-based solar-term number,1..24.yearis a proleptic-Gregorian year.location_funis the observer's location function, taken from the lunisolar calendar (e.g.&Calendrical.Chinese.location/1).
Returns
{:ok, t:Date.t/0}inCalendrical.Gregorian.{:error, %Calendrical.InvalidSolarTermError{}}whenindexis not in1..24.
Examples
iex> Calendrical.Lunisolar.solar_term(5, 2025, &Calendrical.Chinese.location/1)
{:ok, ~D[2025-04-04 Calendrical.Gregorian]}
@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
indexis the 1-based solar-term number,1..24.
Returns
{:ok, name}with the term's romanised (pinyin) name, such as"qingming".{:error, %Calendrical.InvalidSolarTermError{}}whenindexis not in1..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}}
Returns the name of a year in the Lunar sexagesimal cycle, as its celestial stem and terrestrial branch.
Arguments
yearis 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}