DateTime API only outputs DAY in the future? PHP - php

If I send a DAY and TIME (not DATE) to the DateTime API like this :
$tz = new \DateTimeZone("UTC");
$now = new \DateTime("now", $tz);
$then = \DateTime::createFromFormat('l g A', 'Thursday 8 PM', $tz);
And it is currently Saturday 26th March, $then, when echoed as follows :
echo $then->format('l jS, F');
Will return :
Thursday 31st, March
How do I make it return the date of the Thursday in the CURRENT week that has just passed :
Thursday 24th March
Not the next Thursday?

DateTime::__construct() and strtotime() uses a lot of rules to guess what you mean when you specify an incomplete or relative date. Most of the times thy guess right but some of the rules are very similar and the final result is not the one expected by the programmer.
DateTime::createFromFormat() is limited, it cannot understand all the formats from the list.
You try to parse a relative date format. When only the day name is specified, it is interpreted as the next occurrence of the specified day of the week (see the dayname entry under the Day-based Notations table in the documentation.)
You can get the date you want by using Thursday this week instead. DateTime::createFromFormat() doesn't understand relative dates that contain references to the week but DateTime::__construct() does.
Try this:
$then = new \DateTime('Thursday this week 8 PM', $tz);

Could always call the 'sub' method on it, subtracting 7 days.
$tz = new \DateTimeZone("UTC");
$now = new \DateTime("now", $tz);
$then = \DateTime::createFromFormat('l g A', 'Thursday 8 PM', $tz);
$then->sub(new DateInterval('P7D'));
echo $then->format('l jS, F');

Related

PHP Display next delivery date for twice weekly deliveries

I have a website where we show next delivery date, eg for a weekly delivery each Tuesday -- although the actual day that deliveries go out needs to be manually changed sometimes. I need to update it to (eg) Tuesday and Friday deliveries, and I'm struggling to find a good method.
Here's how I've got it set up currently:
<?php
$start_date = '2020-05-05'; // Date in the past to start counting from: YYYY-MM-DD
$date_interval = new DateInterval('P7D'); // DELIVERY EVERY x DAYS: PxD (usually P7D)
// create a DateTime object that represents start of sequence
$start_datetime = DateTime::createFromFormat('Y-m-d', $start_date);
// create a DateTime object representing the current date
$current_datetime = new DateTime('today');
// determine end date for DatePeriod object that will later be used.
// This is no further out than current date plus the interval.
$end_datetime = new DateTime('tomorrow');
$end_datetime->add($date_interval);
$date_period = new DatePeriod($start_datetime, $date_interval, $end_datetime);
// iterate until the last date in the set
foreach($date_period as $dp) {
$next_delivery = $dp;
}
echo $next_delivery->format('l, M j, Y');
?>
The dateperiod class says that it accepts ISO 8601 time periods, but as far as I can tell there's no way to set it to "next Tuesday or Friday, whichever is closer".
One hacky method I've thought of that would work is to run this function twice, once for Tuesday and once for Friday, compare with today's date, and display whichever is sooner in the future. But surely there's a more elegant way?
That's a very long winded way to find the next available Tuesday or Friday. One way to do it is check to see what day of the week it is and dynamically set the day based on that:
$day = (in_array((int) date('w'), [0,1,5,6], true)) ? 'Tuesday' : 'Friday';
echo (new DateTime("next {$day}"))->format('l, M j, Y');
output:
Tuesday, May 12, 2020
The code above checks to see if the day is Sunday, Monday, Friday, Or Saturday. If so, set the next day to "Tuesday". Otherwise, set it to "Friday". Then you can use a relative datetime value to set your date.
If you want a more human readable version of this you can spell out the days:
$day = (in_array(date('l'), ['Sunday', 'Monday', 'Friday', 'Saturday'], true)) ? 'Tuesday' : 'Friday';
It is not very difficult to find the next available Tuesday or Friday.
It is the smaller date of both.
echo min(date_create('next Tuesday'),date_create('next Friday'))->format('l, M j, Y');
Alternatively with a PHP extension for DateTime API called dt. You can find it here.
This class supports date calculations with crontab expressions. The days of the week start with 0 for Sunday. If you want to calculate the next Tuesday or Friday at 14:00 as an example, you only have to do this:
$cron = "0 14 * * 2,5"; //next Tuesday or Friday 14:00
$next_delivery = dt::create('today')->nextCron($cron); //today is 2020-05-10
echo $next_delivery->format('l, M j, Y, H:i');
//Tuesday, May 12, 2020, 14:00

PHP and Carbon: How to get a relative time in a different year

I can use this, new \Carbon\Carbon('last sun of September'), to get the last Sunday in September this year. But what if I want the last Sunday in September last year?
I tried both of these:
new \Carbon\Carbon('last sun of September last year')
new \Carbon\Carbon('last year last sun of September')
Both both gave the DD/MM of the last Sunday THIS year, and changed the YYYY to last year.
This seems to only be possible in 2 steps. Either of these work for me:
$lastYear = (new \DateTime('last year'))->format('Y');
$dt = new \DateTime('last sun of September ' . $lastYear);
$dt2 = new \DateTime('last year');
$dt2->modify('last sun of September');
var_dump($dt); // 2017-09-24 00:00:00.000000
var_dump($dt2); // 2017-09-24 00:00:00.000000
Personally I think the second approach is cleaner.
I tested it with DateTime, as Carbon is just an extension for that and internally uses the PHP DateTime parser. See also http://php.net/manual/en/datetime.formats.relative.php

