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);
}
Related
I want to generate a period based on the current date.
For Example:
Input: 2020-03-10 08:54:23
Output: 01 Apr 2020 - 31 Mar 2020
My way:
Part 1 (from):
get first day of next month
get next month
if actual month December, update also year. Otherwise the year remains the same
Part 2 (to):
get last day of next month of next year
get next month
update year
My Code:
function createLicenceDate($orderDate){
//Part 1: First part of period => 01 Apr 2020
//remove time (is not necessary)
$explodeOnlyDate = explode(" ", $orderDate);
//get date
$onlyDate = $explodeOnlyDate[0];
//seperate day, month and year
$explodeDate = explode("-", $onlyDate);
//First day of next month
$newDay = 01;
//check if actual date is december. If true = update also the year
if($explodeDate[1] != 12){
//update month
$newMonth = $explodeDate[1] + 1;
//year remains the same
$year = $explodeDate[0];
}else{
//new month is january
$newMonth = 1;
//update year
$year = $explodeDate[0] + 1;
}
//date as string
$fromString = $newDay.'-'.$newMonth.'-'.$year;
//convert string to date
$from = date('d M Y', strtotime($fromString));
//Part 2: Second part of period => 31 Mar 2020
//update year
$untilNewYear = $explodeDate[0] + 1;
//get last day of next month by getting amount of days of a month of a year
$untilNewDay = cal_days_in_month(CAL_GREGORIAN, $explodeDate[1], $untilNewYear);
//the month always remains unchanged
$untilNewMonth = $explodeDate[1];
//date as string
$untilString = $untilNewDay."-".$untilNewMonth."-".$untilNewYear;
//string as date
$until = date('d M Y', strtotime($untilString));
$licenseValidationDates [] = $from;
$licenseValidationDates [] = $until;
return $licenseValidationDates;
}
My code works. But I'm sure this is not the best/most efficient solution. I decided to go this way because i had many problems with alternatives and date functions of php. I especially had problems with leap years and February. The conversion from date to string and vice versa was another problem.
Can you suggest a "better" solution? Is my way too much trouble?
I have some strange behaviour happening when I run the following on the last day of May (31st). If I change my system time to 30th May, or the last day of say, June (30th), it functions normally.
For some reason though, on the 31st, it will skip the next month (June), and instead replace it with July. So it will output July twice. Here is an example:
31 days in 05, 31 days in 07, 31 days in 07, 31 days in 08,
Code which generated the above
<?php
$datepicker_month_range = 3;
// create an array of dates for a number of months specified by the user. 0 is used for this month
for ($month = 0; $month <= $datepicker_month_range; $month++) {
$dt_dates = new DateTime();
$dt_dates->add(new DateInterval("P{$month}M")); // example, P1M == plus 1 month
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $dt_dates->format('m'), $dt_dates->format('Y'));
echo $days_in_month." days in ".$dt_dates->format('m').", ";
for ($day = 1; $day <= $days_in_month; $day++) {
$date = $dt_dates->format('Y')."-".$dt_dates->format('m')."-".sprintf('%02d', $day); // leading zeros 05-..
$month_days[] = $date;
}
}
//print_r($month_days);
?>
Later on, if print_r($month_days) is run, the complete dates are outputted with July outputted twice like in the previous expression.
What is causing this behaviour?
Thanks.
Ok, after reading the comments, it seems this is a duplicate of PHP DateTime::modify adding and subtracting months
but here is how I got around the problem.
$month_beginning = $dt_dates->format('Y-m-01');
$dt_dates = new DateTime($month_beginning); // rollback the date to the first so we can increment months safely
All together
for ($month = 0; $month <= $datepicker_month_range; $month++) {
$dt_dates = new DateTime();
$month_beginning = $dt_dates->format('Y-m-01');
$dt_dates = new DateTime($month_beginning); // rollback the date to the first so we can increment months safely
$dt_dates->add(new DateInterval("P{$month}M")); // P1M == plus 1 month
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $dt_dates->format('m'), $dt_dates->format('Y'));
//echo $days_in_month." days in ".$dt_dates->format('m').", ";
for ($day = 1; $day <= $days_in_month; $day++) {
$date = $dt_dates->format('Y')."-".$dt_dates->format('m')."-".sprintf('%02d', $day); // leading zeros 05-..
$month_days[] = $date; // holds dates for datepicker month ranges
}
}
I'm working on a little payments system and must generate a list of payment days (monthly) given an initial date and number of payments. So, for example:
Given:
startday: 2015/06/22
qtty: 6
I should get the day from initial date (22) and generate a list of 6 sequential monthly dates:
2015/06/22 (if initial date should be included, and is > than today)
2015/07/22
2015/08/24
2015/09/22
2015/10/22
2015/11/23
As you can see, generated dates should not be weekends (sat/dom) and -if possible- nor holidays
Is there any function that could help me achieve this? TIA
I think this might do what you want, including holidays:
<?php
$startday = strtotime("2015/08/24");
$qtty = 5;
// Add as many holidays as desired.
$holidays = array();
$holidays[] = "4 Jul";
$holidays[] = "25 Dec";
for( $i = 0; $i < $qtty; $i++ ) {
$next_month = strtotime("+".$i." month", $startday); // Also works with "+ 30 days"
while( in_array(date("d M", $next_month), $holidays)) { // Is holiday
$next_month = strtotime("+ 1 day", $next_month);
if( date("N", $next_month) > 5 ) { // Is weekend
$next_month = strtotime("next Monday", $next_month); // or "previous Friday"
}
}
echo(date( "Y-m-d", $next_month) . '</br>');
}
?>
Will echo
2015-08-25
2015-09-25
2015-10-26 // Because of weekend
2015-11-25
2015-12-28 // Because of Christmas and weekend
And with a start date of 2015/10/31 the output will be:
2015-11-02 // Because of weekend
2015-12-01 // Because the 31st of Nov is the 1st of Dec
2015-12-31
2016-02-01 // Because the weekend
2016-03-02 // Because the 31st of Feb is the 2st of Mars (as 2016 is leep year)
As a good extra tips, depending on how you want to solve the 31st of Jan problem, if you want the last of each month, you can always use the following:
$first_of_the_month = strtotime(date("Y-m-1", $startday));
$next_month = strtotime("+ 1 month", $first_of_the_month);
$last_of_the_month = date("Y-m-t", $next_month);
echo($last_of_the_month); // 2015-09-30
How can I get the first week day in a given week number?
I'm making a function in PHP for a calendar app.
The idea: When I click on a link that basically uses strtotime with +1 month it only jumps to that same day of course. I need to get the week numbers correct.
Example: When I use the menu to move from August to September, it shouldn't select the same date I was in in August, but the first day of the first week number in September (Monday, 2th of September, week number 36).
And from September to October: Week number 40, Tuesday the 1th of October.
I found a function that exactly like you want it. This is setISODate
$date = new DateTime();
$date->setISODate(2013, 35, 1);
echo $date->format('Y-m-d');
You can change Y-m-d date format as you want
Output
2013-08-26 // This week number and monday
Usage
setISODate(year, week, day)
Try this code
<?php
$week = 3;
$year = 2009;
$timestamp = mktime( 0, 0, 0, 1, 1, $year ) + ( $week * 7 * 24 * 60 * 60 );
$timestamp_for_monday = $timestamp - 86400 * ( date( 'N', $timestamp ) - 1 );
$date_for_monday = date( 'Y-m-d', $timestamp_for_monday );
?>
Like every1 already said, ISO weeks start with monday. Quote from http://en.wikipedia.org/wiki/ISO_week_date : Weeks start with Monday.
If I understand your problem correctly, you need first-next-date in next month if selected week is in 2 different months?
function getFirstWeekDay($year, $week) {
$dt = (new DateTime)->setISODate($year, $week, 1);
$dtC = clone $dt;
for ($N = $dt->format('N'); $N < 7; $N++) {
$dtC->modify('+1 day');
if ($dt->format('m') !== $dtC->format('m')) {
return $dtC;
}
}
return $dt;
}
echo getFirstWeekDay(2013, 40)->format('W\t\h \w\e\e\k \i\s Y-m-d');
# 40th week is 2013-10-01
echo getFirstWeekDay(2013, 1)->format('W\t\h \w\e\e\k \i\s Y-m-d');
# 1th week is 2013-01-01
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);