diff --git a/src/Chronos.php b/src/Chronos.php index eadcbb1..eefe0d1 100644 --- a/src/Chronos.php +++ b/src/Chronos.php @@ -1040,6 +1040,25 @@ public function setTimezone(DateTimeZone|string $value): static return parent::setTimezone(static::safeCreateDateTimeZone($value)); } + /** + * Change the timezone while keeping the local time. + * + * Unlike `setTimezone()` which converts the time to the new timezone, + * this method keeps the same wall clock time but changes the timezone. + * + * For example, if you have 10:00 AM in New York and shift to Chicago, + * you'll get 10:00 AM in Chicago (not 9:00 AM as setTimezone would give). + * + * @param \DateTimeZone|string $timezone The new timezone + * @return static + */ + public function shiftTimezone(DateTimeZone|string $timezone): static + { + $timezone = static::safeCreateDateTimeZone($timezone); + + return new static($this->format('Y-m-d H:i:s.u'), $timezone); + } + /** * Return time zone set for this instance. * diff --git a/tests/TestCase/DateTime/InstanceTest.php b/tests/TestCase/DateTime/InstanceTest.php index 54a9d97..1ebee24 100644 --- a/tests/TestCase/DateTime/InstanceTest.php +++ b/tests/TestCase/DateTime/InstanceTest.php @@ -44,4 +44,45 @@ public function testInstanceFromDateTimeKeepsMicros() $carbon = Chronos::instance($datetime); $this->assertSame($micro, $carbon->micro); } + + public function testShiftTimezone(): void + { + $dt = Chronos::create(2024, 6, 15, 10, 30, 0, 0, 'America/New_York'); + $shifted = $dt->shiftTimezone('America/Chicago'); + + // Same wall clock time + $this->assertSame(10, $shifted->hour); + $this->assertSame(30, $shifted->minute); + $this->assertSame(0, $shifted->second); + + // Different timezone + $this->assertSame('America/Chicago', $shifted->tzName); + + // Different UTC time (Chicago is 1 hour behind NY in summer) + $this->assertNotEquals($dt->getTimestamp(), $shifted->getTimestamp()); + } + + public function testShiftTimezoneVsSetTimezone(): void + { + $dt = Chronos::create(2024, 6, 15, 10, 0, 0, 0, 'America/New_York'); + + // setTimezone converts - same moment, different wall clock + $converted = $dt->setTimezone('America/Chicago'); + $this->assertSame(9, $converted->hour); + $this->assertSame($dt->getTimestamp(), $converted->getTimestamp()); + + // shiftTimezone keeps wall clock - different moment + $shifted = $dt->shiftTimezone('America/Chicago'); + $this->assertSame(10, $shifted->hour); + $this->assertNotEquals($dt->getTimestamp(), $shifted->getTimestamp()); + } + + public function testShiftTimezonePreservesMicroseconds(): void + { + $dt = Chronos::create(2024, 6, 15, 10, 30, 45, 123456, 'America/New_York'); + $shifted = $dt->shiftTimezone('Europe/London'); + + $this->assertSame(123456, $shifted->microsecond); + $this->assertSame(45, $shifted->second); + } }