Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/Chronos.php
Original file line number Diff line number Diff line change
Expand Up @@ -1618,6 +1618,89 @@ public function previous(?int $dayOfWeek = null): static
return $this->modify("last $day, midnight");
}

/**
* Get the next occurrence of a given day of the week at a specific time.
*
* Unlike `next()`, this method considers both the day AND the time. If
* today is the target day and the specified time hasn't passed yet,
* it returns today at that time. Otherwise, it returns next week.
*
* This is useful when you need a relative date that always points to
* the next future occurrence of a specific day and time.
*
* ### 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);
* ```
*
* @param int $dayOfWeek The day of the week (use Chronos::MONDAY, etc.)
* @param int $hour The hour (0-23)
* @param int $minute The minute (0-59)
* @param int $second The second (0-59)
* @return static
*/
public function nextOccurrenceOf(
int $dayOfWeek,
int $hour,
int $minute = 0,
int $second = 0,
): static {
// If today is the target day
if ($this->dayOfWeek === $dayOfWeek) {
$todayAtTime = $this->setTime($hour, $minute, $second);
// If the time hasn't passed yet, return today
if ($todayAtTime->greaterThan($this)) {
return $todayAtTime;
}
}

// Otherwise, get next week's occurrence
return $this->next($dayOfWeek)->setTime($hour, $minute, $second);
}

/**
* Get the previous occurrence of a given day of the week at a specific time.
*
* Unlike `previous()`, this method considers both the day AND the time.
* If today is the target day and the specified time has already passed,
* it returns today at that time. Otherwise, it returns last week.
*
* ### Example
*
* ```
* // If it's Tuesday 4pm, get "Tuesday 12pm" (today, already passed)
* // If it's Tuesday 9am, get "Tuesday 12pm" (last week)
* $date = Chronos::now()->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
* ```
*
* @param int $dayOfWeek The day of the week (use Chronos::MONDAY, etc.)
* @param int $hour The hour (0-23)
* @param int $minute The minute (0-59)
* @param int $second The second (0-59)
* @return static
*/
public function previousOccurrenceOf(
int $dayOfWeek,
int $hour,
int $minute = 0,
int $second = 0,
): static {
// If today is the target day
if ($this->dayOfWeek === $dayOfWeek) {
$todayAtTime = $this->setTime($hour, $minute, $second);
// If the time has already passed, return today
if ($todayAtTime->lessThan($this)) {
return $todayAtTime;
}
}

// Otherwise, get last week's occurrence
return $this->previous($dayOfWeek)->setTime($hour, $minute, $second);
}

/**
* Modify to the first occurrence of a given day of the week
* in the current month. If no dayOfWeek is provided, modify to the
Expand Down
98 changes: 98 additions & 0 deletions tests/TestCase/DateTime/DayOfWeekModifiersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,102 @@ public function test3rdWednesdayOfYear()
$d = Chronos::createFromDate(1975, 8, 5)->nthOfYear(3, 3);
$this->assertDateTime($d, 1975, 1, 15, 0, 0, 0);
}

/**
* Test nextOccurrenceOf when today is the target day and time hasn't passed.
*/
public function testNextOccurrenceOfSameDayBeforeTime()
{
// It's Tuesday 9am, looking for Tuesday 12pm -> should be today
$d = Chronos::create(2024, 1, 9, 9, 0, 0); // Tuesday
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
}

/**
* Test nextOccurrenceOf when today is the target day but time has passed.
*/
public function testNextOccurrenceOfSameDayAfterTime()
{
// It's Tuesday 4pm, looking for Tuesday 12pm -> should be next Tuesday
$d = Chronos::create(2024, 1, 9, 16, 0, 0); // Tuesday
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 16, 12, 0, 0);
}

/**
* Test nextOccurrenceOf when today is not the target day.
*/
public function testNextOccurrenceOfDifferentDay()
{
// It's Monday, looking for Tuesday 12pm -> should be tomorrow
$d = Chronos::create(2024, 1, 8, 9, 0, 0); // Monday
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
}

/**
* Test nextOccurrenceOf with seconds.
*/
public function testNextOccurrenceOfWithSeconds()
{
$d = Chronos::create(2024, 1, 8, 9, 0, 0); // Monday
$result = $d->nextOccurrenceOf(Chronos::WEDNESDAY, 14, 30, 45);
$this->assertDateTime($result, 2024, 1, 10, 14, 30, 45);
}

/**
* Test nextOccurrenceOf at exact same time returns next week.
*/
public function testNextOccurrenceOfAtExactTime()
{
// It's Tuesday 12pm exactly, looking for Tuesday 12pm -> should be next week
$d = Chronos::create(2024, 1, 9, 12, 0, 0); // Tuesday 12pm
$result = $d->nextOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 16, 12, 0, 0);
}

/**
* Test previousOccurrenceOf when today is the target day and time has passed.
*/
public function testPreviousOccurrenceOfSameDayAfterTime()
{
// It's Tuesday 4pm, looking for previous Tuesday 12pm -> should be today
$d = Chronos::create(2024, 1, 9, 16, 0, 0); // Tuesday
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
}

/**
* Test previousOccurrenceOf when today is the target day but time hasn't passed.
*/
public function testPreviousOccurrenceOfSameDayBeforeTime()
{
// It's Tuesday 9am, looking for previous Tuesday 12pm -> should be last Tuesday
$d = Chronos::create(2024, 1, 9, 9, 0, 0); // Tuesday
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 2, 12, 0, 0);
}

/**
* Test previousOccurrenceOf when today is not the target day.
*/
public function testPreviousOccurrenceOfDifferentDay()
{
// It's Wednesday, looking for previous Tuesday 12pm -> should be yesterday
$d = Chronos::create(2024, 1, 10, 9, 0, 0); // Wednesday
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 9, 12, 0, 0);
}

/**
* Test previousOccurrenceOf at exact same time returns last week.
*/
public function testPreviousOccurrenceOfAtExactTime()
{
// It's Tuesday 12pm exactly, looking for previous Tuesday 12pm -> should be last week
$d = Chronos::create(2024, 1, 9, 12, 0, 0); // Tuesday 12pm
$result = $d->previousOccurrenceOf(Chronos::TUESDAY, 12, 0);
$this->assertDateTime($result, 2024, 1, 2, 12, 0, 0);
}
}