PHP DateTime::createFromFormat behavoiur - php

Today I've encountered something confusing for me with the behaviour of the \DateTime::createFromFormat function.
In my case I have a string, representing the date in the following format m/Y (05/2017). When I want to convert the string to DateTime object I've encountered the following issue:
$date = \DateTime::createFromFormat('m/Y', '02/2017');
When I dump the $date variable, the date property inside is '2017-03-03 11:06:36.000000'
But if I add the date before the month $date = \DateTime::createFromFormat('d/m/Y', '01/02/2017'); I get back an object with correct date property. (unfortunately I cant change the format of the date and add the day. It must be m/Y).
The fix I've come up with is to concatenate the first day of the month to the date string I have $date = '01/'.$dateString; but I rather not to do that because it's hardcoded.
What is wrong here? Does the createFromFormat function lack information of how to create the object? I'm quite confused with this. Thanks for everyone's help in advance!

By default, PHP will populate missing date values with those of the current date/time; so
$date = \DateTime::createFromFormat('m/Y', '02/2017');
will populate the missing day value with the current date; and as 31st February is an invalid date, it will roll forward into March. Likewise, hours/minutes/seconds will be populated with the missing time values based on the current time.
If you want to force the behaviour of forcing to the beginning of the month/time, then modify your mask with a leading !
$date = \DateTime::createFromFormat('!m/Y', '02/2017');
This will populate the missing day with the 1st of the month, and the time with 00:00:00
Alternatively, a trailing | will have the same effect
$date = \DateTime::createFromFormat('m/Y|', '02/2017');

You cannot store incomplete dates, not at least in a dedicated date format that can be used for complex date calculations (nothing prevents you from creating your own MonthYear class). So when you create a DateTime() object with incomplete information something needs to happen:
Crash
Use some default values
PHP opts for the second option and makes a decision inherited from the C language date library:
Assume that missing data means "now"
Try to fix automatically the invalid dates that this algorithm can create
In this case, Feb 2017 becomes 31 Feb 2017 (because "now" is 31 May 2017) and PHP follows this reasoning: February only had 28 days in year 2017 but I have three more; the user probably wants to move these three extra days into March. Thus 3 Mar 2017.
I see no reason to avoid hard-coding 01 because, after all, it is a hard-coded value (why the first day of the month and not the last one or any other day?).
$input = '05/2017';
$date = \DateTime::createFromFormat('d/m/Y', "01/$input");

Related

Check day and difference time of a date

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).

DateTime converts wrong when system time is 30 March

Assume system time is set to 2017-03-30. Then this code will convert the date wrong:
<?php
$dateTime = DateTime::createFromFormat('m-Y', '02-2017');
$converted = $dateTime->format('Y-m');
print_r($converted);
The value of $converted is
2017-03
but only when run from the browser. Run from command line, it gives the correct result 2017-02.
Anyone knows why? February does not have 30 days, so that might be a reason, but still.
Edit: Changed format from 'Y-m-d' to 'Y-m'.
Edit 2: Added information about command line vs browser.
The rules used by DateTime::__construct(), DateTime::createFromFormat() and strtotime() to parse various date & time formats and the values it uses to fill missing components are explained in the documentation.
When it parses an incomplete date, it uses the values from the current date and time for the missing components.
In your specific case, 02-2017 is converted to "February 2017" using the current day of month (30) for the missing day of month. I.e. 30 February 2017 that is then normalized to 2 March 2017.
You can tell DateTime::createFromFormat() to initialize all the components to the Unix epoch (instead of the current date & time) by placing an exclamation mark (!) in the format string:
$dateTime = DateTime::createFromFormat('!m-Y', '02-2017');
print_r($dateTime);
It outputs:
DateTime Object
(
[date] => 2017-02-01 00:00:00.000000
[timezone_type] => 3
[timezone] => UTC
)
What you have here is over-run. The process goes like this:
1) You give the dateTime object a formatted date, but without a day.
2) The dateTime object then can not use null days so instead uses todays date.
3) You state in your question that todays date is 2017-03-30 therefore to apply this to the given date value of 02-2017 would make:
30-02-2017
4) This is obviously not valid so the dateTime object over-runs this value and turns it into 02-03-2017.
You reqest an output format of Year - Month which gives you 2017-03.
Solution:
Always set a day value in your dates.

PHP method strtotime is not resulting well

