Calendrical.Korean (Calendrical v0.5.0)

Copy Markdown

Implementation of the Korean lunisolar calendar.

In a ‘regular’ Korean 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.

The default epoch for the Korean lunisolar calendar is ~D[-2332-02-15] which is the traditional year of the founding of the first Korean nation. The epoch can be changed by setting the :korean_epoch configuration key in config.exs:

config :calendrical,
  korean_epoch: ~D[-2332-02-15]

Two month numbering conventions

The Korean lunisolar (Dangi) calendar (like the Japanese and Chinese 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월 in Korean — "intercalary Nth month") where month is the preceding traditional month number. This is the convention used by cultural references and primary sources. Calendrical.Korean.new/3 and the return value of lunar_month_of_year/1 use this convention.

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.Korean 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 Gregorian date of Korean thanksgiving (15th day of 8th lunar month) for a given Gregorian year.

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.Korean.cyclic_year(~D[4356-04-01 Calendrical.Korean])
36

iex> Calendrical.Korean.cyclic_year(~D[4357-01-01 Calendrical.Korean])
37

iex> Calendrical.Korean.cyclic_year(~D[4321-01-01 Calendrical.Korean])
1

iex> Calendrical.Korean.cyclic_year(~D[4320-01-01 Calendrical.Korean])
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.

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.

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

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

iex> Calendrical.Korean.leap_month(~D[4356-13-29 Calendrical.Korean])
3

iex> Calendrical.Korean.leap_month(4357)
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.Korean.leap_month?(~D[4356-01-01 Calendrical.Korean])
false

iex> Calendrical.Korean.leap_month?(~D[4356-03-29 Calendrical.Korean])
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.Korean.leap_month?(4356, 1)
false

iex> Calendrical.Korean.leap_month?(4356, 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.Korean.leap_year?(4356)
true

iex> Calendrical.Korean.leap_year?(4355)
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.Korean.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.Korean.lunar_month_of_year(~D[4356-02-01 Calendrical.Korean])
2

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

iex> Calendrical.Korean.lunar_month_of_year(~D[4356-04-01 Calendrical.Korean])
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.Korean 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월 in Korean — "intercalary Nth month"). See the moduledoc for the full ordinal-vs-traditional discussion.

This function is the right entry point for creating dates from cultural references, holidays defined in the lunisolar calendar, 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.Korean 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

# Buddha's birthday is the 8th day of traditional lunar month 4.
# In Y4356 (a leap year with the intercalary at ordinal m3),
# traditional M4 is at ordinal m5.
iex> Calendrical.Korean.new(4356, 4, 8)
{:ok, ~D[4356-05-08 Calendrical.Korean]}

# In an ordinary year, traditional M4 = ordinal m4.
iex> Calendrical.Korean.new(4355, 4, 8)
{:ok, ~D[4355-04-08 Calendrical.Korean]}

# First day of the intercalary 2nd month of Y4356
iex> Calendrical.Korean.new(4356, {2, :leap}, 1)
{:ok, ~D[4356-03-01 Calendrical.Korean]}

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.

thanksgiving_for_gregorian_year(gregorian_year)

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

Returns the Gregorian date of Korean thanksgiving (15th day of 8th lunar month) for a given Gregorian year.

Arguments

  • year is any year in the Gregorian calendar.

Returns

  • The Gregorian date of the Korean thanksgiving date for the given year.

Example

iex> Calendrical.Korean.thanksgiving_for_gregorian_year(2021)
~D[2021-09-21]

iex> Calendrical.Korean.thanksgiving_for_gregorian_year(2022)
~D[2022-09-10]

iex> Calendrical.Korean.thanksgiving_for_gregorian_year(2023)
~D[2023-09-29]

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 Y4356 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.Korean.traditional_leap_month(4356)
2

iex> Calendrical.Korean.traditional_leap_month(4357)
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.