|
|
|
@ -58,6 +58,41 @@ export class CommandService {
|
|
|
|
|
|
|
|
|
|
|
|
const todayYMD = ymdFromDateInTZ(new Date());
|
|
|
|
const todayYMD = ymdFromDateInTZ(new Date());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Helpers para validar y normalizar fechas explícitas
|
|
|
|
|
|
|
|
const isLeap = (y: number) => (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0);
|
|
|
|
|
|
|
|
const daysInMonth = (y: number, m: number) => {
|
|
|
|
|
|
|
|
if (m === 2) return isLeap(y) ? 29 : 28;
|
|
|
|
|
|
|
|
return [31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m - 1];
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const isValidYMD = (ymd: string): boolean => {
|
|
|
|
|
|
|
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
|
|
|
|
|
|
|
|
if (!m) return false;
|
|
|
|
|
|
|
|
const Y = parseInt(m[1], 10);
|
|
|
|
|
|
|
|
const MM = parseInt(m[2], 10);
|
|
|
|
|
|
|
|
const DD = parseInt(m[3], 10);
|
|
|
|
|
|
|
|
if (MM < 1 || MM > 12) return false;
|
|
|
|
|
|
|
|
const dim = daysInMonth(Y, MM);
|
|
|
|
|
|
|
|
if (!dim || DD < 1 || DD > dim) return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const normalizeDateToken = (t: string): string | null => {
|
|
|
|
|
|
|
|
// YYYY-MM-DD
|
|
|
|
|
|
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(t)) {
|
|
|
|
|
|
|
|
return isValidYMD(t) ? t : null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// YY-MM-DD -> 20YY-MM-DD
|
|
|
|
|
|
|
|
const m = /^(\d{2})-(\d{2})-(\d{2})$/.exec(t);
|
|
|
|
|
|
|
|
if (m) {
|
|
|
|
|
|
|
|
const yy = parseInt(m[1], 10);
|
|
|
|
|
|
|
|
const mm = m[2];
|
|
|
|
|
|
|
|
const dd = m[3];
|
|
|
|
|
|
|
|
const yyyy = 2000 + yy;
|
|
|
|
|
|
|
|
const ymd = `${String(yyyy)}-${mm}-${dd}`;
|
|
|
|
|
|
|
|
return isValidYMD(ymd) ? ymd : null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type DateCandidate = { index: number; ymd: string };
|
|
|
|
type DateCandidate = { index: number; ymd: string };
|
|
|
|
const dateCandidates: DateCandidate[] = [];
|
|
|
|
const dateCandidates: DateCandidate[] = [];
|
|
|
|
const dateTokenIndexes = new Set<number>();
|
|
|
|
const dateTokenIndexes = new Set<number>();
|
|
|
|
@ -67,11 +102,11 @@ export class CommandService {
|
|
|
|
const raw = parts[i];
|
|
|
|
const raw = parts[i];
|
|
|
|
const low = raw.toLowerCase().replace(/^[([{¿¡"']+/, '').replace(/[.,;:!?)\]}¿¡"'']+$/, '');
|
|
|
|
const low = raw.toLowerCase().replace(/^[([{¿¡"']+/, '').replace(/[.,;:!?)\]}¿¡"'']+$/, '');
|
|
|
|
|
|
|
|
|
|
|
|
// Fecha explícita YYYY-MM-DD
|
|
|
|
// Fecha explícita en formatos permitidos: YYYY-MM-DD o YY-MM-DD (expandido a 20YY)
|
|
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(low)) {
|
|
|
|
{
|
|
|
|
// Validar rango básico y filtrar pasado según hoy en TZ
|
|
|
|
const norm = normalizeDateToken(low);
|
|
|
|
if (low >= todayYMD) {
|
|
|
|
if (norm && norm >= todayYMD) {
|
|
|
|
dateCandidates.push({ index: i, ymd: low });
|
|
|
|
dateCandidates.push({ index: i, ymd: norm });
|
|
|
|
dateTokenIndexes.add(i);
|
|
|
|
dateTokenIndexes.add(i);
|
|
|
|
continue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|