From ef8614e5c3a13fec05ae97c1ca671e8ab669c7cf Mon Sep 17 00:00:00 2001 From: Callum Alpass Date: Mon, 9 Jun 2025 14:03:01 +1000 Subject: [PATCH] Improve date handling, error reporting, and consistency across services and helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Update date formatting: - In FilterService.ts and CacheManager.ts, replace native Date ISO string extraction (using toISOString().split('T')[0]) with date-fns’ format(date, 'yyyy-MM-dd') to ensure local timezone handling and consistency. • Enhance safe date parsing: - Modify FilterService.ts methods (due date and scheduled date checks, as well as getDueDateGroupFromDate and getScheduledDateGroupFromDate) to use parseISO from date-fns wrapped in try…catch, providing error logging and graceful fallback (e.g., returning 'Invalid Date') when parsing fails. - Similarly, in helpers.ts, use parseISO and other date-fns utilities (startOfDay, isBefore) to safely parse and compare dates in isTaskOverdue and isRecurringTaskDueOn, with error handling to prevent unexpected failures. • Improve note date extraction: - In CacheManager.ts and AgendaView.ts, adjust parsing for note createdDate values to handle both full ISO timestamps and simple YYYY-MM-DD formats, ensuring accurate date grouping and consistent comparisons in the agenda view. • General consistency and robustness: - Replace multiple usages of "new Date(...)" with date-fns equivalents to standardize date operations across the codebase, reduce timezone-related issues, and enhance error handling. This commit ensures more reliable date computation and formatting throughout the project while providing clear error notifications when parsing issues occur. --- src/services/FilterService.ts | 98 +++++++++++++++++++++-------------- src/utils/CacheManager.ts | 15 ++++-- src/utils/helpers.ts | 30 +++++++---- src/views/AgendaView.ts | 10 +++- 4 files changed, 97 insertions(+), 56 deletions(-) diff --git a/src/services/FilterService.ts b/src/services/FilterService.ts index 05d73b25..db2930b3 100644 --- a/src/services/FilterService.ts +++ b/src/services/FilterService.ts @@ -102,7 +102,7 @@ export class FilterService extends EventEmitter { // Get tasks with due dates in the range (existing logic) for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) { - const dateStr = date.toISOString().split('T')[0]; + const dateStr = format(date, 'yyyy-MM-dd'); // CORRECT: Uses local timezone const pathsForDate = this.cacheManager.getTaskPathsByDate(dateStr); pathsForDate.forEach(path => pathsInRange.add(path)); } @@ -254,20 +254,28 @@ export class FilterService extends EventEmitter { // Check due date if (task.due) { - const dueDate = new Date(task.due); - if (query.includeOverdue && dueDate < new Date()) { - // This is an overdue task and we want to include overdue tasks - inRange = true; - } else if (dueDate >= startDate && dueDate <= endDate) { - inRange = true; + try { + const dueDate = parseISO(task.due); // Safe parsing + if (query.includeOverdue && isBefore(dueDate, new Date())) { + // This is an overdue task and we want to include overdue tasks + inRange = true; + } else if (dueDate >= startDate && dueDate <= endDate) { + inRange = true; + } + } catch (error) { + console.error(`Error parsing due date ${task.due}:`, error); } } // Check scheduled date if due date doesn't qualify if (!inRange && task.scheduled) { - const scheduledDate = new Date(task.scheduled); - if (scheduledDate >= startDate && scheduledDate <= endDate) { - inRange = true; + try { + const scheduledDate = parseISO(task.scheduled); // Safe parsing + if (scheduledDate >= startDate && scheduledDate <= endDate) { + inRange = true; + } + } catch (error) { + console.error(`Error parsing scheduled date ${task.scheduled}:`, error); } } @@ -420,25 +428,30 @@ export class FilterService extends EventEmitter { * Helper method to get due date group from a specific date string */ private getDueDateGroupFromDate(dueDate: string): string { - const due = new Date(dueDate); - const today = new Date(); - today.setHours(0, 0, 0, 0); + try { + const due = parseISO(dueDate); // Safe parsing + const today = new Date(); + today.setHours(0, 0, 0, 0); - const tomorrow = new Date(today); - tomorrow.setDate(tomorrow.getDate() + 1); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); - const thisWeek = new Date(today); - thisWeek.setDate(thisWeek.getDate() + 7); + const thisWeek = new Date(today); + thisWeek.setDate(thisWeek.getDate() + 7); - const dueDateOnly = new Date(due); - dueDateOnly.setHours(0, 0, 0, 0); + const dueDateOnly = new Date(due); + dueDateOnly.setHours(0, 0, 0, 0); - if (dueDateOnly < today) return 'Overdue'; - if (dueDateOnly.getTime() === today.getTime()) return 'Today'; - if (dueDateOnly.getTime() === tomorrow.getTime()) return 'Tomorrow'; - if (dueDateOnly <= thisWeek) return 'This week'; - - return 'Later'; + if (dueDateOnly < today) return 'Overdue'; + if (dueDateOnly.getTime() === today.getTime()) return 'Today'; + if (dueDateOnly.getTime() === tomorrow.getTime()) return 'Tomorrow'; + if (dueDateOnly <= thisWeek) return 'This week'; + + return 'Later'; + } catch (error) { + console.error(`Error parsing due date ${dueDate}:`, error); + return 'Invalid Date'; + } } private getScheduledDateGroup(task: TaskInfo, targetDate?: Date): string { @@ -450,25 +463,30 @@ export class FilterService extends EventEmitter { * Helper method to get scheduled date group from a specific date string */ private getScheduledDateGroupFromDate(scheduledDate: string): string { - const scheduled = new Date(scheduledDate); - const today = new Date(); - today.setHours(0, 0, 0, 0); + try { + const scheduled = parseISO(scheduledDate); // Safe parsing + const today = new Date(); + today.setHours(0, 0, 0, 0); - const tomorrow = new Date(today); - tomorrow.setDate(tomorrow.getDate() + 1); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); - const thisWeek = new Date(today); - thisWeek.setDate(thisWeek.getDate() + 7); + const thisWeek = new Date(today); + thisWeek.setDate(thisWeek.getDate() + 7); - const scheduledDateOnly = new Date(scheduled); - scheduledDateOnly.setHours(0, 0, 0, 0); + const scheduledDateOnly = new Date(scheduled); + scheduledDateOnly.setHours(0, 0, 0, 0); - if (scheduledDateOnly < today) return 'Past scheduled'; - if (scheduledDateOnly.getTime() === today.getTime()) return 'Today'; - if (scheduledDateOnly.getTime() === tomorrow.getTime()) return 'Tomorrow'; - if (scheduledDateOnly <= thisWeek) return 'This week'; - - return 'Later'; + if (scheduledDateOnly < today) return 'Past scheduled'; + if (scheduledDateOnly.getTime() === today.getTime()) return 'Today'; + if (scheduledDateOnly.getTime() === tomorrow.getTime()) return 'Tomorrow'; + if (scheduledDateOnly <= thisWeek) return 'This week'; + + return 'Later'; + } catch (error) { + console.error(`Error parsing scheduled date ${scheduledDate}:`, error); + return 'Invalid Date'; + } } /** diff --git a/src/utils/CacheManager.ts b/src/utils/CacheManager.ts index 178dbe88..6d7a2195 100644 --- a/src/utils/CacheManager.ts +++ b/src/utils/CacheManager.ts @@ -3,6 +3,7 @@ import { TaskInfo, NoteInfo, IndexedFile, FileEventHandlers } from '../types'; import { extractNoteInfo, extractTaskInfo, debounce } from './helpers'; import { FieldMapper } from '../services/FieldMapper'; import * as YAML from 'yaml'; +import { format } from 'date-fns'; /** * Unified cache manager that provides centralized data access and caching @@ -362,7 +363,7 @@ export class CacheManager { async getTasksDueOnDate(date: Date): Promise { // Ensure cache is initialized first await this.ensureInitialized(); - const dateStr = date.toISOString().split('T')[0]; + const dateStr = format(date, 'yyyy-MM-dd'); // CORRECT: Uses local timezone const taskPaths = this.tasksByDate.get(dateStr) || new Set(); const results: TaskInfo[] = []; @@ -389,7 +390,7 @@ export class CacheManager { async getNotesForDate(date: Date, forceRefresh = false): Promise { // Ensure cache is initialized first await this.ensureInitialized(); - const dateStr = date.toISOString().split('T')[0]; + const dateStr = format(date, 'yyyy-MM-dd'); // CORRECT: Uses local timezone const notePaths = this.notesByDate.get(dateStr) || new Set(); const results: NoteInfo[] = []; @@ -702,7 +703,10 @@ export class CacheManager { // Add to new indexes if (noteInfo.createdDate) { - const dateStr = noteInfo.createdDate.split('T')[0]; + // Extract just the date part - handle both YYYY-MM-DD and full ISO timestamps + const dateStr = noteInfo.createdDate.includes('T') + ? noteInfo.createdDate.split('T')[0] + : noteInfo.createdDate; if (!this.notesByDate.has(dateStr)) { this.notesByDate.set(dateStr, new Set()); } @@ -804,7 +808,10 @@ export class CacheManager { const oldNote = oldData as NoteInfo; if (oldNote.createdDate) { - const dateStr = oldNote.createdDate.split('T')[0]; + // Extract just the date part - handle both YYYY-MM-DD and full ISO timestamps + const dateStr = oldNote.createdDate.includes('T') + ? oldNote.createdDate.split('T')[0] + : oldNote.createdDate; const pathSet = this.notesByDate.get(dateStr); if (pathSet) { pathSet.delete(path); diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index bd67d4f0..f7be8c10 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,5 +1,5 @@ import { normalizePath, TFile, Vault } from 'obsidian'; -import { format } from 'date-fns'; +import { format, parseISO, startOfDay, isBefore, isSameDay as isSameDayFns } from 'date-fns'; import { TimeInfo, TaskInfo, TimeEntry } from '../types'; import * as YAML from 'yaml'; import { YAMLCache } from './YAMLCache'; @@ -475,11 +475,16 @@ export function extractTaskInfo( export function isTaskOverdue(task: {due?: string}): boolean { if (!task.due) return false; - const dueDate = new Date(task.due); - const today = new Date(); - today.setHours(0, 0, 0, 0); - - return dueDate < today; + try { + // Safely parse the date string using date-fns + const dueDate = startOfDay(parseISO(task.due)); + const today = startOfDay(new Date()); + + return isBefore(dueDate, today); // Use date-fns for comparison + } catch (error) { + console.error(`Error parsing due date ${task.due}:`, error); + return false; + } } /** @@ -516,9 +521,14 @@ export function isRecurringTaskDueOn(task: any, date: Date): boolean { } // Fall back to using the original due date else if (task.due) { - const originalDueDate = new Date(task.due); - return originalDueDate.getDate() === dayOfMonth && - originalDueDate.getMonth() === targetDate.getMonth(); + try { + const originalDueDate = parseISO(task.due); // Safe parsing + return originalDueDate.getDate() === dayOfMonth && + originalDueDate.getMonth() === targetDate.getMonth(); + } catch (error) { + console.error(`Error parsing due date ${task.due}:`, error); + return false; + } } return false; default: @@ -623,7 +633,7 @@ export function extractNoteInfo(content: string, path: string, file?: TFile): {t // If it's a full ISO timestamp or similar, extract just the date part else { try { - const date = new Date(createdDate); + const date = parseISO(createdDate); // Use parseISO for safe parsing if (!isNaN(date.getTime())) { // Format to YYYY-MM-DD to ensure consistency createdDate = format(date, "yyyy-MM-dd"); diff --git a/src/views/AgendaView.ts b/src/views/AgendaView.ts index 3eb2d61c..88012e36 100644 --- a/src/views/AgendaView.ts +++ b/src/views/AgendaView.ts @@ -334,8 +334,14 @@ export class AgendaView extends ItemView { const notesForDate = allNotes.filter(note => { if (note.createdDate) { - const noteCreatedDate = note.createdDate.split('T')[0]; - return noteCreatedDate === dateStr; + try { + // Safely parse the note's date and compare it to the agenda's date + const noteDate = parseISO(note.createdDate); + return isSameDay(noteDate, dayData.date); + } catch (e) { + // Handle cases where the date string might be invalid + return false; + } } return false; });