Calendrical.LunarJapanese (Calendrical v0.5.0)

Copy Markdown

Implementation of the Japanese lunisolar calendar.

In a normal Japanese lunisolar calendar, one year is divided into 12 months, with one month corresponding to the time between two full moons.

Since the cycle of the moon is not an even number of days, a month in the lunar calendar can vary between 29 and 30 days and a normal year can have 353, 354, or 355 days.

We define the epoch to the first new moon in the first year of the Taika era which is recorded as the first imperial era.

The epoch can be changed by setting the :lunar_japanese_epoch configuration key in config.exs:

# Alternative epoch starting from the reign of Emperor
# Huangdi
config :calendrical,
  lunar_japanese_epoch: ~D[0645-07-20]

Two month numbering conventions

The Japanese lunisolar calendar (like the Chinese and Korean ones) has two distinct month numbering conventions. Choosing the wrong one silently produces dates that are off by one full lunar month after the intercalary month in leap years. The conventions are:

  • Ordinal — months counted monotonically 1..12 in ordinary years and 1..13 in leap years. The intercalary appears at whatever position the astronomical no-zhongqi rule places it; it is not separately labelled. This is the convention Date.new/4 accepts, the Date.t struct stores, and Date.convert/2 returns. It is what the standard Calendar behaviour callbacks expect.

  • Traditional — months always numbered 1..12, with the intercalary expressed as {month, :leap} (read 閏N月 — "intercalary Nth month") where month is the preceding traditional month number. This is the convention used by primary chronicles and cultural references. Calendrical.LunarJapanese.new/3 and the return value of lunar_month_of_year/1 use this convention.

As an example, in lunar year 1210 (= AD 1854, 嘉永7 / Kaei 7) the intercalary is the 8th ordinal month, equivalent to the traditional notation {7, :leap} (閏7月):

# Build a date using traditional notation:
iex> {:ok, date} = Calendrical.LunarJapanese.new(1210, {7, :leap}, 1)
iex> Date.convert(date, Calendar.ISO)
{:ok, ~D[1854-08-24]}

# The same day via Date.new/4 uses ordinal numbering (m8 = the leap):
iex> {:ok, date} = Date.new(1210, 8, 1, Calendrical.LunarJapanese)
iex> Date.convert(date, Calendar.ISO)
{:ok, ~D[1854-08-24]}

# The Date.t struct stores months in ordinal form:
iex> {:ok, date} = Calendrical.LunarJapanese.new(1210, {7, :leap}, 1)
iex> {date.month, Calendrical.LunarJapanese.lunar_month_of_year(date)}
{8, {7, :leap}}

Converting between the two: if a year is a leap year, traditional numbers below the leap-month position equal the ordinal numbers, and traditional numbers at or above the leap-month position equal ordinal-minus-one. leap_month/1 returns the ordinal position of the intercalary; traditional_leap_month/1 returns the traditional number that the intercalary repeats.

Summary

Functions

Identifies whether this calendar is month or week based.

Returns the calendar year as displayed on rendered calendars.

Defines the CLDR calendar type for this calendar.

Returns the year in the lunisolar sexigesimal 60-year cycle.

Returns the cyclic year as displayed on rendered calendars.

Calculates the day and era from the given year, month, and day.

Calculates the day of the year from the given year, month, and day.

Returns how many days there are in the given month.

Returns how many days there are in the given year and month.

Returns the number days in a a week.

Returns the number days in a given year.

Returns the extended year as displayed on rendered calendars.

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

Calculates the ISO week of the year from the given year, month, and day.

Returns the ordinal position (1..13) of the leap month for a year, or nil if the year is not a leap year.

Returns a boolean indicating if the given year and month is a leap month.

Returns a boolean indicating if the given year and month is a leap month.

Returns a boolean indicating if the given year is a leap year.

Returns the lunar month of the year for a given date or year and month.

Returns a Date.Range.t/0 representing a given month of a year.

Returns the number of months in a leap year.

Returns the number of months in a normal year.

Returns the number of months in a given year.

Converts the t:Calendar.iso_days format to the datetime format specified by this calendar.

Returns the t:Calendar.iso_days format of the specified date.

Returns a Calendar.date/0 in the Calendrical.LunarJapanese calendar formed by a calendar year, a traditional lunar month number, and a day number.

Raising variant of new/3.

Returns the gregorian date of the Luanr New Year for a given gregorian year.

Returns the number of periods in a given year. A period corresponds to a month in month-based calendars and a week in week-based calendars.

Adds an increment number of date_parts to a year-month-day.

Returns a Date.Range.t/0 representing a given quarter of a year.

Returns the quarter of the year from the given year, month, and day.

Returns the related gregorain year as displayed on rendered calendars.

