Skip to content

elixir-cldr/cldr_calendars_lunisolar

Repository files navigation

Lunisolar Calendars

This library implements the Chinese, Japanese and Korean lunisolar calendars. Lunisolar calendars use the lunar cycle to define months but the solar cycle to define years. In reconciling these two cycles, occasionally one of the lunar months is extended to bring the cycles into alignment. Since the number of months in a year does not change (they are always numbered 1 to 12), the extended month is called a "leap month".

The traditional Chinese, Japanese and Korean calendars all use the same astronomical principles with the only difference being the reference point from which the observations are made and the preferred epoch date. Today, the Chinese calendar uses Beijing as the reference, the Japanese calendar uses Tokyo and the Korean calendar uses Seoul.

Installation

The minimum supported Elixir version is 1.12.

The package can be installed by adding ex_cldr_calendars_lunisolar and tz_world to the list of dependencies in mix.exs. Additionally, add either tzdata or tz as a time zone database.

def deps do
  [
    {:ex_cldr_calendars_lunisolar, "~> 1.0"},

    # Provides time zone lookup for astro
    # the is a transitive dependency that converts locations
    # time zones.
    {:tz_world, "~> 1.3"},

    # Choose tz or tzdata as time zone databases.
    {:tz, "~> 0.26"}
  ]
end

Documentation can be found at https://hexdocs.pm/ex_cldr_calendars_lunisolar.

Install the time zone geo data

In order to map a location to a time zone, the dependency tz_world needs a time zone geography data set to be donwloaded. This is done once with the following mix task:

mix tz_world.update

Usage

ex_cldr_calendars_lunisolar conforms to both the Calendar and Cldr.Calendar behaviours and therefore the functions in the Date, DateTime, NaiveDateTime, Time and Calendar functions are supported.

For Elixir version 1.12 and later Sigil_D supports user-defined calendars:

iex> ~D[4660-03-30 Cldr.Calendar.Chinese]
~D[4660-03-30 Cldr.Calendar.Chinese]

Lunisolar Date representation

Lunisolar calendars have a leap year when the lunar cycle falls too far out of alignment with the solar year. In those years, like Gregorian 2023, a leap month is inserted into the calendar. In 2023, the leap month is month 2 so the sequence of months goes "month 1" -> "month 2" -> "leap month 2" -> "month 3". The Elixir date structures can't accomodate this kind of annotation so the lunisolar calendar implementations in the library adopt a different approach. The month in the date struct is an ordinal month (ie considered the nth month) not the cardinal month as in other calendars. To create dates using the traditional lunisolar month notation see the next section.

This means that the month numbers in a lunisolar leap year are:

Calendar month Date struct month Example for Gregorian 2023 (Korean calendar 4356) using Date.to_string/2 in :ko locale
1 1 "4356. 1. 1."
2 2 "4356. 2. 1."
leap 2 3 "4356. 윤2. 1."
3 4 "4356. 3. 1."
4 5 "4356. 4. 1."

Dates with lunar months

Key events in China, Japan, Korea and other territories are defined by their lunar dates. Lunar new year is 01-01 (month-year), Buddha's birthday is celebrated on 04-08 and the Korean thanksgiving day is 08-15. Note that these month numbers do not map directly to the date struct's ordinal month numbers. To facilitate creating dates in the traditional notation, the functions Cldr.Calendar.Chinese.new/3, Cldr.Calendar.LunarJapanese.new/3 and Cldr.Calendar.Korean.new/3 are provided. The notation {lunar_month, :leap} is used to denote the leap month in a leap year.

# New Years day
iex> Cldr.Calendar.Chinese.new(4660, 1, 1)
{:ok, ~D[4660-01-01 Cldr.Calendar.Chinese]}

#Buddha's birthday
iex> Cldr.Calendar.LunarJapanese.new(1379, 4, 8)
{:ok, ~D[1379-05-08 Cldr.Calendar.LunarJapanese]}

# Korean thanksgiving day
iex> Cldr.Calendar.Korean.new(4356, 8, 15)
{:ok, ~D[4356-09-15 Cldr.Calendar.Korean]}

# A day in the leap month
iex> Cldr.Calendar.Chinese.new(4660, {3, :leap}, 1)
{:ok, ~D[4660-04-01 Cldr.Calendar.Chinese]}
iex> Cldr.Calendar.Chinese.new(4660, {4, :leap}, 1)
{:error, :invalid_date}

Localization

ex_cldr_calendars_lunisolar depends on ex_cldr_calendars which supports calendar localization. For full date and time formatting see ex_cldr_dates_times.

Basic localization is executed by the Cldr.Calendar.localize/3. For example:

# Months are ordinal numbers so in Gregorian 2023, Korean 4356
# the ordinal month 3 is the Korean leap month 2
iex> Cldr.Calendar.localize(~D[4356-03-01 Cldr.Calendar.Korean], :month, locale: :ko)
"윤2월"

# Since there is a leap month prior to ordinal month 4
# the month number localizes to 3
iex> Cldr.Calendar.localize(~D[4356-04-01 Cldr.Calendar.Korean], :month, locale: :ko)
"3월"

iex> Cldr.Calendar.localize(~D[4660-04-01 Cldr.Calendar.Chinese], :day_of_week)
"Thu"

iex> Cldr.Calendar.localize(~D[4660-04-01 Cldr.Calendar.Chinese], :day_of_week, locale: :zh)
"周四"

Relationship to other libraries

This library is part of the CLDR-based libraries for Elixir including: