Spaces:
Sleeping
Sleeping
| // 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 | |
| }; | |