* @link http://cakephp.org CakePHP(tm) Project * @license http://www.opensource.org/licenses/mit-license.php MIT License */ namespace Cake\Chronos\Traits; use Cake\Chronos\ChronosInterface; use DateTime; use DateTimeImmutable; /** * Provides a suite of modifier methods. * * These methods let you modify the various aspects * of a DateTime with a series of fluent methods. * * This trait expects that the implementing class * also implements a copy() method. This method can be obtained * using the CopyTrait. */ trait ModifierTrait { /** * Names of days of the week. * * @var array */ protected static $days = [ ChronosInterface::MONDAY => 'Monday', ChronosInterface::TUESDAY => 'Tuesday', ChronosInterface::WEDNESDAY => 'Wednesday', ChronosInterface::THURSDAY => 'Thursday', ChronosInterface::FRIDAY => 'Friday', ChronosInterface::SATURDAY => 'Saturday', ChronosInterface::SUNDAY => 'Sunday', ]; /** * First day of week * * @var int */ protected static $weekStartsAt = ChronosInterface::MONDAY; /** * Last day of week * * @var int */ protected static $weekEndsAt = ChronosInterface::SUNDAY; /** * Get the first day of week * * @return int */ public static function getWeekStartsAt() { return static::$weekStartsAt; } /** * Set the first day of week * * @param int $day The day the week starts with. * @return void */ public static function setWeekStartsAt($day) { static::$weekStartsAt = $day; } /** * Get the last day of week * * @return int */ public static function getWeekEndsAt() { return static::$weekEndsAt; } /** * Set the last day of week * * @param int $day The day the week ends with. * @return void */ public static function setWeekEndsAt($day) { static::$weekEndsAt = $day; } /** * Set the date to a different date. * * Workaround for a PHP bug related to the first day of a month * * @param int $year The year to set. * @param int $month The month to set. * @param int $day The day to set. * @return static * @see https://bugs.php.net/bug.php?id=63863 */ public function setDate($year, $month, $day) { return $this->modify('+0 day')->setDateParent($year, $month, $day); } /** * Just calling to parent setDate * It used in overwritten setDate * * @param int $year The year to set. * @param int $month The month to set. * @param int $day The day to set. * @return static */ private function setDateParent($year, $month, $day) { return parent::setDate($year, $month, $day); } /** * Set the date and time all together * * @param int $year The year to set. * @param int $month The month to set. * @param int $day The day to set. * @param int $hour The hour to set. * @param int $minute The minute to set. * @param int $second The second to set. * @return static */ public function setDateTime($year, $month, $day, $hour, $minute, $second = 0) { return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second); } /** * Set the time by time string * * @param string $time Time as string. * @return static */ public function setTimeFromTimeString($time) { $time = explode(":", $time); $hour = (int)$time[0]; $minute = isset($time[1]) ? (int)$time[1] : 0; $second = isset($time[2]) ? (int)$time[2] : 0; return $this->setTime($hour, $minute, $second); } /** * Set the instance's timestamp * * @param int $value The timestamp value to set. * @return static */ public function timestamp($value) { return $this->setTimestamp($value); } /** * Set the instance's year * * @param int $value The year value. * @return static */ public function year($value) { return $this->setDate($value, $this->month, $this->day); } /** * Set the instance's month * * @param int $value The month value. * @return static */ public function month($value) { return $this->setDate($this->year, $value, $this->day); } /** * Set the instance's day * * @param int $value The day value. * @return static */ public function day($value) { return $this->setDate($this->year, $this->month, $value); } /** * Set the instance's hour * * @param int $value The hour value. * @return static */ public function hour($value) { return $this->setTime($value, $this->minute, $this->second); } /** * Set the instance's minute * * @param int $value The minute value. * @return static */ public function minute($value) { return $this->setTime($this->hour, $value, $this->second); } /** * Set the instance's second * * @param int $value The seconds value. * @return static */ public function second($value) { return $this->setTime($this->hour, $this->minute, $value); } /** * Add years to the instance. Positive $value travel forward while * negative $value travel into the past. * * @param int $value The number of years to add. * @return static */ public function addYears($value) { return $this->modify((int)$value . ' year'); } /** * Add a year to the instance * * @param int $value The number of years to add. * @return static */ public function addYear($value = 1) { return $this->addYears($value); } /** * Remove a year from the instance * * @param int $value The number of years to remove. * @return static */ public function subYear($value = 1) { return $this->subYears($value); } /** * Remove years from the instance. * * @param int $value The number of years to remove. * @return static */ public function subYears($value) { return $this->addYears(-1 * $value); } /** * Add months to the instance. Positive $value travels forward while * negative $value travels into the past. * * When adding or subtracting months, if the resulting time is a date * that does not exist, the result of this operation will always be the * last day of the intended month. * * ### Example: * * ``` * (new Chronos('2015-01-03'))->addMonths(1); // Results in 2015-02-03 * * (new Chronos('2015-01-31'))->addMonths(1); // Results in 2015-02-28 * ``` * * @param int $value The number of months to add. * @return static */ public function addMonths($value) { $day = $this->day; $date = $this->modify((int)$value . ' month'); if ($date->day !== $day) { return $date->modify('last day of previous month'); } return $date; } /** * Add a month to the instance * * When adding or subtracting months, if the resulting time is a date * that does not exist, the result of this operation will always be the * last day of the intended month. * * ### Example: * * ``` * (new Chronos('2015-01-03'))->addMonth(); // Results in 2015-02-03 * * (new Chronos('2015-01-31'))->addMonth(); // Results in 2015-02-28 * ``` * * @param int $value The number of months to add. * @return static */ public function addMonth($value = 1) { return $this->addMonths($value); } /** * Remove a month from the instance * * When adding or subtracting months, if the resulting time is a date * that does not exist, the result of this operation will always be the * last day of the intended month. * * ### Example: * * ``` * (new Chronos('2015-03-01'))->subMonth(); // Results in 2015-02-01 * * (new Chronos('2015-03-31'))->subMonth(); // Results in 2015-02-28 * ``` * * @param int $value The number of months to remove. * @return static */ public function subMonth($value = 1) { return $this->subMonths($value); } /** * Remove months from the instance * * When adding or subtracting months, if the resulting time is a date * that does not exist, the result of this operation will always be the * last day of the intended month. * * ### Example: * * ``` * (new Chronos('2015-03-01'))->subMonths(1); // Results in 2015-02-01 * * (new Chronos('2015-03-31'))->subMonths(1); // Results in 2015-02-28 * ``` * * @param int $value The number of months to remove. * @return static */ public function subMonths($value) { return $this->addMonths(-1 * $value); } /** * Add months with overflowing to the instance. Positive $value * travels forward while negative $value travels into the past. * * @param int $value The number of months to add. * @return static */ public function addMonthsWithOverflow($value) { return $this->modify((int)$value . ' month'); } /** * Add a month with overflow to the instance * * @param int $value The number of months to add. * @return static */ public function addMonthWithOverflow($value = 1) { return $this->modify((int)$value . ' month'); } /** * Remove a month with overflow from the instance * * @param int $value The number of months to remove. * @return static */ public function subMonthWithOverflow($value = 1) { return $this->subMonthsWithOverflow($value); } /** * Remove months with overflow from the instance * * @param int $value The number of months to remove. * @return static */ public function subMonthsWithOverflow($value) { return $this->addMonthsWithOverflow(-1 * $value); } /** * Add days to the instance. Positive $value travels forward while * negative $value travels into the past. * * @param int $value The number of days to add. * @return static */ public function addDays($value) { $value = (int)$value; return $this->modify("$value day"); } /** * Add a day to the instance * * @param int $value The number of days to add. * @return static */ public function addDay($value = 1) { $value = (int)$value; return $this->modify("$value day"); } /** * Remove a day from the instance * * @param int $value The number of days to remove. * @return static */ public function subDay($value = 1) { $value = (int)$value; return $this->modify("-$value day"); } /** * Remove days from the instance * * @param int $value The number of days to remove. * @return static */ public function subDays($value) { $value = (int)$value; return $this->modify("-$value day"); } /** * Add weekdays to the instance. Positive $value travels forward while * negative $value travels into the past. * * @param int $value The number of weekdays to add. * @return static */ public function addWeekdays($value) { return $this->modify((int)$value . ' weekdays ' . $this->format('H:i:s')); } /** * Add a weekday to the instance * * @param int $value The number of weekdays to add. * @return static */ public function addWeekday($value = 1) { return $this->addWeekdays($value); } /** * Remove weekdays from the instance * * @param int $value The number of weekdays to remove. * @return static */ public function subWeekdays($value) { $value = (int)$value; return $this->modify("$value weekdays ago, " . $this->format('H:i:s')); } /** * Remove a weekday from the instance * * @param int $value The number of weekdays to remove. * @return static */ public function subWeekday($value = 1) { return $this->subWeekdays($value); } /** * Add weeks to the instance. Positive $value travels forward while * negative $value travels into the past. * * @param int $value The number of weeks to add. * @return static */ public function addWeeks($value) { $value = (int)$value; return $this->modify("$value week"); } /** * Add a week to the instance * * @param int $value The number of weeks to add. * @return static */ public function addWeek($value = 1) { $value = (int)$value; return $this->modify("$value week"); } /** * Remove a week from the instance * * @param int $value The number of weeks to remove. * @return static */ public function subWeek($value = 1) { $value = (int)$value; return $this->modify("-$value week"); } /** * Remove weeks to the instance * * @param int $value The number of weeks to remove. * @return static */ public function subWeeks($value) { $value = (int)$value; return $this->modify("-$value week"); } /** * Add hours to the instance. Positive $value travels forward while * negative $value travels into the past. * * @param int $value The number of hours to add. * @return static */ public function addHours($value) { $value = (int)$value; return $this->modify("$value hour"); } /** * Add an hour to the instance * * @param int $value The number of hours to add. * @return static */ public function addHour($value = 1) { $value = (int)$value; return $this->modify("$value hour"); } /** * Remove an hour from the instance * * @param int $value The number of hours to remove. * @return static */ public function subHour($value = 1) { $value = (int)$value; return $this->modify("-$value hour"); } /** * Remove hours from the instance * * @param int $value The number of hours to remove. * @return static */ public function subHours($value) { $value = (int)$value; return $this->modify("-$value hour"); } /** * Add minutes to the instance. Positive $value travels forward while * negative $value travels into the past. * * @param int $value The number of minutes to add. * @return static */ public function addMinutes($value) { $value = (int)$value; return $this->modify("$value minute"); } /** * Add a minute to the instance * * @param int $value The number of minutes to add. * @return static */ public function addMinute($value = 1) { $value = (int)$value; return $this->modify("$value minute"); } /** * Remove a minute from the instance * * @param int $value The number of minutes to remove. * @return static */ public function subMinute($value = 1) { $value = (int)$value; return $this->modify("-$value minute"); } /** * Remove minutes from the instance * * @param int $value The number of minutes to remove. * @return static */ public function subMinutes($value) { $value = (int)$value; return $this->modify("-$value minute"); } /** * Add seconds to the instance. Positive $value travels forward while * negative $value travels into the past. * * @param int $value The number of seconds to add. * @return static */ public function addSeconds($value) { $value = (int)$value; return $this->modify("$value second"); } /** * Add a second to the instance * * @param int $value The number of seconds to add. * @return static */ public function addSecond($value = 1) { $value = (int)$value; return $this->modify("$value second"); } /** * Remove a second from the instance * * @param int $value The number of seconds to remove. * @return static */ public function subSecond($value = 1) { $value = (int)$value; return $this->modify("-$value second"); } /** * Remove seconds from the instance * * @param int $value The number of seconds to remove. * @return static */ public function subSeconds($value) { $value = (int)$value; return $this->modify("-$value second"); } /** * Resets the time to 00:00:00 * * @return static */ public function startOfDay() { return $this->modify('midnight'); } /** * Resets the time to 23:59:59 * * @return static */ public function endOfDay() { return $this->modify('23:59:59'); } /** * Resets the date to the first day of the month and the time to 00:00:00 * * @return static */ public function startOfMonth() { return $this->modify('first day of this month midnight'); } /** * Resets the date to end of the month and time to 23:59:59 * * @return static */ public function endOfMonth() { return $this->modify('last day of this month, 23:59:59'); } /** * Resets the date to the first day of the year and the time to 00:00:00 * * @return static */ public function startOfYear() { return $this->modify('first day of january midnight'); } /** * Resets the date to end of the year and time to 23:59:59 * * @return static */ public function endOfYear() { return $this->modify('last day of december, 23:59:59'); } /** * Resets the date to the first day of the decade and the time to 00:00:00 * * @return static */ public function startOfDecade() { $year = $this->year - $this->year % ChronosInterface::YEARS_PER_DECADE; return $this->modify("first day of january $year, midnight"); } /** * Resets the date to end of the decade and time to 23:59:59 * * @return static */ public function endOfDecade() { $year = $this->year - $this->year % ChronosInterface::YEARS_PER_DECADE + ChronosInterface::YEARS_PER_DECADE - 1; return $this->modify("last day of december $year, 23:59:59"); } /** * Resets the date to the first day of the century and the time to 00:00:00 * * @return static */ public function startOfCentury() { $year = $this->startOfYear()->year(($this->year - 1) - ($this->year - 1) % ChronosInterface::YEARS_PER_CENTURY + 1)->year; return $this->modify("first day of january $year, midnight"); } /** * Resets the date to end of the century and time to 23:59:59 * * @return static */ public function endOfCentury() { $year = $this->endOfYear()->year(($this->year - 1) - ($this->year - 1) % ChronosInterface::YEARS_PER_CENTURY + ChronosInterface::YEARS_PER_CENTURY)->year; return $this->modify("last day of december $year, 23:59:59"); } /** * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 * * @return static */ public function startOfWeek() { $dt = $this; if ($dt->dayOfWeek !== static::$weekStartsAt) { $dt = $dt->previous(static::$weekStartsAt); } return $dt->startOfDay(); } /** * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59 * * @return static */ public function endOfWeek() { $dt = $this; if ($dt->dayOfWeek !== static::$weekEndsAt) { $dt = $dt->next(static::$weekEndsAt); } return $dt->endOfDay(); } /** * Modify to the next occurrence of a given day of the week. * If no dayOfWeek is provided, modify to the next occurrence * of the current day of the week. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function next($dayOfWeek = null) { if ($dayOfWeek === null) { $dayOfWeek = $this->dayOfWeek; } $day = static::$days[$dayOfWeek]; return $this->modify("next $day, midnight"); } /** * Modify to the previous occurrence of a given day of the week. * If no dayOfWeek is provided, modify to the previous occurrence * of the current day of the week. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function previous($dayOfWeek = null) { if ($dayOfWeek === null) { $dayOfWeek = $this->dayOfWeek; } $day = static::$days[$dayOfWeek]; return $this->modify("last $day, midnight"); } /** * Modify to the first occurrence of a given day of the week * in the current month. If no dayOfWeek is provided, modify to the * first day of the current month. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function firstOfMonth($dayOfWeek = null) { $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; return $this->modify("first $day of this month, midnight"); } /** * Modify to the last occurrence of a given day of the week * in the current month. If no dayOfWeek is provided, modify to the * last day of the current month. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function lastOfMonth($dayOfWeek = null) { $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; return $this->modify("last $day of this month, midnight"); } /** * Modify to the given occurrence of a given day of the week * in the current month. If the calculated occurrence is outside the scope * of the current month, then return false and no modifications are made. * Use the supplied consts to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int $nth The offset to use. * @param int $dayOfWeek The day of the week to move to. * @return mixed */ public function nthOfMonth($nth, $dayOfWeek) { $dt = $this->copy()->firstOfMonth(); $check = $dt->format('Y-m'); $dt = $dt->modify("+$nth " . static::$days[$dayOfWeek]); return ($dt->format('Y-m') === $check) ? $dt : false; } /** * Modify to the first occurrence of a given day of the week * in the current quarter. If no dayOfWeek is provided, modify to the * first day of the current quarter. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function firstOfQuarter($dayOfWeek = null) { return $this->day(1)->month($this->quarter * ChronosInterface::MONTHS_PER_QUARTER - 2)->firstOfMonth($dayOfWeek); } /** * Modify to the last occurrence of a given day of the week * in the current quarter. If no dayOfWeek is provided, modify to the * last day of the current quarter. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function lastOfQuarter($dayOfWeek = null) { return $this->day(1)->month($this->quarter * ChronosInterface::MONTHS_PER_QUARTER)->lastOfMonth($dayOfWeek); } /** * Modify to the given occurrence of a given day of the week * in the current quarter. If the calculated occurrence is outside the scope * of the current quarter, then return false and no modifications are made. * Use the supplied consts to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int $nth The offset to use. * @param int $dayOfWeek The day of the week to move to. * @return mixed */ public function nthOfQuarter($nth, $dayOfWeek) { $dt = $this->copy()->day(1)->month($this->quarter * ChronosInterface::MONTHS_PER_QUARTER); $lastMonth = $dt->month; $year = $dt->year; $dt = $dt->firstOfQuarter()->modify("+$nth" . static::$days[$dayOfWeek]); return ($lastMonth < $dt->month || $year !== $dt->year) ? false : $dt; } /** * Modify to the first occurrence of a given day of the week * in the current year. If no dayOfWeek is provided, modify to the * first day of the current year. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function firstOfYear($dayOfWeek = null) { $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; return $this->modify("first $day of january, midnight"); } /** * Modify to the last occurrence of a given day of the week * in the current year. If no dayOfWeek is provided, modify to the * last day of the current year. Use the supplied consts * to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int|null $dayOfWeek The day of the week to move to. * @return mixed */ public function lastOfYear($dayOfWeek = null) { $day = $dayOfWeek === null ? 'day' : static::$days[$dayOfWeek]; return $this->modify("last $day of december, midnight"); } /** * Modify to the given occurrence of a given day of the week * in the current year. If the calculated occurrence is outside the scope * of the current year, then return false and no modifications are made. * Use the supplied consts to indicate the desired dayOfWeek, ex. ChronosInterface::MONDAY. * * @param int $nth The offset to use. * @param int $dayOfWeek The day of the week to move to. * @return mixed */ public function nthOfYear($nth, $dayOfWeek) { $dt = $this->copy()->firstOfYear()->modify("+$nth " . static::$days[$dayOfWeek]); return $this->year === $dt->year ? $dt : false; } /** * Modify the current instance to the average of a given instance (default now) and the current instance. * * @param \Cake\Chronos\ChronosInterface|null $dt The instance to compare with. * @return static */ public function average(ChronosInterface $dt = null) { $dt = ($dt === null) ? static::now($this->tz) : $dt; return $this->addSeconds((int)($this->diffInSeconds($dt, false) / 2)); } }