Skip to content

Add timestamp-based add/sub methods for DST-safe time arithmetic#520

Open
dereuromark wants to merge 2 commits into3.xfrom
dst-timestamp-methods
Open

Add timestamp-based add/sub methods for DST-safe time arithmetic#520
dereuromark wants to merge 2 commits into3.xfrom
dst-timestamp-methods

Conversation

@dereuromark
Copy link
Copy Markdown
Member

@dereuromark dereuromark commented Apr 3, 2026

Summary

This PR adds elapsed-time variants of addSeconds(), addMinutes(), and addHours() that correctly handle DST transitions by manipulating Unix timestamps directly instead of using wall clock time.

Related to #519 and #518 which demonstrate this issue.

The Problem

When adding time across DST transitions, the existing methods (addMinutes(), addHours(), addSeconds()) add "wall clock" time, which can produce unexpected results:

// Australia/Melbourne DST ends April 5, 2026 at 3:00 AM
// Clocks go back from 3:00 AM AEDT (+11) to 2:00 AM AEST (+10)
$time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne');
$startOfDay = $time->startOfDay(); // 00:00:00+11:00

// diffInMinutes returns elapsed time (600 minutes = 10 hours)
$diff = $time->diffInMinutes($startOfDay); // 600

// But addMinutes uses wall clock time, so they're NOT inverses
$reconstructed = $startOfDay->addMinutes(600);
// Returns 10:00:00+10:00, NOT the expected 09:00:00+10:00

How Carbon Handles This

Carbon provides two sets of methods:

Method Behavior
addMinutes() Wall clock time via DateInterval
addUTCMinutes() Elapsed time via timestamp arithmetic

This allows developers to choose the semantics they need.

Solution

This PR adds similar elapsed-time methods to Chronos:

  • addElapsedHours() / subElapsedHours()
  • addElapsedMinutes() / subElapsedMinutes()
  • addElapsedSeconds() / subElapsedSeconds()

These methods ensure that diffInMinutes() and addElapsedMinutes() are true inverses:

$time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne');
$startOfDay = $time->startOfDay();

$diff = $time->diffInMinutes($startOfDay); // 600

// Using elapsed time correctly reconstructs the original time
$reconstructed = $startOfDay->addElapsedMinutes($diff);
// Returns 09:00:00+10:00 ✓

Changes

  • Added 6 new methods to Chronos.php
  • Added comprehensive tests including DST transition scenarios
  • Updated documentation in docs/en/modifying.md

Adds new methods that use Unix timestamp arithmetic instead of wall clock
time, ensuring correct behavior across DST transitions:

- addHoursWithTimestamp() / subHoursWithTimestamp()
- addMinutesWithTimestamp() / subMinutesWithTimestamp()
- addSecondsWithTimestamp() / subSecondsWithTimestamp()

These methods are the true inverse of diffInMinutes/diffInSeconds/diffInHours,
which return elapsed time based on timestamps.
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 adds DST-safe, elapsed-time variants of Chronos’ second/minute/hour arithmetic by implementing timestamp-based add/sub methods and documenting/testing their behavior across DST transitions.

Changes:

  • Added add*WithTimestamp() / sub*WithTimestamp() methods for hours, minutes, and seconds in Chronos.
  • Added new test coverage for these methods, including DST “fall back” and “spring forward” scenarios.
  • Updated modifying-time documentation to explain wall-clock vs elapsed-time semantics and point users to the new methods.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/Chronos.php Introduces timestamp-based add/sub methods to perform elapsed-time arithmetic across DST.
tests/TestCase/DateTime/TimestampAddTest.php Adds unit tests validating timestamp-based behavior and DST transition correctness.
docs/en/modifying.md Documents DST considerations and the new timestamp-based APIs.

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

Renamed from *WithTimestamp to *Elapsed for clearer semantics:
- addElapsedHours() / subElapsedHours()
- addElapsedMinutes() / subElapsedMinutes()
- addElapsedSeconds() / subElapsedSeconds()

Also fixed "Daylight Saving Time" spelling (was "Savings").
@dereuromark dereuromark marked this pull request as ready for review April 3, 2026 05:53
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.

3 participants