Related
Has anyone ever had this bug before, or know a way to fix it?
I want to get a list of all months in the current year (start date and end date) in an array, so i'm doing like this (open to suggestions for cleaner easier ways)
//Create a months array
$months = [];
//Get start and end of all months
for($i = 1; $i <= 12; $i++){
$array = [];
$array['start'] = Carbon::create()->month($i)->startOfMonth()->format('d/m/y');
$array['end'] = Carbon::create()->month($i)->endOfMonth()->format('d/m/y');
array_push($months, $array);
}
Which produces this result
As you can see, its looped and retrieved the months, but notice that it skips February completely and adds March twice.
If I manually run and return this code
return Carbon::create()->month(2)->startOfMonth()->format('d/m/y');
It returns 01/03/2018.
Why does carbon print out March for month 2? Has anyone ever had this issue before or know of a way to fix it?
Carbon::create()->month(2) will first create today's date, and then set the month to 2, but keep the other values. Because today's date is the 29th of August, the date ends up referring to the 29th of February, which (this year at least) doesn't exist. PHP rolls these "fake" dates over into the next month, so February 29th becomes March 1st.
If you explicitly set the day of the month first, this should work as expected:
Carbon::create()->day(1)->month($i);
(Also, if you'd tried this yesterday it would have worked fine, and you might never have noticed the bug. If you tried it tomorrow, you would have ended up with March 2nd and probably noticed it a lot faster. Dates are great fun.)
Figured this out whilst commenting on a suggested answer.
Its because todays date is 29th August 2018, and there are not 29 days in February.
Basically Carbon is creating an instance from todays date with Carbon::create() but then when using ->month(2) will try to get the 29th Feb and error. It needs to work from the first of the month so change it to
Carbon::create()->startOfMonth()->month($i)->startOfMonth()->format('d/m/y');
So it references from the 1st of the month and it will work as expected.
Pure PHP is enough for this:
$datePeriod = new \DatePeriod(
new \DateTimeImmutable(date('01-01-Y')),
new \DateInterval('P1M'),
new \DateTimeImmutable(date('31-12-Y'))
);
$dates = [];
foreach ($datePeriod as $date) {
$dates[] = [
'start' => $date->modify('first day of this month')->format('d/m/y'),
'end' => $date->modify('last day of this month')->format('d/m/y'),
];
}
It's 0 based by the looks. so month(0) would be January, 1 = February, etc.
Change your loop to
for($i = 0; $i < 12; $i++){
$array = [];
$array['start'] = Carbon::create()->month($i)->startOfMonth()->format('d/m/y');
$array['end'] = Carbon::create()->month($i)->endOfMonth()->format('d/m/y');
array_push($months, $array);
}
I wan't to get a PHP date() from specified weekday in a given week.
For example:
Weekday: Thursday - in week:8 - year:13 (means 2013).
I would like to return a date from these specified values. The phpdate will in this case return: "21 Feb 2013", which is a Thursday in week 8 of 2013.
Please fill in this php-method:
function getDateWithSpecifiedValues($weekDayStr,$week,$year) {
//return date();
}
Where the example:
getDateWithSpecifiedValues("Tuesday",8,13);
will return a phpdate of "19 Feb 2013"
First, you have to define what you mean by "week of the year". There are several different definitions. Does the first week start on Jan 1? On the first Sunday or Monday? Is it number 1 or 0?
There is a standard definition, codified in ISO 8601, which says that weeks run from Monday through Sunday, the first one of the year is the one with at least 4 days of the new year in it, and that week is number 1. Your example expected output is consistent with that definition.
So you can convert the values by putting them into a string and passing that string to strptime, along with a custom format string telling it what the fields in the string are. For example, the the week number itself should be indicated in the format string by %V.
For the weekday, the format depends on how you want to provide it as input to your function. If you have the full name (e.g. "Thursday"), that's %A. If you have the abbreviated name (e.g. "Thu"), that's %a. If you have a number (e.g. 4), that's either %w (if Sundays are 0) or %u (if Sundays are 7). (If you're not sure, you can always just use %w and pass the number % 7.)
Now, the year should be %G (full year) or %g (just the last two digits). It's different from the normal calendar year fields (%Y for 2014 and %y for 13) because, for example, week 1 of 2014 actually started on December 30, 2013, which obviously has a '%Y' of 2013 where we want 2014. However, the G fields don't work properly with strptime, so you'll have to use the Y's.
For example:
$date_array = strptime("$weekDayStr $week $year", '%A %V %y');
That's a good start, but the return value of strptime is an array:
array('tm_sec' => seconds, 'tm_min' => minutes, tm_hour => hour,
tm_mday => day of month, tm_mon => month number (0..11), tm_year => year - 1900)
And that array is not the input expected by any of the other common date or time functions, as far as I can tell. You have to pull the values out yourself and modify them in some cases and pass the result to something to get what you want. For instance:
$time_t = mktime($date_array['tm_hour'], $date_array['tm_min'],
$date_array['tm_sec'], $date_array['tm_mon']+1,
$date_array['tm_mday'], $date_array['tm_year']+1900);
And then you can return that in whatever form you need. Here I'm returning it as a string:
function getDateWithSpecifiedValues($weekDayStr,$week,$year) {
$date_array = strptime("$weekDayStr $week $year", '%A %V %y');
$time_t = mktime($date_array['tm_hour'], $date_array['tm_min'],
$date_array['tm_sec'], $date_array['tm_mon']+1,
$date_array['tm_mday'], $date_array['tm_year']+1900);
return strftime('%d %b %Y', $time_t);
}
For example,
php > print(getDateWithSpecifiedValues('Thursday',8,13)."\n");
21 Feb 2013
Try this function:
function getDateWithSpecifiedValues($weekDayStr, $week, $year) {
$dt = DateTime::createFromFormat('y, l', "$year, $weekDayStr");
return $dt->setISODate($dt->format('o'), $week, $dt->format('N'))->format('j M Y');
}
demo
I'm fairly certain neither 21st of January or the 19th of January is in week 8.
You can however use strptime to parse custom formats:
var_dump(strptime("Thursday 8 13", "%A %V %y"));
array(9) {
["tm_sec"]=>
int(0)
["tm_min"]=>
int(0)
["tm_hour"]=>
int(0)
["tm_mday"]=>
int(21)
["tm_mon"]=>
int(1)
["tm_year"]=>
int(113)
["tm_wday"]=>
int(4)
["tm_yday"]=>
int(58)
["unparsed"]=>
string(0) ""
}
See the documentation for strptime for the meaning of each value in the returned array.
I need to create functions in PHP that let me step up/down given datetime units. Specifically, I need to be able to move to the next/previous month from the current one.
I thought I could do this using DateTime::add/sub(P1M). However, when trying to get the previous month, it messes up if the date value = 31- looks like it's actually trying to count back 30 days instead of decrementing the month value!:
$prevMonth = new DateTime('2010-12-31');
Try to decrement the month:
$prevMonth->sub(new DateInterval('P1M')); // = '2010-12-01'
$prevMonth->add(DateInterval::createFromDateString('-1 month')); // = '2010-12-01'
$prevMonth->sub(DateInterval::createFromDateString('+1 month')); // = '2010-12-01'
$prevMonth->add(DateInterval::createFromDateString('previous month')); // = '2010-12-01'
This certainly seems like the wrong behavior. Anyone have any insight?
Thanks-
NOTE: PHP version 5.3.3
(Credit actually belongs to Alex for pointing this out in the comments)
The problem is not a PHP one but a GNU one, as outlined here:
Relative items in date strings
The key here is differentiating between the concept of 'this date last month', which, because months are 'fuzzy units' with different numbers of dates, is impossible to define for a date like Dec 31 (because Nov 31 doesn't exist), and the concept of 'last month, irrespective of date'.
If all we're interested in is the previous month, the only way to gaurantee a proper DateInterval calculation is to reset the date value to the 1st, or some other number that every month will have.
What really strikes me is how undocumented this issue is, in PHP and elsewhere- considering how much date-dependent software it's probably affecting.
Here's a safe way to handle it:
/*
Handles month/year increment calculations in a safe way,
avoiding the pitfall of 'fuzzy' month units.
Returns a DateTime object with incremented month/year values, and a date value == 1.
*/
function incrementDate($startDate, $monthIncrement = 0, $yearIncrement = 0) {
$startingTimeStamp = $startDate->getTimestamp();
// Get the month value of the given date:
$monthString = date('Y-m', $startingTimeStamp);
// Create a date string corresponding to the 1st of the give month,
// making it safe for monthly/yearly calculations:
$safeDateString = "first day of $monthString";
// Increment date by given month/year increments:
$incrementedDateString = "$safeDateString $monthIncrement month $yearIncrement year";
$newTimeStamp = strtotime($incrementedDateString);
$newDate = DateTime::createFromFormat('U', $newTimeStamp);
return $newDate;
}
Easiest way to achieve this in my opinion is using mktime.
Like this:
$date = mktime(0,0,0,date('m')-1,date('d'),date('Y'));
echo date('d-m-Y', $date);
Greetz Michael
p.s mktime documentation can be found here: http://nl2.php.net/mktime
You could go old school on it and just use the date and strtotime functions.
$date = '2010-12-31';
$monthOnly = date('Y-m', strtotime($date));
$previousMonth = date('Y-m-d', strtotime($monthOnly . ' -1 month'));
(This maybe should be a comment but it's to long for one)
Here is how it works on windows 7 Apache 2.2.15 with PHP 5.3.3:
<?php $dt = new DateTime('2010-12-31');
$dt->sub(new DateInterval('P1M'));
print $dt->format('Y-m-d').'<br>';
$dt->add(DateInterval::createFromDateString('-1 month'));
print $dt->format('Y-m-d').'<br>';
$dt->sub(DateInterval::createFromDateString('+1 month'));
print $dt->format('Y-m-d').'<br>';
$dt->add(DateInterval::createFromDateString('previous month'));
print $dt->format('Y-m-d').'<br>'; ?>
2010-12-01
2010-11-01
2010-10-01
2010-09-01
So this does seem to confirm it's related to the GNU above.
Note: IMO the code below works as expected.
$dt->sub(new DateInterval('P1M'));
Current month: 12
Last month: 11
Number of Days in 12th month: 31
Number of Days in 11th month: 30
Dec 31st - 31 days = Nov 31st
Nov 31st = Nov 1 + 31 Days = 1st of Dec (30+1)
Given an arbitrary string, for example ("I'm going to play croquet next Friday" or "Gadzooks, is it 17th June already?"), how would you go about extracting the dates from there?
If this is looking like a good candidate for the too-hard basket, perhaps you could suggest an alternative. I want to be able to parse Twitter messages for dates. The tweets I'd be looking at would be ones which users are directing at this service, so they could be coached into using an easier format, however I'd like it to be as transparent as possible. Is there a good middle ground you could think of?
If you have the horsepower, you could try the following algorithm. I'm showing an example, and leaving the tedious work up to you :)
//Attempt to perform strtotime() on each contiguous subset of words...
//1st iteration
strtotime("Gadzooks, is it 17th June already")
strtotime("is it 17th June already")
strtotime("it 17th June already")
strtotime("17th June already")
strtotime("June already")
strtotime("already")
//2nd iteration
strtotime("Gadzooks, is it 17th June")
strtotime("is it 17th June")
strtotime("17th June") //date!
strtotime("June") //date!
//3rd iteration
strtotime("Gadzooks, is it 17th")
strtotime("is it 17th")
strtotime("it 17th")
strtotime("17th") //date!
//4th iteration
strtotime("Gadzooks, is it")
//etc
And we can assume that strtotime("17th June") is more accurate than strtotime("17th") simply because it contains more words... i.e. "next Friday" will always be more accurate than "Friday".
I would do it this way:
First check if the entire string is a valid date with strtotime(). If so, you're done.
If not, determine how many words are in your string (split on whitespace for example). Let this number be n.
Loop over every n-1 word combination and use strtotime() to see if the phrase is a valid date. If so you've found the longest valid date string within your original string.
If not, loop over every n-2 word combination and use strtotime() to see if the phrase is a valid date. If so you've found the longest valid date string within your original string.
...and so on until you've found a valid date string or searched every single/individual word. By finding the longest matches, you'll get the most informed dates (if that makes sense). Since you're dealing with tweets, your strings will never be huge.
Inspired by Juan Cortes's broken link based off Dolph's algorithm, I went ahead and wrote it up myself. Note that I decided to just return on first successful match.
<?php
function extractDatetime($string) {
if(strtotime($string)) return $string;
$string = str_replace(array(" at ", " on ", " the "), " ", $string);
if(strtotime($string)) return $string;
$list = explode(" ", $string);
$first_length = count($list);
for($j=0; $j < $first_length; $j++) {
$original_length = count($list);
for($i=0; $i < $original_length; $i++) {
$temp_list = $list;
for($k = 0; $k < $i; $k++) unset($temp_list[$k]);
//echo "<code>".implode(" ", $temp_list)."</code><br/>"; // for visualizing the tests, if you want to see it
if(strtotime(implode(" ", $temp_list))) return implode(" ", $temp_list);
}
array_pop($list);
}
return false;
}
Inputs
$array = array(
"Gadzooks, is it 17th June already",
"I’m going to play croquet next Friday",
"Where was the dog yesterday at 6 PM?",
"Where was Steve on Monday at 7am?"
);
foreach($array as $a) echo "$a => ".extractDatetime(str_replace("?", "", $a))."<hr/>";
Outputs
Gadzooks, is it 17th June already
is it 17th June already
it 17th June already
17th June already
June already
already
Gadzooks, is it 17th June
is it 17th June
it 17th June
17th June
Gadzooks, is it 17th June already => 17th June
-----
I’m going to play croquet next Friday
going to play croquet next Friday
to play croquet next Friday
play croquet next Friday
croquet next Friday
next Friday
I’m going to play croquet next Friday => next Friday
-----
Where was Rav Four yesterday 6 PM
was Rav Four yesterday 6 PM
Rav Four yesterday 6 PM
Four yesterday 6 PM
yesterday 6 PM
Where was the Rav Four yesterday at 6 PM? => yesterday 6 PM
-----
Where was Steve Monday 7am
was Steve Monday 7am
Steve Monday 7am
Monday 7am
Where was Steve on Monday at 7am? => Monday 7am
-----
Something like the following might do it:
$months = array(
"01" => "January",
"02" => "Feberuary",
"03" => "March",
"04" => "April",
"05" => "May",
"06" => "June",
"07" => "July",
"08" => "August",
"09" => "September",
"10" => "October",
"11" => "November",
"12" => "December"
);
$weekDays = array(
"01" => "Monday",
"02" => "Tuesday",
"03" => "Wednesday",
"04" => "Thursday",
"05" => "Friday",
"06" => "Saturday",
"07" => "Sunday"
);
foreach($months as $value){
if(strpos(strtolower($string),strtolower($value))){
\\ extract and assign as you like...
}
}
Probably do a nother loop to check for other weekDays or other formats, or just nest.
Use the strtotime php function.
Of course you would need to set up some rules to parse them since you need to get rid of all the extra content on the string, but aside from that, it's a very flexible function that will more than likely help you out here.
For example, it can take strings like "next Friday" and "June 15th" and return the appropriate UNIX timestamp for the date in the string. I guess that if you consider some basic rules like looking for "next X" and week and month names you would be able to do this.
If you could locate the "next Friday" from the "I'm going to play croquet next Friday" you could extract the date. Looks like a fun project to do! But keep in mind that strtotime only takes english phrases and will not work with any other language.
For example, a rule that will locate all the "Next weekday" cases would be as simple as:
$datestring = "I'm going to play croquet next Friday";
$weekdays = array('monday','tuesday','wednesday',
'thursday','friday','saturday','sunday');
foreach($weekdays as $weekday){
if(strpos(strtolower($datestring),"next ".$weekday) !== false){
echo date("F j, Y, g:i a",strtotime("next ".$weekday));
}
}
This will return the date of the next weekday mentioned on the string as long as it follows the rule! In this particular case, the output was June 18, 2010, 12:00 am.
With a few (maybe more than a few!) of those rules you will more than likely extract the correct date in a high percentage of the cases, considering that the users use correct spelling though.
Like it's been pointed out, with regular expressions and a little patience you can do this. The hardest part of coding is deciding what way you are going to approach your problem, not coding it once you know what!
Following Dolph Mathews idea and basically ignoring my previous answer, I built a pretty nice function that does exactly that. It returns the string it thinks is the one that matches a date, the unix datestamp of it, and the date itself either with the user specified format or the predefined one (F j, Y).I wrote a small post about it on Extracting a date from a string with PHP. As a teaser, here's the output of the two example strings:
Input: “I’m going to play croquet next Friday”
Output: Array (
[string] => "next friday",
[unix] => 1276844400,
[date] => "June 18, 2010"
)
Input: “Gadzooks, is it 17th June already?”
Output: Array (
[string] => "17th june",
[unix] => 1276758000,
[date] => "June 17, 2010"
)
I hope it helps someone.
Based on Dolph's suggestion, I wrote out a function that I think serves the purpose.
public function parse_date($text, $offset, $length){
$parseArray = preg_split( "/[\s,.]/", $text);
$dateTest = implode(" ", array_slice($parseArray, $offset, $length == 0 ? null : $length));
$date = strtotime($dateTest);
if ($date){
return $date;
}
//make the string one word shorter in the front
$offset++;
//have we reached the end of the array?
if($offset > count($parseArray)){
//reset the start of the string
$offset = 0;
//trim the end by one
$length--;
//reached the very bottom with no date found
if(abs($length) >= count($parseArray)){
return false;
}
}
//try to find the date with the new substring
return $this->parse_date($text, $offset, $length);
}
You would call it like this:
parse_date('Setting the due date january 5th 2017 now', 0 , 0)
What you're looking for a is a temporal expression parser. You might look at the Wikipedia article to get started. Keep in mind that the parsers can get pretty complicated, because this really a language recognition problem. That is commonly a problem tackled by the artificial intelligence/computational linguistics field.
Majority of suggested algorithms are in fact pretty lame. I suggest using some nice regex for dates and testing the sentence with it. Use this as an example:
(\d{1,2})?
((mon|tue|wed|thu|fri|sat|sun)|(monday|tuesday|wednesday|thursday|friday|saturday|sunday))?
(\d{1,2})? (\d{2,4})?
I skipped months, since I'm not sure I remember them in the right order.
This is the easiest solution, yet I will do the job better than other compute-power based solutions. (And yeah, it's hardly a fail-proof regex, but you get the point). Then apply the strtotime function on the matched string. This is the simplest and the fastest solution.
I have a simple situation where I have a user supplied week number X, and I need to find out that week's monday's date (e.g. 12 December). How would I achieve this? I know year and week.
Some code based mainly on previous proposals:
$predefinedYear = 2009;
$predefinedWeeks = 47;
// find first mоnday of the year
$firstMon = strtotime("mon jan {$predefinedYear}");
// calculate how much weeks to add
$weeksOffset = $predefinedWeeks - date('W', $firstMon);
// calculate searched monday
$searchedMon = strtotime("+{$weeksOffset} week " . date('Y-m-d', $firstMon));
An idea to get you started:
take first day of year
add 7 * X days
use strtodate, passing in "last Monday" and the date calculated above.
May need to add one day to the above.
Depending on the way you are calculating week numbers and the start of the week this may sometimes be out. (i.e. if the monday in the first week of the year was actually in the previous year!)
TEST THIS THOROUGHLY - but I've used a similar approach for similar calcualtions in the past.
This will solve the problem for you. It mainly derives from Mihail Dimitrov's answer, but simplifies and condenses this somewhat. It can be a one-line solution if you really want it to be.
function getMondaysDate($year, $week) {
if (!is_numeric($year) || !is_numeric($week)) {
return null;
// or throw Exception, etc.
}
$timestamp = strtotime("+$week weeks Monday January $year");
$prettyDate = date('d M Y');
return $prettyDate;
}
A couple of notes:
As above, strtotime("Monday January $year") will give you the timestamp of the first Monday of the year.
As above +X weeks will increment a specified date by that many weeks.
You can validate this by trying:
date('c',strtotime('Sunday Jan 2018'));
// "2018-01-07T00:00:00+11:00" (or whatever your timezone is)
date('c',strtotime('+1 weeks Sunday Jan 2018'));
// "2018-01-14T00:00:00+11:00" (or whatever your timezone is)
date('c',strtotime('+52 weeks Sunday Jan 2018'));
// "2019-01-06T00:00:00+11:00"
Due to reputation restriction i can't post multiple links
for details check
http://php.net/manual/en/function.date.php and http://php.net/manual/en/function.mktime.php
you can use something like this :
use mktime to get a timestamp of the week : $stamp = mktime(0,0,0,0,<7*x>,) {used something similar a few years back, so i'm not sure it works like this}
and then use $wDay = date('N',$stamp). You now have the day of the week, the timestamp of the monday should be
mktime(0,0,0,0,<7*x>-$wDay+1,) {the 'N' parameter returns 1 for monday, 6 for sunday}
hope this helps
//To calculate 12 th Monday from this Monday(2014-04-07)
$n_monday=12;
$cur_mon=strtotime("next Monday");
for($i=1;$i<=$n_monday;$i++){
echo date('Y-m-d', $cur_mon);
$cur_mon=strtotime(date('Y-m-d', strtotime("next Monday",$cur_mon)));
}
Out Put
2014-04-07
2014-04-14
2014-04-21
2014-04-28
2014-05-05
2014-05-12
2014-05-19
2014-05-26
2014-06-02
2014-06-09
2014-06-16
2014-06-23