| 232 | * @returns A Date object representing the UTC anchor for that date |
| 233 | */ |
| 234 | export function parseDateToUTC(dateString: string): Date { |
| 235 | if (!dateString) { |
| 236 | const error = new Error("Date string cannot be empty"); |
| 237 | tasknotesLogger.error("Date parsing error:", { |
| 238 | category: "validation", |
| 239 | operation: "date-parsing", |
| 240 | details: { dateString }, |
| 241 | error: error.message, |
| 242 | }); |
| 243 | throw error; |
| 244 | } |
| 245 | |
| 246 | // Trim whitespace |
| 247 | const trimmed = dateString.trim(); |
| 248 | |
| 249 | try { |
| 250 | // Handle date with day name format (e.g., "2024-01-26 Fri") |
| 251 | const dateWithDayNameMatch = trimmed.match( |
| 252 | /^(\d{4}-\d{2}-\d{2})\s+(Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)$/i |
| 253 | ); |
| 254 | if (dateWithDayNameMatch) { |
| 255 | // Extract just the date part and continue with normal parsing |
| 256 | const dateOnly = dateWithDayNameMatch[1]; |
| 257 | return parseDateToUTC(dateOnly); |
| 258 | } |
| 259 | |
| 260 | // For date-only strings (YYYY-MM-DD), create a Date at UTC midnight |
| 261 | const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); |
| 262 | if (dateOnlyMatch) { |
| 263 | const [, year, month, day] = dateOnlyMatch; |
| 264 | const yearNum = parseInt(year, 10); |
| 265 | const monthNum = parseInt(month, 10); |
| 266 | const dayNum = parseInt(day, 10); |
| 267 | |
| 268 | // Validate date components |
| 269 | if (monthNum < 1 || monthNum > 12) { |
| 270 | throw new Error(`Invalid month in date: ${dateString}`); |
| 271 | } |
| 272 | |
| 273 | if (dayNum < 1 || dayNum > 31) { |
| 274 | throw new Error(`Invalid day in date: ${dateString}`); |
| 275 | } |
| 276 | |
| 277 | // Create Date object at UTC midnight for this calendar day |
| 278 | const parsed = new Date(Date.UTC(yearNum, monthNum - 1, dayNum)); |
| 279 | |
| 280 | // Validate that the date didn't roll over (e.g., Feb 31 -> March 3) |
| 281 | if ( |
| 282 | parsed.getUTCFullYear() !== yearNum || |
| 283 | parsed.getUTCMonth() !== monthNum - 1 || |
| 284 | parsed.getUTCDate() !== dayNum |
| 285 | ) { |
| 286 | throw new Error(`Invalid date values: ${dateString}`); |
| 287 | } |
| 288 | |
| 289 | return parsed; |
| 290 | } |
| 291 | |