// date-utils.js — Date handling utilities for the calendar const monthAbbr = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'] const DAY_MS = 86400000 const WEEK_MS = 7 * DAY_MS /** * Get ISO week information for a given date * @param {Date} date - The date to get week info for * @returns {Object} Object containing week number and year */ const isoWeekInfo = date => { const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())) const day = d.getUTCDay() || 7 d.setUTCDate(d.getUTCDate() + 4 - day) const year = d.getUTCFullYear() const yearStart = new Date(Date.UTC(year, 0, 1)) const diffDays = Math.floor((d - yearStart) / DAY_MS) + 1 return { week: Math.ceil(diffDays / 7), year } } /** * Convert a Date object to a local date string (YYYY-MM-DD format) * @param {Date} date - The date to convert (defaults to new Date()) * @returns {string} Date string in YYYY-MM-DD format */ function toLocalString(date = new Date()) { const pad = n => String(Math.floor(Math.abs(n))).padStart(2, '0') return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` } /** * Convert a local date string (YYYY-MM-DD) to a Date object * @param {string} dateString - Date string in YYYY-MM-DD format * @returns {Date} Date object */ function fromLocalString(dateString) { const [year, month, day] = dateString.split('-').map(Number) return new Date(year, month - 1, day) } /** * Get the index of Monday for a given date (0-6, where Monday = 0) * @param {Date} d - The date * @returns {number} Monday index (0-6) */ const mondayIndex = d => (d.getDay() + 6) % 7 /** * Pad a number with leading zeros to make it 2 digits * @param {number} n - Number to pad * @returns {string} Padded string */ const pad = n => String(n).padStart(2, '0') /** * Calculate number of days between two date strings (inclusive) * @param {string} aStr - First date string (YYYY-MM-DD) * @param {string} bStr - Second date string (YYYY-MM-DD) * @returns {number} Number of days inclusive */ function daysInclusive(aStr, bStr) { const a = fromLocalString(aStr) const b = fromLocalString(bStr) const A = new Date(a.getFullYear(), a.getMonth(), a.getDate()).getTime() const B = new Date(b.getFullYear(), b.getMonth(), b.getDate()).getTime() return Math.floor(Math.abs(B - A) / DAY_MS) + 1 } /** * Add days to a date string * @param {string} str - Date string in YYYY-MM-DD format * @param {number} n - Number of days to add (can be negative) * @returns {string} New date string */ function addDaysStr(str, n) { const d = fromLocalString(str) d.setDate(d.getDate() + n) return toLocalString(d) } /** * Get localized weekday names starting from Monday * @returns {Array} Array of localized weekday names */ function getLocalizedWeekdayNames() { const res = [] const base = new Date(2025, 0, 6) // A Monday for (let i = 0; i < 7; i++) { const d = new Date(base) d.setDate(base.getDate() + i) res.push(d.toLocaleDateString(undefined, { weekday: 'short' })) } return res } /** * Get localized month name * @param {number} idx - Month index (0-11) * @param {boolean} short - Whether to return short name * @returns {string} Localized month name */ function getLocalizedMonthName(idx, short = false) { const d = new Date(2025, idx, 1) return d.toLocaleDateString(undefined, { month: short ? 'short' : 'long' }) } /** * Format a date range for display * @param {Date} startDate - Start date * @param {Date} endDate - End date * @returns {string} Formatted date range string */ function formatDateRange(startDate, endDate) { if (toLocalString(startDate) === toLocalString(endDate)) return toLocalString(startDate) const startISO = toLocalString(startDate) const endISO = toLocalString(endDate) const [sy, sm] = startISO.split('-') const [ey, em, ed] = endISO.split('-') if (sy === ey && sm === em) return `${startISO}/${ed}` if (sy === ey) return `${startISO}/${em}-${ed}` return `${startISO}/${endISO}` } // Export all functions and constants export { monthAbbr, DAY_MS, WEEK_MS, isoWeekInfo, toLocalString, fromLocalString, mondayIndex, pad, daysInclusive, addDaysStr, getLocalizedWeekdayNames, getLocalizedMonthName, formatDateRange }