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]);
|
||||
}
|
||||
|
||||
// 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]));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
// 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++) {
|
||||
const holidays = hd.getHolidays(year);
|
||||
for (let year = 2020; year <= 2050; year++) {
|
||||
const sriLankanHolidays = await HolidayDataProvider.getSriLankanHolidays(year);
|
||||
|
||||
for (const holiday of holidays) {
|
||||
if (!holiday.date || typeof holiday.date !== "object") {
|
||||
continue;
|
||||
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);
|
||||
|
||||
const dateStr = holiday.date.toISOString().split("T")[0];
|
||||
const name = holiday.name || "Unknown Holiday";
|
||||
const description = holiday.type || "Public Holiday";
|
||||
for (let year = 2020; year <= 2050; year++) {
|
||||
const holidays = hd.getHolidays(year);
|
||||
|
||||
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
|
||||
`;
|
||||
for (const holiday of holidays) {
|
||||
if (!holiday.date || typeof holiday.date !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
await db.query(query, [
|
||||
country.code,
|
||||
name,
|
||||
description,
|
||||
dateStr,
|
||||
true
|
||||
]);
|
||||
const dateStr = holiday.date.toISOString().split("T")[0];
|
||||
const name = holiday.name || "Unknown Holiday";
|
||||
const description = holiday.type || "Public Holiday";
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
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