Skip to content

feat: normalizeDate() uses defaultTimeZone to reinterpret Z-suffix timestamps - #1

Merged
wikytam merged 1 commit into
mainfrom
feat/defaultTimeZone-reinterpret
Sep 18, 2026
Merged

wikytam merged 1 commit into
mainfrom
feat/defaultTimeZone-reinterpret

Conversation

@wikytam

@wikytam wikytam commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Problem

When backends like Drizzle or Prisma store timestamps in local time but append Z suffix (e.g. "2026-09-18T14:26:06.136Z" for Vietnam time), new Date() misinterprets them as UTC, causing a 7-hour shift.

Solution

A single, focused change in normalizeDate() that uses defaultTimeZone config to reinterpret Z-suffix strings. All date methods automatically benefit.

Changes

A. normalizeDate(value, defaultTimeZone?) — Reinterpret Z-suffix timestamps

When defaultTimeZone is set and differs from "UTC", Z-suffix ISO strings have their Z replaced with the real UTC offset:

// "2026-09-18T14:26:06.136Z" + defaultTimeZone="Asia/Ho_Chi_Minh"
// -> "2026-09-18T14:26:06.136+07:00"
// -> correctly parsed as 07:26:06 UTC

B. All date methods pass this.defaultTimeZone to normalizeDate()

  • asDate(), asTime(), asDatetime(), asTimestamp(), asRelativeTime()

C. New helper: getIANAOffset(iana, refDate)

Uses Intl.DateTimeFormat to resolve IANA timezone names to UTC offset strings. Handles DST transitions and half-hour offsets (e.g. Asia/Kolkata = +05:30).

D. parseDate() and formatDate() also support timezone reinterpretation

Usage

import { Formatter, configureFormatter, formatter } from "@wikytam/helpers"

// Configure once at app entry
configureFormatter({
  locale: "vi-VN",
  timeZone: "Asia/Ho_Chi_Minh",
  defaultTimeZone: "Asia/Ho_Chi_Minh", // <-- key setting
})

// Backend returns "2026-09-18T14:26:06.136Z" (actually VN time)
formatter.asDatetime("2026-09-18T14:26:06.136Z")
// -> "18 thg 9, 2026, 14:26:06" (correct!)
// Without defaultTimeZone it would show "21:26:06" (wrong, +7h shift)

Backward Compatibility

  • defaultTimeZone defaults to "UTC" — existing behavior is unchanged
  • Date objects and UNIX timestamps (numbers) are not affected
  • Strings with explicit offset (e.g. +07:00) are not reinterpretated

Tests

25 new tests covering:

  • Z-suffix reinterpretation for multiple timezones (VN, Tokyo, New York)
  • DST transitions (EST vs EDT)
  • Half-hour offsets (Asia/Kolkata)
  • Edge cases: Date objects, UNIX timestamps, non-Z strings
  • getIANAOffset() utility
  • parseDate() with timezone param
  • formatDate() global utility with defaultTimeZone

285 tests total, all passing.

Version

1.0.4 -> 1.1.0 (new feature, semver minor)

…mestamps

When backends (Drizzle, Prisma) append Z to timestamps that are actually
stored in local time, normalizeDate() now replaces the Z with the real
UTC offset from defaultTimeZone config.

Changes:
- Add getIANAOffset() helper using Intl.DateTimeFormat
- normalizeDate() accepts optional defaultTimeZone parameter
- All date methods (asDate, asTime, asDatetime, asTimestamp,
  asRelativeTime) pass this.defaultTimeZone to normalizeDate()
- parseDate() also supports defaultTimeZone for reinterpretation
- formatDate() global utility uses formatter's defaultTimeZone
- Export getIANAOffset from package
- Bump version to 1.1.0
- 285 tests passing (25 new timezone tests)
@wikytam
wikytam merged commit 8680290 into main Sep 18, 2026
3 checks passed
@wikytam
wikytam deleted the feat/defaultTimeZone-reinterpret branch September 18, 2026 08:28
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.

1 participant