ts-time-utils
Version:
A comprehensive TypeScript utility library for time, dates, durations, and calendar operations with full tree-shaking support
218 lines (217 loc) • 7.89 kB
JavaScript
/**
* Advanced date parsing utilities
*/
/**
* Parse various date formats intelligently
* @param input - date string, number, or Date object
*/
export function parseDate(input) {
if (input instanceof Date) {
return isNaN(input.getTime()) ? null : input;
}
if (typeof input === 'number') {
const date = new Date(input);
return isNaN(date.getTime()) ? null : date;
}
if (typeof input !== 'string') {
return null;
}
// Try native Date parsing first, but validate the result
const nativeDate = new Date(input);
if (!isNaN(nativeDate.getTime())) {
// Additional validation for edge cases like "2025-02-30"
if (input.includes('-') && input.match(/^\d{4}-\d{2}-\d{2}/)) {
const [year, month, day] = input.split('-').map(Number);
const testDate = new Date(year, month - 1, day);
if (testDate.getFullYear() !== year || testDate.getMonth() !== month - 1 || testDate.getDate() !== day) {
return null;
}
}
return nativeDate;
}
// Try common patterns
const patterns = [
/^(\d{4})-(\d{2})-(\d{2})$/, // YYYY-MM-DD
/^(\d{2})\/(\d{2})\/(\d{4})$/, // MM/DD/YYYY
/^(\d{2})-(\d{2})-(\d{4})$/, // MM-DD-YYYY
/^(\d{4})(\d{2})(\d{2})$/, // YYYYMMDD
];
for (const pattern of patterns) {
const match = input.match(pattern);
if (match) {
const [, first, second, third] = match;
// Try different interpretations based on pattern
if (pattern.source.includes('(\\d{4})')) {
// Year first format
const date = new Date(parseInt(first), parseInt(second) - 1, parseInt(third));
if (!isNaN(date.getTime()))
return date;
}
else {
// Month/day first format (assuming US format)
const date = new Date(parseInt(third), parseInt(first) - 1, parseInt(second));
if (!isNaN(date.getTime()))
return date;
}
}
}
return null;
}
/**
* Parse relative date strings like "tomorrow", "next monday", "2 weeks ago"
* @param input - relative date string
*/
export function parseRelativeDate(input) {
const now = new Date();
const lowercaseInput = input.toLowerCase().trim();
// Handle simple cases
switch (lowercaseInput) {
case 'now':
case 'today':
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
case 'yesterday':
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
case 'tomorrow':
return new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
}
// Handle "X time ago" or "in X time"
const agoMatch = lowercaseInput.match(/^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/);
if (agoMatch) {
const [, amount, unit] = agoMatch;
return subtractTimeUnits(now, parseInt(amount), unit);
}
const inMatch = lowercaseInput.match(/^in\s+(\d+)\s+(second|minute|hour|day|week|month|year)s?$/);
if (inMatch) {
const [, amount, unit] = inMatch;
return addTimeUnits(now, parseInt(amount), unit);
}
// Handle "next/last weekday"
const weekdays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
const nextWeekdayMatch = lowercaseInput.match(/^next\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/);
if (nextWeekdayMatch) {
const targetDay = weekdays.indexOf(nextWeekdayMatch[1]);
const daysUntilTarget = (targetDay - now.getDay() + 7) % 7 || 7;
return new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysUntilTarget);
}
const lastWeekdayMatch = lowercaseInput.match(/^last\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/);
if (lastWeekdayMatch) {
const targetDay = weekdays.indexOf(lastWeekdayMatch[1]);
const daysSinceTarget = (now.getDay() - targetDay + 7) % 7 || 7;
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysSinceTarget);
}
return null;
}
/**
* Parse "time ago" strings like "5 minutes ago", "2 hours ago"
* @param input - time ago string
*/
export function parseTimeAgo(input) {
const now = new Date();
const lowercaseInput = input.toLowerCase().trim();
// Handle simple cases
if (lowercaseInput === 'just now' || lowercaseInput === 'now') {
return now;
}
// Handle "X time ago"
const match = lowercaseInput.match(/^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/);
if (match) {
const [, amount, unit] = match;
return subtractTimeUnits(now, parseInt(amount), unit);
}
return null;
}
/**
* Parse custom date format
* @param dateString - date string to parse
* @param format - format pattern (e.g., "YYYY-MM-DD", "DD/MM/YYYY")
*/
export function parseCustomFormat(dateString, format) {
// Simple implementation for common patterns
const formatMap = {
'YYYY-MM-DD': /^(\d{4})-(\d{2})-(\d{2})$/,
'DD/MM/YYYY': /^(\d{2})\/(\d{2})\/(\d{4})$/,
'MM/DD/YYYY': /^(\d{2})\/(\d{2})\/(\d{4})$/,
'DD-MM-YYYY': /^(\d{2})-(\d{2})-(\d{4})$/,
'MM-DD-YYYY': /^(\d{2})-(\d{2})-(\d{4})$/,
};
const regex = formatMap[format];
if (!regex)
return null;
const match = dateString.match(regex);
if (!match)
return null;
const [, first, second, third] = match;
let year, month, day;
switch (format) {
case 'YYYY-MM-DD':
year = parseInt(first);
month = parseInt(second) - 1;
day = parseInt(third);
break;
case 'DD/MM/YYYY':
case 'DD-MM-YYYY':
day = parseInt(first);
month = parseInt(second) - 1;
year = parseInt(third);
break;
case 'MM/DD/YYYY':
case 'MM-DD-YYYY':
month = parseInt(first) - 1;
day = parseInt(second);
year = parseInt(third);
break;
default:
return null;
}
const date = new Date(year, month, day);
// Validate that the date components match what was parsed
// This catches cases like February 30th which JS converts to March 2nd
if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
return null;
}
return isNaN(date.getTime()) ? null : date;
}
/**
* Try parsing with multiple formats
* @param dateString - date string to parse
* @param formats - array of format patterns to try
*/
export function parseManyFormats(dateString, formats) {
for (const format of formats) {
const result = parseCustomFormat(dateString, format);
if (result)
return result;
}
return null;
}
// Helper functions
function addTimeUnits(date, amount, unit) {
const result = new Date(date);
switch (unit) {
case 'second':
result.setSeconds(result.getSeconds() + amount);
break;
case 'minute':
result.setMinutes(result.getMinutes() + amount);
break;
case 'hour':
result.setHours(result.getHours() + amount);
break;
case 'day':
result.setDate(result.getDate() + amount);
break;
case 'week':
result.setDate(result.getDate() + (amount * 7));
break;
case 'month':
result.setMonth(result.getMonth() + amount);
break;
case 'year':
result.setFullYear(result.getFullYear() + amount);
break;
}
return result;
}
function subtractTimeUnits(date, amount, unit) {
return addTimeUnits(date, -amount, unit);
}