I'm trying to find the weekly periods for a given month and year. Dates should start on a Monday and end on a Sunday. If the 1st of the month is a Sunday (Ex May 2011), it should be the first element.
May 2011
May 1 (Sunday)
May 2 - May 8 (Monday - Sunday)
May 9 - May 15 (Monday - Sunday)
May 17 - Ma6y 22 (Monday - Sunday)
May 23 - May 29 (Monday - Sunday)
May 30 - May 31 (Monday - Tuesday)
September 2012
September 1 - September 2
September 3 - September 9
September 10 - September 16
September 17 - September 23
September 24 - September 30
I am using this function to calculate the week numbers for two dates - I.e. the 1st day of the month and last day of the month.
public function getWeekNumbers($startDate, $endDate)
{
$p = new DatePeriod(
new DateTime($startDate),
new DateInterval('P1W'),
new DateTime($endDate)
);
$weekNumberList = array();
foreach ($p as $w)
{
$weekNumber = $w->format('W');
$weekNumberList[] = ltrim($weekNumber, '0');
}
return $weekNumberList;
}
Strangely, for the month of January, it returns week numbers of [52, 1, 2, 3, 4] when I'm expecting [1, 2, 3, 4, 5].
Once I have the week numbers, I'm using them like so:
//The following loop will populate the dataset with the entire month's durations - regardless if hours were worked or not.
$firstDayOfMonth = date('Y-m-d', strtotime("first day of {$this->year}-{$monthName}"));
$lastDayOfMonth = date('Y-m-d', strtotime("last day of {$this->year}-{$monthName}"));
foreach ($this->getWeekNumbers($firstDayOfMonth, $lastDayOfMonth) as $key => $weekId)
{
// find first mоnday of the year
$firstMon = strtotime("mon jan {$this->year}");
// calculate how many weeks to add
$weeksOffset = $weekId - date('W', $firstMon);
$beginDays = $weeksOffset * 7;
$endDays = ($weeksOffset * 7) + 6;
$searchedMon = strtotime(date('Y-m-d', $firstMon) . " +{$beginDays} days");
$searchedSun = strtotime(date('Y-m-d', $firstMon) . " +{$endDays} days");
echo date("M d", $searchedMon) . " - " . date("M d", $searchedSun);
}
Since, the getWeekNumbers function isn't returning the week numbers I'm expecting, it's not surprising that the output of the above function is
Dec 24 - Dec 30 (2012)
Jan 02 - Jan 08 (2012)
Jan 09 - Jan 15 (2012)
Jan 16 - Jan 22 (2012)
Jan 23 - Jan 29 (2012)
Note that the 1st line (Dec 24 - Dec 30) is the end of the current year (2012) and not the end of last year (2011).
Ideally, I want it to look like
Any ideas? Thanks!!
If you need all weeks for selected month, and all dates for selected week, then this is all you need:
function getWeekDays($month, $year)
{
$p = new DatePeriod(
DateTime::createFromFormat('!Y-n-d', "$year-$month-01"),
new DateInterval('P1D'),
DateTime::createFromFormat('!Y-n-d', "$year-$month-01")->add(new DateInterval('P1M'))
);
$datesByWeek = array();
foreach ($p as $d) {
$dateByWeek[ $d->format('W') ][] = $d;
}
return $dateByWeek;
}
getWeekDays() function returns multi dimension array. first key is week number. 2 level is array, that has dates saved as DateTime object.
Fetch example:
print_r( getWeekDays(5, 2011) ); # May 2011
print_r( getWeekDays(9, 2012) ); # Sep 2012
I had a little time extra, so I written an example ;-)
$datesByWeek = getWeekDays(8, 2012);
$o = '<table border="1">';
$o.= '<tr><th>Week</th><th>Monday</th><th>Tuesday</th><th>Wednesday</th><th>Thursday</th><th>Friday</th><th>Saturday</th><th>Sunday</th></tr>';
foreach ($datesByWeek as $week => $dates) {
$firstD = $dates[0];
$lastD = $dates[count($dates)-1];
$o.= "<tr>";
$o.= "<td>" . $firstD->format('M d') . ' - ' . $lastD->format('M d') . "</td>";
$N = $firstD->format('N');
for ($i = 1; $i < $N; $i++) {
$o.= "<td>-</td>";
}
foreach ($dates as $d) {
$o.= "<td>" . $d->format('d.') . " / 0.00</td>";
# for selected date do you magic
}
$N = $lastD->format('N');
for ($i = $N; $i < 7; $i++) {
$o.= "<td>-</td>";
}
$o.= "</tr>";
}
$o.= '</table>';
echo $o;
Output looks like:
The following assumes that the user can pick the month and year for which they wan to run the report (the value posted being 1-12 for month and YYYY for year). There may be a more elegant way to do it, but this seems to work for me. Also, at the top of your post, you say that you want the weeks to be Monday - Sunday. However, your example/screenshot at the bottom shows weeks being Sunday to Saturday. The below is for the originally-stated goal of Monday - Sunday.
$month = $_POST["month"];
$year = $_POST["year"];
$endDate = date("t", strtotime($year."-".$month."-01"));
$dayOfWeekOfFirstOfMonth = date("w", strtotime($year."-".$month."-01"));
$lastDayOfFirstWeek = 8 - $dayOfWeekOfFirstOfMonth;
$weeksArray = array(array("firstDay"=>1, "lastDay"=>$lastDayOfFirstWeek));
$loopDate = $lastDayOfFirstWeek + 1;
while($loopDate < $endDate)
{
$weeksArray[] = array("firstDay"=>$loopDate, "lastDay"=>($loopDate+6 > $endDate ? $endDate : $loopDate+6));
$loopDate+=7;
}
foreach($weeksArray as $week)
{
echo date("M d", strtotime($year."-".$month."-".$week["firstDay"])) . " - " . date("M d", strtotime($year."-".$month."-".$week["lastDay"])) . "\n";
}
this works perfect!!! phpfiddle here
<?php
// start and end must be timestamps !!!!
$start = 1346976000; // Thu 2012-09-06
$end = 1348704000; // Tue 2012-09-26
// generate the weeks
$weeks = generateweeks($start, $end);
// diaplay the weeks
echo 'From: '.fDate($start).'<br>';
foreach ($weeks as $week){
echo fDate($week['start']).' '.fDate($week['end']).'<br>';
}
echo 'To: '.fDate($end).'<br>';
/* outputs this:
From: Thu 2012-09-06
Thu 2012-09-06 Sun 2012-09-09
Mon 2012-09-10 Sun 2012-09-16
Mon 2012-09-17 Sun 2012-09-23
Mon 2012-09-24 Wed 2012-09-26
To: Wed 2012-09-26
*/
// $start and $end must be unix timestamps (any range)
// returns an array of arrays with 'start' and 'end' elements set
// for each week (or part of week) for the given interval
// return values are also in timestamps
function generateweeks($start,$end){
$ret = array();
$start = E2D($start);
$end = E2D($end);
$ns = nextSunday($start);
while(true){
if($ns>=$end) {insert($ret,$start,$end);return $ret;}
insert($ret,$start,$ns);
$start = $ns +1;
$ns+=7;
}
}
// helper function to append the array and convert back to unix timestamp
function insert(&$arr, $start, $end){$arr[] = array('start'=>D2E($start), 'end'=>D2E($end));}
// recives any date on CD format returns next Sunday on CD format
function nextSunday($Cdate){return $Cdate + 6 - $Cdate % 7;}
// recives any date on CD format returns previous Monday on CD format // finaly not used here
function prevMonday($Cdate){return $Cdate - $Cdate % 7;}
// recives timestamp returns CD
function E2D($what){return floor($what/86400)+2;} // floor may be optional in some circunstances
// recives CD returns timestamp
function D2E($what){return ($what-2)*86400;} // 24*60*60
// just format the timestamp for output, you can adapt it to your needs
function fDate($what){return date('D Y-m-d',$what);}
Related
I'm having trouble coming up with a solution for this and Google doesn't seem to be yielding anything (I'm also unsure what to search for) but basically, what I want is to get the last three 15-day periods at any given date.
A period being the 1st to the 15th of the month and 16th to the last day of the month.
Let's say today, we're on Sep 22, 2016. The date ranges I want to get are:
Sep 1 - Sep 15
Aug 16 - Aug 31
Aug 1 - Aug 15
Similarly if we were on Sep 12, 2016, the dates I'd want to get are:
Aug 16 - Aug 31
Aug 1 - Aug 15
Jul 16 - July 31
Right now I have some pseudo code that goes like this:
Get day (number)
check if greater than 15
Get last 15-day period date ranges
Continue to get 2 more date ranges before that
I guess what I'm having trouble with here is translating 3 and 4 into PHP code.
Can someone shed some light here?
We can simplify these requirements into two distinct things.
Define a given month into 2 periods. The first, being the 1st day of the month to the 15th day of the month. The second, being the 16th day of the month to the last day of the month.
Given a date, determine which are the last 3 periods subsequent the current period from that date, exclusive.
Step 1
The first part is easy.
$date = new DateTimeImmutable("Sep 22, 2016");
// Period 1 (from 1st day of month to 15th)
$p1Start = $date->modify("first day of this month");
$p1End = $p1Start->add(new DateInterval("P14D"));
// Period 2 (from 16th day of month to last day of month)
$p2Start = $p1End->add(new DateInterval("P1D"));
$p2End = $p2Start->modify("last day of this month");
echo "Period 1 Starts On: {$p1Start->format('M j, Y')}\n";
echo "Period 1 Ends On: {$p1End->format('M j, Y')}\n\n";
echo "Period 2 Starts On: {$p2Start->format('M j, Y')}\n";
echo "Period 2 Ends On: {$p2End->format('M j, Y')}\n";
Output you get from this is as expected:
Period 1 Starts On: Sep 1, 2016
Period 1 Ends On: Sep 15, 2016
Period 2 Starts On: Sep 16, 2016
Period 2 Ends On: Sep 30, 2016
Now you just need to put all of this in a function and reuse the same solution to get to step 2.
function getPeriods($dateString) {
$date = new DateTimeImmutable($dateString);
// Period 1 (from 1st day of month to 15th)
$p1Start = $date->modify("first day of this month");
$p1End = $p1Start->add(new DateInterval("P14D"));
// Period 2 (from 16th day of month to last day of month)
$p2Start = $p1End->add(new DateInterval("P1D"));
$p2End = $p2Start->modify("last day of this month");
return [
"period1" => ["start" => $p1Start, "end" => $p1End],
"period2" => ["start" => $p2Start, "end" => $p2End],
];
}
So now let's say you start with a date like Sep 12, 2016. You can figure out which period this date falls in by checking the return value from this function like so.
$date = "Sep 12, 2016";
$periods = getPeriods($date);
if ($date >= $periods["period1"]["start"] && $date <= $periods["period1"]["end"]) {
// It's in Period1
} else {
// It's in Period2
}
Step 2
Now let's just modify this function a little to simplify so that the solution can be expanded upon. So we'll name it getPeriod instead of getPeriods and have it only return one period from a given date.
function getPeriod($dateString) {
$periods = [];
$date = new DateTimeImmutable($dateString);
// Period 1 (from 1st day of month to 15th)
$p1Start = $date->modify("first day of this month");
$p1End = $p1Start->add(new DateInterval("P14D"));
// Period 2 (from 16th day of month to last day of month)
$p2Start = $p1End->add(new DateInterval("P1D"));
$p2End = $p2Start->modify("last day of this month");
// Figure out which period the given date belongs in
if ($date >= $p1Start && $date <= $p1End) {
$period = ["start" => $p1Start, "end" => $p1End];
} else {
$period = ["start" => $p2Start, "end" => $p2End];
}
return $period;
}
So to get the previous period from this date we just simply take the start date of the current period, returned by this function, subtract 1 day from that, and send the new date back to the function again to get the previous period.
Similarly, to get the next period just take the end date, add 1 day, and send that back to the function.
Final Result
Here's an example.
// We want up to 3 periods previous to this given date's period.
$date = "Sep 12, 2016";
$periods = [];
$currentPeriod = getPeriod($date);
for ($i = 0; $i < 3; $i++) {
$currentPeriod = $periods[] = getPeriod($currentPeriod["start"]->sub(new DateInterval("P1D"))->format("c"));
}
// Print out the period dates
$i = 1;
foreach($periods as $period) {
echo "Period $i: {$period['start']->format('M j, Y')} - {$period['end']->format('M j, Y')}\n";
$i++;
}
Output is what you'd expect...
Period 1: Aug 16, 2016 - Aug 31, 2016
Period 2: Aug 1, 2016 - Aug 15, 2016
Period 3: Jul 16, 2016 - Jul 31, 2016
function getDateRangesStartingFrom(\Carbon\Carbon $date, $numberOfMonths = 3)
{
$ranges = [];
for ($i = 0; $i < $numberOfMonths; $i++)
{
$month = [];
/**
* Generates the first 01-15 range.
*/
$rangeOne = \Carbon\Carbon::createFromFormat('Y-m-d', "{$date->year}-{$date->month}-01")->addMonths($i);
$rangeTwo = (clone $rangeOne)->addDays(14);
$month[] = [$rangeOne, $rangeTwo];
/**
* Generates the second 16-X (depending on the month) range.
*/
$rangeThree = (clone $rangeTwo)->addDays(1);
$rangeFour = (clone $rangeTwo)->addDays($rangeOne->daysInMonth - 15);
$month[] = [$rangeThree, $rangeFour];
/**
* We push all the ranges into the global array so we can return it later.
*/
$ranges[$rangeOne->format('F')] = $month;
}
return $ranges;
}
$ranges = getDateRangesStartingFrom(\Carbon\Carbon::createFromFormat('Y-m-d', '2016-09-22'));
var_dump($ranges);
This is how I would do it.
I use Carbon so that dealing with dates is much easier, go check it out!
I have created a function named getDateRangesStartingFrom(\Carbon\Carbon $date) that takes a parameter that represents from which date (or the month, essentially, since that's what we care about) the ranges should begin from to generate the ranges.
I then make a loop of $numberOfMonths iterations to begin the generation.
First, we proceed by creating the first part of the range, by using the year and month of the starting date. I then add the iterator index that represents what month we are in. Second, we create a second date that represents the second half of the first range (1-15).
After, we create the other two parts, by subtracting the days in the month by the days we've added before, so that we get the remaining days of the month.
We push all these ranges into a function scoped variable, and return the results.
Sherif's answer is good as well but take this as an alternative if you want to try Carbon.
Tried to find a solution written as simple code as possible.
I have some comments in code. I think that make clear how code is working.
Check the working code here
<?php
// use one of the two examples.
// $now = DateTime::createFromFormat('Y-m-d', '2016-09-12');
$now = new DateTime();
print_r(getPeriods($now));
function getPeriods(DateTime $now)
{
$currentDay = $now->format('d');
$dates = []; // Hold all date ranges
$start = clone $now;
$end = clone $now;
if ($currentDay > 15) { // For dates after 15th day of month
// Starting period is the first day of previous month
$start->modify('first day of previous month');
//Ending date is the 15th day of this month
$end->modify('first day of this month')->modify('14 days');
} else { // For dates before 15th day of month
//Starting period is the 16th day of the previous of previous month.
$start->modify('first day of previous month')
->modify('first day of previous month')
->modify('15 days');
//Ending period is the last day of previous month
$end->modify('last day of previous month');
}
$dates[] = $start;
$i = 2;
// $c will hold all intermediate dates of periods between
// $start and $end
$c = clone $start;
while ($c < $end) {
if ($c->format('d') > 15) {
// if we are in the 16th of the month,
// give me the last day of the month
$c->modify('last day of this month');
} else {
// give me the 15th day of month
$c->modify('14 days');
}
$dates[] = clone $c; // always clone $c so can not change other references
if ($i > 0) {
$c->modify('1 day');
$dates[] = clone $c;
}
$i--;
}
return array_chunk($dates, 2);
}
There is this Unix timestamp and it needs to generate the first days of the week as an array.
$time = '1456034400';
// This present Month February 2016
// in calendar the February has the start of the week
// Sunday 7
// Sunday 14
// Sunday 21
// Sunday 28
How do you get an array like this from the Unix Timestamp:
$weekdays = array(
0 => 7,
1 => 14,
2 => 21,
3 => 28
);
And this method needs to work and be accurate for any given month in years not just Feb 2016.
function getSundays($y, $m)
{
return new DatePeriod(
new DateTime("first Sunday of $y-$m"),
DateInterval::createFromDateString('next sunday'),
new DateTime("last day of $y-$m")
);
}
$days="";
foreach (getSundays(2016, 04) as $Sunday) {
$days[] = $Sunday->format("d");
}
var_dump($days);
https://3v4l.org/DVYM5
A bit faster way (since it uses a simple calculation to iterate weeks):
$time = 1456034400;
$firstDay = strtotime('first Sunday of '.date('M',$time).' '.date('Y',$time));
$lastDay = mktime(0,0,0,date('m',$time)+1,1,date('Y', $time));
$weekdays = array();
for ($i = $firstDay; $i < $lastDay; $i += 7*24*3600){
$weekdays[] = date('d',$i);
}
https://3v4l.org/lQkB2/perf#tabs
function getDates($year){
$dates = array();
for($i = 1; $i <= 366; $i++){
$month = date('m', mktime(0,0,0,1,$i,$year)); // outputs month 01 for jan etc
$wk = date('W', mktime(0,0,0,1,$i,$year)); // this outputs 01 if 1st week etc
$wkDay = date('D', mktime(0,0,0,1,$i,$year)); //weekday eg mon ,sun etc
echo $day = date('d', mktime(0,0,0,1,$i,$year)); // outputs day eg 01,13,23 etc
$dates[$month][$wk][$wkDay] = $day; // storing date in array
}
return $dates;
}
$dates = getDates(2014);
echo '<br/>'.$dates['01']['01']['Wed']; // getting 01
echo '<br/>'.$dates['01']['01']['Thu']; // 01 (should get 02 as it is 2nd jan thu in 2014)
echo '<br/>'.$dates['01']['01']['Fri']; // 03
when i echo $day in for loop i m receiving 01 02 03 04 etc which is correct but when i echo same in $dates array above i am receiving 01 for both 1st month 1st week wednesday and 1st month 1st week thursday. Why? Where Am i wrong? All other dates i am receiving for 2014 calender is correct.
#ymas is right. Because you are using 366 days in 2014, number 366 is the first day in 2015. This results that the first day of 2015 overwrite the value in your current array. Fix this by check the number of days in the year and use that value instead of the static 366. That will be something like this:
function getDates($year){
$dates = array();
$daysInYear = date("z", mktime(0,0,0,12,31,$year)) + 1;
for($i = 1; $i <= $daysInYear; $i++){
$month = date('m', mktime(0,0,0,1,$i,$year)); // outputs month 01 for jan etc
$wk = date('W', mktime(0,0,0,1,$i,$year)); // this outputs 01 if 1st week etc
$wkDay = date('D', mktime(0,0,0,1,$i,$year)); //weekday eg mon ,sun etc
echo $day = date('d', mktime(0,0,0,1,$i,$year)); // outputs day eg 01,13,23 etc
$dates[$month][$wk][$wkDay] = $day; // storing date in array
}
return $dates;
}
Note that you should not use 365 as static value because then 31 dec will not be available on leap year.
I would recommend using the DateTime object for this:
function newGetDates($year) {
$start = new DateTime("$year-01-01");
$end = new DateTime("$year-12-31 23:59");
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
$dates = array();
foreach($period as $day) {
$dates[$day->format('m')][$day->format('W')][$day->format('D')]
= $day->format('d');
}
return $dates;
}
You are generating same timestamp 4x in the loop, which is overkill. Store timestamp in a variable, and then call date() function with that variable: see how.
Even better, faster and less memory consumption solution would be to use DateTime, like this:
function getDates($year) {
$dt = new DateTime("$year-01-01");
while ($year == $dt->format('Y')) {
$dates[$dt->format('m')][$dt->format('W')][$dt->format('D')] = $dt->format('d');
$dt->modify('+1 day');
}
return $dates;
}
demo
In my script, I have a given end date. To get the start date, I subtract 23 months to the end date. Basically, what my script should do is to output 24 months (w/ year) - the last month/year to be printed should always be the specified end date.
For some reason, my script isn't returning my desired results. Given the $end = '2013-07-05', the script returns the result correctly. It prints out Aug 11 to Jul 13 which is correct.
But for some dates (e.g. $end = '2013-07-31'), the output is wrong. The result should be Sep 11 to Aug 13. But in this case, it outputs Aug 11 to Aug 13 which is absolutely wrong.
Here's my code:
<?php
$end = strtotime('2013-07-31 +1 month');
$date = strtotime('2013-07-31 -23 month');
$start = $month = $date;
$months = "";
while($month < $end)
{
$months .= date('M y', intval($month))." ";
$month = strtotime("+1 month", intval($month));
}
echo $months;
?>
I think there's something wrong with strtotime(). Thanks in advance.
You can't really use month calculations like that, especially when dealing with end-of-month values:
e.g. if it's July 31, what's -1 month to strtotime?
php > echo date('r', strtotime('2013-07-31 -1 month'));
Mon, 01 Jul 2013 00:00:00 -0600
A human would probably pick out June 30th, but strtotime isn't human. This DOES work for February 28th and generally any date where the day value is <= 28. Once you get into the 29,30,31 area, then you get these unexepected results
php > echo date('r', strtotime('2013-04-28 -1 month'));
Thu, 28 Mar 2013 00:00:00 -0600
How about
$endMonth = '8';
$year = '2013';
$i = 24;
while( $i > 0 ){
$month = ($endMonth - $i)%12;
if( $month == 0 ){
$year = $year - 1;
$month = 12;
}
$months .= date('M y', strtotime($year.'-'.$month.'-02'));
$i--;
}
Based on Marc B's answer I modified the script to deal with the 29,30,31 of each month. What I did was, if the date is 29, 30, or 31, it will be subtracted with 3 days so that the date will be either 28 or below and would work just fine with the current code that I have. It worked for me so I guess I'll just stick with this for now. Here's the updated code:
<?php
$dt = "2013-07-31";
$dy = strtotime($dt);
$day = date("d", $dy);
if (($day == 29) || ($day == 30) || ($day == 31)){
$dt = strtotime("$dt -3 days");
$dt = date('Y-m-d', $dt);
}
$end = strtotime("$dt +1 month");
$date = strtotime("$dt -23 month");
$start = $month = $date;
$months = "";
while($month < $end)
{
$months .= date('M y', intval($month))." ";
$month = strtotime("+1 month", intval($month));
}
echo $months;
?>
Thanks for your help and insights. :)
Given a Month and a weekday, I need to build a function that can retrieve the day number of all Mondays, Tuesdays, Wednesdays, Thursdays and Fridays.
Let's say I give the function this month, September 2012 and weekday number 1. The function should retrieve all the Mondays in September 2012 which are: 3, 10, 17 and 24
Please note that to me weekday number 1 is Monday, number 2 Tuesday, 3 is Wednesday, 4 Thursday and 5 Friday.
So far I've have done: getting the first day of the week given today's date (I post the function below). But I don't know how to follow from here in a simple way, I've been many hours on it and I suspect there's a better way to do it. Can you please show me how?
function getFirstDayOfWeek($date) {
$getdate = getdate($date);
// How many days ahead monday are we?
switch ( $getdate['wday'] ) {
case 0: // we are on sunday
$days = 6;
break;
default: // any other day
$days = $getdate['wday']-1;
break;
}
$seconds = $days*24*60*60;
$monday = date($getdate[0])-$seconds;
return $monday;
}
Thanks a ton
Not very smart, but would works for you:
// sept. 2012
$month = 9;
// loop through month days
for ($i = 1; $i <= 31; $i++) {
// given month timestamp
$timestamp = mktime(0, 0, 0, $month, $i, 2012);
// to be sure we have not gone to the next month
if (date("n", $timestamp) == $month) {
// current day in the loop
$day = date("N", $timestamp);
// if this is between 1 to 5, weekdays, 1 = Monday, 5 = Friday
if ($day == 1 OR $day <= 5) {
// write it down now
$days[$day][] = date("j", $timestamp);
}
}
}
// to see if it works :)
print_r($days);