Skip to content

Commit 608bbc3

Browse files
committed
Merge branch '3.next' into 3.x
# Conflicts: # docs/en/index.rst
2 parents 847e4b1 + d994972 commit 608bbc3

File tree

11 files changed

+1331
-0
lines changed

11 files changed

+1331
-0
lines changed

src/Chronos.php

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,38 @@ public static function hasTestNow(): bool
327327
return static::$testNow !== null;
328328
}
329329

330+
/**
331+
* Temporarily sets "now" to the given value and executes the callback.
332+
*
333+
* After the callback is executed, the previous value of "now" is restored.
334+
* This is useful for testing time-sensitive code without affecting other tests.
335+
*
336+
* ### Example:
337+
*
338+
* ```
339+
* $result = Chronos::withTestNow('2023-06-15 12:00:00', function () {
340+
* return Chronos::now()->format('Y-m-d');
341+
* });
342+
* // $result === '2023-06-15'
343+
* ```
344+
*
345+
* @template T
346+
* @param \Cake\Chronos\Chronos|string|null $testNow The instance to use as "now".
347+
* @param callable(): T $callback The callback to execute.
348+
* @return T The return value of the callback.
349+
*/
350+
public static function withTestNow(Chronos|string|null $testNow, callable $callback): mixed
351+
{
352+
$previous = static::getTestNow();
353+
static::setTestNow($testNow);
354+
355+
try {
356+
return $callback();
357+
} finally {
358+
static::setTestNow($previous);
359+
}
360+
}
361+
330362
/**
331363
* Determine if there is just a time in the time string
332364
*
@@ -1024,6 +1056,25 @@ public function setTimezone(DateTimeZone|string $value): static
10241056
return parent::setTimezone(static::safeCreateDateTimeZone($value));
10251057
}
10261058

1059+
/**
1060+
* Change the timezone while keeping the local time.
1061+
*
1062+
* Unlike `setTimezone()` which converts the time to the new timezone,
1063+
* this method keeps the same wall clock time but changes the timezone.
1064+
*
1065+
* For example, if you have 10:00 AM in New York and shift to Chicago,
1066+
* you'll get 10:00 AM in Chicago (not 9:00 AM as setTimezone would give).
1067+
*
1068+
* @param \DateTimeZone|string $timezone The new timezone
1069+
* @return static
1070+
*/
1071+
public function shiftTimezone(DateTimeZone|string $timezone): static
1072+
{
1073+
$timezone = static::safeCreateDateTimeZone($timezone);
1074+
1075+
return new static($this->format('Y-m-d H:i:s.u'), $timezone);
1076+
}
1077+
10271078
/**
10281079
* Return time zone set for this instance.
10291080
*
@@ -1636,6 +1687,89 @@ public function previous(?int $dayOfWeek = null): static
16361687
return $this->modify("last $day, midnight");
16371688
}
16381689

1690+
/**
1691+
* Get the next occurrence of a given day of the week at a specific time.
1692+
*
1693+
* Unlike `next()`, this method considers both the day AND the time. If
1694+
* today is the target day and the specified time hasn't passed yet,
1695+
* it returns today at that time. Otherwise, it returns next week.
1696+
*
1697+
* This is useful when you need a relative date that always points to
1698+
* the next future occurrence of a specific day and time.
1699+
*
1700+
* ### Example
1701+
*
1702+
* ```
1703+
* // If it's Tuesday 9am, get "Tuesday 12pm" (today)
1704+
* // If it's Tuesday 4pm, get "Tuesday 12pm" (next week)
1705+
* $date = Chronos::now()->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
1706+
* ```
1707+
*
1708+
* @param int $dayOfWeek The day of the week (use Chronos::MONDAY, etc.)
1709+
* @param int $hour The hour (0-23)
1710+
* @param int $minute The minute (0-59)
1711+
* @param int $second The second (0-59)
1712+
* @return static
1713+
*/
1714+
public function nextOccurrenceOf(
1715+
int $dayOfWeek,
1716+
int $hour,
1717+
int $minute = 0,
1718+
int $second = 0,
1719+
): static {
1720+
// If today is the target day
1721+
if ($this->dayOfWeek === $dayOfWeek) {
1722+
$todayAtTime = $this->setTime($hour, $minute, $second);
1723+
// If the time hasn't passed yet, return today
1724+
if ($todayAtTime->greaterThan($this)) {
1725+
return $todayAtTime;
1726+
}
1727+
}
1728+
1729+
// Otherwise, get next week's occurrence
1730+
return $this->next($dayOfWeek)->setTime($hour, $minute, $second);
1731+
}
1732+
1733+
/**
1734+
* Get the previous occurrence of a given day of the week at a specific time.
1735+
*
1736+
* Unlike `previous()`, this method considers both the day AND the time.
1737+
* If today is the target day and the specified time has already passed,
1738+
* it returns today at that time. Otherwise, it returns last week.
1739+
*
1740+
* ### Example
1741+
*
1742+
* ```
1743+
* // If it's Tuesday 4pm, get "Tuesday 12pm" (today, already passed)
1744+
* // If it's Tuesday 9am, get "Tuesday 12pm" (last week)
1745+
* $date = Chronos::now()->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
1746+
* ```
1747+
*
1748+
* @param int $dayOfWeek The day of the week (use Chronos::MONDAY, etc.)
1749+
* @param int $hour The hour (0-23)
1750+
* @param int $minute The minute (0-59)
1751+
* @param int $second The second (0-59)
1752+
* @return static
1753+
*/
1754+
public function previousOccurrenceOf(
1755+
int $dayOfWeek,
1756+
int $hour,
1757+
int $minute = 0,
1758+
int $second = 0,
1759+
): static {
1760+
// If today is the target day
1761+
if ($this->dayOfWeek === $dayOfWeek) {
1762+
$todayAtTime = $this->setTime($hour, $minute, $second);
1763+
// If the time has already passed, return today
1764+
if ($todayAtTime->lessThan($this)) {
1765+
return $todayAtTime;
1766+
}
1767+
}
1768+
1769+
// Otherwise, get last week's occurrence
1770+
return $this->previous($dayOfWeek)->setTime($hour, $minute, $second);
1771+
}
1772+
16391773
/**
16401774
* Modify to the first occurrence of a given day of the week
16411775
* in the current month. If no dayOfWeek is provided, modify to the
@@ -2689,6 +2823,25 @@ public function toNative(): DateTimeImmutable
26892823
return new DateTimeImmutable($this->format('Y-m-d H:i:s.u'), $this->getTimezone());
26902824
}
26912825

2826+
/**
2827+
* Returns the date and time as an associative array.
2828+
*
2829+
* @return array{year: int, month: int, day: int, hour: int, minute: int, second: int, microsecond: int, timezone: string}
2830+
*/
2831+
public function toArray(): array
2832+
{
2833+
return [
2834+
'year' => $this->year,
2835+
'month' => $this->month,
2836+
'day' => $this->day,
2837+
'hour' => $this->hour,
2838+
'minute' => $this->minute,
2839+
'second' => $this->second,
2840+
'microsecond' => $this->microsecond,
2841+
'timezone' => $this->timezone->getName(),
2842+
];
2843+
}
2844+
26922845
/**
26932846
* Get a part of the object
26942847
*

src/ChronosDate.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,20 @@ public function toNative(DateTimeZone|string|null $timezone = null): DateTimeImm
16541654
return $this->toDateTimeImmutable($timezone);
16551655
}
16561656

1657+
/**
1658+
* Returns the date as an associative array.
1659+
*
1660+
* @return array{year: int, month: int, day: int}
1661+
*/
1662+
public function toArray(): array
1663+
{
1664+
return [
1665+
'year' => $this->year,
1666+
'month' => $this->month,
1667+
'day' => $this->day,
1668+
];
1669+
}
1670+
16571671
/**
16581672
* Get a part of the object
16591673
*

0 commit comments

Comments
 (0)