Skip to content

fix(blog-list): avoid timezone shift when rendering string dates#79

Merged
SoonIter merged 1 commit into
mainfrom
syt/fix-data-formatter
Apr 21, 2026
Merged

fix(blog-list): avoid timezone shift when rendering string dates#79
SoonIter merged 1 commit into
mainfrom
syt/fix-data-formatter

Conversation

@SoonIter
Copy link
Copy Markdown
Member

@SoonIter SoonIter commented Apr 21, 2026

Summary

image
  • fix blog-list string date rendering to use the literal YYYY-MM-DD portion for display
  • avoid day shifts caused by timezone parsing for values like 2025-12-31 16:00:00
  • keep existing Date and timestamp inputs using the current formatter fallback

Testing

  • npx biome check src/blog-list/index.tsx
  • npx tsc --noEmit -p tsconfig.json
  • npm run preflight (script is not defined in this repository)

Copilot AI review requested due to automatic review settings April 21, 2026 07:18
@SoonIter SoonIter merged commit 97fc9d4 into main Apr 21, 2026
8 checks passed
@SoonIter SoonIter deleted the syt/fix-data-formatter branch April 21, 2026 07:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates BlogList date rendering so string dates display consistently by extracting and formatting the literal YYYY-MM-DD portion, avoiding timezone-based day shifts during parsing.

Changes:

  • Add string-date parsing via an ISO date-prefix regex and getDatePartsFromString.
  • Introduce formatBlogDate to format string dates via extracted parts, with fallback to existing Date/timestamp normalization.
  • Update BlogCard to render the preformatted date string.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/blog-list/index.tsx
return classNames.filter(Boolean).join(' ');
};

// 2025-6-26 16:00:00
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The example comment shows 2025-6-26 16:00:00, but ISO_DATE_PREFIX_RE requires zero-padded month/day (\d{2}), so that example would not match and the string would fall back to new Date(value) (reintroducing the timezone-shift behavior). Either update the example to YYYY-MM-DD ... or relax the regex to accept 1–2 digit month/day (and normalize accordingly).

Suggested change
// 2025-6-26 16:00:00
// 2025-06-26 16:00:00

Copilot uses AI. Check for mistakes.
Comment thread src/blog-list/index.tsx
Comment on lines +98 to +103
const date =
value instanceof Date
? value
: typeof value === 'string'
? new Date(value)
: new Date(value);
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

normalizeDate now contains a nested ternary where both non-Date branches are identical (new Date(value)), which adds complexity without changing behavior. Consider reverting to value instanceof Date ? value : new Date(value) to keep the helper straightforward.

Suggested change
const date =
value instanceof Date
? value
: typeof value === 'string'
? new Date(value)
: new Date(value);
const date = value instanceof Date ? value : new Date(value);

Copilot uses AI. Check for mistakes.
Comment thread src/blog-list/index.tsx
Comment on lines +84 to +89
const [, year, month, day] = match;

return {
year: Number(year),
month: Number(month),
day: Number(day),
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

getDatePartsFromString will happily accept out-of-range values like 2026-99-99 (regex match) and new Date(year, month - 1, day) will normalize them into a different valid date rather than returning undefined. If the intent is to treat invalid inputs as missing dates (matching the previous Invalid Date -> undefined behavior), add range checks (and ideally verify the constructed Date’s Y/M/D match the parsed parts).

Suggested change
const [, year, month, day] = match;
return {
year: Number(year),
month: Number(month),
day: Number(day),
const [, yearString, monthString, dayString] = match;
const year = Number(yearString);
const month = Number(monthString);
const day = Number(dayString);
if (
!Number.isInteger(year) ||
!Number.isInteger(month) ||
!Number.isInteger(day) ||
month < 1 ||
month > 12 ||
day < 1 ||
day > 31
) {
return undefined;
}
const normalizedDate = new Date(year, month - 1, day);
if (
Number.isNaN(normalizedDate.getTime()) ||
normalizedDate.getFullYear() !== year ||
normalizedDate.getMonth() !== month - 1 ||
normalizedDate.getDate() !== day
) {
return undefined;
}
return {
year,
month,
day,

Copilot uses AI. Check for mistakes.
Comment thread src/blog-list/index.tsx
Comment on lines +112 to +119
if (typeof value === 'string') {
const dateParts = getDatePartsFromString(value);

if (dateParts) {
return formatter.format(
new Date(dateParts.year, dateParts.month - 1, dateParts.day),
);
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

When dateFormatOptions includes an explicit timeZone, formatting a string date via new Date(year, month - 1, day) (local-midnight) can still shift the rendered day when the formatter time zone differs from the client’s local zone. Consider either (a) documenting that the string-date fast path ignores timeZone semantics and is intended to be a locale-only display, or (b) adjusting the construction strategy based on the requested timeZone (e.g., use a UTC-based Date when dateFormatOptions.timeZone is set).

Copilot uses AI. Check for mistakes.
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