From 831778ce51e3decac7f3c13250bdeccee7832de0 Mon Sep 17 00:00:00 2001 From: borja Date: Sat, 6 Sep 2025 23:24:12 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20usar=20TZ=20por=20entorno=20y=20parsear?= =?UTF-8?q?=20'hoy'/'ma=C3=B1ana'=20como=20fechas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5) --- src/services/command.ts | 55 +++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/services/command.ts b/src/services/command.ts index 9f77062..52f1041 100644 --- a/src/services/command.ts +++ b/src/services/command.ts @@ -29,40 +29,57 @@ export class CommandService { const parts = (message || '').trim().split(/\s+/); const action = (parts[1] || '').toLowerCase(); - const today = new Date(); - today.setHours(0, 0, 0, 0); - const formatYMD = (d: Date) => d.toISOString().slice(0, 10); + // Zona horaria configurable (por defecto Europe/Madrid) + const TZ = process.env.TZ && process.env.TZ.trim() ? process.env.TZ : 'Europe/Madrid'; + + // Utilidades locales para operar con fechas en la TZ elegida sin depender del huso del host + const ymdFromDateInTZ = (d: Date): string => { + const fmt = new Intl.DateTimeFormat('en-GB', { + timeZone: TZ, + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).formatToParts(d); + const get = (t: string) => fmt.find(p => p.type === t)?.value || ''; + return `${get('year')}-${get('month')}-${get('day')}`; + }; + + const addDaysToYMD = (ymd: string, days: number): string => { + const [Y, M, D] = ymd.split('-').map(n => parseInt(n, 10)); + const base = new Date(Date.UTC(Y, (M || 1) - 1, D || 1)); + base.setUTCDate(base.getUTCDate() + days); + return ymdFromDateInTZ(base); + }; + + const todayYMD = ymdFromDateInTZ(new Date()); type DateCandidate = { index: number; ymd: string }; const dateCandidates: DateCandidate[] = []; const dateTokenIndexes = new Set(); for (let i = 2; i < parts.length; i++) { - const p = parts[i]; - const low = p.toLowerCase(); - - if (/^\d{4}-\d{2}-\d{2}$/.test(p)) { - const d = new Date(p); - if (!isNaN(d.getTime())) { - d.setHours(0, 0, 0, 0); - if (d >= today) { - dateCandidates.push({ index: i, ymd: formatYMD(d) }); - dateTokenIndexes.add(i); - } + // Normalizar token: minúsculas y sin puntuación adyacente simple + const raw = parts[i]; + const low = raw.toLowerCase().replace(/^[([{¿¡"']+/, '').replace(/[.,;:!?)\]}¿¡"'']+$/, ''); + + // Fecha explícita YYYY-MM-DD + if (/^\d{4}-\d{2}-\d{2}$/.test(low)) { + // Validar rango básico y filtrar pasado según hoy en TZ + if (low >= todayYMD) { + dateCandidates.push({ index: i, ymd: low }); + dateTokenIndexes.add(i); } continue; } + // Tokens naturales "hoy"/"mañana" (con o sin acento) if (low === 'hoy') { - dateCandidates.push({ index: i, ymd: formatYMD(today) }); + dateCandidates.push({ index: i, ymd: todayYMD }); dateTokenIndexes.add(i); continue; } - if (low === 'mañana' || low === 'manana') { - const tmr = new Date(today); - tmr.setDate(tmr.getDate() + 1); - dateCandidates.push({ index: i, ymd: formatYMD(tmr) }); + dateCandidates.push({ index: i, ymd: addDaysToYMD(todayYMD, 1) }); dateTokenIndexes.add(i); continue; }