// Date Utilities with Timezone Support // Fixes inconsistent date handling across the app // ============================================ // TYPES // ============================================ export type DateFormat = 'display' | 'input' | 'iso' | 'short' | 'long'; interface DateFormatOptions { format?: DateFormat; includeTime?: boolean; timezone?: 'local' | 'utc'; } // ============================================ // CONSTANTS // ============================================ // Indian Standard Time offset (UTC+5:30) const IST_OFFSET_HOURS = 5; const IST_OFFSET_MINUTES = 30; // ============================================ // TIMEZONE UTILITIES // ============================================ /** * Get current date in IST timezone */ export const getCurrentDateIST = (): Date => { const now = new Date(); const utc = now.getTime() + (now.getTimezoneOffset() * 60000); return new Date(utc + (IST_OFFSET_HOURS * 3600000) + (IST_OFFSET_MINUTES * 60000)); }; /** * Convert date to IST timezone */ export const toIST = (date: Date): Date => { const utc = date.getTime() + (date.getTimezoneOffset() * 60000); return new Date(utc + (IST_OFFSET_HOURS * 3600000) + (IST_OFFSET_MINUTES * 60000)); }; /** * Get start of day in IST */ export const getStartOfDayIST = (date: Date = new Date()): Date => { const ist = toIST(date); return new Date(ist.getFullYear(), ist.getMonth(), ist.getDate(), 0, 0, 0, 0); }; /** * Get end of day in IST */ export const getEndOfDayIST = (date: Date = new Date()): Date => { const ist = toIST(date); return new Date(ist.getFullYear(), ist.getMonth(), ist.getDate(), 23, 59, 59, 999); }; // ============================================ // FORMATTING // ============================================ /** * Format date to DD/MM/YYYY (Indian format) */ export const formatDisplayDate = (dateStr: string | Date): string => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; if (isNaN(date.getTime())) return '-'; return date.toLocaleDateString('en-IN', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'Asia/Kolkata' }); }; /** * Format date to YYYY-MM-DD (input field format) */ export const formatInputDate = (dateStr: string | Date): string => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; if (isNaN(date.getTime())) return ''; 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}`; }; /** * Format date to DD/MM/YY (short format) */ export const formatShortDate = (dateStr: string | Date): string => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; if (isNaN(date.getTime())) return ''; const dd = date.getDate().toString().padStart(2, '0'); const mm = (date.getMonth() + 1).toString().padStart(2, '0'); const yy = date.getFullYear().toString().slice(-2); return `${dd}/${mm}/${yy}`; }; /** * Format date with time (DD/MM/YYYY HH:mm) */ export const formatDateTime = (dateStr: string | Date): string => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; if (isNaN(date.getTime())) return '-'; const datePart = formatDisplayDate(date); const timePart = date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: true, timeZone: 'Asia/Kolkata' }); return `${datePart} ${timePart}`; }; /** * Format date to long format (e.g., "15 March 2024") */ export const formatLongDate = (dateStr: string | Date): string => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; if (isNaN(date.getTime())) return '-'; return date.toLocaleDateString('en-IN', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'Asia/Kolkata' }); }; /** * Format date in Marathi (e.g., "१५ मार्च २०२४") */ export const formatMarathiDate = (dateStr: string | Date): string => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; if (isNaN(date.getTime())) return '-'; return date.toLocaleDateString('mr-IN', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'Asia/Kolkata' }); }; // ============================================ // CONVENIENCE FUNCTIONS // ============================================ /** * Get today's date in YYYY-MM-DD format (for input fields) */ export const getTodayDate = (): string => { return formatInputDate(new Date()); }; /** * Get today's date in DD/MM/YYYY format (for display) */ export const getTodayDisplayDate = (): string => { return formatDisplayDate(new Date()); }; /** * Parse date string to Date object (handles multiple formats) */ export const parseDate = (dateStr: string): Date | null => { if (!dateStr) return null; // Try ISO format (YYYY-MM-DD) if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { return new Date(dateStr + 'T00:00:00'); } // Try DD/MM/YYYY format if (/^\d{2}\/\d{2}\/\d{4}$/.test(dateStr)) { const [day, month, year] = dateStr.split('/'); return new Date(`${year}-${month}-${day}T00:00:00`); } // Try DD/MM/YY format if (/^\d{2}\/\d{2}\/\d{2}$/.test(dateStr)) { const [day, month, year] = dateStr.split('/'); return new Date(`20${year}-${month}-${day}T00:00:00`); } // Fallback to native parsing const parsed = new Date(dateStr); return isNaN(parsed.getTime()) ? null : parsed; }; // ============================================ // DATE CALCULATIONS // ============================================ /** * Add days to a date */ export const addDays = (date: Date, days: number): Date => { const result = new Date(date); result.setDate(result.getDate() + days); return result; }; /** * Get difference in days between two dates */ export const getDaysDifference = (date1: Date, date2: Date): number => { const oneDay = 24 * 60 * 60 * 1000; const d1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()); const d2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); return Math.round(Math.abs((d2.getTime() - d1.getTime()) / oneDay)); }; /** * Check if date is today */ export const isToday = (dateStr: string | Date): boolean => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; const today = new Date(); return ( date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate() ); }; /** * Check if date is in the past */ export const isPastDate = (dateStr: string | Date): boolean => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; const today = getStartOfDayIST(); return date < today; }; /** * Check if date is in the future */ export const isFutureDate = (dateStr: string | Date): boolean => { const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; const today = getEndOfDayIST(); return date > today; }; // ============================================ // FINANCIAL YEAR UTILITIES (India: April-March) // ============================================ /** * Get current financial year */ export const getCurrentFinancialYear = (): { start: Date; end: Date; label: string } => { const now = new Date(); const year = now.getFullYear(); const month = now.getMonth(); // 0-indexed // If before April (month < 3), FY is previous year to current year // If April or later (month >= 3), FY is current year to next year const fyStartYear = month < 3 ? year - 1 : year; const start = new Date(fyStartYear, 3, 1); // April 1 const end = new Date(fyStartYear + 1, 2, 31); // March 31 const label = `FY ${fyStartYear}-${(fyStartYear + 1).toString().slice(-2)}`; return { start, end, label }; }; /** * Get all financial years between two dates */ export const getFinancialYears = (startDate: Date, endDate: Date): string[] => { const years: string[] = []; let current = new Date(startDate); while (current <= endDate) { const fy = getCurrentFinancialYear(); const year = current.getMonth() < 3 ? current.getFullYear() - 1 : current.getFullYear(); const label = `FY ${year}-${(year + 1).toString().slice(-2)}`; if (!years.includes(label)) { years.push(label); } current.setMonth(current.getMonth() + 1); } return years; }; /** * Check if date falls in a specific financial year */ export const isInFinancialYear = (date: Date, fyLabel: string): boolean => { const match = fyLabel.match(/FY (\d{4})-(\d{2})/); if (!match) return false; const startYear = parseInt(match[1]); const fyStart = new Date(startYear, 3, 1); const fyEnd = new Date(startYear + 1, 2, 31, 23, 59, 59); return date >= fyStart && date <= fyEnd; }; // ============================================ // UNIVERSAL FORMAT FUNCTION // ============================================ /** * Format date with options */ export const formatDate = ( dateStr: string | Date, options: DateFormatOptions = {} ): string => { const { format = 'display', includeTime = false, timezone = 'local' } = options; let date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr; if (isNaN(date.getTime())) return '-'; if (timezone === 'utc') { date = new Date(date.toUTCString()); } switch (format) { case 'input': return formatInputDate(date); case 'short': return formatShortDate(date); case 'long': return formatLongDate(date); case 'iso': return date.toISOString(); default: if (includeTime) { return formatDateTime(date); } return formatDisplayDate(date); } }; export default { getCurrentDateIST, toIST, getStartOfDayIST, getEndOfDayIST, formatDisplayDate, formatInputDate, formatShortDate, formatDateTime, formatLongDate, formatMarathiDate, getTodayDate, getTodayDisplayDate, parseDate, addDays, getDaysDifference, isToday, isPastDate, isFutureDate, getCurrentFinancialYear, getFinancialYears, isInFinancialYear, formatDate };