function DateFormat($dt)
{
return $newDate = date("d/m/Y", strtotime($dt));
}
$cr='2014-02-31';
echo DateFormat($cr);
Input: $cr='2014-02-31';
Output: 03/03/2014
I am passing 2014-02-31 and getting output 03/03/2014.
Please help me out.
PHP's date functions work with dates not strings. And that's an important distinction. Strings are just a bunch of characters in a specified order. Dates have months, days, years, hours, minutes, seconds, timezones, etc. When PHP works with dates it takes all of them into consideration.
So when you pass Feb 31 to a PHP date function it is going to try to make sense of it as a date and not a string. This means it isn't just going to take that date cut it up into bits and then rearrange them as you are expecting. It is going to turn that date into a date representation it can work with and then manipulate it.
As we all know, February does not have 31 days. As a result of the invalid date, PHP is trying to be helpful and taking three days after last day in February of that year (since Feb only has 28 days this year) and giving you that date.
The issue is February most years only has 28 days. 2/31 would be logically translated to 3/3. On a leap year you'd get 3/4...
The strtotime() method as it needs to be very flexible to be able to handle stuff without borking like:
strtotime('2014 February + 31 day - 1 year');
And no I don't think it should error out. When you have a well formed date string, PHP has a deceptively named method called checkdate() you could use:
$crappy_date='2014-02-31';
$date_parts = explode('-', $crappy_date);
$valid = checkdate($date_parts[1], $date_parts[2], $date_parts[0]);

Getting timestamp from relative date/time string with specific timezone AND specific $now value

I'm aware of two ways to get a timestamp from a relative date/time string in PHP:
strtotime: Allows user to specify their own $now value
DateTime object: Allows user to specify their own $timezone value
Is there a way to get a timestamp from a date/time string that allows one to specify both the timezone AND the $now value? It seems like the only way would be to use strtotime while temporarily overriding the default timezone for the entire php application and then setting it back immediately afterwards. That just seems like a hacky solution, and it would be nice if there were a cleaner way.
Edit: there seems to be some misunderstanding about what I'm trying to do. Here's a more concrete example:
"I want to find the timestamp corresponding to the string 'next tuesday at 3:00pm' within the America/Los_Angeles timezone AND specifying an arbitrary value for $now, such as March 14th, 2014 at 8:05am."
I've prepared an example. This may be want you want:
<?php
// Including the timezone int the time strings (thanks #Mike B!!!)
// will make it very easy. just strtotime() is required
// create a timestamp for March 14th PDT
$now = strtotime('14 March 2014 8:05am America/Los_Angeles');
// get the requested timestamp.
$nexTuesday = strtotime('next tuesday 3:00 pm America/Los_Angeles', $now);

PHP & MySQL are returning different dates for same timestamp

I am encountering a slightly frustrating problem and I have a feeling there is a simple solution to it. When I pass the same UNIX timestamp to the PHP date and MySQL FROM_UNIXTIME methods they are returning two very different dates. I would like to return a value from MySQL that matches the one returned by PHP.
Here is the code I am currently using along with it's output. The timestamp provided represents the date Tue, 01 Jan 2013 (01/01/2013). Also for reference, here are the format values.
MySQL Format
%j = Day of year (001..366).
%m = Month, numeric (00..12).
%y = Year, numeric (two digits).
PHP Format
z = The day of the year starting from 0 (0 through 365).
m = Numeric representation of a month, with leading zeros (01 through 12).
y = A two digit representation of a year (Examples: 99 or 03).
MySQL Query
SELECT FROM_UNIXTIME(1357018200, '%j-%m-%y');
-> 366-12-12
PHP Code
echo date('z-m-y', 1357018200);
-> 0-01-13
Any help would be greatly appreciated, thanks for your time. :)
Other Information
MySQL Version: 5.5.23
MySQL system_time_zone: CDT
MySQL time_zone: SYSTEM
PHP date_default_timezone_get: America/Chicago (CDT)
Your PHP and MySQL aren't agreeing on the time. Using the time converter at:
http://www.4webhelp.net/us/timestamp.php?action=stamp&stamp=1357018200&timezone=-6
gives the result "Monday, December 31st 2012, 23:30:00 (GMT -6)" so your PHP is giving the wrong result. Although you've given the timezone that PHP is running in, can you double check by running:
date_default_timezone_set ("America/Chicago");
echo date('z-m-y', 1357018200)."\r\n";
Which should give "365-12-12".
I guess it's possible either something is setting the timezone incorrectly somewhere else or possibly that "America/Chicago (CDT)" is an old setting in your php.ini file from a previous version of PHP.
Looking at the list of allowed timezone values from http://php.net/manual/en/timezones.america.php there is no "America/Chicago (CDT)" listed, so you should figure out where it's getting set to that bogus value as it may cause other issues.
Once you've got your timezone issues sorted out, the answer to your actual question is:
function sqlStyleDate($timestamp){
$dayOfYear = date("z", $timestamp) + 1;
$year = date("y", $timestamp);
$month = date("n", $timestamp);
$result = "$dayOfYear-$year-$month";
return $result;
}
echo sqlStyleDate(1357018200)."\r\n";
That will convert PHPs 0-365 day of year, to be the same as MySQL's 1-366 day of year.

Categories