Skip to content

Commit c6816fd

Browse files
authored
Add nextOccurrenceOf() and previousOccurrenceOf() methods (#496)
These methods solve the problem of constructing a relative date that always points to the next (or previous) future occurrence of a specific day and time. Unlike next()/previous(), these methods consider BOTH the day AND the time. If today is the target day: - nextOccurrenceOf: returns today if time hasn't passed, else next week - previousOccurrenceOf: returns today if time has passed, else last week Example: // If it's Tuesday 9am, get Tuesday 12pm (today) // If it's Tuesday 4pm, get Tuesday 12pm (next week) $date = Chronos::now()->nextOccurrenceOf(Chronos::TUESDAY, 12, 0); Related to #406
1 parent c1a505a commit c6816fd

2 files changed

Lines changed: 181 additions & 0 deletions

File tree

src/Chronos.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,89 @@ public function previous(?int $dayOfWeek = null): static
16691669
return $this->modify("last $day, midnight");
16701670
}
16711671

1672+
/**
1673+
* Get the next occurrence of a given day of the week at a specific time.
1674+
*
1675+
* Unlike `next()`, this method considers both the day AND the time. If
1676+
* today is the target day and the specified time hasn't passed yet,
1677+
* it returns today at that time. Otherwise, it returns next week.
1678+
*
1679+
* This is useful when you need a relative date that always points to
1680+
* the next future occurrence of a specific day and time.
1681+
*
1682+
* ### Example
1683+
*
1684+
* ```
1685+
* // If it's Tuesday 9am, get "Tuesday 12pm" (today)
1686+
* // If it's Tuesday 4pm, get "Tuesday 12pm" (next week)
1687+
* $date = Chronos::now()->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
1688+
* ```
1689+
*
1690+
* @param int $dayOfWeek The day of the week (use Chronos::MONDAY, etc.)
1691+
* @param int $hour The hour (0-23)
1692+
* @param int $minute The minute (0-59)
1693+
* @param int $second The second (0-59)
1694+
* @return static
1695+
*/
1696+
public function nextOccurrenceOf(
1697+
int $dayOfWeek,
1698+
int $hour,
1699+
int $minute = 0,
1700+
int $second = 0,
1701+
): static {
1702+
// If today is the target day
1703+
if ($this->dayOfWeek === $dayOfWeek) {
1704+
$todayAtTime = $this->setTime($hour, $minute, $second);
1705+
// If the time hasn't passed yet, return today
1706+
if ($todayAtTime->greaterThan($this)) {
1707+
return $todayAtTime;
1708+
}
1709+
}
1710+
1711+
// Otherwise, get next week's occurrence
1712+
return $this->next($dayOfWeek)->setTime($hour, $minute, $second);
1713+
}
1714+
1715+
/**
1716+
* Get the previous occurrence of a given day of the week at a specific time.
1717+
*
1718+
* Unlike `previous()`, this method considers both the day AND the time.
1719+
* If today is the target day and the specified time has already passed,
1720+
* it returns today at that time. Otherwise, it returns last week.
1721+
*
1722+
* ### Example
1723+
*
1724+
* ```
1725+
* // If it's Tuesday 4pm, get "Tuesday 12pm" (today, already passed)
1726+
* // If it's Tuesday 9am, get "Tuesday 12pm" (last week)
1727+
* $date = Chronos::now()->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
1728+
* ```
1729+
*
1730+
* @param int $dayOfWeek The day of the week (use Chronos::MONDAY, etc.)
1731+
* @param int $hour The hour (0-23)
1732+
* @param int $minute The minute (0-59)
1733+
* @param int $second The second (0-59)
1734+
* @return static
1735+
*/
1736+
public function previousOccurrenceOf(
1737+
int $dayOfWeek,
1738+
int $hour,
1739+
int $minute = 0,
1740+
int $second = 0,
1741+
): static {
1742+
// If today is the target day
1743+
if ($this->dayOfWeek === $dayOfWeek) {
1744+
$todayAtTime = $this->setTime($hour, $minute, $second);
1745+
// If the time has already passed, return today
1746+
if ($todayAtTime->lessThan($this)) {
1747+
return $todayAtTime;
1748+
}
1749+
}
1750+
1751+
// Otherwise, get last week's occurrence
1752+
return $this->previous($dayOfWeek)->setTime($hour, $minute, $second);
1753+
}
1754+
16721755
/**
16731756
* Modify to the first occurrence of a given day of the week
16741757
* in the current month. If no dayOfWeek is provided, modify to the

tests/TestCase/DateTime/DayOfWeekModifiersTest.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,102 @@ public function test3rdWednesdayOfYear()
306306
$d = Chronos::createFromDate(1975, 8, 5)->nthOfYear(3, 3);
307307
$this->assertDateTime($d, 1975, 1, 15, 0, 0, 0);
308308
}
309+
310+
/**
311+
* Test nextOccurrenceOf when today is the target day and time hasn't passed.
312+
*/
313+
public function testNextOccurrenceOfSameDayBeforeTime()
314+
{
315+
// It's Tuesday 9am, looking for Tuesday 12pm -> should be today
316+
$d = Chronos::create(2024, 1, 9, 9, 0, 0); // Tuesday
317+
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
318+
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
319+
}
320+
321+
/**
322+
* Test nextOccurrenceOf when today is the target day but time has passed.
323+
*/
324+
public function testNextOccurrenceOfSameDayAfterTime()
325+
{
326+
// It's Tuesday 4pm, looking for Tuesday 12pm -> should be next Tuesday
327+
$d = Chronos::create(2024, 1, 9, 16, 0, 0); // Tuesday
328+
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
329+
$this->assertDateTime($result, 2024, 1, 16, 12, 0, 0);
330+
}
331+
332+
/**
333+
* Test nextOccurrenceOf when today is not the target day.
334+
*/
335+
public function testNextOccurrenceOfDifferentDay()
336+
{
337+
// It's Monday, looking for Tuesday 12pm -> should be tomorrow
338+
$d = Chronos::create(2024, 1, 8, 9, 0, 0); // Monday
339+
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
340+
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
341+
}
342+
343+
/**
344+
* Test nextOccurrenceOf with seconds.
345+
*/
346+
public function testNextOccurrenceOfWithSeconds()
347+
{
348+
$d = Chronos::create(2024, 1, 8, 9, 0, 0); // Monday
349+
$result = $d->nextOccurrenceOf(Chronos::WEDNESDAY, 14, 30, 45);
350+
$this->assertDateTime($result, 2024, 1, 10, 14, 30, 45);
351+
}
352+
353+
/**
354+
* Test nextOccurrenceOf at exact same time returns next week.
355+
*/
356+
public function testNextOccurrenceOfAtExactTime()
357+
{
358+
// It's Tuesday 12pm exactly, looking for Tuesday 12pm -> should be next week
359+
$d = Chronos::create(2024, 1, 9, 12, 0, 0); // Tuesday 12pm
360+
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
361+
$this->assertDateTime($result, 2024, 1, 16, 12, 0, 0);
362+
}
363+
364+
/**
365+
* Test previousOccurrenceOf when today is the target day and time has passed.
366+
*/
367+
public function testPreviousOccurrenceOfSameDayAfterTime()
368+
{
369+
// It's Tuesday 4pm, looking for previous Tuesday 12pm -> should be today
370+
$d = Chronos::create(2024, 1, 9, 16, 0, 0); // Tuesday
371+
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
372+
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
373+
}
374+
375+
/**
376+
* Test previousOccurrenceOf when today is the target day but time hasn't passed.
377+
*/
378+
public function testPreviousOccurrenceOfSameDayBeforeTime()
379+
{
380+
// It's Tuesday 9am, looking for previous Tuesday 12pm -> should be last Tuesday
381+
$d = Chronos::create(2024, 1, 9, 9, 0, 0); // Tuesday
382+
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
383+
$this->assertDateTime($result, 2024, 1, 2, 12, 0, 0);
384+
}
385+
386+
/**
387+
* Test previousOccurrenceOf when today is not the target day.
388+
*/
389+
public function testPreviousOccurrenceOfDifferentDay()
390+
{
391+
// It's Wednesday, looking for previous Tuesday 12pm -> should be yesterday
392+
$d = Chronos::create(2024, 1, 10, 9, 0, 0); // Wednesday
393+
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
394+
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
395+
}
396+
397+
/**
398+
* Test previousOccurrenceOf at exact same time returns last week.
399+
*/
400+
public function testPreviousOccurrenceOfAtExactTime()
401+
{
402+
// It's Tuesday 12pm exactly, looking for previous Tuesday 12pm -> should be last week
403+
$d = Chronos::create(2024, 1, 9, 12, 0, 0); // Tuesday 12pm
404+
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
405+
$this->assertDateTime($result, 2024, 1, 2, 12, 0, 0);
406+
}
309407
}

0 commit comments

Comments
 (0)