Files
worklenz/worklenz-backend/src/services/sri-lankan-holiday-service.ts
Chamika J 903a9475b1 feat(sri-lankan-holidays): add comprehensive holiday management for Sri Lanka
- Introduced a new SQL migration file to populate Sri Lankan holidays from verified sources for the years 2020-2050.
- Implemented holiday data fetching and population logic in AdminCenterController and HolidayController to automatically sync holidays for Sri Lanka.
- Added a holiday data provider service to centralize holiday data retrieval and storage.
- Created a script for updating Sri Lankan holidays, including generating SQL for new years and validating holiday data.
- Developed documentation outlining the annual update process for Sri Lankan holidays, ensuring accurate data management.
- Included a JSON file containing detailed holiday information and metadata for Sri Lankan public holidays.
2025-07-31 10:43:49 +05:30

221 lines
7.8 KiB
TypeScript

import moment from "moment";
interface SriLankanHoliday {
name: string;
date: string;
type: "Public" | "Bank" | "Mercantile" | "Poya";
description: string;
is_recurring: boolean;
is_poya: boolean;
country_code: string;
color_code: string;
}
export class SriLankanHolidayService {
private static readonly COUNTRY_CODE = "LK";
// Fixed recurring holidays (same date every year)
private static readonly FIXED_HOLIDAYS = [
{
name: "Independence Day",
month: 2,
day: 4,
type: "Public" as const,
description: "Commemorates the independence of Sri Lanka from British rule in 1948",
color_code: "#DC143C"
},
{
name: "Sinhala and Tamil New Year Day",
month: 4,
day: 13,
type: "Public" as const,
description: "Traditional New Year celebrated by Sinhalese and Tamil communities",
color_code: "#DC143C"
},
{
name: "Day after Sinhala and Tamil New Year",
month: 4,
day: 14,
type: "Public" as const,
description: "Second day of traditional New Year celebrations",
color_code: "#DC143C"
},
{
name: "May Day",
month: 5,
day: 1,
type: "Public" as const,
description: "International Workers' Day",
color_code: "#DC143C"
},
{
name: "Christmas Day",
month: 12,
day: 25,
type: "Public" as const,
description: "Christian celebration of the birth of Jesus Christ",
color_code: "#DC143C"
}
];
// Poya days names (in order of Buddhist months)
private static readonly POYA_NAMES = [
{ name: "Duruthu", description: "Commemorates the first visit of Buddha to Sri Lanka" },
{ name: "Navam", description: "Commemorates the appointment of Sariputta and Moggallana as Buddha's chief disciples" },
{ name: "Medin", description: "Commemorates Buddha's first visit to his father's palace after enlightenment" },
{ name: "Bak", description: "Commemorates Buddha's second visit to Sri Lanka" },
{ name: "Vesak", description: "Most sacred day for Buddhists - commemorates birth, enlightenment and passing of Buddha" },
{ name: "Poson", description: "Commemorates the introduction of Buddhism to Sri Lanka by Arahat Mahinda" },
{ name: "Esala", description: "Commemorates Buddha's first sermon and the arrival of the Sacred Tooth Relic" },
{ name: "Nikini", description: "Commemorates the first Buddhist council" },
{ name: "Binara", description: "Commemorates Buddha's visit to heaven to preach to his mother" },
{ name: "Vap", description: "Marks the end of Buddhist Lent and Buddha's return from heaven" },
{ name: "Il", description: "Commemorates Buddha's ordination of sixty disciples" },
{ name: "Unduvap", description: "Commemorates the arrival of Sanghamitta Theri with the Sacred Bo sapling" }
];
/**
* Calculate Poya days for a given year
* Note: This is a simplified calculation. For production use, consider using
* astronomical calculations or an API that provides accurate lunar calendar dates
*/
private static calculatePoyaDays(year: number): SriLankanHoliday[] {
const poyaDays: SriLankanHoliday[] = [];
// This is a simplified approach - in reality, you would need astronomical calculations
// or use a service that provides accurate Buddhist lunar calendar dates
// For now, we'll use approximate dates based on lunar month cycles
// Starting from a known Vesak date (May full moon)
// and calculating other Poya days based on lunar month intervals
const baseVesakDate = this.getVesakDate(year);
for (let i = 0; i < 12; i++) {
const monthsFromVesak = i - 4; // Vesak is the 5th month
const poyaDate = moment(baseVesakDate).add(monthsFromVesak * 29.53, "days"); // Lunar month average
// Adjust to the nearest full moon date (would need proper calculation in production)
const poyaInfo = this.POYA_NAMES[i];
poyaDays.push({
name: `${poyaInfo.name} Full Moon Poya Day`,
date: poyaDate.format("YYYY-MM-DD"),
type: "Poya",
description: poyaInfo.description,
is_recurring: false,
is_poya: true,
country_code: this.COUNTRY_CODE,
color_code: "#8B4513"
});
}
return poyaDays;
}
/**
* Get approximate Vesak date for a year
* Vesak typically falls on the full moon in May
*/
private static getVesakDate(year: number): Date {
// This is a simplified calculation
// In production, use astronomical calculations or a reliable API
const may1 = new Date(year, 4, 1); // May 1st
const fullMoonDay = 15; // Approximate - would need proper lunar calculation
return new Date(year, 4, fullMoonDay);
}
/**
* Get Easter date for a year (Western/Gregorian calendar)
* Using Computus algorithm
*/
private static getEasterDate(year: number): Date {
const a = year % 19;
const b = Math.floor(year / 100);
const c = year % 100;
const d = Math.floor(b / 4);
const e = b % 4;
const f = Math.floor((b + 8) / 25);
const g = Math.floor((b - f + 1) / 3);
const h = (19 * a + b - d - g + 15) % 30;
const i = Math.floor(c / 4);
const k = c % 4;
const l = (32 + 2 * e + 2 * i - h - k) % 7;
const m = Math.floor((a + 11 * h + 22 * l) / 451);
const month = Math.floor((h + l - 7 * m + 114) / 31);
const day = ((h + l - 7 * m + 114) % 31) + 1;
return new Date(year, month - 1, day);
}
/**
* Get all Sri Lankan holidays for a given year
*/
public static getHolidaysForYear(year: number): SriLankanHoliday[] {
const holidays: SriLankanHoliday[] = [];
// Add fixed holidays
for (const holiday of this.FIXED_HOLIDAYS) {
holidays.push({
...holiday,
date: `${year}-${String(holiday.month).padStart(2, "0")}-${String(holiday.day).padStart(2, "0")}`,
is_recurring: true,
is_poya: false,
country_code: this.COUNTRY_CODE
});
}
// Add Poya days
const poyaDays = this.calculatePoyaDays(year);
holidays.push(...poyaDays);
// Add Good Friday (2 days before Easter)
const easter = this.getEasterDate(year);
const goodFriday = moment(easter).subtract(2, "days");
holidays.push({
name: "Good Friday",
date: goodFriday.format("YYYY-MM-DD"),
type: "Public",
description: "Christian commemoration of the crucifixion of Jesus Christ",
is_recurring: false,
is_poya: false,
country_code: this.COUNTRY_CODE,
color_code: "#DC143C"
});
// Add day after Vesak
const vesakDay = poyaDays.find(p => p.name.includes("Vesak"));
if (vesakDay) {
const dayAfterVesak = moment(vesakDay.date).add(1, "day");
holidays.push({
name: "Day after Vesak Full Moon Poya Day",
date: dayAfterVesak.format("YYYY-MM-DD"),
type: "Public",
description: "Additional day for Vesak celebrations",
is_recurring: false,
is_poya: false,
country_code: this.COUNTRY_CODE,
color_code: "#DC143C"
});
}
// Note: Eid and Deepavali dates would need to be calculated based on
// Islamic and Hindu calendars respectively, or fetched from an external source
return holidays.sort((a, b) => a.date.localeCompare(b.date));
}
/**
* Generate SQL insert statements for holidays
*/
public static generateSQL(year: number, tableName = "country_holidays"): string {
const holidays = this.getHolidaysForYear(year);
const values = holidays.map(holiday => {
return `('${this.COUNTRY_CODE}', '${holiday.name.replace(/'/g, "''")}', '${holiday.description.replace(/'/g, "''")}', '${holiday.date}', ${holiday.is_recurring})`;
}).join(",\n ");
return `INSERT INTO ${tableName} (country_code, name, description, date, is_recurring)
VALUES
${values}
ON CONFLICT (country_code, name, date) DO NOTHING;`;
}
}