4

I sometime get unexpected results when using ordinal values with strtotime. For example, why does

date("M j", strtotime("second Tuesday February 2011"))

result in "Feb 15" (which is actually the third Tuesday in 2011?

jalperin
  • 2,664
  • 9
  • 30
  • 32
  • Related: http://stackoverflow.com/questions/4357490/getting-first-wednesday-of-a-month-with-php-strtotime-more-crazy-php-date-behavi – Paul Dixon Jan 25 '11 at 20:18

3 Answers3

5

You are missing an 'of'.

$ php -r 'echo date("M j", strtotime("second Tuesday February 2011"));'
Feb 15

$ php -r 'echo date("M j", strtotime("second Tuesday of February 2011"));'
Feb 8

PHP Version:

$ php -v
PHP 5.3.3 (cli) (built: Aug 22 2010 19:41:55)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

The documentation tells you the cause for this:

Also observe that the "of" in "ordinal space dayname space 'of' " and "'last' space dayname space 'of' " does something special.

  • It sets the day-of-month to 1.
  • "ordinal dayname 'of' " does not advance to another day. (Example: "first tuesday of july 2008" means "2008-07-01").
  • "ordinal dayname " does advance to another day. (Example: "first tuesday july 2008" means "2008-07-08", see also point 4 in the list above).
Community
  • 1
  • 1
etarion
  • 16,935
  • 4
  • 43
  • 66
  • Did you actually test this? I don't get that same output at all. The 2nd line gives me `Dec 31`. PHP 5.2.6 – Peter Bailey Jan 25 '11 at 20:19
  • The quoted results is copy&paste (with spaces added to force line breaks in SO) from my terminal. – etarion Jan 25 '11 at 20:20
  • older versions seem indeed to have problems with that (tested with 5.2.6) ... but the use with "of" is also documented: http://www.php.net/manual/en/datetime.formats.relative.php – etarion Jan 25 '11 at 20:25
  • I am using php 5.2.11, and, like @Peter Bailey above, I get "December 31" when adding the "of." (I'm running interactively, not via CLI.) – jalperin Jan 25 '11 at 20:41
  • After reading some more, I think that the "of" solution works only in php 5.3. – jalperin Jan 25 '11 at 21:20
2

The manual for strtotime() tells what you seek

In PHP 5 prior to 5.2.7, requesting a given occurrence of a given weekday in a month where that weekday was the first day of the month would incorrectly add one week to the returned timestamp. This has been corrected in 5.2.7 and later versions.

In short, it's a bug in the version you're using.

If you're looking for some sort of applicable fix, dropping the ordinal value seems to work (as if the first/second/third indicate full weeks)

echo date("M j", strtotime("Tuesday February 2011")), '<br>';
echo date("M j", strtotime("first Tuesday February 2011")), '<br>';
echo date("M j", strtotime("second Tuesday February 2011")), '<br>';
echo date("M j", strtotime("third Tuesday February 2011")), '<br>';
Peter Bailey
  • 105,256
  • 31
  • 182
  • 206
  • His format is also off - see my answer, the version used there is 5.3.3. – etarion Jan 25 '11 at 20:15
  • I'm running 5.2.11, but the problem still occurs. – jalperin Jan 25 '11 at 20:41
  • Dropping the ordinal if it's "first" DOES seem to function as a workaround, but it's awkward. Given @etarion's comments, I'm concerned whether it would work differently in 5.3. – jalperin Jan 25 '11 at 20:50
  • Actually, as I think about it some more, dropping the ordinal is NOT really a solution since it only works if the ordinal was "first". "second", "third", "fourth" are still incorrect. – jalperin Jan 26 '11 at 14:13
  • It was never really a "solution" - just a hack. – Peter Bailey Jan 26 '11 at 16:16
0

It seems that it's not safe to rely on strtotime to deal with ordinal date computations -- at least in versions of PHP < 5.3. (I've tested with 5.2.9 and 5.2.11 and neither works despite the claim in the online documentation that the bug was fixed in 5.2.7.)

Adding "of" as suggested apparently only works in php 5.3+ and dropping the ordinal altogether will return the "first" occurrence, but other ordinals will be 7 days off.

The best solution for PHP 5.2 seems to be something like this:

$recurrOrdinal = "last";
$dayOfWeek = "Thursday";
$monthYear = "March 2011";

echo ordinalDate($recurrOrdinal, $dayOfWeek, $monthYear);

function ordinalDate($recurrOrdinal, $dayOfWeek, $monthYear)    {
    $firstDate = date("j", strtotime($dayOfWeek . " " . $monthYear) );
    if ($recurrOrdinal == "first")
    $computed = $firstDate; 
    elseif ($recurrOrdinal == "second")
    $computed = $firstDate + 7; 
    elseif ($recurrOrdinal == "third")
    $computed = $firstDate + 14; 
    elseif ($recurrOrdinal == "fourth")
    $computed = $firstDate + 21; 
    elseif ($recurrOrdinal == "last")   {
    if ( ($firstDate + 28) <= date("t", strtotime($monthYear)) )
        $computed = $firstDate + 28; 
    else
        $computed = $firstDate + 21; 
    }
    return date("Y-m-d", strtotime($computed . " " . $monthYear) );
}
jalperin
  • 2,664
  • 9
  • 30
  • 32