thai_holidays

pub package license: MIT

Pure-Dart Thai public-holiday calendar: fixed-date holidays, movable Buddhist holidays (Makha / Visakha / Asalha Bucha and Khao Phansa), the Royal Ploughing Ceremony, cabinet-announced special "bridge" days, stack-aware in-lieu (substitution) days, and business-day helpers.

Movable Buddhist dates are computed from the Thai lunisolar calendar via the sibling package thai_lunar, including the athikamat (double-eighth-month) leap-year shift.

All dates are UTC, date-only.

Features

  • Fixed-date holidays with historical effective-year gating (newer royal holidays are not emitted for years before they existed).
  • Movable Buddhist holidays via thai_lunar, including the athikamat leap-year month shift.
  • Cabinet-announced special ("bridge") holidays, seeded per year.
  • Royal Ploughing Ceremony (announced annually, seeded for known years).
  • Stack-aware in-lieu (substitution) days for holidays on a weekend.
  • Stable, locale-independent ThaiHolidayId for every holiday.
  • Government vs. bank distinction via a bankHoliday flag.
  • Range and point queries: holidayOn / holidaysOn / thaiHolidaysBetween.
  • Business-day helpers: isBusinessDay, nextBusinessDay, addBusinessDays (negative n supported), businessDaysBetween.

Install

dependencies:
  thai_holidays: ^0.1.0

Usage

import 'package:thai_holidays/thai_holidays.dart';

void main() {
  // All public holidays in 2026 (a leap / athikamat year), sorted by date.
  for (final h in thaiHolidays(2026)) {
    print('${h.date.toIso8601String().substring(0, 10)}  ${h.name(thai: false)}'
        '  [${h.type.name}]  (id: ${h.id.name})');
  }

  // Identify a holiday without string-matching localized names.
  final ny = holidayOn(DateTime.utc(2026, 1, 1));
  print(ny?.id == ThaiHolidayId.newYear); // true

  // All holidays on a date (dates can collide).
  print(holidaysOn(DateTime.utc(2026, 7, 30)).length);

  // Range query across a year boundary.
  final boundary = thaiHolidaysBetween(
      DateTime.utc(2025, 12, 28), DateTime.utc(2026, 1, 5));
  print(boundary.length);

  // Business-day helpers.
  print(isBusinessDay(DateTime.utc(2026, 1, 1)));        // false (holiday)
  print(nextBusinessDay(DateTime.utc(2025, 12, 31)));    // next working day
  print(addBusinessDays(DateTime.utc(2026, 1, 5), 5));   // 5 business days on
  print(businessDaysBetween(
      DateTime.utc(2026, 1, 1), DateTime.utc(2026, 1, 31)));
}

Suppressing in-lieu days

final core = thaiHolidays(2026, includeSubstitution: false);

API

  • List<ThaiHoliday> thaiHolidays(int year, {bool includeSubstitution = true})
  • List<ThaiHoliday> thaiHolidaysBetween(DateTime start, DateTime end, {bool includeSubstitution = true}) — inclusive [start, end], across year boundaries
  • bool isThaiHoliday(DateTime date)
  • ThaiHoliday? holidayOn(DateTime date) — the holiday on a date, or null
  • List<ThaiHoliday> holidaysOn(DateTime date) — all holidays on a date
  • bool isBusinessDay(DateTime date)
  • DateTime nextBusinessDay(DateTime date) — strictly after date
  • DateTime addBusinessDays(DateTime date, int n)n < 0 goes backward
  • int businessDaysBetween(DateTime a, DateTime b) — half-open (a, b], signed
  • ThaiHoliday (date, id, nameTh, nameEn, type, bankHoliday, observedFor) + name({bool thai = true})
  • enum ThaiHolidayType { fixed, lunar, substitution, special }
  • enum ThaiHolidayId { ... } — stable, locale-independent holiday identifier

Government vs. bank holidays

The encoded set is the Royal Thai Government public-holiday list (cabinet resolutions / Royal Gazette). Most entries are also Bank of Thailand (BOT) bank holidays, but two are government holidays that banks do not observe — Khao Phansa and the Royal Ploughing Ceremony. These carry bankHoliday == false; an in-lieu day inherits the flag of the holiday it observes. Filter with where((h) => h.bankHoliday) for the bank-holiday subset.

Supported range & data currency

Accurate from ~2017 onward and current through 2026 for annually-announced entries. Earlier years are approximate. Two data classes are not rule-derivable and are maintained as per-year overlays in lib/src/special.dart:

  • Cabinet special ("bridge") holidays — announced ad hoc each year.
  • Royal Ploughing Ceremony — its date is set annually by royal astrologers; only seeded for years with a confirmed date (others are omitted, not guessed).

See NOTICE.md for the seeded dates, sources, and effective-from years.

In-lieu (substitution) rule

When a public holiday falls on a Saturday or Sunday, a compensatory holiday is granted on the next working weekday that is not itself a holiday. The rule is stack-aware: when several holidays fall on the same weekend (e.g. the three consecutive Songkran days), each in-lieu day is pushed past the previously allocated ones so they never collide. In-lieu entries have type == ThaiHolidayType.substitution, observedFor set to the original holiday's date, and the id of the holiday they observe.

Sources

The holiday set and in-lieu rule follow Royal Thai Government cabinet resolutions / Royal Gazette announcements (publicized by the PRD), cross-checked against the vacanza/holidays Thailand provider. See NOTICE.md for per-date citations. Lunar dates come from thai_lunar, a pure-Dart port of pythaidate (MIT).

License

MIT — see LICENSE.

Libraries

thai_holidays
Thai public holidays for any Gregorian year: fixed-date holidays, movable Buddhist holidays (Makha / Visakha / Asalha Bucha, Khao Phansa) computed via the thai_lunar lunisolar engine, in-lieu (substitution) days, and business-day helpers.