Improve date handling, error reporting, and consistency across services and helpers

• 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.
This commit is contained in:
Callum Alpass 2025-06-09 14:03:01 +10:00
parent 5830d9343f
commit ef8614e5c3
4 changed files with 97 additions and 56 deletions

View file

@ -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';
}
}
/**

View file

@ -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<TaskInfo[]> {
// 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<NoteInfo[]> {
// 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);

View file

@ -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");

View file

@ -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;
});