How to validate a Chinese calendar calculation

Reference dates

Validate lunar-date conversion, solar-term timing, almanac classifications and interface behavior separately. Agreement in one part does not establish that the others are correct. A useful set of checks includes month and year boundaries as well as dates within a month.

Choose an independent reference with a stated calendar convention and coverage. A second website using the same calculation engine may reproduce the same errors. Record the reference edition or version alongside the expected values so that a later change can be traced.

The Atlas supports civil dates from AD 1 through 2100. Its pinned calculation engine uses Julian civil dates through 1582-10-04, followed by Gregorian 1582-10-15. October 5 through October 14 do not occur in that civil-date sequence. A comparison using the Gregorian calendar extended backwards before its introduction, called the proleptic Gregorian calendar, is testing a different mapping for earlier dates.

This convention describes the software's civil-date labels. It does not establish which calendar was historically used in China. Test the supported endpoints and the handling of dates outside them. A civil-date range is also different from a range of complete lunar years, whose boundaries fall on different days.

Store each result as the full civil date and calendar convention, lunar year, lunar month number, leap flag and lunar day. Distinguish the numbered month from its position in the year: leap month six is the seventh chronological month, but its month number remains six.

Test cases

Exact date checks

These dates cover Chinese New Year, a valid Gregorian leap day, and both ends of ordinary and leap lunar months. In this table, the lunar-year label is the Gregorian year in which that lunar year begins. “Ordinary” means the leap flag is false.

Gregorian dateLunar yearMonthKindLunar day
2024-02-09202312Ordinary30
2024-02-1020241Ordinary1
2024-02-2920241Ordinary20
2025-07-2420256Ordinary30
2025-07-2520256Leap1
2025-08-2220256Leap29
2025-08-2320257Ordinary1
2033-12-21203311Ordinary30
2033-12-22203311Leap1
2034-01-19203311Leap29
2034-01-20203312Ordinary1
2034-02-1920341Ordinary1

Run each date in both directions. Gregorian-to-lunar conversion should produce all four lunar fields, and lunar-to-Gregorian conversion should recover the stated date. Then test the whole sequence around the boundary, not only its first day. This distinguishes a wrong month start from a display label that drops the leap flag.

A round trip alone cannot establish accuracy. Two functions can share the same wrong table and still undo one another perfectly. Comparing against independent expected values is a separate check. An exhaustive comparison should record how many dates were compared, which reference was used and every mismatch.

Structural and input checks

CheckExpected behavior
Advance from the last day of a lunar monthDay one of the next chronological month, with the correct month number, leap flag and lunar year
Advance from an ordinary month into its leap monthRepeat the month number and change the leap flag to true
Advance from a leap month into the following monthClear the leap flag and advance the numbered month, including any required year rollover
Request day 30 of the 29-day leap month six in 2025Reject the nonexistent lunar date
Request a leap month in a year where that leap month does not occurReject it rather than substituting the ordinary month
Enter February 29, 2000, then February 29, 2100Accept the first Gregorian date and reject the second
Enter February 29, 1500 under the Atlas's Julian conventionAccept it as a valid Julian date
Advance one civil day from 1582-10-04 under the Atlas's cutover conventionReach 1582-10-15, exactly one elapsed day later
Convert a valid date to lunar and backRecover the original date without losing leap-month identity

The Julian calendar adds a leap day every fourth year. The Gregorian rule excludes century years unless they are divisible by 400, which is why a leap-day test must specify the civil calendar.

The rejection checks describe a strict date-entry interface. An underlying library may also offer documented normalization, such as rolling an out-of-range day into the next month. Test these behaviors separately rather than mistaking a normalized value for the original input.

Interface checks

Test language switching, browser refresh, month navigation and the Almanac entry separately from the conversion functions. A language switch should preserve the selected date and displayed month. Browsing another month should preserve the selected day until a new day is chosen. Check that the Almanac entry opens the device-local Gregorian date and that a permanent date page retains its written civil date in another time zone.

Include a test moment when the device-local date differs from the calendar's reference date. This catches the mistake of using the astronomy reference clock to choose the Almanac date, or using the browser's clock to reconstruct lunar-month boundaries.

Solar terms and boundaries

A solar-term comparison needs the term's identity, civil date, time, time standard and published precision. Put both values in the same civil calendar and time standard before comparing them. A time published to the minute cannot establish a match to the second.

Test the assigned calendar date separately from the timestamp tolerance. A difference of less than a minute may be acceptable for a rounded display yet put an event on the wrong side of midnight. Document any known uncertainty for that particular boundary instead of widening the tolerance for all dates.

Include a new moon and principal term occurring on the same reference date. The term belongs to the new calendar month even if its instant precedes the conjunction. A test based only on the order of event timestamps can miss this distinction.

For leap-month numbering, include the 2033–2034 sequence. Ordinary month eight of 2033 and month one of 2034 have no principal term, while the intervening leap month is eleven. A calculation that treats every month without a principal term as leap will fail these checks.

Almanac and historical checks

Almanac classifications

Yi means suitable activities and Ji means activities to avoid. Matching a new moon or lunar date does not validate these lists. Test their lookup rules against the selected almanac convention or dataset, then check the English activity labels separately. An explicit “none” result also needs to remain distinguishable from missing data.

Where an almanac classification changes at a solar term, record whether its boundary is the start of that term's calendar day or the term's exact instant. Test both sides of the chosen boundary. These month classifications need not change at the start of a lunar month.

Ganzhi, the stem-and-branch cycle, likewise needs a declared year boundary. A year label based on Chinese New Year and one based on Beginning of Spring, or Lichun, can differ during the interval between those boundaries. Compare each field using its stated convention.

Historical conversion

For a historical document, establish its dynasty or era year, locality, month designation, leap label and any recorded Ganzhi day before choosing a conversion method. Earlier calendars used different astronomical methods and time conventions. A modern calculation extended backwards may therefore differ from the calendar actually used for the record.

Keep the transcription, its interpretation and the converted date as separate fields. Where a document supplies both a numbered date and a Ganzhi day, test their agreement rather than discarding one to make the other fit.

AD 1–2100

Privacy Policy