Returns the traditional number (1..12) of the leap month for a year, or nil if the year is not a leap year.

Determines if the date given is valid according to this calendar.

Returns a Date.Range.t/0 representing a given week of a year.

Calculates the week of the year from the given year, month, and day.

Calculates the week of the year from the given year, month, and day.

Returns the number of weeks in a given year.

Returns a Date.Range.t/0 representing a given year.

Calculates the year and era from the given year.

Calculates the year and era from the given date.

Functions

calendar_base()

Identifies whether this calendar is month or week based.

calendar_year(year, month, day)

@spec calendar_year(Calendar.year(), Calendar.month(), Calendar.day()) ::
  Calendar.year()

Returns the calendar year as displayed on rendered calendars.

cldr_calendar_type()

Defines the CLDR calendar type for this calendar.

This type is used in support of Calendrical. localize/3.

cyclic_year(date)

@spec cyclic_year(date :: Date.t()) :: Calendrical.Lunisolar.cycle()

Returns the year in the lunisolar sexigesimal 60-year cycle.

Traditionally years are numbered only within the cycle however in this implementation the year is an offset from the epoch date. It can be converted to the current year in the current cycle with this function.

The cycle year is commonly shown on lunisolar calendars and it forms part of the traditional Chinese zodiac.

Arguments

*year and month representing the calendar year and month.

Returns

  • the integer year within the sexigesimal cycle of 60 years.

Examples

iex> Calendrical.LunarJapanese.cyclic_year(~D[1380-04-01 Calendrical.LunarJapanese])
60

iex> Calendrical.LunarJapanese.cyclic_year(~D[1381-01-01 Calendrical.LunarJapanese])
1

iex> Calendrical.LunarJapanese.cyclic_year(~D[1200-01-02 Calendrical.LunarJapanese])
60

iex> Calendrical.LunarJapanese.cyclic_year(~D[1260-01-01 Calendrical.LunarJapanese])
60

cyclic_year(year, month)

cyclic_year(year, month, day)

@spec cyclic_year(Calendar.year(), Calendar.month(), Calendar.day()) ::
  Calendar.year()

Returns the cyclic year as displayed on rendered calendars.

date_to_iso_days(year, month, day)

day_of_era(year, month, day)

@spec day_of_era(Calendar.year(), Calendar.month(), Calendar.day()) ::
  {day :: Calendar.day(), era :: Calendar.era()}

Calculates the day and era from the given year, month, and day.

By default we consider on two eras: before the epoch and on-or-after the epoch.

day_of_year(year, month, day)

@spec day_of_year(Calendar.year(), Calendar.month(), Calendar.day()) :: Calendar.day()

Calculates the day of the year from the given year, month, and day.

days_in_month(month)

@spec days_in_month(Calendar.month()) ::
  Calendar.month()
  | {:ambiguous, Range.t() | [pos_integer()]}
  | {:error, :undefined}

Returns how many days there are in the given month.

Must be implemented in derived calendars because we cannot know what the calendar format is.

days_in_month(year, month)

@spec days_in_month(Calendar.year(), Calendar.month()) :: Calendar.month()

Returns how many days there are in the given year and month.

days_in_week()

Returns the number days in a a week.

days_in_year(year)

Returns the number days in a given year.

The year is the number of years since the epoch.

elapsed_years(cycle, cyclical_year)

epoch()

epoch_day_of_week()

extended_year(year, month, day)

@spec extended_year(Calendar.year(), Calendar.month(), Calendar.day()) ::
  Calendar.year()

Returns the extended year as displayed on rendered calendars.

first_day_of_week()

gregorian_date_for_lunar(gregorian_year, lunar_month, lunar_day)

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

Arguments

  • gregorian_year is any year in the Gregorian calendar.

  • lunar_month is either a cardinal month number between 1 and 12 or for a leap month the 2-tuple in the format {month, :leap}.

  • day is any day number valid for year and lunar_month.

Returns

  • A gregorian date t:Date.t().

iso_week_of_year(year, month, day)

@spec iso_week_of_year(Calendar.year(), Calendar.month(), Calendar.day()) ::
  {:error, :not_defined}

Calculates the ISO week of the year from the given year, month, and day.

By default this function always returns {:error, :not_defined}.

last_day_of_week()

leap_month(year)

@spec leap_month(date_or_year :: Date.t() | Calendar.year()) :: Calendar.month() | nil

Returns the ordinal position (1..13) of the leap month for a year, or nil if the year is not a leap year.

This is the position of the intercalary in the monotonic 1..13 ordinal sequence used by Date.t structs and the standard Calendar callbacks. See traditional_leap_month/1 for the traditional notation (the preceding-month number that the intercalary repeats).

Arguments

