Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions src/blog-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,59 @@ const getClassName = (...classNames: Array<string | false | undefined>) => {
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.
const ISO_DATE_PREFIX_RE = /^(\d{4})-(\d{2})-(\d{2})(?:$|[T\s])/;

const getDatePartsFromString = (value: string) => {
const match = ISO_DATE_PREFIX_RE.exec(value.trim());

if (!match) {
return undefined;
}

const [, year, month, day] = match;

return {
year: Number(year),
month: Number(month),
day: Number(day),
Comment on lines +84 to +89
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.
};
};

const normalizeDate = (value?: BlogDateValue) => {
if (value === undefined) {
return undefined;
}

const date = value instanceof Date ? value : new Date(value);
const date =
value instanceof Date
? value
: typeof value === 'string'
? new Date(value)
: new Date(value);
Comment on lines +98 to +103
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.

return Number.isNaN(date.getTime()) ? undefined : date;
};

const formatBlogDate = (
value: BlogDateValue | undefined,
formatter: Intl.DateTimeFormat,
) => {
if (typeof value === 'string') {
const dateParts = getDatePartsFromString(value);

if (dateParts) {
return formatter.format(
new Date(dateParts.year, dateParts.month - 1, dateParts.day),
);
}
Comment on lines +112 to +119
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.
}

const date = normalizeDate(value);

return date ? formatter.format(date) : undefined;
};

const getPostKey = (post: BlogListItem, index: number) => {
if (post.id !== undefined) {
return post.id;
Expand Down Expand Up @@ -171,7 +214,7 @@ function BlogCard({
interactive,
renderInlineMarkdown,
}: BlogCardProps) {
const normalizedDate = normalizeDate(post.date);
const formattedDate = formatBlogDate(post.date, dateFormatter);
const isInteractive = interactive && Boolean(post.href);
const hasTitle = hasContent(post.title);
const hasDescription = hasContent(post.description);
Expand All @@ -195,10 +238,8 @@ function BlogCard({

const cardContent = (
<>
{normalizedDate ? (
<span className={styles.date}>
{dateFormatter.format(normalizedDate)}
</span>
{formattedDate ? (
<span className={styles.date}>{formattedDate}</span>
) : null}
{hasTitle ? <div className={titleClassName}>{post.title}</div> : null}
{hasDescription ? (
Expand Down
Loading