602 lines
15 KiB
TypeScript
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;
|
|
}
|
|
}; |