Returns

  • the ordinal position of the leap month (1..13), or

  • nil if there is no leap month in the given year.

Examples

# Y1379 is a leap year; the intercalary is at ordinal m3
# (= traditional {2, :leap})
iex> Calendrical.LunarJapanese.leap_month(1379)
3

iex> Calendrical.LunarJapanese.leap_month(~D[1379-13-29 Calendrical.LunarJapanese])
3

iex> Calendrical.LunarJapanese.leap_month(1380)
nil

leap_month?(date)

@spec leap_month?(date :: Date.t()) :: boolean()

Returns a boolean indicating if the given year and month is a leap month.

Arguements

Returns

  • A booelan indicating if the given year and month is a leap month.

Examples

iex> Calendrical.LunarJapanese.leap_month?(~D[1379-01-01 Calendrical.LunarJapanese])
false

iex> Calendrical.LunarJapanese.leap_month?(~D[1379-03-29 Calendrical.LunarJapanese])
true

leap_month?(year, month)

@spec leap_month?(year :: Calendar.year(), month :: Calendar.month()) :: boolean()

Returns a boolean indicating if the given year and month is a leap month.

Arguements

Returns

  • A booelan indicating if the given year and month is a leap month.

Examples

iex> Calendrical.LunarJapanese.leap_month?(1379, 1)
false

iex> Calendrical.LunarJapanese.leap_month?(1379, 3)
true

leap_year?(year)

@spec leap_year?(date_or_year :: Calendar.year() | Date.t()) :: boolean()

Returns a boolean indicating if 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

Returns

  • A booelan indicating if the given year is a leap year.

Examples

iex> Calendrical.LunarJapanese.leap_year?(1379)
true

iex> Calendrical.LunarJapanese.leap_year?(1378)
false

lunar_month_of_year(date)

@spec lunar_month_of_year(date :: Date.t()) :: Calendrical.Lunisolar.lunar_month()

Returns the lunar month of the year for a given date or year and month.

The lunar month number in the traditional lunisolar calendar is between 1 and 12 with a leap month added when there are 13 new moons between Winter solstices. This intercalary leap month is not representable in its traditional form in the Calendar.date/0 struct.

This function takes a date, or year and month, and returns either the month number between 1 and 12 or a 2-tuple representing the leap month. This 2-tuple looks like {month_number, :leap}.

The value returned from this function can be passed to Calendrical.LunarJapanese.new/3 to define a date using traditional lunar months.

Arguments

*year and month representing the calendar year and month.

Returns

  • the lunar month as either an integer between 1 and 12 or a tuple of the form {lunar_month, :leap}.

Examples

iex> Calendrical.LunarJapanese.lunar_month_of_year(~D[1379-02-01 Calendrical.LunarJapanese])
2

iex> Calendrical.LunarJapanese.lunar_month_of_year(~D[1379-03-01 Calendrical.LunarJapanese])
{2, :leap}

iex> Calendrical.LunarJapanese.lunar_month_of_year(~D[1379-04-01 Calendrical.LunarJapanese])
3

lunar_month_of_year(year, month)

@spec lunar_month_of_year(year :: Calendar.year(), month :: Calendar.month()) ::
  Calendrical.Lunisolar.lunar_month()

month(year, month)

Returns a Date.Range.t/0 representing a given month of a year.

months_in_leap_year()

Returns the number of months in a leap year.

months_in_ordinary_year()

Returns the number of months in a normal year.

months_in_year(year)

Returns the number of months in a given year.

naive_datetime_from_iso_days(arg)

Converts the t:Calendar.iso_days format to the datetime format specified by this calendar.

naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond)

Returns the t:Calendar.iso_days format of the specified date.

new(year, month, day)

@spec new(
  year :: Calendar.year(),
  month :: Calendrical.Lunisolar.lunar_month(),
  day :: Calendar.day()
) ::
  {:ok, Date.t()} | {:error, atom()}

Returns a Calendar.date/0 in the Calendrical.LunarJapanese calendar formed by a calendar year, a traditional lunar month number, and a day number.

The lunar month is that used in traditional lunisolar calendar notation. It is either a number between 1 and 12 (the number of months in an ordinary year) or a leap month specified by the 2-tuple {month, :leap} where month is the preceding traditional month number that the intercalary repeats (read 閏N月 — "intercalary Nth month"). See the moduledoc for the full ordinal-vs-traditional discussion.

This function is the right entry point for creating dates from primary chronicles, cultural references, or any other source that reports a lunisolar date in its native form. To build a date from an ordinal (1..13) month number — the form stored on the Date.t struct itself — use Date.new/4 instead.

Arguments

  • year is any year in the Calendrical.LunarJapanese calendar.

  • lunar_month is either a traditional month number between 1 and 12, or for an intercalary month the 2-tuple {month, :leap} where month is the preceding traditional month number.

  • day is any day number valid for year and lunar_month.

