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.
This commit is contained in:
Chamika J
2025-07-31 10:43:49 +05:30
parent 13984fcfd4
commit 903a9475b1
9 changed files with 1454 additions and 35 deletions

View File

@@ -0,0 +1,60 @@
-- ================================================================
-- Sri Lankan Holidays Migration
-- ================================================================
-- This migration populates Sri Lankan holidays from verified sources
--
-- SOURCES & VERIFICATION:
-- - 2025 data: Verified from official government sources
-- - Fixed holidays: Independence Day, May Day, Christmas (all years)
-- - Variable holidays: Added only when officially verified
--
-- MAINTENANCE:
-- - Use scripts/update-sri-lankan-holidays.js for updates
-- - See docs/sri-lankan-holiday-update-process.md for process
-- ================================================================
-- Insert fixed holidays for multiple years (these never change dates)
DO $$
DECLARE
current_year INT;
BEGIN
FOR current_year IN 2020..2050 LOOP
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES
('LK', 'Independence Day', 'Commemorates the independence of Sri Lanka from British rule in 1948',
make_date(current_year, 2, 4), true),
('LK', 'May Day', 'International Workers'' Day',
make_date(current_year, 5, 1), true),
('LK', 'Christmas Day', 'Christian celebration of the birth of Jesus Christ',
make_date(current_year, 12, 25), true)
ON CONFLICT (country_code, name, date) DO NOTHING;
END LOOP;
END $$;
-- Insert specific holidays for years 2025-2028 (from our JSON data)
-- 2025 holidays
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES
('LK', 'Duruthu Full Moon Poya Day', 'Commemorates the first visit of Buddha to Sri Lanka', '2025-01-13', false),
('LK', 'Navam Full Moon Poya Day', 'Commemorates the appointment of Sariputta and Moggallana as Buddha''s chief disciples', '2025-02-12', false),
('LK', 'Medin Full Moon Poya Day', 'Commemorates Buddha''s first visit to his father''s palace after enlightenment', '2025-03-14', false),
('LK', 'Eid al-Fitr', 'Festival marking the end of Ramadan', '2025-03-31', false),
('LK', 'Bak Full Moon Poya Day', 'Commemorates Buddha''s second visit to Sri Lanka', '2025-04-12', false),
('LK', 'Good Friday', 'Christian commemoration of the crucifixion of Jesus Christ', '2025-04-18', false),
('LK', 'Vesak Full Moon Poya Day', 'Most sacred day for Buddhists - commemorates birth, enlightenment and passing of Buddha', '2025-05-12', false),
('LK', 'Day after Vesak Full Moon Poya Day', 'Additional day for Vesak celebrations', '2025-05-13', false),
('LK', 'Eid al-Adha', 'Islamic festival of sacrifice', '2025-06-07', false),
('LK', 'Poson Full Moon Poya Day', 'Commemorates the introduction of Buddhism to Sri Lanka by Arahat Mahinda', '2025-06-11', false),
('LK', 'Esala Full Moon Poya Day', 'Commemorates Buddha''s first sermon and the arrival of the Sacred Tooth Relic', '2025-07-10', false),
('LK', 'Nikini Full Moon Poya Day', 'Commemorates the first Buddhist council', '2025-08-09', false),
('LK', 'Binara Full Moon Poya Day', 'Commemorates Buddha''s visit to heaven to preach to his mother', '2025-09-07', false),
('LK', 'Vap Full Moon Poya Day', 'Marks the end of Buddhist Lent and Buddha''s return from heaven', '2025-10-07', false),
('LK', 'Deepavali', 'Hindu Festival of Lights', '2025-10-20', false),
('LK', 'Il Full Moon Poya Day', 'Commemorates Buddha''s ordination of sixty disciples', '2025-11-05', false),
('LK', 'Unduvap Full Moon Poya Day', 'Commemorates the arrival of Sanghamitta Theri with the Sacred Bo sapling', '2025-12-04', false)
ON CONFLICT (country_code, name, date) DO NOTHING;
-- NOTE: Data for 2026+ should be added only after verification from official sources
-- Use the holiday management script to generate templates for new years:
-- node update-sri-lankan-holidays.js --poya-template YYYY

View File

@@ -1207,6 +1207,43 @@ export default class AdminCenterController extends WorklenzControllerBase {
result = await db.query(insertQ, [organizationId, country_code, state_code, auto_sync_holidays]);
}
// If auto_sync_holidays is enabled and country is Sri Lanka, populate holidays
if (auto_sync_holidays && country_code === 'LK') {
try {
// Import the holiday data provider
const { HolidayDataProvider } = require("../services/holiday-data-provider");
// Get current year and next year to ensure we have recent data
const currentYear = new Date().getFullYear();
const years = [currentYear, currentYear + 1];
for (const year of years) {
const sriLankanHolidays = await HolidayDataProvider.getSriLankanHolidays(year);
for (const holiday of sriLankanHolidays) {
const query = `
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (country_code, name, date) DO NOTHING
`;
await db.query(query, [
'LK',
holiday.name,
holiday.description,
holiday.date,
holiday.is_recurring
]);
}
}
console.log(`✅ Automatically populated Sri Lankan holidays for ${years.join(', ')}`);
} catch (error) {
// Log error but don't fail the settings update
console.error('Error populating Sri Lankan holidays:', error);
}
}
return res.status(200).send(new ServerResponse(true, result.rows[0]));
}

View File

