calendar/date-utils.js

140 lines
4.2 KiB
JavaScript

// 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<string>} 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
}