1<?php 2 3namespace Cron; 4 5use DateTime; 6use InvalidArgumentException; 7 8 9/** 10 * Day of week field. Allows: * / , - ? L # 11 * 12 * Days of the week can be represented as a number 0-7 (0|7 = Sunday) 13 * or as a three letter string: SUN, MON, TUE, WED, THU, FRI, SAT. 14 * 15 * 'L' stands for "last". It allows you to specify constructs such as 16 * "the last Friday" of a given month. 17 * 18 * '#' is allowed for the day-of-week field, and must be followed by a 19 * number between one and five. It allows you to specify constructs such as 20 * "the second Friday" of a given month. 21 */ 22class DayOfWeekField extends AbstractField 23{ 24 public function isSatisfiedBy(DateTime $date, $value) 25 { 26 if ($value == '?') { 27 return true; 28 } 29 30 // Convert text day of the week values to integers 31 $value = $this->convertLiterals($value); 32 33 $currentYear = $date->format('Y'); 34 $currentMonth = $date->format('m'); 35 $lastDayOfMonth = $date->format('t'); 36 37 // Find out if this is the last specific weekday of the month 38 if (strpos($value, 'L')) { 39 $weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L'))); 40 $tdate = clone $date; 41 $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth); 42 while ($tdate->format('w') != $weekday) { 43 $tdateClone = new DateTime(); 44 $tdate = $tdateClone 45 ->setTimezone($tdate->getTimezone()) 46 ->setDate($currentYear, $currentMonth, --$lastDayOfMonth); 47 } 48 49 return $date->format('j') == $lastDayOfMonth; 50 } 51 52 // Handle # hash tokens 53 if (strpos($value, '#')) { 54 list($weekday, $nth) = explode('#', $value); 55 56 // 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601 57 if ($weekday === '0') { 58 $weekday = 7; 59 } 60 61 // Validate the hash fields 62 if ($weekday < 0 || $weekday > 7) { 63 throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given"); 64 } 65 if ($nth > 5) { 66 throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month'); 67 } 68 // The current weekday must match the targeted weekday to proceed 69 if ($date->format('N') != $weekday) { 70 return false; 71 } 72 73 $tdate = clone $date; 74 $tdate->setDate($currentYear, $currentMonth, 1); 75 $dayCount = 0; 76 $currentDay = 1; 77 while ($currentDay < $lastDayOfMonth + 1) { 78 if ($tdate->format('N') == $weekday) { 79 if (++$dayCount >= $nth) { 80 break; 81 } 82 } 83 $tdate->setDate($currentYear, $currentMonth, ++$currentDay); 84 } 85 86 return $date->format('j') == $currentDay; 87 } 88 89 // Handle day of the week values 90 if (strpos($value, '-')) { 91 $parts = explode('-', $value); 92 if ($parts[0] == '7') { 93 $parts[0] = '0'; 94 } elseif ($parts[1] == '0') { 95 $parts[1] = '7'; 96 } 97 $value = implode('-', $parts); 98 } 99 100 // Test to see which Sunday to use -- 0 == 7 == Sunday 101 $format = in_array(7, str_split($value)) ? 'N' : 'w'; 102 $fieldValue = $date->format($format); 103 104 return $this->isSatisfied($fieldValue, $value); 105 } 106 107 public function increment(DateTime $date, $invert = false) 108 { 109 if ($invert) { 110 $date->modify('-1 day'); 111 $date->setTime(23, 59, 0); 112 } else { 113 $date->modify('+1 day'); 114 $date->setTime(0, 0, 0); 115 } 116 117 return $this; 118 } 119 120 public function validate($value) 121 { 122 $value = $this->convertLiterals($value); 123 124 foreach (explode(',', $value) as $expr) { 125 if (!preg_match('/^(\*|[0-7](L?|#[1-5]))([\/\,\-][0-7]+)*$/', $expr)) { 126 return false; 127 } 128 } 129 130 return true; 131 } 132 133 private function convertLiterals($string) 134 { 135 return str_ireplace( 136 array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'), 137 range(0, 6), 138 $string 139 ); 140 } 141} 142