views:

88

answers:

1

i have made a function which works just fine with 32-bit dates (acceptable with strtotime), but that is not scalable. i need to randomly generate peoples dates of birth, so i must use DateTime (and only <=5.2.13, to make it even more difficult) functions to generate them.

here is what i had:

public static function randomDate($start_date, $end_date, $format = DateTimeHelper::DATE_FORMAT_SQL_DATE)
{
    if($start_date instanceof DateTime)     $start_date = $start_date->format(DateTimeHelper::DATE_FORMAT_YMDHMS);
    if($end_date instanceof DateTime)       $end_date   = $end_date->format(DateTimeHelper::DATE_FORMAT_YMDHMS);

    // Convert timetamps to millis
    $min = strtotime($start_date);
    $max = strtotime($end_date);

    // Generate random number using above bounds
    $val = rand($min, $max);

    // Convert back to desired date format
    return date($format, $val);
}

so now, how can i generate a random date between two DateTimes?

thanks!

+1  A: 

Edit: fixed bugs as per comments.

Suppose you have the details on start and end dates, lets say as in what is returned by getdate(), then you can generate a date without having to go through a timestamp:

$year = rand($start_details['year'], $end_details['year']);

$isleap = $year % 400 == 0 || ($year % 100 != 0 && $year % 4);
$min_yday = $year > $start_details['year'] ? 0 : $start_details['yday'];
$max_yday = $year == $end_details['year'] ? $end_details['yday'] : ($isleap ? 365 : 364);

$yday = rand($min_yday, $max_yday);

$sec_in_day = 24 * 60 * 60;

$date_details = getdate($yday * $sec_in_day + ($isleap ? 2 * 365 * $sec_in_day : 0));
return sprintf('%04d-%02d-%02d', $year, $date_details['mon'], $date_details['mday']);

Something like that (I didn't test). The code above assumes UTC, you might want to offset according to your time zone.5

R. Hill
+1 I think this is probably the way to go. Instead of getdate() you can use the format() method of the DateTime object to extract the year and yday from dates outside of the normal 32bit range if reqd. However, a couple of queries with your implementation: `$max_yday = $year == $end_details['year'] ? $end_details['yday'] : ($isleap ? 366 : 365);` - shouldn't this be `($isleap ? 365 : 364)`? yday is in the range 0..365, 365 is max day in leap year (the 365th day of the year) when starting at 0 (the 1st). (?)
w3d
Also: `$date_details = getdate($yday * $sec_in_day + ($isleap ? 0 : $sec_in_leapyear));` - does this not imply that 1970 (year 0 in unix timestamp) is a leap year? Which it is not. If `$isleap` then wouldn't you need to add 2 (normal) years to get to 1972 (the first leap year) otherwise add nothing?
w3d
Ah yes, you're right re. max day, should be a zero-based index, since it's converted into seconds later (day 1 starts at 0 second). You're also right re. 1970 not being a leap year, my mistake, somehow I had in mind it was a leap year.
R. Hill