/** * Date and Time Utilities for WIB (UTC+7) Timezone Handling * Implements D-1 data filtering logic for dashboard display before 17:00 WIB */ const CUTOFF_HOUR = process.env.CUTOFF_HOUR_OVERRIDE ? parseInt(process.env.CUTOFF_HOUR_OVERRIDE, 10) : 17; /** * Get current date/time in WIB (Asia/Jakarta, UTC+7) timezone * @returns {Date} Current date in WIB */ function getCurrentWibDate() { const now = new Date(); // Convert to WIB using toLocaleString with Jakarta timezone const wibDateString = now.toLocaleString('en-US', { timeZone: 'Asia/Jakarta', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); // Parse the formatted string back to Date object return new Date(wibDateString); } /** * Check if current WIB time is before the cutoff hour (default 17:00) * @returns {boolean} True if before cutoff, false otherwise */ function isBeforeCutoffTime() { const wibNow = getCurrentWibDate(); const currentHour = wibNow.getHours(); return currentHour < CUTOFF_HOUR; } /** * Get the effective query date (D-1 if before cutoff, else today) * @returns {Date} Effective date for data queries */ function getEffectiveQueryDate() { const wibToday = getCurrentWibDate(); if (isBeforeCutoffTime()) { // Return D-1 (previous day) const yesterday = new Date(wibToday); yesterday.setDate(yesterday.getDate() - 1); return yesterday; } return wibToday; } /** * Calculate effective day of cycle accounting for D-1 filter * @param {Date|string} cycleStartDate - Cycle start date * @returns {number} Effective cycle day (0-indexed from start date) */ function getEffectiveCycleDay(cycleStartDate) { const effectiveDate = getEffectiveQueryDate(); const startDate = new Date(cycleStartDate); // Reset hours to compare dates only effectiveDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0); const diffMs = effectiveDate.getTime() - startDate.getTime(); // Return -1 if effective date is before cycle start (edge case) if (diffMs < 0) { return -1; } const dayOfCycle = Math.floor(diffMs / (1000 * 60 * 60 * 24)); return dayOfCycle; } /** * Format date as YYYY-MM-DD for database queries * @param {Date} date - Date object to format * @returns {string} Date in YYYY-MM-DD format */ function formatDateForDb(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * Get previous date (D-1) from a given date * @param {string} dateString - Date string in YYYY-MM-DD format * @returns {string} Previous date in YYYY-MM-DD format */ function getPreviousDate(dateString) { const date = new Date(dateString); date.setDate(date.getDate() - 1); return formatDateForDb(date); } /** * Create API response metadata for D-1 filtering * @param {boolean} isD1Applied - Whether D-1 filter was applied * @returns {object} Metadata object for API response */ function createResponseMetadata(isD1Applied = false) { const wibNow = getCurrentWibDate(); const effectiveDate = getEffectiveQueryDate(); return { effectiveDate: formatDateForDb(effectiveDate), queryDate: formatDateForDb(wibNow), isD1Filter: isD1Applied, currentWibTime: wibNow.toLocaleTimeString('id-ID', { timeZone: 'Asia/Jakarta', hour: '2-digit', minute: '2-digit' }) }; } module.exports = { getCurrentWibDate, isBeforeCutoffTime, getEffectiveQueryDate, getEffectiveCycleDay, formatDateForDb, getPreviousDate, createResponseMetadata, CUTOFF_HOUR // Export for testing };