1<?php 2 3namespace Cron\Tests; 4 5use Cron\CronExpression; 6use DateTime; 7use DateTimeZone; 8use InvalidArgumentException; 9use PHPUnit_Framework_TestCase; 10 11/** 12 * @author Michael Dowling <mtdowling@gmail.com> 13 */ 14class CronExpressionTest extends PHPUnit_Framework_TestCase 15{ 16 /** 17 * @covers Cron\CronExpression::factory 18 */ 19 public function testFactoryRecognizesTemplates() 20 { 21 $this->assertEquals('0 0 1 1 *', CronExpression::factory('@annually')->getExpression()); 22 $this->assertEquals('0 0 1 1 *', CronExpression::factory('@yearly')->getExpression()); 23 $this->assertEquals('0 0 * * 0', CronExpression::factory('@weekly')->getExpression()); 24 } 25 26 /** 27 * @covers Cron\CronExpression::__construct 28 * @covers Cron\CronExpression::getExpression 29 * @covers Cron\CronExpression::__toString 30 */ 31 public function testParsesCronSchedule() 32 { 33 // '2010-09-10 12:00:00' 34 $cron = CronExpression::factory('1 2-4 * 4,5,6 */3'); 35 $this->assertEquals('1', $cron->getExpression(CronExpression::MINUTE)); 36 $this->assertEquals('2-4', $cron->getExpression(CronExpression::HOUR)); 37 $this->assertEquals('*', $cron->getExpression(CronExpression::DAY)); 38 $this->assertEquals('4,5,6', $cron->getExpression(CronExpression::MONTH)); 39 $this->assertEquals('*/3', $cron->getExpression(CronExpression::WEEKDAY)); 40 $this->assertEquals('1 2-4 * 4,5,6 */3', $cron->getExpression()); 41 $this->assertEquals('1 2-4 * 4,5,6 */3', (string) $cron); 42 $this->assertNull($cron->getExpression('foo')); 43 44 try { 45 $cron = CronExpression::factory('A 1 2 3 4'); 46 $this->fail('Validation exception not thrown'); 47 } catch (InvalidArgumentException $e) { 48 } 49 } 50 51 /** 52 * @covers Cron\CronExpression::__construct 53 * @covers Cron\CronExpression::getExpression 54 * @dataProvider scheduleWithDifferentSeparatorsProvider 55 */ 56 public function testParsesCronScheduleWithAnySpaceCharsAsSeparators($schedule, array $expected) 57 { 58 $cron = CronExpression::factory($schedule); 59 $this->assertEquals($expected[0], $cron->getExpression(CronExpression::MINUTE)); 60 $this->assertEquals($expected[1], $cron->getExpression(CronExpression::HOUR)); 61 $this->assertEquals($expected[2], $cron->getExpression(CronExpression::DAY)); 62 $this->assertEquals($expected[3], $cron->getExpression(CronExpression::MONTH)); 63 $this->assertEquals($expected[4], $cron->getExpression(CronExpression::WEEKDAY)); 64 $this->assertEquals($expected[5], $cron->getExpression(CronExpression::YEAR)); 65 } 66 67 /** 68 * Data provider for testParsesCronScheduleWithAnySpaceCharsAsSeparators 69 * 70 * @return array 71 */ 72 public static function scheduleWithDifferentSeparatorsProvider() 73 { 74 return array( 75 array("*\t*\t*\t*\t*\t*", array('*', '*', '*', '*', '*', '*')), 76 array("* * * * * *", array('*', '*', '*', '*', '*', '*')), 77 array("* \t * \t * \t * \t * \t *", array('*', '*', '*', '*', '*', '*')), 78 array("*\t \t*\t \t*\t \t*\t \t*\t \t*", array('*', '*', '*', '*', '*', '*')), 79 ); 80 } 81 82 /** 83 * @covers Cron\CronExpression::__construct 84 * @covers Cron\CronExpression::setExpression 85 * @covers Cron\CronExpression::setPart 86 * @expectedException InvalidArgumentException 87 */ 88 public function testInvalidCronsWillFail() 89 { 90 // Only four values 91 $cron = CronExpression::factory('* * * 1'); 92 } 93 94 /** 95 * @covers Cron\CronExpression::setPart 96 * @expectedException InvalidArgumentException 97 */ 98 public function testInvalidPartsWillFail() 99 { 100 // Only four values 101 $cron = CronExpression::factory('* * * * *'); 102 $cron->setPart(1, 'abc'); 103 } 104 105 /** 106 * Data provider for cron schedule 107 * 108 * @return array 109 */ 110 public function scheduleProvider() 111 { 112 return array( 113 array('*/2 */2 * * *', '2015-08-10 21:47:27', '2015-08-10 22:00:00', false), 114 array('* * * * *', '2015-08-10 21:50:37', '2015-08-10 21:50:00', true), 115 array('* 20,21,22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true), 116 // Handles CSV values 117 array('* 20,22 * * *', '2015-08-10 21:50:00', '2015-08-10 22:00:00', false), 118 // CSV values can be complex 119 array('* 5,21-22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true), 120 array('7-9 * */9 * *', '2015-08-10 22:02:33', '2015-08-18 00:07:00', false), 121 // 15th minute, of the second hour, every 15 days, in January, every Friday 122 array('1 * * * 7', '2015-08-10 21:47:27', '2015-08-16 00:01:00', false), 123 // Test with exact times 124 array('47 21 * * *', strtotime('2015-08-10 21:47:30'), '2015-08-10 21:47:00', true), 125 // Test Day of the week (issue #1) 126 // According cron implementation, 0|7 = sunday, 1 => monday, etc 127 array('* * * * 0', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), 128 array('* * * * 7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), 129 array('* * * * 1', strtotime('2011-06-15 23:09:00'), '2011-06-20 00:00:00', false), 130 // Should return the sunday date as 7 equals 0 131 array('0 0 * * MON,SUN', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), 132 array('0 0 * * 1,7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), 133 array('0 0 * * 0-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), 134 array('0 0 * * 7-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), 135 array('0 0 * * 4-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), 136 array('0 0 * * 7-3', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false), 137 array('0 0 * * 3-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false), 138 array('0 0 * * 3-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false), 139 // Test lists of values and ranges (Abhoryo) 140 array('0 0 * * 2-7', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false), 141 array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false), 142 array('0 0 * * 2-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false), 143 array('0 0 * * 4-7', strtotime('2011-07-19 00:00:00'), '2011-07-21 00:00:00', false), 144 // Test increments of ranges 145 array('0-12/4 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true), 146 array('4-59/2 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true), 147 array('4-59/2 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:06:00', true), 148 array('4-59/3 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:07:00', false), 149 //array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false), 150 // Test Day of the Week and the Day of the Month (issue #1) 151 array('0 0 1 1 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false), 152 array('0 0 1 JAN 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false), 153 array('0 0 1 * 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false), 154 array('0 0 L * *', strtotime('2011-07-15 00:00:00'), '2011-07-31 00:00:00', false), 155 // Test the W day of the week modifier for day of the month field 156 array('0 0 2W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true), 157 array('0 0 1W * *', strtotime('2011-05-01 00:00:00'), '2011-05-02 00:00:00', false), 158 array('0 0 1W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true), 159 array('0 0 3W * *', strtotime('2011-07-01 00:00:00'), '2011-07-04 00:00:00', false), 160 array('0 0 16W * *', strtotime('2011-07-01 00:00:00'), '2011-07-15 00:00:00', false), 161 array('0 0 28W * *', strtotime('2011-07-01 00:00:00'), '2011-07-28 00:00:00', false), 162 array('0 0 30W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false), 163 array('0 0 31W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false), 164 // Test the year field 165 array('* * * * * 2012', strtotime('2011-05-01 00:00:00'), '2012-01-01 00:00:00', false), 166 // Test the last weekday of a month 167 array('* * * * 5L', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false), 168 array('* * * * 6L', strtotime('2011-07-01 00:00:00'), '2011-07-30 00:00:00', false), 169 array('* * * * 7L', strtotime('2011-07-01 00:00:00'), '2011-07-31 00:00:00', false), 170 array('* * * * 1L', strtotime('2011-07-24 00:00:00'), '2011-07-25 00:00:00', false), 171 array('* * * * TUEL', strtotime('2011-07-24 00:00:00'), '2011-07-26 00:00:00', false), 172 array('* * * 1 5L', strtotime('2011-12-25 00:00:00'), '2012-01-27 00:00:00', false), 173 // Test the hash symbol for the nth weekday of a given month 174 array('* * * * 5#2', strtotime('2011-07-01 00:00:00'), '2011-07-08 00:00:00', false), 175 array('* * * * 5#1', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true), 176 array('* * * * 3#4', strtotime('2011-07-01 00:00:00'), '2011-07-27 00:00:00', false), 177 ); 178 } 179 180 /** 181 * @covers Cron\CronExpression::isDue 182 * @covers Cron\CronExpression::getNextRunDate 183 * @covers Cron\DayOfMonthField 184 * @covers Cron\DayOfWeekField 185 * @covers Cron\MinutesField 186 * @covers Cron\HoursField 187 * @covers Cron\MonthField 188 * @covers Cron\YearField 189 * @covers Cron\CronExpression::getRunDate 190 * @dataProvider scheduleProvider 191 */ 192 public function testDeterminesIfCronIsDue($schedule, $relativeTime, $nextRun, $isDue) 193 { 194 $relativeTimeString = is_int($relativeTime) ? date('Y-m-d H:i:s', $relativeTime) : $relativeTime; 195 196 // Test next run date 197 $cron = CronExpression::factory($schedule); 198 if (is_string($relativeTime)) { 199 $relativeTime = new DateTime($relativeTime); 200 } elseif (is_int($relativeTime)) { 201 $relativeTime = date('Y-m-d H:i:s', $relativeTime); 202 } 203 $this->assertEquals($isDue, $cron->isDue($relativeTime)); 204 $next = $cron->getNextRunDate($relativeTime, 0, true); 205 $this->assertEquals(new DateTime($nextRun), $next); 206 } 207 208 /** 209 * @covers Cron\CronExpression::isDue 210 */ 211 public function testIsDueHandlesDifferentDates() 212 { 213 $cron = CronExpression::factory('* * * * *'); 214 $this->assertTrue($cron->isDue()); 215 $this->assertTrue($cron->isDue('now')); 216 $this->assertTrue($cron->isDue(new DateTime('now'))); 217 $this->assertTrue($cron->isDue(date('Y-m-d H:i'))); 218 } 219 220 /** 221 * @covers Cron\CronExpression::isDue 222 */ 223 public function testIsDueHandlesDifferentTimezones() 224 { 225 $cron = CronExpression::factory('0 15 * * 3'); //Wednesday at 15:00 226 $date = '2014-01-01 15:00'; //Wednesday 227 $utc = new DateTimeZone('UTC'); 228 $amsterdam = new DateTimeZone('Europe/Amsterdam'); 229 $tokyo = new DateTimeZone('Asia/Tokyo'); 230 231 date_default_timezone_set('UTC'); 232 $this->assertTrue($cron->isDue(new DateTime($date, $utc))); 233 $this->assertFalse($cron->isDue(new DateTime($date, $amsterdam))); 234 $this->assertFalse($cron->isDue(new DateTime($date, $tokyo))); 235 236 date_default_timezone_set('Europe/Amsterdam'); 237 $this->assertFalse($cron->isDue(new DateTime($date, $utc))); 238 $this->assertTrue($cron->isDue(new DateTime($date, $amsterdam))); 239 $this->assertFalse($cron->isDue(new DateTime($date, $tokyo))); 240 241 date_default_timezone_set('Asia/Tokyo'); 242 $this->assertFalse($cron->isDue(new DateTime($date, $utc))); 243 $this->assertFalse($cron->isDue(new DateTime($date, $amsterdam))); 244 $this->assertTrue($cron->isDue(new DateTime($date, $tokyo))); 245 } 246 247 /** 248 * @covers Cron\CronExpression::getPreviousRunDate 249 */ 250 public function testCanGetPreviousRunDates() 251 { 252 $cron = CronExpression::factory('* * * * *'); 253 $next = $cron->getNextRunDate('now'); 254 $two = $cron->getNextRunDate('now', 1); 255 $this->assertEquals($next, $cron->getPreviousRunDate($two)); 256 257 $cron = CronExpression::factory('* */2 * * *'); 258 $next = $cron->getNextRunDate('now'); 259 $two = $cron->getNextRunDate('now', 1); 260 $this->assertEquals($next, $cron->getPreviousRunDate($two)); 261 262 $cron = CronExpression::factory('* * * */2 *'); 263 $next = $cron->getNextRunDate('now'); 264 $two = $cron->getNextRunDate('now', 1); 265 $this->assertEquals($next, $cron->getPreviousRunDate($two)); 266 } 267 268 /** 269 * @covers Cron\CronExpression::getMultipleRunDates 270 */ 271 public function testProvidesMultipleRunDates() 272 { 273 $cron = CronExpression::factory('*/2 * * * *'); 274 $this->assertEquals(array( 275 new DateTime('2008-11-09 00:00:00'), 276 new DateTime('2008-11-09 00:02:00'), 277 new DateTime('2008-11-09 00:04:00'), 278 new DateTime('2008-11-09 00:06:00') 279 ), $cron->getMultipleRunDates(4, '2008-11-09 00:00:00', false, true)); 280 } 281 282 /** 283 * @covers Cron\CronExpression::getMultipleRunDates 284 * @covers Cron\CronExpression::setMaxIterationCount 285 */ 286 public function testProvidesMultipleRunDatesForTheFarFuture() { 287 // Fails with the default 1000 iteration limit 288 $cron = CronExpression::factory('0 0 12 1 * */2'); 289 $cron->setMaxIterationCount(2000); 290 $this->assertEquals(array( 291 new DateTime('2016-01-12 00:00:00'), 292 new DateTime('2018-01-12 00:00:00'), 293 new DateTime('2020-01-12 00:00:00'), 294 new DateTime('2022-01-12 00:00:00'), 295 new DateTime('2024-01-12 00:00:00'), 296 new DateTime('2026-01-12 00:00:00'), 297 new DateTime('2028-01-12 00:00:00'), 298 new DateTime('2030-01-12 00:00:00'), 299 new DateTime('2032-01-12 00:00:00'), 300 ), $cron->getMultipleRunDates(9, '2015-04-28 00:00:00', false, true)); 301 } 302 303 /** 304 * @covers Cron\CronExpression 305 */ 306 public function testCanIterateOverNextRuns() 307 { 308 $cron = CronExpression::factory('@weekly'); 309 $nextRun = $cron->getNextRunDate("2008-11-09 08:00:00"); 310 $this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00")); 311 312 // true is cast to 1 313 $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", true, true); 314 $this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00")); 315 316 // You can iterate over them 317 $nextRun = $cron->getNextRunDate($cron->getNextRunDate("2008-11-09 00:00:00", 1, true), 1, true); 318 $this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00")); 319 320 // You can skip more than one 321 $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 2, true); 322 $this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00")); 323 $nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 3, true); 324 $this->assertEquals($nextRun, new DateTime("2008-11-30 00:00:00")); 325 } 326 327 /** 328 * @covers Cron\CronExpression::getRunDate 329 */ 330 public function testSkipsCurrentDateByDefault() 331 { 332 $cron = CronExpression::factory('* * * * *'); 333 $current = new DateTime('now'); 334 $next = $cron->getNextRunDate($current); 335 $nextPrev = $cron->getPreviousRunDate($next); 336 $this->assertEquals($current->format('Y-m-d H:i:00'), $nextPrev->format('Y-m-d H:i:s')); 337 } 338 339 /** 340 * @covers Cron\CronExpression::getRunDate 341 * @ticket 7 342 */ 343 public function testStripsForSeconds() 344 { 345 $cron = CronExpression::factory('* * * * *'); 346 $current = new DateTime('2011-09-27 10:10:54'); 347 $this->assertEquals('2011-09-27 10:11:00', $cron->getNextRunDate($current)->format('Y-m-d H:i:s')); 348 } 349 350 /** 351 * @covers Cron\CronExpression::getRunDate 352 */ 353 public function testFixesPhpBugInDateIntervalMonth() 354 { 355 $cron = CronExpression::factory('0 0 27 JAN *'); 356 $this->assertEquals('2011-01-27 00:00:00', $cron->getPreviousRunDate('2011-08-22 00:00:00')->format('Y-m-d H:i:s')); 357 } 358 359 public function testIssue29() 360 { 361 $cron = CronExpression::factory('@weekly'); 362 $this->assertEquals( 363 '2013-03-10 00:00:00', 364 $cron->getPreviousRunDate('2013-03-17 00:00:00')->format('Y-m-d H:i:s') 365 ); 366 } 367 368 /** 369 * @see https://github.com/mtdowling/cron-expression/issues/20 370 */ 371 public function testIssue20() { 372 $e = CronExpression::factory('* * * * MON#1'); 373 $this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00'))); 374 $this->assertFalse($e->isDue(new DateTime('2014-04-14 00:00:00'))); 375 $this->assertFalse($e->isDue(new DateTime('2014-04-21 00:00:00'))); 376 377 $e = CronExpression::factory('* * * * SAT#2'); 378 $this->assertFalse($e->isDue(new DateTime('2014-04-05 00:00:00'))); 379 $this->assertTrue($e->isDue(new DateTime('2014-04-12 00:00:00'))); 380 $this->assertFalse($e->isDue(new DateTime('2014-04-19 00:00:00'))); 381 382 $e = CronExpression::factory('* * * * SUN#3'); 383 $this->assertFalse($e->isDue(new DateTime('2014-04-13 00:00:00'))); 384 $this->assertTrue($e->isDue(new DateTime('2014-04-20 00:00:00'))); 385 $this->assertFalse($e->isDue(new DateTime('2014-04-27 00:00:00'))); 386 } 387 388 /** 389 * @covers Cron\CronExpression::getRunDate 390 */ 391 public function testKeepOriginalTime() 392 { 393 $now = new \DateTime; 394 $strNow = $now->format(DateTime::ISO8601); 395 $cron = CronExpression::factory('0 0 * * *'); 396 $cron->getPreviousRunDate($now); 397 $this->assertEquals($strNow, $now->format(DateTime::ISO8601)); 398 } 399 400 /** 401 * @covers Cron\CronExpression::__construct 402 * @covers Cron\CronExpression::factory 403 * @covers Cron\CronExpression::isValidExpression 404 * @covers Cron\CronExpression::setExpression 405 * @covers Cron\CronExpression::setPart 406 */ 407 public function testValidationWorks() 408 { 409 // Invalid. Only four values 410 $this->assertFalse(CronExpression::isValidExpression('* * * 1')); 411 // Valid 412 $this->assertTrue(CronExpression::isValidExpression('* * * * 1')); 413 } 414} 415