class TimeManager { static ALLOWED_RANGES = ['today', 'yesterday', 'last2days', 'last7days', 'last30days', 'last90days', 'previous7days', 'previous30days', 'previous90days']; constructor(timezone = 'America/New_York', dayStartsAt = 1) { this.timezone = timezone; this.dayStartsAt = dayStartsAt; } getDayBounds(date) { try { const now = new Date(); const targetDate = new Date(date); // For today if ( targetDate.getUTCFullYear() === now.getUTCFullYear() && targetDate.getUTCMonth() === now.getUTCMonth() && targetDate.getUTCDate() === now.getUTCDate() ) { // If current time is before day start (1 AM ET / 6 AM UTC), // use previous day's start until now const todayStart = new Date(Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), this.dayStartsAt + 5, 0, 0, 0 )); if (now < todayStart) { const yesterdayStart = new Date(todayStart); yesterdayStart.setUTCDate(yesterdayStart.getUTCDate() - 1); return { start: yesterdayStart, end: now }; } return { start: todayStart, end: now }; } // For past days, use full 24-hour period const normalizedDate = new Date(Date.UTC( targetDate.getUTCFullYear(), targetDate.getUTCMonth(), targetDate.getUTCDate() )); const dayStart = new Date(normalizedDate); dayStart.setUTCHours(this.dayStartsAt + 5, 0, 0, 0); const dayEnd = new Date(dayStart); dayEnd.setUTCDate(dayEnd.getUTCDate() + 1); return { start: dayStart, end: dayEnd }; } catch (error) { console.error('Error in getDayBounds:', error); throw new Error(`Failed to calculate day bounds: ${error.message}`); } } getDateRange(period) { try { const now = new Date(); const todayBounds = this.getDayBounds(now); const end = new Date(); switch (period) { case 'today': return { start: todayBounds.start, end }; case 'yesterday': { const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1); return this.getDayBounds(yesterday); } case 'last2days': { const twoDaysAgo = new Date(now); twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); return this.getDayBounds(twoDaysAgo); } case 'last7days': { const start = new Date(now); start.setDate(start.getDate() - 6); return { start: this.getDayBounds(start).start, end }; } case 'previous7days': { const end = new Date(now); end.setDate(end.getDate() - 7); const start = new Date(end); start.setDate(start.getDate() - 6); return { start: this.getDayBounds(start).start, end: this.getDayBounds(end).end }; } case 'last30days': { const start = new Date(now); start.setDate(start.getDate() - 29); return { start: this.getDayBounds(start).start, end }; } case 'previous30days': { const end = new Date(now); end.setDate(end.getDate() - 30); const start = new Date(end); start.setDate(start.getDate() - 29); return { start: this.getDayBounds(start).start, end: this.getDayBounds(end).end }; } case 'last90days': { const start = new Date(now); start.setDate(start.getDate() - 89); return { start: this.getDayBounds(start).start, end }; } case 'previous90days': { const end = new Date(now); end.setDate(end.getDate() - 90); const start = new Date(end); start.setDate(start.getDate() - 89); return { start: this.getDayBounds(start).start, end: this.getDayBounds(end).end }; } default: throw new Error(`Unsupported time period: ${period}`); } } catch (error) { console.error('Error in getDateRange:', error); throw error; } } getPreviousPeriod(period) { try { const now = new Date(); switch (period) { case 'today': return 'yesterday'; case 'yesterday': { // Return bounds for 2 days ago const twoDaysAgo = new Date(now); twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); return this.getDayBounds(twoDaysAgo); } case 'last7days': { // Return bounds for previous 7 days const end = new Date(now); end.setDate(end.getDate() - 7); const start = new Date(end); start.setDate(start.getDate() - 7); return { start: this.getDayBounds(start).start, end: this.getDayBounds(end).end }; } case 'last30days': { const end = new Date(now); end.setDate(end.getDate() - 30); const start = new Date(end); start.setDate(start.getDate() - 30); return { start: this.getDayBounds(start).start, end: this.getDayBounds(end).end }; } case 'last90days': { const end = new Date(now); end.setDate(end.getDate() - 90); const start = new Date(end); start.setDate(start.getDate() - 90); return { start: this.getDayBounds(start).start, end: this.getDayBounds(end).end }; } default: throw new Error(`Unsupported time period: ${period}`); } } catch (error) { console.error('Error in getPreviousPeriod:', error); throw error; } } getCurrentBusinessDayEnd() { try { const now = new Date(); const todayBounds = this.getDayBounds(now); // If current time is before day start (1 AM ET / 6 AM UTC), // then we're still in yesterday's business day const todayStart = new Date(Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), this.dayStartsAt + 5, 0, 0, 0 )); if (now < todayStart) { const yesterdayBounds = this.getDayBounds(new Date(now.getTime() - 24 * 60 * 60 * 1000)); return yesterdayBounds.end; } // Return the earlier of current time or today's end return now < todayBounds.end ? now : todayBounds.end; } catch (error) { console.error('Error in getCurrentBusinessDayEnd:', error); return new Date(); } } isValidTimeRange(timeRange) { return TimeManager.ALLOWED_RANGES.includes(timeRange); } isToday(date) { const now = new Date(); const targetDate = new Date(date); return ( targetDate.getUTCFullYear() === now.getUTCFullYear() && targetDate.getUTCMonth() === now.getUTCMonth() && targetDate.getUTCDate() === now.getUTCDate() ); } formatDate(date) { try { return date.toLocaleString('en-US', { timeZone: this.timezone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch (error) { console.error('Error formatting date:', error); return date.toISOString(); } } } export const createTimeManager = (timezone, dayStartsAt) => new TimeManager(timezone, dayStartsAt);