142 lines
4.3 KiB
JavaScript
142 lines
4.3 KiB
JavaScript
const https = require('https');
|
|
|
|
/**
|
|
* Fetch holiday data from timor.tech API for a specific year
|
|
* @param {number} year
|
|
* @returns {Promise<object>}
|
|
*/
|
|
function fetchHolidaysFromNetwork(year) {
|
|
return new Promise((resolve, reject) => {
|
|
// Note: timor.tech API works best with trailing slash
|
|
const url = `https://timor.tech/api/holiday/year/${year}/`;
|
|
https.get(url, { headers: { 'User-Agent': 'WorkTime-Manager/1.0' } }, (res) => {
|
|
let rawData = '';
|
|
res.on('data', (chunk) => { rawData += chunk; });
|
|
res.on('end', () => {
|
|
try {
|
|
const parsed = JSON.parse(rawData);
|
|
if (parsed.code === 0 && parsed.holiday) {
|
|
resolve(parsed.holiday);
|
|
} else {
|
|
reject(new Error(`API returned code ${parsed.code}`));
|
|
}
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
}).on('error', (err) => {
|
|
reject(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get holiday data for a specific year, from cache or network
|
|
* @param {object} db DB wrapper instance
|
|
* @param {number} year
|
|
* @returns {Promise<object|null>}
|
|
*/
|
|
async function getHolidays(db, year) {
|
|
try {
|
|
const cached = db.get('SELECT holiday_data FROM holiday_cache WHERE year = ?', [year]);
|
|
if (cached) {
|
|
return JSON.parse(cached.holiday_data);
|
|
}
|
|
|
|
// Not cached, fetch from network
|
|
console.log(`[Holiday] Fetching holiday data from network for year ${year}...`);
|
|
const holidays = await fetchHolidaysFromNetwork(year);
|
|
|
|
// Save to cache
|
|
db.run(
|
|
'INSERT OR REPLACE INTO holiday_cache (year, holiday_data, updated_at) VALUES (?, ?, datetime(\'now\', \'localtime\'))',
|
|
[year, JSON.stringify(holidays)]
|
|
);
|
|
return holidays;
|
|
} catch (e) {
|
|
console.error(`[Holiday] Error getting holidays for ${year}:`, e.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to check if a date is weekend (Sat/Sun) in local time
|
|
* @param {string} dateStr YYYY-MM-DD
|
|
* @returns {boolean}
|
|
*/
|
|
function isWeekendDefault(dateStr) {
|
|
const d = new Date(dateStr + 'T00:00:00');
|
|
const day = d.getDay();
|
|
return day === 0 || day === 6;
|
|
}
|
|
|
|
/**
|
|
* Calculate the number of workdays for each month in a given year
|
|
* @param {number} year
|
|
* @param {object|null} holidayMap
|
|
* @returns {Array<{month: number, days: number}>}
|
|
*/
|
|
function calculateMonthlyWorkdays(year, holidayMap) {
|
|
const result = [];
|
|
for (let m = 1; m <= 12; m++) {
|
|
const daysInMonth = new Date(year, m, 0).getDate();
|
|
let workdays = 0;
|
|
|
|
for (let day = 1; day <= daysInMonth; day++) {
|
|
const dStr = `${year}-${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
const mmDd = `${String(m).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
|
|
let isWork = true;
|
|
if (holidayMap && holidayMap[mmDd]) {
|
|
// If date is in the map, holiday === true means rest day, holiday === false means work day (調休补班)
|
|
isWork = !holidayMap[mmDd].holiday;
|
|
} else {
|
|
// Fall back to default weekends
|
|
isWork = !isWeekendDefault(dStr);
|
|
}
|
|
|
|
if (isWork) {
|
|
workdays++;
|
|
}
|
|
}
|
|
result.push({ month: m, days: workdays });
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sync holiday data from network, calculate and update database workdays
|
|
* @param {object} db
|
|
* @param {number} year
|
|
* @returns {Promise<Array<{month: number, days: number}>>}
|
|
*/
|
|
async function syncYearWorkdays(db, year) {
|
|
// Clear cache first to force fresh fetch on manual sync request
|
|
db.run('DELETE FROM holiday_cache WHERE year = ?', [year]);
|
|
|
|
const holidayMap = await getHolidays(db, year);
|
|
if (!holidayMap) {
|
|
throw new Error('无法从网络同步节假日数据,请检查网络连接');
|
|
}
|
|
|
|
const monthlyWorkdays = calculateMonthlyWorkdays(year, holidayMap);
|
|
|
|
// Save to workdays table
|
|
for (const item of monthlyWorkdays) {
|
|
const existing = db.get('SELECT id FROM workdays WHERE year = ? AND month = ?', [year, item.month]);
|
|
if (existing) {
|
|
db.run('UPDATE workdays SET days = ? WHERE year = ? AND month = ?', [item.days, year, item.month]);
|
|
} else {
|
|
db.run('INSERT INTO workdays (year, month, days) VALUES (?, ?, ?)', [year, item.month, item.days]);
|
|
}
|
|
}
|
|
|
|
return monthlyWorkdays;
|
|
}
|
|
|
|
module.exports = {
|
|
getHolidays,
|
|
syncYearWorkdays,
|
|
calculateMonthlyWorkdays
|
|
};
|