const { DateTime } = require('luxon'); const TIMEZONE = 'America/New_York'; const DB_TIMEZONE = 'UTC-05:00'; const BUSINESS_DAY_START_HOUR = 1; // 1 AM Eastern const WEEK_START_DAY = 7; // Sunday (Luxon uses 1 = Monday, 7 = Sunday) const DB_DATETIME_FORMAT = 'yyyy-LL-dd HH:mm:ss'; const isDateTime = (value) => DateTime.isDateTime(value); const ensureDateTime = (value, { zone = TIMEZONE } = {}) => { if (!value) return null; if (isDateTime(value)) { return value.setZone(zone); } if (value instanceof Date) { return DateTime.fromJSDate(value, { zone }); } if (typeof value === 'number') { return DateTime.fromMillis(value, { zone }); } if (typeof value === 'string') { let dt = DateTime.fromISO(value, { zone, setZone: true }); if (!dt.isValid) { dt = DateTime.fromSQL(value, { zone }); } return dt.isValid ? dt : null; } return null; }; const getNow = () => DateTime.now().setZone(TIMEZONE); const getDayStart = (input = getNow()) => { const dt = ensureDateTime(input); if (!dt || !dt.isValid) { const fallback = getNow(); return fallback.set({ hour: BUSINESS_DAY_START_HOUR, minute: 0, second: 0, millisecond: 0 }); } const sameDayStart = dt.set({ hour: BUSINESS_DAY_START_HOUR, minute: 0, second: 0, millisecond: 0 }); return dt.hour < BUSINESS_DAY_START_HOUR ? sameDayStart.minus({ days: 1 }) : sameDayStart; }; const getDayEnd = (input = getNow()) => { return getDayStart(input).plus({ days: 1 }).minus({ milliseconds: 1 }); }; const getWeekStart = (input = getNow()) => { const dt = ensureDateTime(input); if (!dt || !dt.isValid) { return getDayStart(); } const startOfWeek = dt.set({ weekday: WEEK_START_DAY }).startOf('day'); const normalized = startOfWeek > dt ? startOfWeek.minus({ weeks: 1 }) : startOfWeek; return normalized.set({ hour: BUSINESS_DAY_START_HOUR, minute: 0, second: 0, millisecond: 0 }); }; const getRangeForTimeRange = (timeRange = 'today', now = getNow()) => { const current = ensureDateTime(now); if (!current || !current.isValid) { throw new Error('Invalid reference time for range calculation'); } switch (timeRange) { case 'today': { return { start: getDayStart(current), end: getDayEnd(current) }; } case 'yesterday': { const target = current.minus({ days: 1 }); return { start: getDayStart(target), end: getDayEnd(target) }; } case 'twoDaysAgo': { const target = current.minus({ days: 2 }); return { start: getDayStart(target), end: getDayEnd(target) }; } case 'thisWeek': { return { start: getWeekStart(current), end: getDayEnd(current) }; } case 'lastWeek': { const lastWeek = current.minus({ weeks: 1 }); const weekStart = getWeekStart(lastWeek); const weekEnd = weekStart.plus({ days: 6 }); return { start: weekStart, end: getDayEnd(weekEnd) }; } case 'thisMonth': { const dayStart = getDayStart(current); const monthStart = dayStart.startOf('month').set({ hour: BUSINESS_DAY_START_HOUR }); return { start: monthStart, end: getDayEnd(current) }; } case 'lastMonth': { const lastMonth = current.minus({ months: 1 }); const monthStart = lastMonth .startOf('month') .set({ hour: BUSINESS_DAY_START_HOUR, minute: 0, second: 0, millisecond: 0 }); const monthEnd = monthStart.plus({ months: 1 }).minus({ days: 1 }); return { start: monthStart, end: getDayEnd(monthEnd) }; } case 'last7days': { const dayStart = getDayStart(current); return { start: dayStart.minus({ days: 6 }), end: getDayEnd(current) }; } case 'last30days': { const dayStart = getDayStart(current); return { start: dayStart.minus({ days: 29 }), end: getDayEnd(current) }; } case 'last90days': { const dayStart = getDayStart(current); return { start: dayStart.minus({ days: 89 }), end: getDayEnd(current) }; } case 'previous7days': { const currentPeriodStart = getDayStart(current).minus({ days: 6 }); const previousEndDay = currentPeriodStart.minus({ days: 1 }); const previousStartDay = previousEndDay.minus({ days: 6 }); return { start: getDayStart(previousStartDay), end: getDayEnd(previousEndDay) }; } case 'previous30days': { const currentPeriodStart = getDayStart(current).minus({ days: 29 }); const previousEndDay = currentPeriodStart.minus({ days: 1 }); const previousStartDay = previousEndDay.minus({ days: 29 }); return { start: getDayStart(previousStartDay), end: getDayEnd(previousEndDay) }; } case 'previous90days': { const currentPeriodStart = getDayStart(current).minus({ days: 89 }); const previousEndDay = currentPeriodStart.minus({ days: 1 }); const previousStartDay = previousEndDay.minus({ days: 89 }); return { start: getDayStart(previousStartDay), end: getDayEnd(previousEndDay) }; } default: throw new Error(`Unknown time range: ${timeRange}`); } }; const toDatabaseSqlString = (dt) => { const normalized = ensureDateTime(dt); if (!normalized || !normalized.isValid) { throw new Error('Invalid datetime provided for SQL conversion'); } const dbTime = normalized.setZone(DB_TIMEZONE, { keepLocalTime: true }); return dbTime.toFormat(DB_DATETIME_FORMAT); }; const formatBusinessDate = (input) => { const dt = ensureDateTime(input); if (!dt || !dt.isValid) return ''; return dt.setZone(TIMEZONE).toFormat('LLL d, yyyy'); }; const getTimeRangeLabel = (timeRange) => { const labels = { today: 'Today', yesterday: 'Yesterday', twoDaysAgo: 'Two Days Ago', thisWeek: 'This Week', lastWeek: 'Last Week', thisMonth: 'This Month', lastMonth: 'Last Month', last7days: 'Last 7 Days', last30days: 'Last 30 Days', last90days: 'Last 90 Days', previous7days: 'Previous 7 Days', previous30days: 'Previous 30 Days', previous90days: 'Previous 90 Days' }; return labels[timeRange] || timeRange; }; const getTimeRangeConditions = (timeRange, startDate, endDate) => { if (timeRange === 'custom' && startDate && endDate) { const start = ensureDateTime(startDate); const end = ensureDateTime(endDate); if (!start || !start.isValid || !end || !end.isValid) { throw new Error('Invalid custom date range provided'); } return { whereClause: 'date_placed >= ? AND date_placed <= ?', params: [toDatabaseSqlString(start), toDatabaseSqlString(end)], dateRange: { start: start.toUTC().toISO(), end: end.toUTC().toISO(), label: `${formatBusinessDate(start)} - ${formatBusinessDate(end)}` } }; } const normalizedRange = timeRange || 'today'; const range = getRangeForTimeRange(normalizedRange); return { whereClause: 'date_placed >= ? AND date_placed <= ?', params: [toDatabaseSqlString(range.start), toDatabaseSqlString(range.end)], dateRange: { start: range.start.toUTC().toISO(), end: range.end.toUTC().toISO(), label: getTimeRangeLabel(normalizedRange) } }; }; const getBusinessDayBounds = (timeRange) => { const range = getRangeForTimeRange(timeRange); return { start: range.start.toJSDate(), end: range.end.toJSDate() }; }; const parseBusinessDate = (mysqlDatetime) => { if (!mysqlDatetime || mysqlDatetime === '0000-00-00 00:00:00') { return null; } const dt = DateTime.fromSQL(mysqlDatetime, { zone: DB_TIMEZONE }); if (!dt.isValid) { console.error('[timeUtils] Failed to parse MySQL datetime:', mysqlDatetime, dt.invalidExplanation); return null; } return dt.toUTC().toJSDate(); }; const formatMySQLDate = (input) => { if (!input) return null; const dt = ensureDateTime(input, { zone: 'utc' }); if (!dt || !dt.isValid) return null; return dt.setZone(DB_TIMEZONE).toFormat(DB_DATETIME_FORMAT); }; module.exports = { getBusinessDayBounds, getTimeRangeConditions, formatBusinessDate, getTimeRangeLabel, parseBusinessDate, formatMySQLDate, // Expose helpers for tests or advanced consumers _internal: { getDayStart, getDayEnd, getWeekStart, getRangeForTimeRange, BUSINESS_DAY_START_HOUR } };