Given an arbitrary timestamp (e.g. 2019-02-26 10:30:00) I would like to find the next occurrence of an arbitrary time.
For example, the next occurrence of 12:00:00 will be 2019-02-26 12:00:00 but the next occurrence of 09:00:00 will be the next day at 2019-02-27 09:00:00. The results could be Carbon or Datetime objects. The test time will just be a string as shown.
Is there a way to calculate this in native PHP or PHP Carbon without conditionally boxing in time periods. An obvious way would be to see if the time being tested is past the check time for today, and if it is, taking the result as the check time plus 24 hours (the next day). That feels to me like too much chopping and joining of dates and times, so is there a way to calculate it by considering time to be a simple linear line?
All times will be in a single timezone, with DST. Note: the arbitrary datetimes and check times will stay clear of DST changeovers i.e. 01:00 to 02:00 so hopefully they will not be an issue to take into account.
Short answer is no for PHP (partial answer, I'm no specialist of Carbon but from quick look it's also no, but you can create a macro from following code).
However, with a ternary condition the one-liner is simple enough IMHO (replace the second DateTime($str) with DateTime() if you want to compare with current date and time, and change the >= by > if you want next day when time compared is exactly the same):
$str = '2019-02-26 10:30:00';
$date1 = ( ($a = (new DateTime($str))->setTime(12,00)) >= (new DateTime($str)) ) ? $a : $a->modify('+1 day');
$date2 = ( ($a = (new DateTime($str))->setTime(9,00)) >= (new DateTime($str)) ) ? $a : $a->modify('+1 day');
echo $date1->format('Y-m-d H:i:s'); //2019-02-26 12:00:00
echo $date2->format('Y-m-d H:i:s'); //2019-02-27 09:00:00
quick note: what you gave us is not a timestamp, but a formatted date.
Here is what I am using now through Carbon, which appears to give me the correct results:
$dateTime = Carbon::parse('2019-03-30 17:34:50', 'Europe/London');
$testTime = '16:00:00';
list ($hour, $minute, $second) = explode(':', $testTime);
$nextTimeOccurrence = $dateTime
->copy() // Carbon 1 only
->hour($hour)->minute($minute)->second($second);
if ($dateTime->gt($nextTimeOccurrence)) {
$nextTimeOccurrence = $nextTimeOccurrence->addDay();
}
// $nextTimeOccurrence is the next occurrence of $testTime after $dateTime
The splitting of the time seems clumsy, but might be the best way? The approach is:
Create a timestamp with the test time on the same day as the timestamp I'm checking. This will be the timestamp I am looking for.
If the timestamp I'm checking is after the timestamp created in the previous step, then add a day to it.
I've tested this around DST, and happily Carbon/Datetime keeps the same time when adding a day over a DST period, where a day there would be 25 hours or 23 hours, depending on which way it goes.
I still think there is a more "linear time" way to do this, but this seems simple and robust. Thanks go to #michael-stokoe here at the office for my lead on this.
Related
I have this date / time value (with timezone):
2019-10-22T17:00:00+02:00
Now I would like to check, which weekday this date is (for example: Monday)
and if this date has a difference of 1h between 16:00 o'clock and the date time.
How can I check the two factors as best practice ?
You may use DateTimeImmutable's constructor to parse the date string, DateTime#format to format it / retrieve the week day, and DateTime#diff to fetch the difference:
$date_string = '2019-10-22T17:00:00+02:00';
$date = new \DateTimeImmutable($date_string);
$date_at_16 = $date->setTime(16, 0);
echo $date->format('l'), PHP_EOL;
echo $date->diff($date_at_16)->h;
Demo: https://3v4l.org/R7e9n
Note that:
I've used DateTimeImmutable which is just like DateTime except it cannot be modified, so setTime doesn't also modify the initial date,
you should catch its constructor's potential thrown exception (if the format is invalid),
it should be better to use 'N' to retrieve the day of the week, as it's numerical and therefore more appropriate to store/compare (I've used 'l' for the purpose of this little demo, to get the full name),
if you need to know if it's 1 hour prior to or following 16 o'clock, you may check the DateInterval#invert flag (the date interval is what DateTime#diff returns).
I am trying to project out many recurring appointments based on one database item that declares the appt time, what day of the week it occurs on, and how many times the appt happens until it stops (if it ever stops)
I can get it to work by first storing the minutes and hours into seperate variables, then modifying the object and appending the hours and minutes onto each modified object as its stored in an array.
$t = new DateTime($ra->start_date);
$c = new DateTime($ra->end_date);
$hour = $t->format('H');
$mins = $t->format('i');
$chour = $c->format('H');
$cmins = $c->format('i');
for($k=0; $k<$frequency[$i]; $k++){
$mod = '+1 weekday';
$s = clone $t->modify($mod)->setTime($hour, $mins);
$e = clone $c->modify($mod)->setTime($chour, $cmins);
array_push($starts, $s);
array_push($ends, $e);
}
If I dont do it like this it will modify the date by the desired amount, but it will wipe the time stamp to 00:00:00.
That behavior is explained in the first Note block of the relative time formats documentation:
Note that "tomorrow 11:00" and "11:00 tomorrow" are
different. Considering today's date of "July 23rd, 2008" the first one
produces "2008-07-24 11:00" where as the second one produces
"2008-07-24 00:00".
The reason for this is that "yesterday", "midnight", "today", "noon"
and "tomorrow"
directly influence the current time.
So taking that into account, a slightly more compact way of doing this is to pass the time as part of the relative time you want to add. For demonstration:
$date = new DateTimeImmutable('2019-08-30 14:31:26');
$newDate = $date->modify('+1 weekday ' . $date->format('H:i:s'));
Demo
Since you need mutability to be able to recurrently add days, use regular DateTime objects like you are doing instead of DateTimeImmutable.
Currently, I'm trying to parse out dates when messages were received into timestamps. I have the month and day but the year is not specified. The event always occurs at the most recent (human) reading of the time. It works great in most cases to do this:
$time = strtotime("Jan 2 8:38pm");
That returns a date for this year, which is correct. Unfortunately, I get problems when I try to do for example:
$time = strtotime("Dec 31 8:38pm");
That returns a date which hasn't happened yet, and wont happen for the whole rest of the year. Obviously, my message was not sent in the future. I need it to return December 31st of last year.
For weekdays, I had a solution by prepending 'last' before the weekday like so:
$time = strtotime("Last Saturday 8:38pm");
That always returned the time of the last Saturday. However, trying to do the same thing here doesn't work:
$time = strtotime("Last Dec 31 8:38pm");
This returns false. I know to decrement a date by 1 year, I can do this:
$time = strtotime("Dec 31 8:38pm -1 year");
And that works great for Dec 31. However, Jan 2 will now fail:
$time = strtotime("Jan 2 8:38pm -1 year");
One solution I thought of was to subtract off a year (86400 * 365) from the resulting value if it is past today's date. However, this result will fail if we passed over February of a leap year. In that case, we would end up with a time that was ahead by a day.
The best solution I came up with so far is this:
$time = strtotime($raw_time);
if ($time > time()) {
$time = strtotime($raw_time." -1 year");
}
It seems kind of wasteful to make two calls to strtotime which I know is probably not a very efficient function. Is this the most elegant solution?
Is anyone aware of an option in strtotime which forces the dates to be in the past instead of in the future?
Is there another way to parse these dates that I should consider?
Efficiency is important for this because I am going to be parsing a lot of dates with it, but I would also like simple and readable code so I can understand it later.
Your approach is fine, as there is no date format to get what you want. Another approach could be using the DateTime class:
$datetime = new DateTime($raw_time);
if ($datetime > new DateTime()) {
$datetime->modify('-1 year');
}
You could test which one of the two approaches is faster. My guess is that this is a micro-optimization that won't make a lot of difference.
I am working on project (a Google Transit feed) where I am required to provide the times for each stop on a bus route in the following common format: 21:00:00 and so forth.
Problem is, if times continue past midnight for a given trip, they require it to continue the hour counting accordingly. They explain quite specifically that 02:00:00 should become 26:00:00 and 03:45:00 should become 27:45:00 etc.
I am baffled on how to display such with any of the date() or strtotime() functions.
The only thing I can think of in my particular situation would be to function match and replace any strings in my output between 00:00:00 and 04:00:00, as that would clearly mean (again, for me only) that these are trips originating before midnight, but I don't feel that's the correct way.
Well seeing as it's only displaying on the page, you can
firstly get your date from where ever
Let's say $date = 00:00:00
$exploded_date = explode(":", $date);
This takes $date and puts it into an array so
$exploded_date[0] is hh
$exploded_date[1] is mm
$exploded_date[2] is ss
Then what you can do is use ltrim() to remove the leading 0 from 00 to 04 $exploded_date[0] - This makes it comparable in the if statement I'll do after
if($exploded_date[0] <= 4) {
$exploded_date[0] = ltrim($exploded_date[0], "0");
$exploded_date[0] = $exploded_date[0]+24;
}
Then you can implode the array back together into one string
$date = implode(":", $exploded_date);
// if the hour is 00 to 04 it will come out as 24 to 28
// e.g. 24:35:30
echo $date;
Despite giving you an answer. It's a silly thing to be doing, but it's not your choice so here you go :)
The way you display something doesn't necesarily has to be the same way you store something.
I don't know how you calculate the times, but assuming you have a start date and time, and some interval, you could calculate the end time as follows:
date_default_timezone_set('Europe/London');
$start_datetime = new DateTime('2014-11-11T21:00:00');
$next_stop = new DateTime('2014-11-12T02:00:00');
echo $start_datetime->format('Y-m-d H:i'); // 2014-11-11 21:00
echo $next_stop->format('Y-m-d H:i'); // 2014-11-12 02:00
$interval = $start_datetime->diff($next_stop);
// display next stop: 2014-11-11 26:00
echo ($start_datetime->format('Y') + $interval->y) .'-'
. ($start_datetime->format('m') + $interval->m) .'-'
. ($start_datetime->format('d') + $interval->d) .' '
. ($start_datetime->format('H') + $interval->h) .':'
. ($start_datetime->format('i') + $interval->i);
What I'm doing: create the start date (& time) and the datetime of the next stop. With the DateTime::diff() function I'm calculating the difference, and then, only for display (!) I add up each year, month, day, hour and minute to the datetime year, month etc. of the next stop.
This way you can still store your dates and times in a way every human being and computer system will understand (because let's be honest; to represent a time as 27:45 PM is quite ridiculous...)
I don't know if you only want the hours to be added up and roll over the 24 hour, or also days in a month etc. It's up to you how you handle these cases. Good luck!
strtotime() returns number of seconds since so and so date. OK. So it's all in seconds. Now, if you give a date format which consists of only day, month and year, what time does it return in terms of seconds. The very first second of the day, the last second or undefined in between? The manual does not provide any guidance and common sense would assume the first second. Why is this significant? It could be when comparing or computing time interval between a fully defined date and a partially defined datetime (one without hours, minutes and seconds).
strtotime("1/1/2014")
Is this "guaranteed," as opposed to expected, to return the very first second of the new year?
It will return the time from 00:00:00, e.g. strtotime("1/1/2014"); = strtotime("1/1/2014 00:00:00");
In case you need to be sure, just use:
strtotime("1/1/2014 00:00:00");
Yes, it will always return first second of that day:
echo date('Y-m-d H:i:s', strtotime("1/1/2014"));
# 2014-01-01 00:00:00
demo
but to be sure, just enforce time like #Pekka suggested:
echo strtotime("1/1/2014 00:00:00");