Yes, this is one facility in PHP that is a bit convoluted, but...there are ways of dealing with it.
In fact, I have already adopted and included some custom strtotime() and strftime() functions that handle this in the XPDO layer; am using this for a project right now to ensure proper date of birth handling (and other historical dates); and seems to work pretty well, even on Windows and even in PHP 4, I think). There are still some bugs in what I’m working on when using with MySQL DATE fields (rather than TIMESTAMP or DATETIME), but feel free to take a look at the functions and make use of them to handle dates outside this limited range. I’ll be making some updates that should resolve at least some of these bugs this week.
FYI, these functions are solutions I have adopted from external sources, and thus I want to give proper credit for the code. For the xPDOObject::strtotime() implementation, I adopted a function referred to as safestrtotime() by Ed Lecky-Thompson (as presented at
http://nosheep.net/story/php-strtotime-limitation/). For xPDOObject::strftime() I adopted some additional code that will handle custom mappings for years outside the range handled on 32-bit systems, by some simple addition/subtraction techniques.
I’ll go ahead and publish these here in case someone wants to make these functions more robust or has time to debug them in various situations. I plan on putting a custom version of mktime in here as well, so you can also construct proper timestamps outside the ranges.
PLEASE NOTE: if you’re not going to use them on a class, you’ll need to name the functions something other than strtotime() or strftime().
<?php
function strtotime($strInput) {
$iVal= -1;
for ($i= 1900; $i <= 1969; $i++) {
// Check for this year string in date
$strYear= (string) $i;
if (!(strpos($strInput, $strYear) === false)) {
$replYear= $strYear;
$yearSkew= 1970 - $i;
$strInput= str_replace($strYear, '1970', $strInput);
}
}
$iVal= strtotime($strInput);
if ($yearSkew > 0) {
$numSecs= (60 * 60 * 24 * 365 * $yearSkew);
$iVal= $iVal - $numSecs;
$numLeapYears= 0; // determine number of leap years in period
for ($j= $replYear; $j <= 1969; $j++) {
$thisYear= $j;
$isLeapYear= false;
// Is div by 4?
if (($thisYear % 4) == 0) {
$isLeapYear= true;
}
// Is div by 100?
if (($thisYear % 100) == 0) {
$isLeapYear= false;
}
// Is div by 1000?
if (($thisYear % 1000) == 0) {
$isLeapYear= true;
}
if ($isLeapYear == true) {
$numLeapYears++;
}
}
$iVal= $iVal - (60 * 60 * 24 * $numLeapYears);
}
return $iVal;
}
?>
<?php
function strftime($format= "", $timestamp= false) {
if ($timestamp === false) {
$timestamp= time();
}
if ($timestamp >= 0 && $timestamp <= 2147480047) { // between 1 Jan 1970 00:00:00 and 19 Jan 2038 03:14:07 GMT
return strftime($format, $timestamp);
}
$mappings= array (
array (
"start" => 1902,
"end" => 1951,
"map" => 1986
), // 1902-1951 = 1986-2035
array (
"start" => 1952,
"end" => 1969,
"map" => 1980
), // 1952-1969 = 1980-1997
);
if ($timestamp < 0) {
$year= 1970;
while ($timestamp < 0) {
$days= ($year % 4 == 0 && ($year % 100 > 0 || $year % 400 == 0)) ? 366 : 365;
$timestamp += $days * 86400;
$year--;
}
} else // if ($timestamp > 2147480047)
{
$year= 2038;
while ($timestamp > 0) {
$days= ($year % 4 == 0 && ($year % 100 > 0 || $year % 400 == 0)) ? 366 : 365;
$timestamp -= $days * 86400;
$year++;
}
}
foreach ($mappings as $mapping)
if ($year >= $mapping["start"] && $year <= $mapping["end"]) {
$find= array (
"%y",
"%Y",
"%D"
);
$replace= array (
substr($year,
-2
), $year, "%m/%d/" . substr($year, -2));
$format= & str_replace($find, $replace, $format);
$map_from_1970= mktime(0, 0, 0, 1, 1, $mapping["map"]);
return strftime($format, $timestamp + $map_from_1970);
}
return "strftime (): Year not mapped yet: " . $year;
}
?>
IMPORTANT EDIT: I unwittingly published this with a bug in the strftime implementation here (as Victor would say, "damn cargo cult programmers, copying and pasting code") -- the line that read should have read as it does now. I also believe their to still be a bug here somewhere, but I’m still trying to track it down in conjunction with manipulating the various date and time types from MySQL (and other database platforms)