Returns

  • {:ok, date} where date.month is the ordinal position of the given lunar month within the year (1..12 in ordinary years, 1..13 in leap years), or

  • {:error, reason}.

Examples

# Lunar new year
iex> Calendrical.LunarJapanese.new(1379, 1, 1)
{:ok, ~D[1379-01-01 Calendrical.LunarJapanese]}

# First day of the intercalary 2nd month (閏2月) of Y1379 —
# traditional notation: {2, :leap}; the resulting date.month is
# the ordinal 3
iex> Calendrical.LunarJapanese.new(1379, {2, :leap}, 1)
{:ok, ~D[1379-03-01 Calendrical.LunarJapanese]}

# Kaei 7, traditional 11th month, day 27 (安政改元日) — Y1210 has
# an intercalary {7, :leap}, so traditional M11 is ordinal m12
iex> {:ok, date} = Calendrical.LunarJapanese.new(1210, 11, 27)
iex> Date.convert(date, Calendar.ISO)
{:ok, ~D[1855-01-15]}

new!(year, month, day)

@spec new!(
  year :: Calendar.year(),
  month :: Calendrical.Lunisolar.lunar_month(),
  day :: Calendar.day()
) ::
  Date.t()

Raising variant of new/3.

Raises ArgumentError if the date is not valid in this calendar.

new_year_for_gregorian_year(gregorian_year)

@spec new_year_for_gregorian_year(Calendar.year()) :: Date.t()

Returns the gregorian date of the Luanr New Year for a given gregorian year.

Arguments

  • gregorian_year is any year in the Gregorian calendar.

Returns

  • a Calendar.date/0 representing the Gregorian date of the lunar year of the given Gregorian year.

Example

iex> Calendrical.Chinese.new_year_for_gregorian_year(2021)
~D[2021-02-12]

iex> Calendrical.Chinese.new_year_for_gregorian_year(2022)
~D[2022-02-01]

iex> Calendrical.Chinese.new_year_for_gregorian_year(2023)
~D[2023-01-22]

periods_in_year(year)

Returns the number of periods in a given year. A period corresponds to a month in month-based calendars and a week in week-based calendars.

plus(year, month, day, date_part, increment, options \\ [])

Adds an increment number of date_parts to a year-month-day.

date_part can be :months only.

quarter(year, quarter)

Returns a Date.Range.t/0 representing a given quarter of a year.

quarter_of_year(year, month, day)

@spec quarter_of_year(Calendar.year(), Calendar.month(), Calendar.day()) ::
  Calendrical.quarter()

Returns the quarter of the year from the given year, month, and day.

traditional_leap_month(year)

@spec traditional_leap_month(date_or_year :: Date.t() | Calendar.year()) ::
  Calendar.month() | nil

Returns the traditional number (1..12) of the leap month for a year, or nil if the year is not a leap year.

The intercalary month in lunisolar tradition repeats the number of the preceding non-leap month. For example, the intercalary that appears at ordinal position 3 of Y1379 is written 閏2月 in traditional notation and used as {2, :leap} in this module's API.

Arguments

Returns

  • the traditional number of the leap month (1..12), or

  • nil if there is no leap month in the given year.

Examples

iex> Calendrical.LunarJapanese.traditional_leap_month(1379)
2

iex> Calendrical.LunarJapanese.traditional_leap_month(1210)
7

iex> Calendrical.LunarJapanese.traditional_leap_month(1380)
nil

valid_date?(year, month, day)

Determines if the date given is valid according to this calendar.

week(year, week)

Returns a Date.Range.t/0 representing a given week of a year.

week_of_month(year, month, day)

@spec week_of_month(Calendar.year(), Calendar.month(), Calendar.day()) ::
  {pos_integer(), pos_integer()} | {:error, :not_defined}

Calculates the week of the year from the given year, month, and day.

By default this function always returns {:error, :not_defined}.

week_of_year(year, month, day)

@spec week_of_year(Calendar.year(), Calendar.month(), Calendar.day()) ::
  {:error, :not_defined}

Calculates the week of the year from the given year, month, and day.

By default this function always returns {:error, :not_defined}.

weeks_in_year(year)

Returns the number of weeks in a given year.

year(year)

Returns a Date.Range.t/0 representing a given year.

year_of_era(year)

@spec year_of_era(Calendar.year()) :: {year :: Calendar.year(), era :: Calendar.era()}

Calculates the year and era from the given year.

year_of_era(year, month, day)

@spec year_of_era(Calendar.year(), Calendar.month(), Calendar.day()) ::
  {year :: Calendar.year(), era :: Calendar.era()}

Calculates the year and era from the given date.