/** * Date and time utilities with Spanish locale support */ import { format, formatDistance, formatRelative, parseISO, isValid, addDays, subDays, startOfDay, endOfDay, startOfWeek, endOfWeek, startOfMonth, endOfMonth, differenceInDays, differenceInHours, differenceInMinutes } from 'date-fns'; import { es } from 'date-fns/locale'; // Default locale for Spanish const defaultLocale = es; // Format date for display export const formatDate = ( date: Date | string, formatStr: string = 'dd/MM/yyyy', locale = defaultLocale ): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } return format(dateObj, formatStr, { locale }); } catch { return ''; } }; // Format date and time for display export const formatDateTime = ( date: Date | string, formatStr: string = 'dd/MM/yyyy HH:mm', locale = defaultLocale ): string => { return formatDate(date, formatStr, locale); }; // Format time only export const formatTime = ( date: Date | string, formatStr: string = 'HH:mm', locale = defaultLocale ): string => { return formatDate(date, formatStr, locale); }; // Format date for API (ISO string) export const formatDateForAPI = (date: Date | string): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } return dateObj.toISOString().split('T')[0]; } catch { return ''; } }; // Format datetime for API (full ISO string) export const formatDateTimeForAPI = (date: Date | string): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } return dateObj.toISOString(); } catch { return ''; } }; // Relative time formatting ("hace 2 horas") export const formatRelativeTime = ( date: Date | string, baseDate: Date = new Date(), locale = defaultLocale ): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } return formatDistance(dateObj, baseDate, { addSuffix: true, locale }); } catch { return ''; } }; // Contextual relative formatting ("maƱana a las 14:00") export const formatRelativeDate = ( date: Date | string, baseDate: Date = new Date(), locale = defaultLocale ): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } return formatRelative(dateObj, baseDate, { locale }); } catch { return ''; } }; // Get start of day export const getStartOfDay = (date: Date | string = new Date()): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return startOfDay(dateObj); }; // Get end of day export const getEndOfDay = (date: Date | string = new Date()): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return endOfDay(dateObj); }; // Get start of week (Monday) export const getStartOfWeek = (date: Date | string = new Date()): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return startOfWeek(dateObj, { weekStartsOn: 1, locale: defaultLocale }); }; // Get end of week (Sunday) export const getEndOfWeek = (date: Date | string = new Date()): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return endOfWeek(dateObj, { weekStartsOn: 1, locale: defaultLocale }); }; // Get start of month export const getStartOfMonth = (date: Date | string = new Date()): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return startOfMonth(dateObj); }; // Get end of month export const getEndOfMonth = (date: Date | string = new Date()): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return endOfMonth(dateObj); }; // Add days to date export const addDaysToDate = (date: Date | string, days: number): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return addDays(dateObj, days); }; // Subtract days from date export const subtractDaysFromDate = (date: Date | string, days: number): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return subDays(dateObj, days); }; // Calculate age from birth date export const calculateAge = (birthDate: Date | string): number => { try { const birth = typeof birthDate === 'string' ? parseISO(birthDate) : birthDate; const today = new Date(); if (!isValid(birth)) { return 0; } let age = today.getFullYear() - birth.getFullYear(); const monthDifference = today.getMonth() - birth.getMonth(); if (monthDifference < 0 || (monthDifference === 0 && today.getDate() < birth.getDate())) { age--; } return age; } catch { return 0; } }; // Check if date is today export const isToday = (date: Date | string): boolean => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; const today = new Date(); if (!isValid(dateObj)) { return false; } return formatDate(dateObj, 'yyyy-MM-dd') === formatDate(today, 'yyyy-MM-dd'); } catch { return false; } }; // Check if date is yesterday export const isYesterday = (date: Date | string): boolean => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; const yesterday = subDays(new Date(), 1); if (!isValid(dateObj)) { return false; } return formatDate(dateObj, 'yyyy-MM-dd') === formatDate(yesterday, 'yyyy-MM-dd'); } catch { return false; } }; // Check if date is tomorrow export const isTomorrow = (date: Date | string): boolean => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; const tomorrow = addDays(new Date(), 1); if (!isValid(dateObj)) { return false; } return formatDate(dateObj, 'yyyy-MM-dd') === formatDate(tomorrow, 'yyyy-MM-dd'); } catch { return false; } }; // Check if date is in the past export const isPastDate = (date: Date | string): boolean => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return false; } return dateObj < new Date(); } catch { return false; } }; // Check if date is in the future export const isFutureDate = (date: Date | string): boolean => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return false; } return dateObj > new Date(); } catch { return false; } }; // Get difference in days export const getDaysAgo = (date: Date | string): number => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return 0; } return differenceInDays(new Date(), dateObj); } catch { return 0; } }; // Get difference in hours export const getHoursAgo = (date: Date | string): number => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return 0; } return differenceInHours(new Date(), dateObj); } catch { return 0; } }; // Get difference in minutes export const getMinutesAgo = (date: Date | string): number => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return 0; } return differenceInMinutes(new Date(), dateObj); } catch { return 0; } }; // Get day of week name export const getDayOfWeekName = ( date: Date | string, format: 'long' | 'short' | 'narrow' = 'long', locale = defaultLocale ): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } const formatMap = { long: 'EEEE', short: 'EEE', narrow: 'EEEEE', }; return formatDate(dateObj, formatMap[format], locale); } catch { return ''; } }; // Get month name export const getMonthName = ( date: Date | string, format: 'long' | 'short' | 'narrow' = 'long', locale = defaultLocale ): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } const formatMap = { long: 'MMMM', short: 'MMM', narrow: 'MMMMM', }; return formatDate(dateObj, formatMap[format], locale); } catch { return ''; } }; // Get business hours for a date export const getBusinessHours = (date: Date | string = new Date()) => { const dayOfWeek = getDayOfWeek(date); // Default business hours (can be customized) const businessHours = { monday: { open: '06:00', close: '20:00' }, tuesday: { open: '06:00', close: '20:00' }, wednesday: { open: '06:00', close: '20:00' }, thursday: { open: '06:00', close: '20:00' }, friday: { open: '06:00', close: '20:00' }, saturday: { open: '07:00', close: '21:00' }, sunday: { open: '08:00', close: '15:00' }, }; return businessHours[dayOfWeek as keyof typeof businessHours]; }; // Get day of week (0 = Sunday, 1 = Monday, etc.) export const getDayOfWeek = (date: Date | string = new Date()): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return 'monday'; } const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; return dayNames[dateObj.getDay()]; } catch { return 'monday'; } }; // Check if date is a weekend export const isWeekend = (date: Date | string = new Date()): boolean => { const dayOfWeek = getDayOfWeek(date); return dayOfWeek === 'saturday' || dayOfWeek === 'sunday'; }; // Check if date is a weekday export const isWeekday = (date: Date | string = new Date()): boolean => { return !isWeekend(date); }; // Generate date range export const generateDateRange = ( startDate: Date | string, endDate: Date | string, step: number = 1 ): Date[] => { try { const start = typeof startDate === 'string' ? parseISO(startDate) : startDate; const end = typeof endDate === 'string' ? parseISO(endDate) : endDate; if (!isValid(start) || !isValid(end) || start > end) { return []; } const dates: Date[] = []; let currentDate = new Date(start); while (currentDate <= end) { dates.push(new Date(currentDate)); currentDate = addDays(currentDate, step); } return dates; } catch { return []; } }; // Get date ranges for common periods export const getDateRanges = () => { const today = new Date(); return { today: { start: getStartOfDay(today), end: getEndOfDay(today), }, yesterday: { start: getStartOfDay(subDays(today, 1)), end: getEndOfDay(subDays(today, 1)), }, thisWeek: { start: getStartOfWeek(today), end: getEndOfWeek(today), }, lastWeek: { start: getStartOfWeek(subDays(today, 7)), end: getEndOfWeek(subDays(today, 7)), }, thisMonth: { start: getStartOfMonth(today), end: getEndOfMonth(today), }, lastMonth: { start: getStartOfMonth(subDays(today, 30)), end: getEndOfMonth(subDays(today, 30)), }, last7Days: { start: getStartOfDay(subDays(today, 6)), end: getEndOfDay(today), }, last30Days: { start: getStartOfDay(subDays(today, 29)), end: getEndOfDay(today), }, last90Days: { start: getStartOfDay(subDays(today, 89)), end: getEndOfDay(today), }, }; }; // Parse date string safely export const parseDate = (dateString: string): Date | null => { try { if (!dateString) return null; const parsed = parseISO(dateString); return isValid(parsed) ? parsed : null; } catch { return null; } }; // Format duration in human readable format export const formatDurationFromDates = ( startDate: Date | string, endDate: Date | string ): string => { try { const start = typeof startDate === 'string' ? parseISO(startDate) : startDate; const end = typeof endDate === 'string' ? parseISO(endDate) : endDate; if (!isValid(start) || !isValid(end)) { return ''; } return formatDistance(start, end, { locale: defaultLocale }); } catch { return ''; } }; // Get season from date export const getSeason = (date: Date | string = new Date()): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return 'spring'; } const month = dateObj.getMonth(); if (month >= 2 && month <= 4) return 'spring'; // Mar, Apr, May if (month >= 5 && month <= 7) return 'summer'; // Jun, Jul, Aug if (month >= 8 && month <= 10) return 'fall'; // Sep, Oct, Nov return 'winter'; // Dec, Jan, Feb } catch { return 'spring'; } }; // Spanish holidays (basic implementation) export const isSpanishHoliday = (date: Date | string): boolean => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return false; } const month = dateObj.getMonth() + 1; // 1-based month const day = dateObj.getDate(); // Fixed holidays const fixedHolidays = [ { month: 1, day: 1 }, // New Year { month: 1, day: 6 }, // Epiphany { month: 5, day: 1 }, // Labor Day { month: 8, day: 15 }, // Assumption { month: 10, day: 12 }, // National Day { month: 11, day: 1 }, // All Saints { month: 12, day: 6 }, // Constitution Day { month: 12, day: 8 }, // Immaculate Conception { month: 12, day: 25 }, // Christmas ]; return fixedHolidays.some(holiday => holiday.month === month && holiday.day === day); } catch { return false; } }; // Time zone utilities export const formatDateInTimezone = ( date: Date | string, timezone: string = 'Europe/Madrid', formatStr: string = 'dd/MM/yyyy HH:mm' ): string => { try { const dateObj = typeof date === 'string' ? parseISO(date) : date; if (!isValid(dateObj)) { return ''; } return new Intl.DateTimeFormat('es-ES', { timeZone: timezone, day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }).format(dateObj); } catch { return formatDate(date, formatStr); } }; // Convert HTML date input (YYYY-MM-DD) to end-of-day datetime for API export const formatExpirationDateForAPI = (dateString: string): string | undefined => { try { if (!dateString) return undefined; // Parse the date string (YYYY-MM-DD format from HTML date input) const dateObj = parseISO(dateString); if (!isValid(dateObj)) { return undefined; } // Set to end of day for expiration dates and return ISO string return getEndOfDay(dateObj).toISOString(); } catch { return undefined; } };