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:
@@ -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
|
||||||
@@ -1207,6 +1207,43 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
result = await db.query(insertQ, [organizationId, country_code, state_code, auto_sync_holidays]);
|
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]));
|
return res.status(200).send(new ServerResponse(true, result.rows[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -330,7 +330,8 @@ export default class HolidayController extends WorklenzControllerBase {
|
|||||||
{ code: "AR", name: "Argentina" },
|
{ code: "AR", name: "Argentina" },
|
||||||
{ code: "MX", name: "Mexico" },
|
{ code: "MX", name: "Mexico" },
|
||||||
{ code: "ZA", name: "South Africa" },
|
{ code: "ZA", name: "South Africa" },
|
||||||
{ code: "NZ", name: "New Zealand" }
|
{ code: "NZ", name: "New Zealand" },
|
||||||
|
{ code: "LK", name: "Sri Lanka" }
|
||||||
];
|
];
|
||||||
|
|
||||||
let totalPopulated = 0;
|
let totalPopulated = 0;
|
||||||
@@ -338,35 +339,64 @@ export default class HolidayController extends WorklenzControllerBase {
|
|||||||
|
|
||||||
for (const country of countries) {
|
for (const country of countries) {
|
||||||
try {
|
try {
|
||||||
const hd = new Holidays(country.code);
|
// Special handling for Sri Lanka
|
||||||
|
if (country.code === 'LK') {
|
||||||
|
// Import the holiday data provider
|
||||||
|
const { HolidayDataProvider } = require("../services/holiday-data-provider");
|
||||||
|
|
||||||
for (let year = 2020; year <= 2030; year++) {
|
for (let year = 2020; year <= 2050; year++) {
|
||||||
const holidays = hd.getHolidays(year);
|
const sriLankanHolidays = await HolidayDataProvider.getSriLankanHolidays(year);
|
||||||
|
|
||||||
for (const holiday of holidays) {
|
for (const holiday of sriLankanHolidays) {
|
||||||
if (!holiday.date || typeof holiday.date !== "object") {
|
const query = `
|
||||||
continue;
|
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);
|
||||||
|
|
||||||
const dateStr = holiday.date.toISOString().split("T")[0];
|
for (let year = 2020; year <= 2050; year++) {
|
||||||
const name = holiday.name || "Unknown Holiday";
|
const holidays = hd.getHolidays(year);
|
||||||
const description = holiday.type || "Public Holiday";
|
|
||||||
|
|
||||||
const query = `
|
for (const holiday of holidays) {
|
||||||
INSERT INTO country_holidays (country_code, name, description, date, is_recurring)
|
if (!holiday.date || typeof holiday.date !== "object") {
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
continue;
|
||||||
ON CONFLICT (country_code, name, date) DO NOTHING
|
}
|
||||||
`;
|
|
||||||
|
|
||||||
await db.query(query, [
|
const dateStr = holiday.date.toISOString().split("T")[0];
|
||||||
country.code,
|
const name = holiday.name || "Unknown Holiday";
|
||||||
name,
|
const description = holiday.type || "Public Holiday";
|
||||||
description,
|
|
||||||
dateStr,
|
|
||||||
true
|
|
||||||
]);
|
|
||||||
|
|
||||||
totalPopulated++;
|
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) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -523,19 +523,130 @@ export default class ReportingAllocationController extends ReportingControllerBa
|
|||||||
sunday: false
|
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 workingDays = 0;
|
||||||
let current = startDate.clone();
|
let current = startDate.clone();
|
||||||
while (current.isSameOrBefore(endDate, 'day')) {
|
while (current.isSameOrBefore(endDate, 'day')) {
|
||||||
const day = current.isoWeekday();
|
const day = current.isoWeekday();
|
||||||
|
const currentDateStr = current.format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
// Check if it's a working day AND not a holiday
|
||||||
if (
|
if (
|
||||||
(day === 1 && workingDaysConfig.monday) ||
|
!holidayDates.has(currentDateStr) && (
|
||||||
(day === 2 && workingDaysConfig.tuesday) ||
|
(day === 1 && workingDaysConfig.monday) ||
|
||||||
(day === 3 && workingDaysConfig.wednesday) ||
|
(day === 2 && workingDaysConfig.tuesday) ||
|
||||||
(day === 4 && workingDaysConfig.thursday) ||
|
(day === 3 && workingDaysConfig.wednesday) ||
|
||||||
(day === 5 && workingDaysConfig.friday) ||
|
(day === 4 && workingDaysConfig.thursday) ||
|
||||||
(day === 6 && workingDaysConfig.saturday) ||
|
(day === 5 && workingDaysConfig.friday) ||
|
||||||
(day === 7 && workingDaysConfig.sunday)
|
(day === 6 && workingDaysConfig.saturday) ||
|
||||||
|
(day === 7 && workingDaysConfig.sunday)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
workingDays++;
|
workingDays++;
|
||||||
}
|
}
|
||||||
|
|||||||
219
worklenz-backend/src/data/sri-lankan-holidays.json
Normal file
219
worklenz-backend/src/data/sri-lankan-holidays.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
170
worklenz-backend/src/docs/sri-lankan-holiday-update-process.md
Normal file
170
worklenz-backend/src/docs/sri-lankan-holiday-update-process.md
Normal 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
|
||||||
346
worklenz-backend/src/scripts/update-sri-lankan-holidays.js
Normal file
346
worklenz-backend/src/scripts/update-sri-lankan-holidays.js
Normal 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;
|
||||||
225
worklenz-backend/src/services/holiday-data-provider.ts
Normal file
225
worklenz-backend/src/services/holiday-data-provider.ts
Normal 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
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
221
worklenz-backend/src/services/sri-lankan-holiday-service.ts
Normal file
221
worklenz-backend/src/services/sri-lankan-holiday-service.ts
Normal 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;`;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user