Skip to content

Add timezone support and safe date formatting utilities#31

Merged
marcelsamyn merged 4 commits intomainfrom
claude/fix-timeline-time-error-Uvr5v
Apr 24, 2026
Merged

Add timezone support and safe date formatting utilities#31
marcelsamyn merged 4 commits intomainfrom
claude/fix-timeline-time-error-Uvr5v

Conversation

@marcelsamyn
Copy link
Copy Markdown
Owner

Summary

This PR adds comprehensive timezone support to the database schema and introduces safe date formatting utilities to prevent crashes from invalid date values.

Key Changes

Database Schema Updates

  • Timestamp columns: Converted all timestamp columns to timestamp with time zone (timestamptz) across all tables (nodes, edges, embeddings, sources, user_profiles, scratchpads, etc.)
  • Migration: Added Drizzle migration 0009_timestamp_to_timestamptz.sql to safely convert existing timestamp columns to timestamptz, treating existing values as UTC

New Safe Date Utilities

  • New file: src/lib/safe-date.ts with two exported functions:
    • safeFormatISO(): Safely formats dates as ISO 8601, falling back to current time for invalid dates
    • safeToISOString(): Safely converts dates to ISO strings with the same fallback behavior
    • Internal toValidDate() helper that validates dates and returns current time if invalid

Updated Date Handling

  • src/lib/formatting.ts: Replaced direct formatISO() calls with safeFormatISO() for message, node, edge, and connection timestamp formatting
  • src/lib/conversation-store.ts: Added inline validation when parsing conversation turn timestamps
  • src/lib/extract-graph.ts: Replaced .toISOString() with safeToISOString() for node timestamps
  • src/lib/jobs/dream.ts: Replaced .toISOString() with safeToISOString() for node timestamps
  • src/lib/jobs/ingest-conversation.ts: Replaced .toISOString() with safeToISOString() for message timestamps

Implementation Details

  • The safe date utilities prevent RangeError: Invalid time value crashes that can occur when formatting invalid dates
  • All existing timestamp values are preserved during migration by treating them as UTC
  • The fallback behavior (using current time for invalid dates) ensures graceful degradation rather than failures

https://claude.ai/code/session_01SDE1HZqgcEgcjWs1UPqAY6

claude added 2 commits April 12, 2026 05:59
The root cause was Drizzle ORM's `timestamp()` (without timezone) using a
fragile date conversion: `new Date(value + "+0000")` which produces
non-standard date strings that can result in Invalid Date objects depending
on the runtime environment.

Two-part fix:
1. Schema: Change all `timestamp()` columns to `timestamp({ withTimezone: true })`
   so Drizzle uses `new Date(value)` directly on timezone-aware PostgreSQL
   strings. Includes migration 0009 to convert existing columns.
2. Defense: Add `safeFormatISO` / `safeToISOString` utilities that validate
   dates before formatting, preventing RangeError crashes in `formatISO` and
   `.toISOString()` call sites across formatting, extract-graph, dream, and
   ingest-conversation.

https://claude.ai/code/session_01SDE1HZqgcEgcjWs1UPqAY6
Replace manually written migration with proper drizzle-kit generate output,
which includes the snapshot JSON needed for drizzle-kit migrate to work.

https://claude.ai/code/session_01SDE1HZqgcEgcjWs1UPqAY6
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates database timestamp columns to timestamptz and introduces a new safe-date utility to handle invalid date values gracefully across the application. The review feedback suggests refactoring the internal toValidDate helper into an exported safeToDate function to centralize validation logic and eliminate manual date checks in conversation-store.ts, improving maintainability and consistency.

Comment thread src/lib/safe-date.ts
Comment on lines +1 to +29
import { formatISO } from "date-fns";

/**
* Converts a value to a valid Date, returning the current time if invalid.
*/
function toValidDate(value: Date | string | number): Date {
const date = value instanceof Date ? value : new Date(value);
if (isNaN(date.getTime())) {
return new Date();
}
return date;
}

/**
* Safely formats a date value as ISO 8601.
* Falls back to the current time if the input is an invalid date,
* preventing RangeError: Invalid time value crashes.
*/
export function safeFormatISO(value: Date | string | number): string {
return formatISO(toValidDate(value));
}

/**
* Safely converts a date value to an ISO string.
* Falls back to the current time if the input is an invalid date.
*/
export function safeToISOString(value: Date | string | number): string {
return toValidDate(value).toISOString();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The internal helper function toValidDate is highly reusable. I recommend renaming it to safeToDate to match the naming convention of the other exports in this module and exporting it so it can be used in conversation-store.ts and other parts of the codebase. This avoids duplicating validation logic and the overhead of creating multiple Date objects.

import { formatISO } from "date-fns";

/**
 * Converts a value to a valid Date, returning the current time if invalid.
 */
export function safeToDate(value: Date | string | number): Date {
  const date = value instanceof Date ? value : new Date(value);
  if (isNaN(date.getTime())) {
    return new Date();
  }
  return date;
}

/**
 * Safely formats a date value as ISO 8601.
 * Falls back to the current time if the input is an invalid date,
 * preventing RangeError: Invalid time value crashes.
 */
export function safeFormatISO(value: Date | string | number): string {
  return formatISO(safeToDate(value));
}

/**
 * Safely converts a date value to an ISO string.
 * Falls back to the current time if the input is an invalid date.
 */
export function safeToISOString(value: Date | string | number): string {
  return safeToDate(value).toISOString();
}

Comment thread src/lib/conversation-store.ts Outdated
Comment on lines +60 to +62
timestamp: isNaN(new Date(meta.timestamp).getTime())
? new Date()
: new Date(meta.timestamp),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of duplicating the validation logic and creating multiple Date objects here, use the new safeToDate utility (after exporting it from safe-date.ts). This improves maintainability and ensures consistent fallback behavior across the application. Note: You will need to add the corresponding import for safeToDate.

      timestamp: safeToDate(meta.timestamp),

claude added 2 commits April 12, 2026 06:03
The manually-written 0009_timestamp_to_timestamptz.sql was replaced by the
drizzle-kit generated 0009_married_smasher.sql but the deletion wasn't staged.

https://claude.ai/code/session_01SDE1HZqgcEgcjWs1UPqAY6
- Export `safeToDate` from safe-date.ts (renamed from private `toValidDate`)
  per Gemini review: makes the helper reusable across the codebase
- Use `safeToDate` in conversation-store.ts instead of inline validation,
  removing duplicated logic
- Fix Prettier formatting in schema.ts, extract-graph.ts, dream.ts

https://claude.ai/code/session_01SDE1HZqgcEgcjWs1UPqAY6
@marcelsamyn marcelsamyn merged commit 17d8f49 into main Apr 24, 2026
1 check passed
@marcelsamyn marcelsamyn deleted the claude/fix-timeline-time-error-Uvr5v branch April 24, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants