Files
bakery-ia/frontend/src/utils/date.ts
2025-09-18 08:06:32 +02:00

602 lines
15 KiB
TypeScript

/**
* 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;
}
};