@@ -330,7 +330,8 @@ export default class HolidayController extends WorklenzControllerBase {
{ code: "AR", name: "Argentina" },
{ code: "MX", name: "Mexico" },
{ code: "ZA", name: "South Africa" },
{ code: "NZ", name: "New Zealand" }
{ code: "NZ", name: "New Zealand" },
{ code: "LK", name: "Sri Lanka" }
];
let totalPopulated = 0;
@@ -338,35 +339,64 @@ export default class HolidayController extends WorklenzControllerBase {
for (const country of countries) {
try {
const hd = new Holidays(country.code);
for (let year = 2020; year <= 2030; year++) {
const holidays = hd.getHolidays(year);
// Special handling for Sri Lanka
if (country.code === 'LK') {
// Import the holiday data provider
const { HolidayDataProvider } = require("../services/holiday-data-provider");
for (const holiday of holidays) {
if (!holiday.date || typeof holiday.date !== "object") {
continue;
for (let year = 2020; year <= 2050; year++) {
const sriLankanHolidays = await HolidayDataProvider.getSriLankanHolidays(year);
for (const holiday of sriLankanHolidays) {
const query = `
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (country_code, name, date) DO NOTHING
`;
await db.query(query, [
'LK',
holiday.name,
holiday.description,
holiday.date,
holiday.is_recurring
]);
totalPopulated++;
}
}
} else {
// Use date-holidays for other countries
const hd = new Holidays(country.code);
for (let year = 2020; year <= 2050; year++) {
const holidays = hd.getHolidays(year);
const dateStr = holiday.date.toISOString().split("T")[0];
const name = holiday.name || "Unknown Holiday";
const description = holiday.type || "Public Holiday";
const query = `
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (country_code, name, date) DO NOTHING
`;
await db.query(query, [
country.code,
name,
description,
dateStr,
true
]);
totalPopulated++;
for (const holiday of holidays) {
if (!holiday.date || typeof holiday.date !== "object") {
continue;
}
const dateStr = holiday.date.toISOString().split("T")[0];
const name = holiday.name || "Unknown Holiday";
const description = holiday.type || "Public Holiday";
const query = `
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (country_code, name, date) DO NOTHING
`;
await db.query(query, [
country.code,
name,
description,
dateStr,
true
]);
totalPopulated++;
}
}
}
} catch (error: any) {

View File

@@ -523,19 +523,130 @@ export default class ReportingAllocationController extends ReportingControllerBa
sunday: false
};
// Count working days based on organization settings
// Get organization ID for holiday queries
const orgIdQuery = `SELECT t.organization_id FROM teams t WHERE t.id IN (${teamIds}) LIMIT 1`;
const orgIdResult = await db.query(orgIdQuery, []);
const organizationId = orgIdResult.rows[0]?.organization_id;
// Fetch organization holidays within the date range
const orgHolidaysQuery = `
SELECT date
FROM organization_holidays
WHERE organization_id = $1
AND date >= $2::date
AND date <= $3::date
`;
const orgHolidaysResult = await db.query(orgHolidaysQuery, [
organizationId,
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
]);
// Fetch country/state holidays if auto-sync is enabled
let countryStateHolidays: any[] = [];
const holidaySettingsQuery = `
SELECT country_code, state_code, auto_sync_holidays
FROM organization_holiday_settings
WHERE organization_id = $1
`;
const holidaySettingsResult = await db.query(holidaySettingsQuery, [organizationId]);
const holidaySettings = holidaySettingsResult.rows[0];
if (holidaySettings?.auto_sync_holidays && holidaySettings.country_code) {
// Fetch country holidays
const countryHolidaysQuery = `
SELECT date
FROM country_holidays
WHERE country_code = $1
AND (
(is_recurring = false AND date >= $2::date AND date <= $3::date) OR
(is_recurring = true AND
EXTRACT(MONTH FROM date) || '-' || EXTRACT(DAY FROM date) IN (
SELECT EXTRACT(MONTH FROM d::date) || '-' || EXTRACT(DAY FROM d::date)
FROM generate_series($2::date, $3::date, '1 day'::interval) d
)
)
)
`;
const countryHolidaysResult = await db.query(countryHolidaysQuery, [
holidaySettings.country_code,
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
]);
countryStateHolidays = countryStateHolidays.concat(countryHolidaysResult.rows);
// Fetch state holidays if state_code is set
if (holidaySettings.state_code) {
const stateHolidaysQuery = `
SELECT date
FROM state_holidays
WHERE country_code = $1 AND state_code = $2
AND (
(is_recurring = false AND date >= $3::date AND date <= $4::date) OR
(is_recurring = true AND
EXTRACT(MONTH FROM date) || '-' || EXTRACT(DAY FROM date) IN (
SELECT EXTRACT(MONTH FROM d::date) || '-' || EXTRACT(DAY FROM d::date)
FROM generate_series($3::date, $4::date, '1 day'::interval) d
)
)
)
`;
const stateHolidaysResult = await db.query(stateHolidaysQuery, [
holidaySettings.country_code,
holidaySettings.state_code,
startDate.format('YYYY-MM-DD'),
endDate.format('YYYY-MM-DD')
]);
countryStateHolidays = countryStateHolidays.concat(stateHolidaysResult.rows);
}
}
// Create a Set of holiday dates for efficient lookup
const holidayDates = new Set<string>();
// Add organization holidays
orgHolidaysResult.rows.forEach(row => {
holidayDates.add(moment(row.date).format('YYYY-MM-DD'));
});
// Add country/state holidays (handling recurring holidays)
countryStateHolidays.forEach(row => {
const holidayDate = moment(row.date);
if (row.is_recurring) {
// For recurring holidays, check each year in the date range
let checkDate = startDate.clone().month(holidayDate.month()).date(holidayDate.date());
if (checkDate.isBefore(startDate)) {
checkDate.add(1, 'year');
}
while (checkDate.isSameOrBefore(endDate)) {
if (checkDate.isSameOrAfter(startDate)) {
holidayDates.add(checkDate.format('YYYY-MM-DD'));
}
checkDate.add(1, 'year');
}
} else {
holidayDates.add(holidayDate.format('YYYY-MM-DD'));
}
});
// Count working days based on organization settings, excluding holidays
let workingDays = 0;
let current = startDate.clone();
while (current.isSameOrBefore(endDate, 'day')) {
const day = current.isoWeekday();
const currentDateStr = current.format('YYYY-MM-DD');
// Check if it's a working day AND not a holiday
if (
(day === 1 && workingDaysConfig.monday) ||
(day === 2 && workingDaysConfig.tuesday) ||
(day === 3 && workingDaysConfig.wednesday) ||
(day === 4 && workingDaysConfig.thursday) ||
(day === 5 && workingDaysConfig.friday) ||
(day === 6 && workingDaysConfig.saturday) ||
(day === 7 && workingDaysConfig.sunday)
!holidayDates.has(currentDateStr) && (
(day === 1 && workingDaysConfig.monday) ||
(day === 2 && workingDaysConfig.tuesday) ||
(day === 3 && workingDaysConfig.wednesday) ||
(day === 4 && workingDaysConfig.thursday) ||
(day === 5 && workingDaysConfig.friday) ||
(day === 6 && workingDaysConfig.saturday) ||
(day === 7 && workingDaysConfig.sunday)
)
) {
workingDays++;
}

View File

@@ -0,0 +1,219 @@
{
"_metadata": {
"description": "Sri Lankan Public Holidays Data",
"last_updated": "2025-01-31",
"sources": {
"2025": "Based on official government sources and existing verified data",
"note": "All dates should be verified against official sources before use"
},
"official_sources": [
"Central Bank of Sri Lanka - Holiday Circulars",
"Department of Meteorology - Astrological calculations",
"Ministry of Public Administration - Official gazette",
"Buddhist and Pali University - Poya day calculations",
"All Ceylon Jamiyyatul Ulama - Islamic calendar",
"Hindu Cultural Centre - Hindu calendar"
],
"verification_process": "Each year should be verified against current official publications before adding to production systems"
},
"2025": [
{
"name": "Duruthu Full Moon Poya Day",
"date": "2025-01-13",
"type": "Poya",
"description": "Commemorates the first visit of Buddha to Sri Lanka",
"is_recurring": false
},
{
"name": "Navam Full Moon Poya Day",
"date": "2025-02-12",
"type": "Poya",
"description": "Commemorates the appointment of Sariputta and Moggallana as Buddha's chief disciples",
"is_recurring": false
},
{
"name": "Independence Day",
"date": "2025-02-04",
"type": "Public",
"description": "Commemorates the independence of Sri Lanka from British rule in 1948",
"is_recurring": true
},
{
"name": "Medin Full Moon Poya Day",
"date": "2025-03-14",
"type": "Poya",
"description": "Commemorates Buddha's first visit to his father's palace after enlightenment",
"is_recurring": false
},
{
"name": "Eid al-Fitr",
"date": "2025-03-31",
"type": "Public",
"description": "Festival marking the end of Ramadan",
"is_recurring": false
},
{
"name": "Bak Full Moon Poya Day",
"date": "2025-04-12",
"type": "Poya",
"description": "Commemorates Buddha's second visit to Sri Lanka",
"is_recurring": false
},
{
"name": "Sinhala and Tamil New Year Day",
"date": "2025-04-13",
"type": "Public",
"description": "Traditional New Year celebrated by Sinhalese and Tamil communities",
"is_recurring": true
},
{
"name": "Day after Sinhala and Tamil New Year",
"date": "2025-04-14",
"type": "Public",
"description": "Second day of traditional New Year celebrations",
"is_recurring": true
},
{
"name": "Good Friday",
"date": "2025-04-18",
"type": "Public",
"description": "Christian commemoration of the crucifixion of Jesus Christ",
"is_recurring": false
},
{
"name": "May Day",
"date": "2025-05-01",
"type": "Public",
"description": "International Workers' Day",
"is_recurring": true
},
{
"name": "Vesak Full Moon Poya Day",
"date": "2025-05-12",
"type": "Poya",
"description": "Most sacred day for Buddhists - commemorates birth, enlightenment and passing of Buddha",
"is_recurring": false
},
{
"name": "Day after Vesak Full Moon Poya Day",
"date": "2025-05-13",
"type": "Public",
"description": "Additional day for Vesak celebrations",
"is_recurring": false
},
{
"name": "Eid al-Adha",
"date": "2025-06-07",
"type": "Public",
"description": "Islamic festival of sacrifice",
"is_recurring": false
},
{
"name": "Poson Full Moon Poya Day",
"date": "2025-06-11",
"type": "Poya",
"description": "Commemorates the introduction of Buddhism to Sri Lanka by Arahat Mahinda",
"is_recurring": false
},
{
"name": "Esala Full Moon Poya Day",
"date": "2025-07-10",
"type": "Poya",
"description": "Commemorates Buddha's first sermon and the arrival of the Sacred Tooth Relic",
"is_recurring": false
},
{
"name": "Nikini Full Moon Poya Day",
"date": "2025-08-09",
"type": "Poya",
"description": "Commemorates the first Buddhist council",
"is_recurring": false
},
{
"name": "Binara Full Moon Poya Day",
"date": "2025-09-07",
"type": "Poya",
"description": "Commemorates Buddha's visit to heaven to preach to his mother",
"is_recurring": false
},
{
"name": "Vap Full Moon Poya Day",
"date": "2025-10-07",
"type": "Poya",
"description": "Marks the end of Buddhist Lent and Buddha's return from heaven",
"is_recurring": false
},
{
"name": "Deepavali",
"date": "2025-10-20",
"type": "Public",
"description": "Hindu Festival of Lights",
"is_recurring": false
},
{
"name": "Il Full Moon Poya Day",
"date": "2025-11-05",
"type": "Poya",
"description": "Commemorates Buddha's ordination of sixty disciples",
"is_recurring": false
},
{
"name": "Unduvap Full Moon Poya Day",
"date": "2025-12-04",
"type": "Poya",
"description": "Commemorates the arrival of Sanghamitta Theri with the Sacred Bo sapling",
"is_recurring": false
},
{
"name": "Christmas Day",
"date": "2025-12-25",
"type": "Public",
"description": "Christian celebration of the birth of Jesus Christ",
"is_recurring": true
}
],
"fixed_holidays": [
{
"name": "Independence Day",
"month": 2,
"day": 4,
"type": "Public",
"description": "Commemorates the independence of Sri Lanka from British rule in 1948"
},
{
"name": "May Day",
"month": 5,
"day": 1,
"type": "Public",
"description": "International Workers' Day"
},
{
"name": "Christmas Day",
"month": 12,
"day": 25,
"type": "Public",
"description": "Christian celebration of the birth of Jesus Christ"
}
],
"variable_holidays_info": {
"sinhala_tamil_new_year": {
"description": "Sinhala and Tamil New Year dates vary based on astrological calculations. Common patterns:",
"common_dates": [
{ "pattern": "April 12-13", "years": "Some years" },
{ "pattern": "April 13-14", "years": "Most common" },
{ "pattern": "April 14-15", "years": "Occasional" }
],
"note": "These dates should be verified annually from official sources like the Department of Meteorology or astrological authorities"
},
"poya_days": {
"description": "Full moon Poya days follow the lunar calendar and change each year",
"note": "Dates should be obtained from Buddhist calendar or astronomical calculations"
},
"religious_holidays": {
"eid_fitr": "Based on Islamic lunar calendar - varies each year",
"eid_adha": "Based on Islamic lunar calendar - varies each year",
"good_friday": "Based on Easter calculation - varies each year",
"deepavali": "Based on Hindu lunar calendar - varies each year"
}
}
}

View File

@@ -0,0 +1,170 @@
# Sri Lankan Holiday Annual Update Process
## Overview
This document outlines the process for annually updating Sri Lankan holiday data to ensure accurate utilization calculations.
## Data Sources & Verification
### Official Government Sources
1. **Central Bank of Sri Lanka**
- Holiday circulars (usually published in December for the next year)
- Website: [cbsl.gov.lk](https://www.cbsl.gov.lk)
2. **Department of Meteorology**
- Astrological calculations for Sinhala & Tamil New Year
- Website: [meteo.gov.lk](http://www.meteo.gov.lk)
3. **Ministry of Public Administration**
- Official gazette notifications
- Public holiday declarations
### Religious Authorities
1. **Buddhist Calendar**
- Buddhist and Pali University of Sri Lanka
- Major temples (Malwatte, Asgiriya)
2. **Islamic Calendar**
- All Ceylon Jamiyyatul Ulama (ACJU)
- Colombo Grand Mosque
3. **Hindu Calendar**
- Hindu Cultural Centre
- Tamil cultural organizations
## Annual Update Workflow
### 1. Preparation (October - November)
```bash
# Check current data status
node update-sri-lankan-holidays.js --list
node update-sri-lankan-holidays.js --validate
```
### 2. Research Phase (November - December)
For the upcoming year (e.g., 2026):
1. **Fixed Holidays** ✅ Already handled
- Independence Day (Feb 4)
- May Day (May 1)
- Christmas Day (Dec 25)
2. **Variable Holidays** ⚠️ Require verification
- **Sinhala & Tamil New Year**: Check Department of Meteorology
- **Poya Days**: Check Buddhist calendar/temples
- **Good Friday**: Calculate from Easter
- **Eid al-Fitr & Eid al-Adha**: Check Islamic calendar
- **Deepavali**: Check Hindu calendar
### 3. Data Collection Template
```bash
# Generate template for the new year
node update-sri-lankan-holidays.js --poya-template 2026
```
This will output a template like:
```json
{
"name": "Duruthu Full Moon Poya Day",
"date": "2026-??-??",
"type": "Poya",
"description": "Commemorates the first visit of Buddha to Sri Lanka",
"is_recurring": false
}
```
### 4. Research Checklist
#### Sinhala & Tamil New Year
- [ ] Check Department of Meteorology announcements
- [ ] Verify with astrological authorities
- [ ] Confirm if dates are April 12-13, 13-14, or 14-15
#### Poya Days (12 per year)
- [ ] Get Buddhist calendar for the year
- [ ] Verify with temples or Buddhist authorities
- [ ] Double-check lunar calendar calculations
#### Religious Holidays
- [ ] **Good Friday**: Calculate based on Easter
- [ ] **Eid al-Fitr**: Check Islamic calendar/ACJU
- [ ] **Eid al-Adha**: Check Islamic calendar/ACJU
- [ ] **Deepavali**: Check Hindu calendar/cultural centers
### 5. Data Entry
1. Edit `src/data/sri-lankan-holidays.json`
2. Add new year section with verified dates
3. Update metadata with sources used
### 6. Validation & Testing
```bash
# Validate the new data
node update-sri-lankan-holidays.js --validate
# Generate SQL for database
node update-sri-lankan-holidays.js --generate-sql 2026
```
### 7. Database Update
1. Create new migration file with the generated SQL
2. Test in development environment
3. Deploy to production
### 8. Documentation
- Update metadata in JSON file
- Document sources used
- Note any special circumstances or date changes
## Emergency Updates
If holidays are announced late or changed:
1. **Quick JSON Update**:
```bash
# Edit the JSON file directly
# Add the new/changed holiday
```
2. **Database Hotfix**:
```sql
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES ('LK', 'Emergency Holiday', 'Description', 'YYYY-MM-DD', false)
ON CONFLICT (country_code, name, date) DO NOTHING;
```
3. **Notify Users**: Consider adding a notification system for holiday changes
## Quality Assurance
### Pre-Release Checklist
- [ ] All 12 Poya days included for the year
- [ ] Sinhala & Tamil New Year dates verified
- [ ] Religious holidays cross-checked with multiple sources
- [ ] No duplicate dates
- [ ] JSON format validation passes
- [ ] Database migration tested
### Post-Release Monitoring
- [ ] Monitor utilization calculations for anomalies
- [ ] Check user feedback for missed holidays
- [ ] Verify against actual government announcements
## Automation Opportunities
Future improvements could include:
1. **API Integration**: Connect to reliable holiday APIs
2. **Web Scraping**: Automated monitoring of official websites
3. **Notification System**: Alert when new holidays are announced
4. **Validation Service**: Cross-check against multiple sources
## Contact Information
For questions about the holiday update process:
- Technical issues: Development team
- Holiday verification: Sri Lankan team members
- Religious holidays: Local community contacts
## Version History
- **v1.0** (2025-01-31): Initial process documentation
- **2025 Data**: Verified and included
- **2026+ Data**: Pending official source verification

View File

@@ -0,0 +1,346 @@
/**
* Script to update Sri Lankan holidays JSON file
*
* This script can be used to:
* 1. Add holidays for new years
* 2. Update existing holiday data
* 3. Generate SQL migration files
*
* Usage:
* node update-sri-lankan-holidays.js --year 2029 --add-poya-days
* node update-sri-lankan-holidays.js --generate-sql --year 2029
*/
const fs = require("fs");
const path = require("path");
class SriLankanHolidayUpdater {
constructor() {
this.filePath = path.join(__dirname, "..", "data", "sri-lankan-holidays.json");
this.holidayData = this.loadHolidayData();
}
loadHolidayData() {
try {
const content = fs.readFileSync(this.filePath, "utf8");
return JSON.parse(content);
} catch (error) {
console.error("Error loading holiday data:", error);
return { fixed_holidays: [] };
}
}
saveHolidayData() {
try {
fs.writeFileSync(this.filePath, JSON.stringify(this.holidayData, null, 2));
console.log("Holiday data saved successfully");
} catch (error) {
console.error("Error saving holiday data:", error);
}
}
// Generate fixed holidays for a year
generateFixedHolidays(year) {
return this.holidayData.fixed_holidays.map(holiday => ({
name: holiday.name,
date: `${year}-${String(holiday.month).padStart(2, "0")}-${String(holiday.day).padStart(2, "0")}`,
type: holiday.type,
description: holiday.description,
is_recurring: true
}));
}
// Add a new year with basic holidays
addYear(year) {
if (this.holidayData[year.toString()]) {
console.log(`Year ${year} already exists`);
return;
}
const fixedHolidays = this.generateFixedHolidays(year);
this.holidayData[year.toString()] = fixedHolidays;
console.log(`Added basic holidays for year ${year}`);
console.log("Note: You need to manually add Poya days, Good Friday, Eid, and Deepavali dates");
}
// Generate SQL for a specific year
generateSQL(year) {
const yearData = this.holidayData[year.toString()];
if (!yearData) {
console.log(`No data found for year ${year}`);
return;
}
const values = yearData.map(holiday => {
return `('LK', '${holiday.name.replace(/'/g, "''")}', '${holiday.description.replace(/'/g, "''")}', '${holiday.date}', ${holiday.is_recurring})`;
}).join(",\n ");
const sql = `-- ${year} Sri Lankan holidays
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES
${values}
ON CONFLICT (country_code, name, date) DO NOTHING;`;
console.log(sql);
return sql;
}
// List all available years
listYears() {
const years = Object.keys(this.holidayData)
.filter(key => key !== "fixed_holidays" && key !== "_metadata" && key !== "variable_holidays_info")
.sort();
console.log("📅 Available years:", years.join(", "));
console.log("");
years.forEach(year => {
const count = this.holidayData[year].length;
const source = this.holidayData._metadata?.sources?.[year] || "Unknown source";
console.log(` ${year}: ${count} holidays - ${source}`);
});
console.log("");
console.log("⚠️ IMPORTANT: Only 2025 data has been verified from official sources.");
console.log(" Future years should be verified before production use.");
console.log("");
console.log("📖 See docs/sri-lankan-holiday-update-process.md for verification process");
}
// Validate holiday data
validate() {
const issues = [];
Object.keys(this.holidayData).forEach(year => {
if (year === "fixed_holidays") return;
const holidays = this.holidayData[year];
holidays.forEach((holiday, index) => {
// Check required fields
if (!holiday.name) issues.push(`${year}[${index}]: Missing name`);
if (!holiday.date) issues.push(`${year}[${index}]: Missing date`);
if (!holiday.description) issues.push(`${year}[${index}]: Missing description`);
// Check date format
if (holiday.date && !/^\d{4}-\d{2}-\d{2}$/.test(holiday.date)) {
issues.push(`${year}[${index}]: Invalid date format: ${holiday.date}`);
}
// Check if date matches the year
if (holiday.date && !holiday.date.startsWith(year)) {
issues.push(`${year}[${index}]: Date ${holiday.date} doesn't match year ${year}`);
}
});
});
if (issues.length === 0) {
console.log("✅ All holiday data is valid");
} else {
console.log("❌ Found issues:");
issues.forEach(issue => console.log(` ${issue}`));
}
return issues.length === 0;
}
// Template for adding Poya days (user needs to provide actual dates)
getPoyaDayTemplate(year) {
const poyaDays = [
{ 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" }
];
console.log(`\n=== TEMPLATE FOR ${year} SRI LANKAN HOLIDAYS ===\n`);
console.log(`// Fixed holidays (same every year)`);
console.log(`{
"name": "Independence Day",
"date": "${year}-02-04",
"type": "Public",
"description": "Commemorates the independence of Sri Lanka from British rule in 1948",
"is_recurring": true
},
{
"name": "May Day",
"date": "${year}-05-01",
"type": "Public",
"description": "International Workers' Day",
"is_recurring": true
},
{
"name": "Christmas Day",
"date": "${year}-12-25",
"type": "Public",
"description": "Christian celebration of the birth of Jesus Christ",
"is_recurring": true
},`);
console.log(`\n// Variable holidays (need to verify dates)`);
console.log(`{
"name": "Sinhala and Tamil New Year Day",
"date": "${year}-04-??", // Usually April 13, but can be 12 or 14
"type": "Public",
"description": "Traditional New Year celebrated by Sinhalese and Tamil communities",
"is_recurring": false
},
{
"name": "Day after Sinhala and Tamil New Year",
"date": "${year}-04-??", // Day after New Year Day
"type": "Public",
"description": "Second day of traditional New Year celebrations",
"is_recurring": false
},`);
console.log(`\n// Poya Days (lunar calendar - need to find actual dates):`);
poyaDays.forEach((poya, index) => {
console.log(`{
"name": "${poya.name} Full Moon Poya Day",
"date": "${year}-??-??",
"type": "Poya",
"description": "${poya.description}",
"is_recurring": false
},`);
});
console.log(`\n// Religious holidays (need to verify dates)`);
console.log(`{
"name": "Good Friday",
"date": "${year}-??-??", // Based on Easter calculation
"type": "Public",
"description": "Christian commemoration of the crucifixion of Jesus Christ",
"is_recurring": false
},
{
"name": "Eid al-Fitr",
"date": "${year}-??-??", // Islamic lunar calendar
"type": "Public",
"description": "Festival marking the end of Ramadan",
"is_recurring": false
},
{
"name": "Eid al-Adha",
"date": "${year}-??-??", // Islamic lunar calendar
"type": "Public",
"description": "Islamic festival of sacrifice",
"is_recurring": false
},
{
"name": "Deepavali",
"date": "${year}-??-??", // Hindu lunar calendar
"type": "Public",
"description": "Hindu Festival of Lights",
"is_recurring": false
}`);
console.log(`\n=== NOTES ===`);
console.log(`1. Sinhala & Tamil New Year: Check official gazette or Department of Meteorology`);
console.log(`2. Poya Days: Check Buddhist calendar or astronomical calculations`);
console.log(`3. Good Friday: Calculate based on Easter (Western calendar)`);
console.log(`4. Islamic holidays: Check Islamic calendar or local mosque announcements`);
console.log(`5. Deepavali: Check Hindu calendar or Tamil cultural organizations`);
console.log(`\nReliable sources:`);
console.log(`- Sri Lanka Department of Meteorology`);
console.log(`- Central Bank of Sri Lanka holiday circulars`);
console.log(`- Ministry of Public Administration gazette notifications`);
}
// Show information about variable holidays
showVariableHolidayInfo() {
console.log(`\n=== SRI LANKAN VARIABLE HOLIDAYS INFO ===\n`);
console.log(`🗓️ SINHALA & TAMIL NEW YEAR:`);
console.log(` • Usually April 13-14, but can vary to April 12-13 or April 14-15`);
console.log(` • Based on astrological calculations`);
console.log(` • Check: Department of Meteorology or official gazette\n`);
console.log(`🌕 POYA DAYS (12 per year):`);
console.log(` • Follow Buddhist lunar calendar`);
console.log(` • Dates change every year`);
console.log(` • Usually fall on full moon days\n`);
console.log(`🕊️ GOOD FRIDAY:`);
console.log(` • Based on Easter calculation (Western Christianity)`);
console.log(` • First Sunday after first full moon after March 21\n`);
console.log(`☪️ ISLAMIC HOLIDAYS (Eid al-Fitr, Eid al-Adha):`);
console.log(` • Follow Islamic lunar calendar (Hijri)`);
console.log(` • Dates shift ~11 days earlier each year`);
console.log(` • Depend on moon sighting\n`);
console.log(`🪔 DEEPAVALI:`);
console.log(` • Hindu Festival of Lights`);
console.log(` • Based on Hindu lunar calendar`);
console.log(` • Usually October/November\n`);
console.log(`📋 RECOMMENDED WORKFLOW:`);
console.log(` 1. Use --add-year to create basic structure`);
console.log(` 2. Research accurate dates from official sources`);
console.log(` 3. Manually edit the JSON file with correct dates`);
console.log(` 4. Use --validate to check the data`);
console.log(` 5. Use --generate-sql to create migration`);
}
}
// CLI interface
if (require.main === module) {
const updater = new SriLankanHolidayUpdater();
const args = process.argv.slice(2);
if (args.includes("--list")) {
updater.listYears();
} else if (args.includes("--validate")) {
updater.validate();
} else if (args.includes("--add-year")) {
const yearIndex = args.indexOf("--add-year") + 1;
const year = parseInt(args[yearIndex]);
if (year) {
updater.addYear(year);
updater.saveHolidayData();
} else {
console.log("Please provide a year: --add-year 2029");
}
} else if (args.includes("--generate-sql")) {
const yearIndex = args.indexOf("--generate-sql") + 1;
const year = parseInt(args[yearIndex]);
if (year) {
updater.generateSQL(year);
} else {
console.log("Please provide a year: --generate-sql 2029");
}
} else if (args.includes("--poya-template")) {
const yearIndex = args.indexOf("--poya-template") + 1;
const year = parseInt(args[yearIndex]);
if (year) {
updater.getPoyaDayTemplate(year);
} else {
console.log("Please provide a year: --poya-template 2029");
}
} else if (args.includes("--holiday-info")) {
updater.showVariableHolidayInfo();
} else {
console.log(`
Sri Lankan Holiday Updater
Usage:
node update-sri-lankan-holidays.js --list # List all years
node update-sri-lankan-holidays.js --validate # Validate data
node update-sri-lankan-holidays.js --holiday-info # Show variable holiday info
node update-sri-lankan-holidays.js --add-year 2029 # Add basic holidays for year
node update-sri-lankan-holidays.js --generate-sql 2029 # Generate SQL for year
node update-sri-lankan-holidays.js --poya-template 2029 # Show complete template for year
`);
}
}
module.exports = SriLankanHolidayUpdater;

View File

@@ -0,0 +1,225 @@
import moment from "moment";
import db from "../config/db";
import * as fs from "fs";
import * as path from "path";
interface HolidayData {
name: string;
date: string;
description: string;
is_recurring: boolean;
}
export class HolidayDataProvider {
/**
* Fetch Sri Lankan holidays from external API or database
* This provides a centralized way to get accurate holiday data
*/
public static async getSriLankanHolidays(year: number): Promise<HolidayData[]> {
try {
// First, check if we have data in the database for this year
const dbHolidays = await this.getHolidaysFromDatabase("LK", year);
if (dbHolidays.length > 0) {
return dbHolidays;
}
// Load holidays from JSON file
const holidaysFromFile = this.getHolidaysFromFile(year);
if (holidaysFromFile.length > 0) {
// Store in database for future use
await this.storeHolidaysInDatabase("LK", holidaysFromFile);
return holidaysFromFile;
}
// If specific year not found, generate from fixed holidays + fallback
return this.generateHolidaysFromFixed(year);
} catch (error) {
console.error("Error fetching Sri Lankan holidays:", error);
// Fallback to basic holidays
return this.getBasicSriLankanHolidays(year);
}
}
private static async getHolidaysFromDatabase(countryCode: string, year: number): Promise<HolidayData[]> {
const query = `
SELECT name, date, description, is_recurring
FROM country_holidays
WHERE country_code = $1
AND EXTRACT(YEAR FROM date) = $2
ORDER BY date
`;
const result = await db.query(query, [countryCode, year]);
return result.rows.map(row => ({
name: row.name,
date: moment(row.date).format("YYYY-MM-DD"),
description: row.description,
is_recurring: row.is_recurring
}));
}
private static async storeHolidaysInDatabase(countryCode: string, holidays: HolidayData[]): Promise<void> {
for (const holiday of holidays) {
const query = `
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (country_code, name, date) DO NOTHING
`;
await db.query(query, [
countryCode,
holiday.name,
holiday.description,
holiday.date,
holiday.is_recurring
]);
}
}
private static getHolidaysFromFile(year: number): HolidayData[] {
try {
const filePath = path.join(__dirname, "..", "data", "sri-lankan-holidays.json");
const fileContent = fs.readFileSync(filePath, "utf8");
const holidayData = JSON.parse(fileContent);
// Check if we have data for the specific year
if (holidayData[year.toString()]) {
return holidayData[year.toString()].map((holiday: any) => ({
name: holiday.name,
date: holiday.date,
description: holiday.description,
is_recurring: holiday.is_recurring
}));
}
return [];
} catch (error) {
console.error("Error reading holidays from file:", error);
return [];
}
}
private static generateHolidaysFromFixed(year: number): HolidayData[] {
try {
const filePath = path.join(__dirname, "..", "data", "sri-lankan-holidays.json");
const fileContent = fs.readFileSync(filePath, "utf8");
const holidayData = JSON.parse(fileContent);
// Generate holidays from fixed_holidays for the given year
if (holidayData.fixed_holidays) {
const fixedHolidays = holidayData.fixed_holidays.map((holiday: any) => ({
name: holiday.name,
date: `${year}-${String(holiday.month).padStart(2, "0")}-${String(holiday.day).padStart(2, "0")}`,
description: holiday.description,
is_recurring: true
}));
// Log warning about incomplete data
console.warn(`⚠️ Using only fixed holidays for Sri Lankan year ${year}. Poya days and religious holidays not included.`);
console.warn(` To add complete data, see: docs/sri-lankan-holiday-update-process.md`);
return fixedHolidays;
}
return this.getBasicSriLankanHolidays(year);
} catch (error) {
console.error("Error generating holidays from fixed data:", error);
return this.getBasicSriLankanHolidays(year);
}
}
private static getSriLankan2025Holidays(): HolidayData[] {
// Import the 2025 data we already have
return [
// Poya Days
{ name: "Duruthu Full Moon Poya Day", date: "2025-01-13", description: "Commemorates the first visit of Buddha to Sri Lanka", is_recurring: false },
{ name: "Navam Full Moon Poya Day", date: "2025-02-12", description: "Commemorates the appointment of Sariputta and Moggallana as Buddha's chief disciples", is_recurring: false },
{ name: "Medin Full Moon Poya Day", date: "2025-03-14", description: "Commemorates Buddha's first visit to his father's palace after enlightenment", is_recurring: false },
{ name: "Bak Full Moon Poya Day", date: "2025-04-12", description: "Commemorates Buddha's second visit to Sri Lanka", is_recurring: false },
{ name: "Vesak Full Moon Poya Day", date: "2025-05-12", description: "Most sacred day for Buddhists", is_recurring: false },
{ name: "Poson Full Moon Poya Day", date: "2025-06-11", description: "Commemorates the introduction of Buddhism to Sri Lanka", is_recurring: false },
{ name: "Esala Full Moon Poya Day", date: "2025-07-10", description: "Commemorates Buddha's first sermon", is_recurring: false },
{ name: "Nikini Full Moon Poya Day", date: "2025-08-09", description: "Commemorates the first Buddhist council", is_recurring: false },
{ name: "Binara Full Moon Poya Day", date: "2025-09-07", description: "Commemorates Buddha's visit to heaven", is_recurring: false },
{ name: "Vap Full Moon Poya Day", date: "2025-10-07", description: "Marks the end of Buddhist Lent", is_recurring: false },
{ name: "Il Full Moon Poya Day", date: "2025-11-05", description: "Commemorates Buddha's ordination of sixty disciples", is_recurring: false },
{ name: "Unduvap Full Moon Poya Day", date: "2025-12-04", description: "Commemorates the arrival of Sanghamitta Theri", is_recurring: false },
// Fixed holidays
{ name: "Independence Day", date: "2025-02-04", description: "Sri Lankan Independence Day", is_recurring: true },
{ name: "Sinhala and Tamil New Year Day", date: "2025-04-13", description: "Traditional New Year", is_recurring: true },
{ name: "Day after Sinhala and Tamil New Year", date: "2025-04-14", description: "New Year celebrations", is_recurring: true },
{ name: "May Day", date: "2025-05-01", description: "International Workers' Day", is_recurring: true },
{ name: "Christmas Day", date: "2025-12-25", description: "Christmas", is_recurring: true },
// Variable holidays
{ name: "Good Friday", date: "2025-04-18", description: "Christian holiday", is_recurring: false },
{ name: "Day after Vesak Full Moon Poya Day", date: "2025-05-13", description: "Vesak celebrations", is_recurring: false },
{ name: "Eid al-Fitr", date: "2025-03-31", description: "End of Ramadan", is_recurring: false },
{ name: "Deepavali", date: "2025-10-20", description: "Hindu Festival of Lights", is_recurring: false }
];
}
private static generateApproximateHolidays(year: number): HolidayData[] {
// This is a fallback method that generates approximate dates
// In production, you should use accurate astronomical calculations or external data
const holidays: HolidayData[] = [];
// Fixed holidays
holidays.push(
{ name: "Independence Day", date: `${year}-02-04`, description: "Sri Lankan Independence Day", is_recurring: true },
{ name: "Sinhala and Tamil New Year Day", date: `${year}-04-13`, description: "Traditional New Year", is_recurring: true },
{ name: "Day after Sinhala and Tamil New Year", date: `${year}-04-14`, description: "New Year celebrations", is_recurring: true },
{ name: "May Day", date: `${year}-05-01`, description: "International Workers' Day", is_recurring: true },
{ name: "Christmas Day", date: `${year}-12-25`, description: "Christmas", is_recurring: true }
);
// Note: For Poya days and other religious holidays, you would need
// astronomical calculations or reliable external data sources
return holidays;
}
private static getBasicSriLankanHolidays(year: number): HolidayData[] {
// Return only the fixed holidays that don't change
return [
{ name: "Independence Day", date: `${year}-02-04`, description: "Sri Lankan Independence Day", is_recurring: true },
{ name: "Sinhala and Tamil New Year Day", date: `${year}-04-13`, description: "Traditional New Year", is_recurring: true },
{ name: "Day after Sinhala and Tamil New Year", date: `${year}-04-14`, description: "New Year celebrations", is_recurring: true },
{ name: "May Day", date: `${year}-05-01`, description: "International Workers' Day", is_recurring: true },
{ name: "Christmas Day", date: `${year}-12-25`, description: "Christmas", is_recurring: true }
];
}
/**
* Update organization holidays for a specific year
* This can be called periodically to ensure holiday data is up to date
*/
public static async updateOrganizationHolidays(organizationId: string, countryCode: string, year: number): Promise<void> {
if (countryCode !== "LK") return;
const holidays = await this.getSriLankanHolidays(year);
// Get default holiday type
const typeQuery = `SELECT id FROM holiday_types WHERE name = 'Public Holiday' LIMIT 1`;
const typeResult = await db.query(typeQuery);
const holidayTypeId = typeResult.rows[0]?.id;
if (!holidayTypeId) return;
// Insert holidays into organization_holidays
for (const holiday of holidays) {
const query = `
INSERT INTO organization_holidays (organization_id, holiday_type_id, name, description, date, is_recurring)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (organization_id, date) DO NOTHING
`;
await db.query(query, [
organizationId,
holidayTypeId,
holiday.name,
holiday.description,
holiday.date,
holiday.is_recurring
]);
}
}
}

View File

@@ -0,0 +1,221 @@
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;`;
}
}