src / datetimeHelper.ts
export interface ParsedDateTime {
dateTime: string;
isValid: boolean;
raw: string;
}
function extractRelativeMinutes(input: string): number | null {
const match = input.match(/in\s+(\d+)\s*(min|minutes|hr|hours|hour)s?/i);
if (!match) return null;
let value = parseInt(match[1]);
const unit = match[2].toLowerCase();
if (unit.startsWith('hr') || unit.startsWith('hour')) value *= 60;
return value;
}
export function parseDateTime(input: string, refDate?: Date): ParsedDateTime {
const now = refDate || new Date();
const lower = input.toLowerCase();
// Relative: "in 5 min", "in 1 hour"
const relMins = extractRelativeMinutes(input);
if (relMins !== null) {
const target = new Date(now.getTime() + relMins * 60000);
return { dateTime: fmt(target), isValid: true, raw: input };
}
// Tomorrow with time: "tomorrow 10am", "tomorrow 10:00"
if (lower.includes('tomorrow')) {
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(0, 1, 0, 0);
const timeMatch = input.match(/(\d{1,2}):?(\d{2})?\s*(am|pm)?/i);
if (timeMatch) {
let h = parseInt(timeMatch[1]) || 0;
const m = parseInt(timeMatch[2]) || 0;
const mer = timeMatch[3]?.toLowerCase();
if (mer === 'pm' && h < 12) h += 12;
if (mer === 'am' && h === 12) h = 0;
tomorrow.setHours(h, m, 0, 0);
}
return { dateTime: fmt(tomorrow), isValid: true, raw: input };
}
// ISO format: "2026-04-21 10:00" or "2026-04-21 10am"
const isoMatch = input.match(/(\d{4})[\/.-](\d{1,2})[\/.-](\d{1,2})\s+(\d{1,2}):(\d{2})/);
if (isoMatch) {
const y = Number(isoMatch[1]);
const mo = Number(isoMatch[2]);
const d = Number(isoMatch[3]);
const h = Number(isoMatch[4]);
const m = Number(isoMatch[5]);
const target = new Date(y, mo - 1, d, h, m, 0, 0);
return { dateTime: fmt(target), isValid: true, raw: input };
}
// ISO format without time: "2026-04-21"
const isoDateOnly = input.match(/^(\d{4})[\/.-](\d{1,2})[\/.-](\d{1,2})$/);
if (isoDateOnly) {
const y = Number(isoDateOnly[1]);
const mo = Number(isoDateOnly[2]);
const d = Number(isoDateOnly[3]);
const target = new Date(y, mo - 1, d, 0, 1, 0, 0);
return { dateTime: fmt(target), isValid: true, raw: input };
}
// DD/MM/YYYY format: "21/04/2026 10:00" or "21/04/2026 10am"
const dateMatch = input.match(/(\d{1,2})[\/.-](\d{1,2})[\/.-](\d{2,4})/);
if (dateMatch) {
let d = parseInt(dateMatch[1]);
let mo = parseInt(dateMatch[2]);
let y = parseInt(dateMatch[3]);
if (y < 100) y += 2000;
const target = new Date(y, mo - 1, d, 0, 1, 0, 0);
const timeMatch = input.match(/(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(am|pm)?/i);
if (timeMatch) {
let h = Number(timeMatch[1]);
const m = Number(timeMatch[2]);
const mer = timeMatch[4]?.toLowerCase();
if (mer === 'pm' && h < 12) h += 12;
if (mer === 'am' && h === 12) h = 0;
target.setHours(h, m, 0, 0);
}
return { dateTime: fmt(target), isValid: true, raw: input };
}
return { dateTime: "", isValid: false, raw: input };
}
function fmt(d: Date): string {
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`;
}
export function getCurrentDateTime(): string {
return fmt(new Date());
}