Files

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
};