thai_lunar

pub version license: MIT

A pure-Dart, dependency-free Thai lunar calendar (จันทรคติ / chantarakati, Suriyayatra) engine.

It converts between Gregorian and Thai lunar dates, classifies lunar year types (อธิกมาส athikamat / อธิกวาร athikavar), and computes the Buddhist holidays that fall on full moons — Makha Bucha, Visakha Bucha, Asalha Bucha — plus wan phra (Buddhist sabbath) days by the conventional civil lunar reckoning (waxing 8 & 15, waning 8, and the last day of the lunar month).

All arithmetic is integer — these are integer algorithms; floating point drifts.

Features

  • Gregorian → Thai lunar conversion (gregorianToThaiLunar) and the exact inverse (thaiLunarToGregorian) — round-trips losslessly for every day.
  • Lunar year classification: normal / athikavar (extra day) / athikamat (extra month) via lunarYearType, isAthikamat, isAthikavar.
  • Movable Buddhist holiday full moons via fullMoonOf (Makha Bucha date, Visakha Bucha date, Asalha Bucha date), with the athikamat month shift and the doubled-month-8 (secondEighth) handled for you.
  • Wan phra (Buddhist sabbath) detection: top-level isWanPhra(DateTime) and the computed ThaiLunarDate.isWanPhra getter.
  • เสาร์ห้า (Sao Ha) finder — the rare auspicious Saturday on the 5th of lunar month 5 — via isSaoHa, saoHaType, saoHaDatesIn and nextSaoHa.
  • A rich, value-equal ThaiLunarDate (== / hashCode / copyWith, Comparable so a List sorts chronologically) exposing beYear, ceYear, csYear, month, phase, day, isFullMoon, weekday, isSecondEighth and isYearEndWrap.
  • BE / CE era-aware API (ThaiEra) with beToCe / ceToBe helpers.
  • Zero dependencies, pure Dart.

Install

dependencies:
  thai_lunar: ^0.1.0

Usage

import 'package:thai_lunar/thai_lunar.dart';

// Gregorian -> Thai lunar
final lunar = gregorianToThaiLunar(DateTime.utc(2000, 1, 1));
print(lunar);                 // แรม 9 ค่ำ เดือน 1 พ.ศ. 2542
print(lunar.beYear);          // 2542  (Buddhist Era; the lunar year had not
print(lunar.ceYear);          // 1999  yet rolled over on 1 Jan)
print(lunar.csYear);          // 1361  (Chula Sakarat)
print(lunar.weekday);         // 6     (Saturday; 1=Mon..7=Sun)
print(lunar.isFullMoon);      // false
print(lunar.isWanPhra);       // false (computed from phase/day/month-length)

// Year classification (BE by default; pass era: ThaiEra.ce for CE years)
lunarYearType(2026, era: ThaiEra.ce);   // ThaiLunarYearType.extraMonth
isAthikamat(2026, era: ThaiEra.ce);     // true  (leap month: doubled month 8)
isAthikavar(2025, era: ThaiEra.ce);     // true  (leap day in month 7)

// Holidays — full moon of a lunar month, landing in the given calendar year.
// In an athikamat year the conventional months shift (Makha 3->4, Visakha
// 6->7, Asalha -> the SECOND month 8).
fullMoonOf(2026, 4, era: ThaiEra.ce);                 // Makha   2026-03-03
fullMoonOf(2026, 7, era: ThaiEra.ce);                 // Visakha 2026-05-31
final asalha = fullMoonOf(2026, 8, era: ThaiEra.ce, secondEighth: true); // 2026-07-29
final khaoPhansa = asalha.add(const Duration(days: 1));                  // 2026-07-30

// Wan phra (Buddhist sabbath)
isWanPhra(DateTime.utc(2026, 5, 31));   // true (Visakha full moon)

// Era helpers
beToCe(2569);   // 2026
ceToBe(2026);   // 2569

เสาร์ห้า (Sao Ha)

เสาร์ห้า is a Saturday that coincides with the 5th day of lunar month 5 (เดือน 5) — either the waxing 5th (ขึ้น 5 ค่ำ) or the waning 5th (แรม 5 ค่ำ). It is the strongest auspicious day in Thai belief for consecrating amulets and วัตถุมงคล (sacred objects). It is rare: it does not occur every year, arriving roughly once every 2–3 years. Two grades:

  • เสาร์ห้าใหญ่ (SaoHaType.major) — Saturday + ขึ้น 5 ค่ำ เดือน 5 (waxing). The rarer, "greater" grade.
  • เสาร์ห้าน้อย (SaoHaType.minor) — Saturday + แรม 5 ค่ำ เดือน 5 (waning).
// Classify a single day.
isSaoHa(DateTime.utc(2024, 4, 13));     // true
saoHaType(DateTime.utc(2024, 4, 13));   // SaoHaType.major  (ขึ้น 5 ค่ำ เดือน 5)
saoHaType(DateTime.utc(2011, 4, 23));   // SaoHaType.minor  (แรม 5 ค่ำ เดือน 5)
saoHaType(DateTime.utc(2024, 1, 6));    // null  (an ordinary Saturday)

// All เสาร์ห้า in a Gregorian year (usually none).
saoHaDatesIn(2024);   // [2024-04-13]  (major)
saoHaDatesIn(2022);   // []            (none that year)

// The next one on/after a date (searches forward ~30 years; null if none).
nextSaoHa(DateTime.utc(2021, 1, 1));    // 2024-04-13

Validated range

The engine is validated against the official Thai calendar for roughly 1900–2050 CE (the range over which the conversion and holiday APIs are tested and supported). The underlying arithmetic is a Suriyayatra reconstruction that runs beyond those bounds, but before the early-20th-century standardisation of the Thai calendar it diverges from the official record, so fullMoonOf and thaiLunarToGregorian throw ArgumentError for input outside this range. Year-type classifiers (lunarYearType etc.) do not throw and may be used beyond the range with this caveat in mind.

Notes & caveats

  • Calendar-day contract. gregorianToThaiLunar uses only the DateTime's own year / month / day; the time of day and the time zone are ignored, so you get the Thai lunar date for that wall-clock calendar day. To convert "the UTC day" instead, pass date.toUtc().
  • fullMoonOf returns the occurrence of that lunar month's full moon whose Gregorian date falls within the requested calendar year. A lunar year straddles the Gregorian boundary, so an early month (1..4) belongs to the lunar year that began the previous Gregorian year — this is handled for you.
  • Months 5 and 6 each occur twice in a Gregorian year — once at the head of a lunar year and once again as the previous lunar year wraps round at its end. The year-end occurrence is flagged via ThaiLunarDate.isYearEndWrap and carries the BE year of the lunar year it closes (= CS year + 1181), matching the convention a Thai reader expects for those tail days. Because of this flag thaiLunarToGregorian(gregorianToThaiLunar(d)) == d is exact for every day, including the wrap days.
  • thai_holidays — Thai public holiday dates (uses lunar holidays like these).
  • The same publisher also maintains the Thai-numerals toolkit thainum and a Thai PromptPay helper thai_promptpay.

Attribution

This package is a faithful port of pythaidate by Mark Hollow (MIT, © 2023). The underlying algorithm derives from J.C. Eade, The Calendrical Systems of Mainland South-East Asia (Brill, 1995). See NOTICE.md.

License

MIT — see LICENSE.

Libraries

thai_lunar
A pure-Dart, dependency-free Thai lunisolar (chantarakati / Suriyayatra) calendar engine.