Skip to content

Commit

Permalink
Merge pull request #3074 from briannesbitt/test/overflow-failure
Browse files Browse the repository at this point in the history
Fix timezone issue when add/sub with overflow
  • Loading branch information
kylekatarnls authored Oct 9, 2024
2 parents cda6063 + 523d063 commit fff50e3
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 36 deletions.
3 changes: 2 additions & 1 deletion src/Carbon/Traits/Cast.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public function cast(string $className): mixed
{
if (!method_exists($className, 'instance')) {
if (is_a($className, DateTimeInterface::class, true)) {
return new $className($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone());
return $className::createFromFormat('U.u', $this->rawFormat('U.u'))
->setTimezone($this->getTimezone());
}

throw new InvalidCastException("$className has not the instance() method needed to cast the date.");
Expand Down
6 changes: 4 additions & 2 deletions src/Carbon/Traits/Converter.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ public function toJSON(): ?string
*/
public function toDateTime(): DateTime
{
return new DateTime($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone());
return DateTime::createFromFormat('U.u', $this->rawFormat('U.u'))
->setTimezone($this->getTimezone());
}

/**
Expand All @@ -488,7 +489,8 @@ public function toDateTime(): DateTime
*/
public function toDateTimeImmutable(): DateTimeImmutable
{
return new DateTimeImmutable($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone());
return DateTimeImmutable::createFromFormat('U.u', $this->rawFormat('U.u'))
->setTimezone($this->getTimezone());
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Carbon/Traits/Creator.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ public static function instance(DateTimeInterface $date): static
return clone $date;
}

$instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone());
$instance = parent::createFromFormat('U.u', $date->format('U.u'))
->setTimezone($date->getTimezone());

if ($date instanceof CarbonInterface) {
$settings = $date->getSettings();
Expand Down
22 changes: 19 additions & 3 deletions src/Carbon/Traits/Date.php
Original file line number Diff line number Diff line change
Expand Up @@ -1708,9 +1708,11 @@ public function setUnitNoOverflow(string $valueUnit, int $value, string $overflo
$date = $this->$valueUnit($value);

if ($date < $start) {
$date = $date->setDateTimeFrom($start);
} elseif ($date > $end) {
$date = $date->setDateTimeFrom($end);
return $date->mutateIfMutable($start);
}

if ($date > $end) {
return $date->mutateIfMutable($end);
}

return $date;
Expand Down Expand Up @@ -2952,4 +2954,18 @@ private static function floorZeroPad(int|float $value, int $length): string
{
return str_pad((string) floor($value), $length, '0', STR_PAD_LEFT);
}

/**
* @template T of CarbonInterface
*
* @param T $date
*
* @return T
*/
private function mutateIfMutable(CarbonInterface $date): CarbonInterface
{
return $this instanceof DateTimeImmutable
? $this
: $this->modify('@'.$date->rawFormat('U.u'))->setTimezone($date->getTimezone());
}
}
2 changes: 1 addition & 1 deletion src/Carbon/Traits/IntervalStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function convertDate(DateTimeInterface $dateTime, bool $negated = false):
if ($this->step) {
$carbonDate = Callback::parameter($this->step, $carbonDate->avoidMutation());

return $carbonDate->setDateTimeFrom(($this->step)($carbonDate, $negated));
return $carbonDate->modify(($this->step)($carbonDate, $negated)->format('Y-m-d H:i:s.u e O'));
}

if ($negated) {
Expand Down
4 changes: 2 additions & 2 deletions src/Carbon/Traits/Units.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public function add($unit, $value = 1, ?bool $overflow = null): static
$result = $this->resolveCarbon($unit($this, false));

if ($this !== $result && $this->isMutable()) {
return $this->setDateTimeFrom($result);
return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O'));
}

return $result;
Expand Down Expand Up @@ -415,7 +415,7 @@ public function sub($unit, $value = 1, ?bool $overflow = null): static
$result = $this->resolveCarbon($unit($this, true));

if ($this !== $result && $this->isMutable()) {
return $this->setDateTimeFrom($result);
return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O'));
}

return $result;
Expand Down
11 changes: 11 additions & 0 deletions tests/Carbon/ObjectsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ public function testToDateTime()
$this->assertNotInstanceOf(CarbonInterface::class, $date);

$this->assertSame('2000-03-26', $date->format('Y-m-d'));

// Check it keeps timezone offset during DST
$date = Carbon::create(2290, 11, 2, 1, 10, 10 + 888480 / 1000000, 'America/Toronto');
$this->assertSame(
'2290-11-02 01:10:10.888480 America/Toronto -0400',
$date->toDateTime()->format('Y-m-d H:i:s.u e O'),
);
$this->assertSame(
'2290-11-02 01:10:10.888480 America/Toronto -0500',
$date->copy()->addHour()->toDateTime()->format('Y-m-d H:i:s.u e O'),
);
}

public function testToDateTimeImmutable()
Expand Down
Loading

0 comments on commit fff50e3

Please sign in to comment.