Using Date time diff with timezones

I found a link earlier regarding using time diffs and getting the difference in minutes, hours and days:
How to get time difference in minutes in PHP
I was trying this:
$date1 = new DateTime('first day of this month', new DateTimeZone('Europe/Amsterdam'));
$date2 = new DateTime('first day of this month', new DateTimeZone('Europe/London'));
print_r($date1->format('Y-m-d H:i:s'));
print_r($date2->format('Y-m-d H:i:s'));
The output was like:
2013-12-01 13:00:36
2013-12-01 12:00:36
Then used this:
$diff = $date2->diff($date1);
print_r($diff);
But then i get 0 in all the differences. I want to get the difference between the two without using strtotime.. I is it outputing 0?
Your expectation doesn't make sense, since there is no difference. 2013-12-01 13:00:36 Amsterdam and 2013-12-01 12:00:36 London are the exact same point in time in human history. What you appear to expect is the offset difference between the London and Amsterdam timezones (i.e. GMT and GMT+1 differ by 1), but that has nothing to do with concrete timestamps.
You want to calculate the offset.Use DateTimeZone::getOffset()
$dateTimeZoneAmsterdam = new DateTimeZone("Europe/Amsterdam");
$dateTimeZoneLondon = new DateTimeZone("Europe/London");
$dateTimeAmsterdam = new DateTime('first day of this month', $dateTimeZoneAmsterdam);
$dateTimeLondon = new DateTime('first day of this month', $dateTimeZoneLondon);
$timeOffset = $dateTimeZoneAmsterdam->getOffset($dateTimeLondon);
print_r($timeOffset); // 3600
You are close. Try DateTime::diff

New date with weekend

$date = new DateTime("11:00");
$date->format('Y-m-d H:i W');
this create new datetime which is now with hour 11:00:
2011-12-22 11:00 51
how can i make it with W - weekend?
i would like create new date with hour 11:00 and week 20. (now is 51)
How can i make it?
$date = new DateTime("11:00");
$date->format('W H:i');
the format method accepts the same format identifier as the date function. The W return the week number.

Generate start and ending datetimes in UTC based on a PST date

I am taking in a parameter that is a date in PST timezone which will be in the format of "YYYY-MM-DD" (e.g. "2011-08-15"). This parameter is optional. I have 2 questions that I've been struggling with.
I need to calculate the start and end datetime in UTC for this date.
So if the inputted date is 2011-08-15, I want to get the start and end datetimes:
2011-08-15 07:00:00
2011-08-15 06:59:59
(These are essentially the beginning and end of day)
Second, is to handle the case when the date is not passed in. I want to default to the current PST date them and start from there. So if the current datetime is 2011-08-01 10:00:00, I want to get the same start and end datetimes similar to the first scenario except it's based on the inputted date.
2011-08-01 07:00:00
2011-08-01 06:59:59
I've been pulling my hair out dealing with date and datetime conversions. I'm sure I'm missing something super straightforward.
Parse the date and assume PST timezone:
$date = new DateTime("2011-08-15", new DateTimeZone("PST"));
Change the timezone to UTC: (this does the all conversions for you)
$date->setTimeZone(new DateTimeZone("UTC"));
Calculate start and end. Start is our $date and end is $date + 1 day
$start = $date;
$end = clone $date;
$end->modify("+1 day"); // now $end is $start + 1 day
Print start/end:
printf("start: %s, end: %s\n", $start->format('Y-m-d H:i:s'), $end->format('Y-m-d Hi:s'));
// this prints start: 2011-08-15 07:00:00, end: 2011-08-16 07:00:00
For the last part of your questions, you can easily compare two dates:
if ($date > new DateTime()) { // if $date is after now
// do something
}
So you could do something like that:
if ($date > new DateTime()) {
$date->setTimeZone(new DateTimeZone("UTC"));
}
If you don't much like the OO syntax you could also use the function aliases:
$date = date_create(...);
date_format($date, ...);
date_modify($date, ...);
// ...
Use setTimezone function
Working example (I'm not in PST timezone, so I have to set it explicitly)
$date_input = "20011-09-15";
//$date_input = null; //That will emulate no-input case
date_default_timezone_set("America/Los_Angeles"); //if you are in PST, you don't need this line
$date_start = new DateTime($date_input);
$date_end = new DateTime($date_input);
$date_end->modify("+1 day");
/*$date_start->setTimezone(new DateTimeZone("America/Los_Angeles"));
$date_end->setTimezone(new DateTimeZone("America/Los_Angeles"));*/
//end of date is equal to start of the next day.
//But, if you need something like 2011-08-11 23:59:59 add $date_end->modify('-1 second')
$date_start->setTime(0,0,0);
$date_end->setTime(0,0,0);
echo "Date Start PST:".$date_start->format("Y-m-d H:i:s")."<br/>";
echo "Date End PST:".$date_end->format("Y-m-d H:i:s")."<br/>";
//UTC is equal to London time. Almost :)
$date_start->setTimezone(new DateTimeZone ('Europe/London'));
$date_end->setTimezone(new DateTimeZone ('Europe/London'));
echo "Date Start UTC:".$date_start->format("Y-m-d H:i:s")."<br/>";
echo "Date End UTC:".$date_end->format("Y-m-d H:i:s")."<br/>";

Categories