Skip to content

Use calendar days for "yesterday"/"tomorrow" rounding - #393

Merged
liuliu-dev merged 11 commits into
github:mainfrom
gabrielcaires:fix/yesterday-calendar-days
Nov 7, 2026
Merged

liuliu-dev merged 11 commits into
github:mainfrom
gabrielcaires:fix/yesterday-calendar-days

Conversation

@gabrielcaires

@gabrielcaires gabrielcaires commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #313

Problem

roundToSingleUnit turned N days + H hours into days by rounding the hours (days += Math.round(hours / 24)). "Yesterday" is a calendar concept, so a date 36–37 hours ago that is still on the previous calendar day showed as "2 days ago", while 38h ago (also yesterday) showed "yesterday" again.

Example from the issue (local time): now is Apr 30 13:53, date is Apr 29 00:53 → -P1DT13H → was "2 days ago", now "yesterday".

Change

  • When a duration has days but no weeks/months/years, days is the number of calendar days between relativeTo and the target date (applyDuration(relativeTo, duration)), compared at local midnight and rounded so DST days of 23h/25h still count as one day. Other cases keep the existing rounding.
  • The element passes the now it used for elapsedTime as relativeTo, plus the exact date, to the relative and micro formatters. Calendar days are counted from the real date rather than one rebuilt from the precision-truncated duration (dropped milliseconds, precision="hour"/"day"), and both rounding passes in the micro path agree.
  • On a 25 hour DST day, a full day of elapsed time can stay on one calendar date; that case stays in hours ("24 hours ago") instead of rounding away.

The duration format without micro doesn't call roundToSingleUnit and is unaffected.

Tests

  • New roundToSingleUnit cases for the issue scenario (past and future), for the 2 days + under 1 hour window right after/before midnight, and a case that stays at 2 days.
  • P1DT20H / P1DT18H used the default relativeTo (midnight), where +1d20h is calendar "tomorrow". They now use noon, which gives P2D in both directions.
  • Cases for the exact date option (truncated milliseconds, precision="day") and the DST same-day case.
  • npm test passes (647 tests).

The rewrite proposed in #301 also counts calendar days and would cover this issue.

🤖 Generated with Claude Code

"yesterday" and "tomorrow" were decided by rounding the hours part, so a
date 36-37 hours ago (still yesterday on the calendar) showed as
"2 days ago". When a duration has days but no weeks/months/years, count
the calendar days between relativeTo and the target date instead.

The element now also passes the `now` it used for elapsedTime as
relativeTo, so both calculations agree near midnight.

Fixes github#313

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gabrielcaires
gabrielcaires requested a review from a team as a code owner November 3, 2026 12:43
Copilot AI auto review requested due to automatic review settings November 3, 2026 12:43

Copilot AI left a comment

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.

🟡 Changes recommended

Unresolved DST handling and fractional-second truncation defects produce incorrect relative times, including “now” for a 24-hour-old timestamp and off-by-one future day counts.

3 open issues
What changed in this PR

This PR addresses #313 by using local calendar-day differences for day-level relative-time rounding, so “yesterday” and “tomorrow” better match calendar dates.

Changes:

  • Replaces hour-based day rounding with local calendar-day counting for durations without weeks, months, or years.
  • Passes the same reference time through elapsed-time calculation and relative/micro formatting.
  • Adds regression cases for past and future dates near midnight.
File Description
test/​duration.ts Adds calendar-day regression cases and explicit reference times.
src/​relative-time-element.ts Shares the reference time with relative and micro formatters.
src/​duration.ts Adds local calendar-day counting to single-unit rounding.

Review effort: Balanced (auto)

Comment thread src/duration.ts Resolved
Comment thread src/duration.ts Resolved
Comment thread src/relative-time-element.ts Resolved
gabrielcaires and others added 2 commits November 3, 2026 12:53
Rebuilding the target date from a precision-truncated duration could
cross midnight (dropped milliseconds, precision="hour"/"day"), and
re-rounding an already rounded micro duration could do the same across
DST. roundToSingleUnit now accepts the exact `date` and the element
passes it along with relativeTo.

On a 25 hour DST day a full day of elapsed time can stay on one
calendar date; keep that in hours instead of rounding it to "now".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

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.

🟡 Changes recommended

Unresolved calendar-day rounding issues produce incorrect durations, ignore the configured time zone, and leave labels stale across midnight, while existing tests have become clock-dependent.

4 open issues
3 resolved since last review

Review effort: Max

Comment thread src/duration.ts Resolved
Comment thread src/duration.ts Resolved
Comment thread src/relative-time-element.ts Resolved
Comment thread src/relative-time-element.ts Resolved
- Pass the resolved time zone into calendar-day rounding
- Pick the micro unit from the already-rounded duration instead of rounding twice
- Schedule an update at the next midnight for hour-scale dates
- Freeze the clock in micro day-format tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

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.

🟡 Changes recommended

Unresolved date-handling bugs delay refreshes when DST skips across midnight and treat timestamp 0 as missing, producing incorrect relative-day output.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Treat timestamp zero as a valid date option

src/​duration.ts:245

The new date option accepts numeric timestamps, but this truthiness check treats the Unix epoch (0) as missing. With relativeTo: new Date('1969-12-30T10:00:00.500Z'), date: 0, timeZone: 'UTC', and duration P1DT13H59M59S, the fallback reconstructs December 31 just before midnight and returns P1D instead of P2D. Check against undefined so numeric zero receives the same exact-date handling as new Date(0).

4 resolved since last review

Review effort: Max

Comment thread src/duration.ts Resolved
gabrielcaires and others added 2 commits November 5, 2026 10:02
…r-days

# Conflicts:
#	src/relative-time-element.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

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.

🟡 Changes recommended

The helper misses backward calendar-date changes, leaving labels stale for 30 minutes, and performs unnecessary time-zone formatting calls on every observer tick.

2 open issues
1 resolved since last review

Review effort: Max

Comment thread src/duration.ts Resolved
Comment thread src/duration.ts Resolved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

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.

🔵 Needs a closer look

Human review is needed to assess a spring-forward boundary case where elapsed-hour rounding understates calendar-day differences and produces an incorrect relative-day label.

No open issues 🎉

2 resolved since last review

Review effort: Max

A 23 hour spring-forward day lets under 24 hours span two dates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

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.

🟡 Changes recommended

The fallback in src/duration.ts drops the 21+ hour-to-day carry when days is zero, causing a regression in month/year rounding.

1 open issue

Review effort: Max

Comment thread src/duration.ts Resolved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

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.

🔵 Needs a closer look

Human assessment is needed because date reconstruction around the year 0099–0100 boundary can cause duration rounding to report hundreds of thousands of calendar days instead of one.

No open issues 🎉

1 resolved since last review

Review effort: Max

Date.UTC maps them to 1900-1999.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

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.

🔵 Needs a closer look

Human assessment is needed because explicit-zone era handling can miscount a one-day interval across year zero as 364 days and round it to a year.

No open issues 🎉

Review effort: Max

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@liuliu-dev liuliu-dev left a comment

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.

@gabrielcaires thanks for addressing the copilot findings ❤️ ! The changes LGTM!

@liuliu-dev
liuliu-dev merged commit fd90015 into github:main Nov 7, 2026
2 checks passed
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.

Inconsistent use of "yesterday"

3 participants