I'm trying to write a php script (or line of code) to echo a random time and date between 2 dates, eg
2012-12-24 13:03
which would be between my chosen dates of 1st October 2012 and 1st Jan 2013.
Any ideas how best to do this? Thanks in advance.
Easy :) Just choose 2 random dates, convert to EPOCH, and random between these 2 values :)
EPOCH - The time since 1/1/1970, in seconds.
You can use the strtotime() function to make date-strings turn into epoch time, and the date() function to make it the other way back.
function rand_date($min_date, $max_date) {
/* Gets 2 dates as string, earlier and later date.
Returns date in between them.
*/
$min_epoch = strtotime($min_date);
$max_epoch = strtotime($max_date);
$rand_epoch = rand($min_epoch, $max_epoch);
return date('Y-m-d H:i:s', $rand_epoch);
}
You probably want to define a resolution, for example one minute, or three minutes or 15 seconds or one and a half day or what not. The randomness should be applied on the whole period, I've choosen one minute here for exemplary purposes (there are 132480 minutes in your period).
$start = new Datetime('1st October 2012');
$end = new Datetime('1st Jan 2013');
$interval = new DateInterval('PT1M'); // Resolution: 1 Minute
$period = new DatePeriod($start, $interval, $end);
$random = new RandomIterator($period);
list($result) = iterator_to_array($random, false) ? : [null];
This for example gives:
class DateTime#7 (3) {
public $date =>
string(19) "2012-10-16 02:06:00"
public $timezone_type =>
int(3)
public $timezone =>
string(13) "Europe/Berlin"
}
You can find the RandomIterator here. Without it, it will take a little longer (ca. 1.5 the number of iterations compared to the example above) using:
$count = iterator_count($period);
$random = rand(1, $count);
$limited = new LimitIterator(new IteratorIterator($period), $random - 1, 1);
$limited->rewind();
$result = $limited->current();
I also tried with seconds, but that would take quite long. You probably want first to find a random day (92 days), and then some random time in it.
Also I've run some tests and I could not find any benefit in using DatePeriod so far as long as you're on common resolutions like seconds:
$start = new Datetime('1st October 2012');
$end = new Datetime('1st Jan 2013');
$random = new DateTime('#' . mt_rand($start->getTimestamp(), $end->getTimestamp()));
or minutes:
/**
* #param DateTime $start
* #param DateTime $end
* #param int|DateInterval $resolution in Seconds or as DateInterval
* #return DateTime
*/
$randomTime = function (DateTime $start, DateTime $end, $resolution = 1) {
if ($resolution instanceof DateInterval) {
$interval = $resolution;
$resolution = ($interval->m * 2.62974e6 + $interval->d) * 86400 + $interval->h * 60 + $interval->s;
}
$startValue = floor($start->getTimestamp() / $resolution);
$endValue = ceil($end->getTimestamp() / $resolution);
$random = mt_rand($startValue, $endValue) * $resolution;
return new DateTime('#' . $random);
};
$random = $randomTime($start, $end, 60);
Assuming you want to include October 1st, but not include Jan 1st...
$start = strtotime("2012-10-01 00:00:00");
$end = strtotime("2012-12-31 23:59:59");
$randomDate = date("Y-m-d H:i:s", rand($start, $end));
echo $randomDate;
so crazy it just may worK
function randomDate($start_date, $end_date)
{
//make timetamps
$min = strtotime($start_date);
$max = strtotime($end_date);
//random date
$rand_date = rand($min, $max);
//format it
return date('Y-m-d H:i:s', $rand_date);
}
Here's some code to accomplish this:
$randDate=date('Y-m-d', mt_rand(strtotime('2012-10-01'), strtotime('2013-01-01')));
Okay, here's something
$date_start = strtotime('1 October 2012');
$date_end = strtotime('1 January 2013');
$rand_date = rand($date_start, $date_end);
echo(date('d.m.Y H:i', $rand_date));
Related
I have to create a scheduling component that will plan e-mails that need to be sent out. Users can select a start time, end time, and frequency. Code should produce a random moment for every frequency, between start and end time. Outside of office hours.
Paramaters:
User can select a period between 01/01/2020 (the start) and 01/01/2021 (the end). In this case user selects a timespan of one exactly year.
User can select a frequency. In this case user selects '2 months'.
Function:
Code produces a list of datetimes. The total time (one year) is divided by frequency (2 months). We expect a list of 6 datetimes.
Every datetime is a random moment in said frequency (2 months). Within office hours.
Result:
An example result for these paramaters might as follows, with the calculated frequency bounds for clarity:
[jan/feb] 21-02-2020 11.36
[mrt/apr] 04-03-2020 16.11
[mei/jun] 13-05-2020 09.49
[jul-aug] 14-07-2020 15.25
[sep-okt] 02-09-2020 14.09
[nov-dec] 25-12-2020 13.55
--
I've been thinking about how to implement this best, but I can't figure out an elegant solution.
How could one do this using PHP?
Any insights, references, or code spikes would be greatly appreciated. I'm really stuck on this one.
I think you're just asking for suggestions on how to generate a list of repeating (2 weekly) dates with a random time between say 9am and 5pm? Is that right?
If so - something like this (untested, pseudo code) might be a starting point:
$start = new Datetime('1st January 2021');
$end = new Datetime('1st July 2021');
$day_start = 9;
$day_end = 17;
$date = $start;
$dates = [$date]; // Start date into array
while($date < $end) {
$new_date = clone($date->modify("+ 2 weeks"));
$new_date->setTime(mt_rand($day_start, $day_end), mt_rand(0, 59));
$dates[] = $new_date;
}
var_dump($dates);
Steve's anwser seems good, but you should consider 2 additional things
holiday check, in the while after first $new_date line, like:
$holiday = array('2021-01-01', '2021-01-06', '2021-12-25');
if (!in_array($new_date,$holiday))
also a check if date is a office day or a weekend in a similar way as above with working days as an array.
It's kind of crappy code but I think it will work as you wish.
function getDiffInSeconds(\DateTime $start, \DateTime $end) : int
{
$startTimestamp = $start->getTimestamp();
$endTimestamp = $end->getTimestamp();
return $endTimestamp - $startTimestamp;
}
function getShiftData(\DateTime $start, \DateTime $end) : array
{
$shiftStartHour = \DateTime::createFromFormat('H:i:s', $start->format('H:i:s'));
$shiftEndHour = \DateTime::createFromFormat('H:i:s', $end->format('H:i:s'));
$shiftInSeconds = intval($shiftEndHour->getTimestamp() - $shiftStartHour->getTimestamp());
return [
$shiftStartHour,
$shiftEndHour,
$shiftInSeconds,
];
}
function dayIsWeekendOrHoliday(\DateTime $date, array $holidays = []) : bool
{
$weekendDayIndexes = [
0 => 'Sunday',
6 => 'Saturday',
];
$dayOfWeek = $date->format('w');
if (empty($holidays)) {
$dayIsWeekendOrHoliday = isset($weekendDayIndexes[$dayOfWeek]);
} else {
$dayMonthDate = $date->format('d/m');
$dayMonthYearDate = $date->format('d/m/Y');
$dayIsWeekendOrHoliday = (isset($weekendDayIndexes[$dayOfWeek]) || isset($holidays[$dayMonthDate]) || isset($holidays[$dayMonthYearDate]));
}
return $dayIsWeekendOrHoliday;
}
function getScheduleDates(\DateTime $start, \DateTime $end, int $frequencyInSeconds) : array
{
if ($frequencyInSeconds < (24 * 60 * 60)) {
throw new \InvalidArgumentException('Frequency must be bigger than one day');
}
$diffInSeconds = getDiffInSeconds($start, $end);
// If difference between $start and $end is bigger than two days
if ($diffInSeconds > (2 * 24 * 60 * 60)) {
// If difference is bigger than 2 days we add 1 day to start and subtract 1 day from end
$start->modify('+1 day');
$end->modify('-1 day');
// Getting new $diffInSeconds after $start and $end changes
$diffInSeconds = getDiffInSeconds($start, $end);
}
if ($frequencyInSeconds > $diffInSeconds) {
throw new \InvalidArgumentException('Frequency is bigger than difference between dates');
}
$holidays = [
'01/01' => 'New Year',
'18/04/2020' => 'Easter 1st official holiday because 19/04/2020',
'20/04/2020' => 'Easter',
'21/04/2020' => 'Easter 2nd day',
'27/04' => 'Konings',
'04/05' => '4mei',
'05/05' => '4mei',
'24/12' => 'Christmas 1st day',
'25/12' => 'Christmas 2nd day',
'26/12' => 'Christmas 3nd day',
'27/12' => 'Christmas 3rd day',
'31/12' => 'Old Year'
];
[$shiftStartHour, $shiftEndHour, $shiftInSeconds] = getShiftData($start, $end);
$amountOfNotifications = floor($diffInSeconds / $frequencyInSeconds);
$periodInSeconds = intval($diffInSeconds / $amountOfNotifications);
$maxDaysBetweenNotifications = intval($periodInSeconds / (24 * 60 * 60));
// If $maxDaysBetweenNotifications is equals to 1 then we have to change $periodInSeconds to amount of seconds for one day
if ($maxDaysBetweenNotifications === 1) {
$periodInSeconds = (24 * 60 * 60);
}
$dates = [];
for ($i = 0; $i < $amountOfNotifications; $i++) {
$periodStart = clone $start;
$periodStart->setTimestamp($start->getTimestamp() + ($i * $periodInSeconds));
$seconds = mt_rand(0, $shiftInSeconds);
// If $maxDaysBetweenNotifications is equals to 1 then we have to check only one day without loop through the dates
if ($maxDaysBetweenNotifications === 1) {
$interval = new \DateInterval('P' . $maxDaysBetweenNotifications . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
} else {
// When $maxDaysBetweenNotifications we have to loop through the dates to pick them
$loopsCount = 0;
$maxLoops = 3; // Max loops before breaking and skipping the period
do {
$day = mt_rand(0, $maxDaysBetweenNotifications);
$periodStart->modify($shiftStartHour);
$interval = new \DateInterval('P' . $day . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
// If the day is weekend or holiday then we have to increment $loopsCount by 1 for each loop
if ($dayIsWeekendOrHoliday === true) {
$loopsCount++;
// If $loopsCount is equals to $maxLoops then we have to break the loop
if ($loopsCount === $maxLoops) {
break;
}
}
} while ($dayIsWeekendOrHoliday);
}
// Adds the date to $dates only if the day is not a weekend day and holiday
if ($dayIsWeekendOrHoliday === false) {
$dates[] = $date;
}
}
return $dates;
}
$start = new \DateTime('2020-12-30 08:00:00', new \DateTimeZone('Europe/Sofia'));
$end = new \DateTime('2021-01-18 17:00:00', new \DateTimeZone('Europe/Sofia'));
$frequencyInSeconds = 86400; // 1 day
$dates = getScheduleDates($start, $end, $frequencyInSeconds);
var_dump($dates);
You have to pass $start, $end and $frequencyInSeconds as I showed in example and then you will get your random dates. Notice that I $start and $end must have hours in them because they are used as start and end hours for shifts. Because the rule is to return a date within a shift time only in working days. Also you have to provide frequency in seconds - you can calculate them outside the function or you can change it to calculate them inside. I did it this way because I don't know what are your predefined periods.
This function returns an array of \DateTime() instances so you can do whatever you want with them.
UPDATE 08/01/2020:
Holidays now are part of calculation and they will be excluded from returned dates if they are passed when you are calling the function. You can pass them in d/m and d/m/Y formats because of holidays like Easter and in case when the holiday is on weekend but people will get additional dayoff during the working week.
UPDATE 13/01/2020:
I've made updated code version to fix the issue with infinite loops when $frequencyInSeconds is shorter like 1 day. The new code used few functions getDiffInSeconds, getShiftData and dayIsWeekendOrHoliday as helper methods to reduce code duplication and cleaner and more readable code
I am coding an application where i need to assign random date between two fixed timestamps
how i can achieve this using php i've searched first but only found the answer for Java not php
for example :
$string = randomdate(1262055681,1262055681);
PHP has the rand() function:
$int= rand(1262055681,1262055681);
It also has mt_rand(), which is generally purported to have better randomness in the results:
$int= mt_rand(1262055681,1262055681);
To turn a timestamp into a string, you can use date(), ie:
$string = date("Y-m-d H:i:s",$int);
If given dates are in date time format then use this easiest way of doing this is to convert both numbers to timestamps, then set these as the minimum and maximum bounds on a random number generator.
A quick PHP example would be:
// Find a randomDate between $start_date and $end_date
function randomDate($start_date, $end_date)
{
// Convert to timetamps
$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('Y-m-d H:i:s', $val);
}
This function makes use of strtotime() as suggested by zombat to convert a datetime description into a Unix timestamp, and date() to make a valid date out of the random timestamp which has been generated.
Another solution using PHP DateTime
$start and $end are DateTime objects and we convert into Timestamp. Then we use mt_rand method to get a random Timestamp between them. Finally we recreate a DateTime object.
function randomDateInRange(DateTime $start, DateTime $end) {
$randomTimestamp = mt_rand($start->getTimestamp(), $end->getTimestamp());
$randomDate = new DateTime();
$randomDate->setTimestamp($randomTimestamp);
return $randomDate;
}
You can just use a random number to determine a random date. Get a random number between 0 and number of days between the dates. Then just add that number to the first date.
For example, to get a date a random numbers days between now and 30 days out.
echo date('Y-m-d', strtotime( '+'.mt_rand(0,30).' days'));
Here's another example:
$datestart = strtotime('2009-12-10');//you can change it to your timestamp;
$dateend = strtotime('2009-12-31');//you can change it to your timestamp;
$daystep = 86400;
$datebetween = abs(($dateend - $datestart) / $daystep);
$randomday = rand(0, $datebetween);
echo "\$randomday: $randomday\n";
echo date("Y-m-d", $datestart + ($randomday * $daystep)) . "\n";
The best way :
$timestamp = rand( strtotime("Jan 01 2015"), strtotime("Nov 01 2016") );
$random_Date = date("d.m.Y", $timestamp );
By using carbon and php rand between two dates
$startDate = Carbon::now();
$endDate = Carbon::now()->subDays(7);
$randomDate = Carbon::createFromTimestamp(rand($endDate->timestamp, $startDate->timestamp))->format('Y-m-d');
OR
$randomDate = Carbon::now()->subDays(rand(0, 7))->format('Y-m-d');
An other solution where we can use date_format :
/**
* Method to generate random date between two dates
* #param $sStartDate
* #param $sEndDate
* #param string $sFormat
* #return bool|string
*/
function randomDate($sStartDate, $sEndDate, $sFormat = 'Y-m-d H:i:s') {
// Convert the supplied date to timestamp
$fMin = strtotime($sStartDate);
$fMax = strtotime($sEndDate);
// Generate a random number from the start and end dates
$fVal = mt_rand($fMin, $fMax);
// Convert back to the specified date format
return date($sFormat, $fVal);
}
Source : https://gist.github.com/samcrosoft/6550473
You could use for example :
$date_random = randomDate('2018-07-09 00:00:00','2018-08-27 00:00:00');
The amount of strtotime in here is WAY too high.
For anyone whose interests span before 1971 and after 2038, here's a modern, flexible solution:
function random_date_in_range( $date1, $date2 ){
if (!is_a($date1, 'DateTime')) {
$date1 = new DateTime( (ctype_digit((string)$date1) ? '#' : '') . $date1);
$date2 = new DateTime( (ctype_digit((string)$date2) ? '#' : '') . $date2);
}
$random_u = random_int($date1->format('U'), $date2->format('U'));
$random_date = new DateTime();
$random_date->setTimestamp($random_u);
return $random_date->format('Y-m-d') .'<br>';
}
Call it any number of ways ...
// timestamps
echo random_date_in_range(157766400,1489686923);
// any date string
echo random_date_in_range('1492-01-01','2050-01-01');
// English textual parsing
echo random_date_in_range('last Sunday','now');
// DateTime object
$date1 = new DateTime('1000 years ago');
$date2 = new DateTime('now + 10 months');
echo random_date_in_range($date1, $date2);
As is, the function requires date1 <= date2.
i had a same situation before and none of the above answers fix my problem so i
Came with new function
function randomDate($startDate, $endDate, $count = 1 ,$dateFormat = 'Y-m-d H:i:s')
{
//inspired by
// https://gist.github.com/samcrosoft/6550473
// Convert the supplied date to timestamp
$minDateString = strtotime($startDate);
$maxDateString = strtotime($endDate);
if ($minDateString > $maxDateString)
{
throw new Exception("From Date must be lesser than to date", 1);
}
for ($ctrlVarb = 1; $ctrlVarb <= $count; $ctrlVarb++)
{
$randomDate[] = mt_rand($minDateString, $maxDateString);
}
if (sizeof($randomDate) == 1)
{
$randomDate = date($dateFormat, $randomDate[0]);
return $randomDate;
}elseif (sizeof($randomDate) > 1)
{
foreach ($randomDate as $randomDateKey => $randomDateValue)
{
$randomDatearray[] = date($dateFormat, $randomDateValue);
}
//return $randomDatearray;
return array_values(array_unique($randomDatearray));
}
}
Now the testing Part(Data may change while testing )
$fromDate = '2012-04-02';
$toDate = '2018-07-02';
print_r(randomDate($fromDate,$toDate,1));
result will be
2016-01-25 11:43:22
print_r(randomDate($fromDate,$toDate,1));
array:10 [▼
0 => "2015-08-24 18:38:26"
1 => "2018-01-13 21:12:59"
2 => "2018-06-22 00:18:40"
3 => "2016-09-14 02:38:04"
4 => "2016-03-29 17:51:30"
5 => "2018-03-30 07:28:48"
6 => "2018-06-13 17:57:47"
7 => "2017-09-24 16:00:40"
8 => "2016-12-29 17:32:33"
9 => "2013-09-05 02:56:14"
]
But after the few tests i was thinking about what if the inputs be like
$fromDate ='2018-07-02 09:20:39';
$toDate = '2018-07-02 10:20:39';
So the duplicates may occur while generating the large number of dates such as 10,000
so i have added array_unique and this will return only the non duplicates
if you use laravel then it's for you.
\Carbon\Carbon::now()->subDays(rand(0, 90))->format('Y-m-d');
Simplest of all, this small function works for me
I wrote it in a helper class datetime as a static method
/**
* Return date between two dates
*
* #param String $startDate
* #param String $endDate
* #return String
*
* #author Kuldeep Dangi <kuldeepamy#gmail.com>
*/
public static function getRandomDateTime($startDate, $endDate)
{
$randomTime = mt_rand(strtotime($startDate), strtotime($endDate));
return date(self::DATETIME_FORMAT_MYSQL, $randomTime);
}
Pretty good question; needed to generate some random sample data for an app.
You could use the following function with optional arguments to generate random dates:
function randomDate($startDate, $endDate, $format = "Y-M-d H:i:s", $timezone = "gmt", $mode = "debug")
{
return $result;
}
sample input:
echo 'UTC: ' . randomDate("1942-01-19", "2016-06-03", "Y-M-d H:i:s", "utc") . '<br>';
//1942-Jan-19 07:00:00
echo 'GMT: ' . randomDate("1942-01-19", "2016-06-03", "Y/M/d H:i A", "gmt") . '<br>';
//1942/Jan/19 00:00 AM
echo 'France: ' . randomDate("1942-01-19", "2016-06-03", "Y F", "Europe/Paris") . '<br>';
//1942 January
echo 'UTC - 4 offset time only: ' . randomDate("1942-01-19", "2016-06-03", "H:i:s", -4) . '<br>';
//20:00:00
echo 'GMT +2 offset: ' . randomDate("1942-01-19", "2016-06-03", "Y-M-d H:i:s", 2) . '<br>';
//1942-Jan-19 02:00:00
echo 'No Options: ' . randomDate("1942-01-19", "2016-06-03") . '<br>';
//1942-Jan-19 00:00:00
readers requirements could vary from app to another, in general hope this function is a handy tool where you need to generate some random dates/ sample data for your application.
Please note that the function initially in debug mode, so change it to $mood="" other than debug in production .
The function accepts:
start date
end date
format: any php accepted format for date or time
timezone: name or offset number
mode: debug, epoch, verbose epoch or verbose
the output in not debug mode is random number according to optional specifications.
tested with PHP 7.x
// Find a randomDate between $startDate and $endDate
function randomDate($startDate, $endDate)
{
// Convert to timetamps
$min = strtotime($startDate);
$max = strtotime($endDate);
// Generate random number using above bounds
$val = rand($min, $max);
// Convert back to date
return Carbon::createFromTimestamp($val);
}
dd($this->randomDate('2014-12-10', Carbon::now()->toString()));
Using carbon
$yeni_tarih = date('Y-m-d', strtotime( '+'.mt_rand(-90,0).' days'))." ".date('H', strtotime( '+'.mt_rand(0,24).' hours')).":".rand(1,59).":".rand(1,59);
Full random date and time
Ok so I am working on a calendar application within my CRM system and I need to find the upper and lower bounds of the half an hour surrorunding the timestamp at which somebody entered an event in the calendar in order to run some SQL on the DB to determine if they already have something booked in within that timeslot.
For example I have the timestamp of 1330518155 = 29 February 2012 16:22:35 GMT+4
so I need to get 1330516800 and 1330518600 which equal 16:00 and 16:30.
If anyone has any ideas or think I am approaching developing the calendar in a stupid way let me know! Its my first time on such a task involving so much work with times and dates so any advice appreciated!
Use modulo.
$prev = 1330518155 - (1330518155 % 1800);
$next = $prev + 1800;
The modulo operator gives the remainder part of division.
I didn't read the questions clearly, but this code will round to the nearest half hour, for those who don't need the range between the two. Uses some of SenorAmor's code. Props and his mad elegant solution to the correct question.
$time = 1330518155; //Or whatever your time is in unix timestamp
//Store how many seconds long our rounding interval is
//1800 equals one half hour
//Change this to whatever interval to round by
$INTERVAL_SECONDS = 1800; //30*60
//Find how far off the prior interval we are
$offset = ($time % $INTERVAL_SECONDS);
//Removing this offset takes us to the "round down" half hour
$rounded = $time - $offset;
//Now add the full interval if we should have rounded up
if($offset > ($INTERVAL_SECONDS/2)){
$nearestInterval = $rounded + $INTERVAL_SECONDS;
}
else{
$nearestInterval = $rounded
}
You could use the modulo operator.
$time -= $time % 3600; // nearest hour (always rounds down)
Hopefully this is enough to point you in the right direction, if not please add a comment and I'll try to craft a more specific example.
PHP does have a DateTime class and a whole slough of methods that it provides. You could use these if you like, but I find it easier to use the built-in date() and strtotime() functions.
Here's my solution:
// Assume $timestamp has the original timestamp, i.e. 2012-03-09 16:23:41
$day = date( 'Y-m-d', $timestamp ); // $day is now "2012-03-09"
$hour = (int)date( 'H', $timestamp ); // $hour is now (int)16
$minute = (int)date( 'i', $timestamp ); // $minute is now (int)23
if( $minute < 30 ){
$windowStart = strtotime( "$day $hour:00:00" );
$windowEnd = strtotime( "$day $hour:30:00" );
} else {
$windowStart = strtotime( "$day $hour:30:00" );
if( ++$hour > 23 ){
// if we crossed midnight, fix the date and set the hour to 00
$day = date( 'Y-m-d', $timestamp + (24*60*60) );
$hour = '00';
}
$windowEnd = strtotime( "$day $hour:00:00" );
}
// Now $windowStart and $windowEnd are the unix timestamps of your endpoints
There are a few improvements that can be made on this, but that's the basic core.
[Edit: corrected my variable names!]
[Edit: I've revisited this answer because, to my embarrassment, I realized that it didn't handle the last half-hour of a day correctly. I've fixed that issue. Note that $day is fixed by adding a day's worth of seconds to the timestamp -- doing it this way means we don't have to worry about crossing month boundaries, leap days, etc. because PHP will format it correctly for us regardless.]
If you need to get the current time and then apply the rounding (down) of the time, I would do the following:
$now = date('U');
$offset = ($now % 1800);
$now = $now-$offset;
for ($i = 0;$i < 24; $i++)
{
echo date('g:i',$now);
$now += 1800;
}
Or you could round up by adding the offset, and do something more than just echo the time. The for loop then displays the 12 hours of increments. I used the above in a recent project.
I'd use the localtime and the mktime function.
$localtime = localtime($time, true);
$localtime['tm_sec'] = 0;
$localtime['tm_min'] = 30;
$time = mktime($localtime);
Far from my best work... but here's some functions for working with string or unix time stamp.
/**
* Takes a timestamp like "2016-10-01 17:59:01" and returns "2016-10-01 18:00"
* Note: assumes timestamp is in UTC
*
* #param $timestampString - a valid string which will be converted to unix with time()
* #param int $mins - interval to round to (ex: 15, 30, 60);
* #param string $format - the format to return the timestamp default is Y-m-d H:i
* #return bool|string
*/
function roundTimeString( $timestampString, $mins = 30, $format="Y-m-d H:i") {
return gmdate( $format, roundTimeUnix( time($timestampString), $mins ));
}
/**
* Rounds the time to the nearest minute interval, example: 15 would round times to 0, 15, 30,45
* if $mins = 60, 1:00, 2:00
* #param $unixTimestamp
* #param int $mins
* #return mixed
*/
function roundTimeUnix( $unixTimestamp, $mins = 30 ) {
$roundSecs = $mins*60;
$offset = $unixTimestamp % $roundSecs;
$prev = $unixTimestamp - $offset;
if( $offset > $roundSecs/2 ) {
return $prev + $roundSecs;
}
return $prev;
}
This is a solution using DateTimeInterface and keeping timezone information etc. Will also handle timezones that are not a multiple of 30 minutes offset from GMT (e.g. Asia/Kathmandu).
/**
* Return a DateTimeInterface object that is rounded down to the nearest half hour.
* #param \DateTimeInterface $dateTime
* #return \DateTimeInterface
* #throws \UnexpectedValueException if the $dateTime object is an unknown type
*/
function roundToHalfHour(\DateTimeInterface $dateTime)
{
$hours = (int)$dateTime->format('H');
$minutes = $dateTime->format('i');
# Round down to the last half hour period
$minutes = $minutes >= 30 ? 30 : 0;
if ($dateTime instanceof \DateTimeImmutable) {
return $dateTime->setTime($hours, $minutes);
} elseif ($dateTime instanceof \DateTime) {
// Don't change the object that was passed in, but return a new object
$dateTime = clone $dateTime;
$dateTime->setTime($hours, $minutes);
return $dateTime;
}
throw new UnexpectedValueException('Unexpected DateTimeInterface object');
}
You'll need to have created the DateTime object first though - perhaps with something like $dateTime = new DateTimeImmutable('#' . $timestamp). You can also set the timezone in the constructor.
Math.round(timestamp/1800)*1800
As you probably know, a UNIX timestamp is a number of seconds, so substract/add 1800 (number of seconds in 30 minutes) and you will get the desired result.
Here's a more semantic method for those that have to make a few of these, perhaps at certain times of the day.
$time = strtotime(date('Y-m-d H:00:00'));
You can change that H to any 0-23 number, so you can round to that hour of that day.
I know about the unwanted behaviour of PHP's function
strtotime
For example, when adding a month (+1 month) to dates like: 31.01.2011 -> 03.03.2011
I know it's not officially a PHP bug, and that this solution has some arguments behind it, but at least for me, this behavior has caused a lot waste of time (in the past and present) and I personally hate it.
What I found even stranger is that for example in:
MySQL: DATE_ADD('2011-01-31', INTERVAL 1 MONTH) returns 2011-02-28
or
C# where new DateTime(2011, 01, 31).AddMonths(1); will return 28.02.2011
wolframalpha.com giving 31.01.2013 + 1 month as input; will return Thursday, February 28, 2013
It sees to me that others have found a more decent solution to the stupid question that I saw alot in PHP bug reports "what day will it be, if I say we meet in a month from now" or something like that. The answer is: if 31 does not exists in next month, get me the last day of that month, but please stick to next month.
So MY QUESTION IS: is there a PHP function (written by somebody) that resolves this not officially recognized bug? As I don't think I am the only one who wants another behavior when adding / subtracting months.
I am particulary interested in solutions what also work not just for the end of the month, but a complete replacement of strtotime. Also the case strotime +n months should be also dealt with.
Happy coding!
what you need is to tell PHP to be smarter
$the_date = strtotime('31.01.2011');
echo date('r', strtotime('last day of next month', $the_date));
$the_date = strtotime('31.03.2011');
echo date('r', strtotime('last day of next month', $the_date));
assuming you are only interesting on the last day of next month
reference - http://www.php.net/manual/en/datetime.formats.relative.php
PHP devs surely don't consider this as bug. But in strtotime's docs there are few comments with solutions for your problem (look for 28th Feb examples ;)), i.e. this one extending DateTime class:
<?php
// this will give us 2010-02-28 ()
echo PHPDateTime::DateNextMonth(strftime('%F', strtotime("2010-01-31 00:00:00")), 31);
?>
Class PHPDateTime:
<?php
/**
* IA FrameWork
* #package: Classes & Object Oriented Programming
* #subpackage: Date & Time Manipulation
* #author: ItsAsh <ash at itsash dot co dot uk>
*/
final class PHPDateTime extends DateTime {
// Public Methods
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Calculate time difference between two dates
* ...
*/
public static function TimeDifference($date1, $date2)
$date1 = is_int($date1) ? $date1 : strtotime($date1);
$date2 = is_int($date2) ? $date2 : strtotime($date2);
if (($date1 !== false) && ($date2 !== false)) {
if ($date2 >= $date1) {
$diff = ($date2 - $date1);
if ($days = intval((floor($diff / 86400))))
$diff %= 86400;
if ($hours = intval((floor($diff / 3600))))
$diff %= 3600;
if ($minutes = intval((floor($diff / 60))))
$diff %= 60;
return array($days, $hours, $minutes, intval($diff));
}
}
return false;
}
/**
* Formatted time difference between two dates
*
* ...
*/
public static function StringTimeDifference($date1, $date2) {
$i = array();
list($d, $h, $m, $s) = (array) self::TimeDifference($date1, $date2);
if ($d > 0)
$i[] = sprintf('%d Days', $d);
if ($h > 0)
$i[] = sprintf('%d Hours', $h);
if (($d == 0) && ($m > 0))
$i[] = sprintf('%d Minutes', $m);
if (($h == 0) && ($s > 0))
$i[] = sprintf('%d Seconds', $s);
return count($i) ? implode(' ', $i) : 'Just Now';
}
/**
* Calculate the date next month
*
* ...
*/
public static function DateNextMonth($now, $date = 0) {
$mdate = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
list($y, $m, $d) = explode('-', (is_int($now) ? strftime('%F', $now) : $now));
if ($date)
$d = $date;
if (++$m == 2)
$d = (($y % 4) === 0) ? (($d <= 29) ? $d : 29) : (($d <= 28) ? $d : 28);
else
$d = ($d <= $mdate[$m]) ? $d : $mdate[$m];
return strftime('%F', mktime(0, 0, 0, $m, $d, $y));
}
}
?>
Here's the algorithm you can use. It should be simple enough to implement yourself.
Have the original date and the +1 month date in variables
Extract the month part of both variables
If the difference is greater than 1 month (or if the original is December and the other is not January) change the latter variable to the last day of the next month. You can use for example t in date() to get the last day: date( 't.m.Y' )
Had the same issue recently and ended up writing a class that handles adding/subtracting various time intervals to DateTime objects.
Here's the code:
https://gist.github.com/pavlepredic/6220041#file-gistfile1-php
I've been using this class for a while and it seems to work fine, but I'm really interested in some peer review. What you do is create a TimeInterval object (in your case, you would specify 1 month as the interval) and then call addToDate() method, making sure you set $preventMonthOverflow argument to true. The code will make sure that the resulting date does not overflow into next month.
Sample usage:
$int = new TimeInterval(1, TimeInterval::MONTH);
$date = date_create('2013-01-31');
$future = $int->addToDate($date, true);
echo $future->format('Y-m-d');
Resulting date is:
2013-02-28
Here is an implementation of an improved version of Juhana's answer above:
<?php
function sameDateNextMonth(DateTime $createdDate, DateTime $currentDate) {
$addMon = clone $currentDate;
$addMon->add(new DateInterval("P1M"));
$nextMon = clone $currentDate;
$nextMon->modify("last day of next month");
if ($addMon->format("n") == $nextMon->format("n")) {
$recurDay = $createdDate->format("j");
$daysInMon = $addMon->format("t");
$currentDay = $currentDate->format("j");
if ($recurDay > $currentDay && $recurDay <= $daysInMon) {
$addMon->setDate($addMon->format("Y"), $addMon->format("n"), $recurDay);
}
return $addMon;
} else {
return $nextMon;
}
}
This version takes $createdDate under the presumption that you are dealing with a recurring monthly period, such as a subscription, that started on a specific date, such as the 31st. It always takes $createdDate so late "recurs on" dates won't shift to lower values as they are pushed forward thru lesser-valued months (e.g., so all 29th, 30th or 31st recur dates won't eventually get stuck on the 28th after passing thru a non-leap-year February).
Here is some driver code to test the algorithm:
$createdDate = new DateTime("2015-03-31");
echo "created date = " . $createdDate->format("Y-m-d") . PHP_EOL;
$next = sameDateNextMonth($createdDate, $createdDate);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
foreach(range(1, 12) as $i) {
$next = sameDateNextMonth($createdDate, $next);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
}
Which outputs:
created date = 2015-03-31
next date = 2015-04-30
next date = 2015-05-31
next date = 2015-06-30
next date = 2015-07-31
next date = 2015-08-31
next date = 2015-09-30
next date = 2015-10-31
next date = 2015-11-30
next date = 2015-12-31
next date = 2016-01-31
next date = 2016-02-29
next date = 2016-03-31
next date = 2016-04-30
I have solved it by this way:
$startDate = date("Y-m-d");
$month = date("m",strtotime($startDate));
$nextmonth = date("m",strtotime("$startDate +1 month"));
if((($nextmonth-$month) > 1) || ($month == 12 && $nextmonth != 1))
{
$nextDate = date( 't.m.Y',strtotime("$initialDate +1 week"));
}else
{
$nextDate = date("Y-m-d",strtotime("$initialDate +1 month"));
}
echo $nextDate;
Somewhat similar to the Juhana's answer but more intuitive and less complications expected. Idea is like this:
Store original date and the +n month(s) date in variables
Extract the day part of both variables
If days do not match, subtract number of days from the future date
Plus side of this solution is that works for any date (not just the border dates) and it also works for subtracting months (by putting - instead of +).
Here is an example implementation:
$start = mktime(0,0,0,1,31,2015);
for ($contract = 0; $contract < 12; $contract++) {
$end = strtotime('+ ' . $contract . ' months', $start);
if (date('d', $start) != date('d', $end)) {
$end = strtotime('- ' . date('d', $end) . ' days', $end);
}
echo date('d-m-Y', $end) . '|';
}
And the output is following:
31-01-2015|28-02-2015|31-03-2015|30-04-2015|31-05-2015|30-06-2015|31-07-2015|31-08-2015|30-09-2015|31-10-2015|30-11-2015|31-12-2015|
function ldom($m,$y){
//return tha last date of a given month based on the month and the year
//(factors in leap years)
$first_day= strtotime (date($m.'/1/'.$y));
$next_month = date('m',strtotime ( '+32 day' , $first_day)) ;
$last_day= strtotime ( '-1 day' , strtotime (date($next_month.'/1/'.$y)) ) ;
return $last_day;
}
I'm trying to work with dates for the first time, I did it something about that with Flash but it's different.
I have two different dates and I'd like to see the difference in hours and days with them, I've found too many examples but not what I'm loking for:
<?php
$now_date = strtotime (date ('Y-m-d H:i:s')); // the current date
$key_date = strtotime (date ("2009-11-21 14:08:42"));
print date ($now_date - $key_date);
// it returns an integer like 5813, 5814, 5815, etc... (I presume they are seconds)
?>
How can I convert it to hours or to days?
The DateTime diff function returns a DateInterval object. This object consists of variabeles related to the difference. You can query the days, hours, minutes, seconds just like in the example above.
Example:
<?php
$dateObject = new DateTime(); // No arguments means 'now'
$otherDateObject = new DateTime('2008-08-14 03:14:15');
$diffObject = $dateObject->diff($otherDateObject));
echo "Days of difference: ". $diffObject->days;
?>
See the manual about DateTime.
Sadly, it's a PHP 5.3> only feature.
Well, you can always use date_diff, but that is only for PHP 5.3.0+
The alternative would be math.
How can I convert it [seconds] to hours or to days?
There are 60 seconds per minute, which means there are 3600 seconds per hour.
$hours = $seconds/3600;
And, of course, if you need days ...
$days = $hours/24;
If you dont have PHP5.3 you could use this method from userland (taken from WebDeveloper.com)
function date_time_diff($start, $end, $date_only = true) // $start and $end as timestamps
{
if ($start < $end) {
list($end, $start) = array($start, $end);
}
$result = array('years' => 0, 'months' => 0, 'days' => 0);
if (!$date_only) {
$result = array_merge($result, array('hours' => 0, 'minutes' => 0, 'seconds' => 0));
}
foreach ($result as $period => $value) {
while (($start = strtotime('-1 ' . $period, $start)) >= $end) {
$result[$period]++;
}
$start = strtotime('+1 ' . $period, $start);
}
return $result;
}
$date_1 = strtotime('2005-07-31');
$date_2 = time();
$diff = date_time_diff($date_1, $date_2);
foreach ($diff as $key => $val) {
echo $val . ' ' . $key . ' ';
}
// Displays:
// 3 years 4 months 11 days
TheGrandWazoo mentioned a method for php 5.3>. For lower versions you can devide the number of seconds between the two dates with the number of seconds in a day to find the number of days.
For days, you do:
$days = floor(($now_date - $key_date) / (60 * 60 * 24))
If you want to know how many hours are still left, you can use the modulo operator (%)
$hours = floor((($now_date - $key_date) % * (60 * 60 * 24)) / 60 * 60)
<?php
$now_date = strtotime (date ('Y-m-d H:i:s')); // the current date
$key_date = strtotime (date ("2009-11-21 14:08:42"));
$diff = $now_date - $key_date;
$days = floor($diff/(60*60*24));
$hours = floor(($diff-($days*60*60*24))/(60*60));
print $days." ".$hours." difference";
?>
I prefer to use epoch/unix time deltas. Time represented in seconds and as such you can very quickly divide by 3600 for hours and divide by 24*3600=86400 for days.