PHP method strtotime is not resulting well - php

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]);

Related

PHP doesn't give a proper output for month substraction

Code:
$time = strtotime('2020-03-31');
echo date('Y-m-d', strtotime('-1 month', $time));
Expected Result: Any date from Feb 2020
Actual Result: 2020-03-02
Is there any better way to add or subtract a month from a given date?
Months are an awkward interval to work with, because they don't have a fixed length. Should the algorithm assume that by "1 month" you mean "30 days", or "31 days", or should it just try subtracting 1 from the "month" field in the date structure?
The last option is what is happening here: given "2020-03-31", PHP's date library is subtracting 1 from the "03" to give "2020-02-31". Since that's an invalid date (February 2020 had 29 days), it then "normalises" it to a real date - 2 days after the 29th February was the 2nd March.
Probably you want to use a more specific period to subtract, like 30 days - although note that if the initial input is "2020-03-01" that will give you "2020-01-31", not "2020-02-01".
Ultimately, this is a problem with our irregular calendar, rather than with PHP. It's really up to you to define what you mean by "a month before", and use a more specific algorithm that captures that requirement.
You can make code like below
<?php
$time = strtotime('2020-03-1 -32 days');
echo date('M-Y', $time); // output Feb-2020
?>
The above code will return date as you expected

What is the difference between 2 days and 48 hours in php

$new_date = date('Y-m-d', strtotime("+2 days"));
or
$new_date = date('Y-m-d', strtotime("+48 hours"));
Are they same or different
Are they same or different
They are quite different. Objectively speaking, 2 days does not always equal 48 hours.
Consider crossing a daylight-savings boundary.
For example, 2019-04-07T02:00:00+1100 (AEDT -> AEST)
$twoDays = new DateInterval('P2D');
$fortyEightHours = new DateInterval('PT48H');
$ref = new DateTimeImmutable('2019-04-07T01:00:00',
new DateTimeZone('Australia/Melbourne'));
echo 'Reference: ', $ref->format('r'), PHP_EOL;
echo 'Plus 2 days: ', $ref->add($twoDays)->format('r'), PHP_EOL;
echo 'Plus 48 hours: ', $ref->add($fortyEightHours)->format('r'), PHP_EOL;
This produces
Reference: Sun, 07 Apr 2019 01:00:00 +1100
Plus 2 days: Tue, 09 Apr 2019 01:00:00 +1000
Plus 48 hours: Tue, 09 Apr 2019 00:00:00 +1000
Note, HHVM produces a different result for some reason
Demo ~ https://3v4l.org/L4tKo
This list of common date / time related falsehoods is worth checking out ~ https://github.com/kdeldycke/awesome-falsehood#dates-and-time
It's worth pointing out that using the same reference date and manipulating it with strtotime() produces different results to those above.
Demo ~ https://3v4l.org/O6MTd
I suspect this is because its relative time calculations aren't as nuanced as DateInterval.
Semantically they are different, as demonstrated by Phil in the other answer. This means that in some other libraries that account for this the difference is real.
However, the implementation of strtotime() does not take this into account, so given your example, it makes no difference.
I was interested if it can be followed up through source code, so here goes:
Implementation of strtotime() does this:
strtotime() starts, calling timelib_strtotime() to create the initial ts construct
timelib_strtotime() calls scan() to parse the string
it reads the string you've given to it, and one of the tokens it gets is your +2 days or +48 hours, which is considered as relativetext.
for relativetext, among other things, timelib_set_relative() is called
in timelib_set_relative() it does a lookup from using timelib_lookup_relunit(), which uses timelib_relunit_lookup array, finding that you've provided either a construct with type TIMELIB_DAY, value 1, multiplier 2, or construct TIMELIB_HOUR, value 1, multiplier 24
It saves the information from previous call to s->time->relative construct
this construct gets passed on, until strtotime calls timelib_update_ts with it (and with timezone info!)
in timelib_update_ts we call do_adjust_relative() to act on it. We do it without using time zone parameter, which I would think is the critical mistake
do_adjust_relative() calls timelib_do_normalize()
timelib_do_normalize() for hours does do_range_limit(0, 24, 24, &time->h, &time->d), increasing days and decreasing hours if hours are over the hardcoded limit which is 24, and for days does do_range_limit_days(), increasing months and decreasing days if we are over the limit, taking into account leap time (but NOT time zones!).
on returning to timelib_update_ts, we call do_adjust_timezone with timezone parameter, but at this point we've already made all the adjustments and we know no more if it was done with +48 hours or with +2 days. Thus it makes no difference.
TLDR: strtotime() does not take time zones into account when doing the conversion, so in that function it makes no difference, even though it really should.

PHP DateTime::createFromFormat behavoiur

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");

PHP - Parsing String As Past Date

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.

Month by week of the year?

I'm trying to get the number of the month of the year by the number of a week of the year and the year.
So for example week 1 is in january and returns 1, week 6 is in february so I want 2.
I tried to go with date_parse_from_format('W/Y') but had no success (it's giving me errors).
Is there any way to go with date_parse_from_format() or is there another way?
print date("m",strtotime("2011-W6-1"));
(noting that in 2011, January has six weeks so week 6 (by some definitions) is in month 1).
Just wanted to add a note for the first answer, the week number should be 01-09 for Weeks 1 through 9 (it will always give month 1 if you don't add the leading zero)
date("m",strtotime("2011-W06-1"));
Using PHP DateTime objects (which is the preferred way of dealing with dates see links below for more info) you can accomplish it this way:
$dateTime = new \DateTime();
$dateTime->setISODate($year,$week);
$month = $dateTime->format('n');
Note that the following will not work as week "W" is not a supported format:
$month = \DateTime::createFromFormat("W/Y ", "1/2015")->format('n');
The format used by this method is the same supported by the function you where trying to use date_parse_from_format, hence the errors.
Why PHP DateTime Rocks
DateTime class vs. native PHP date-functions
strtotime notes
PHP/Architect's Guide to Date and Time Programming (Chapter 2)
Something like this will do, this is also tested and works:
function getMonthByNumber($number,$year)
{
return date("F",strtotime('+ '.$number.' weeks', mktime(0,0,0,1,1,$year,-1)));
}
echo getMonthByNumber(27,2011);
Hope this helps

Categories