I'm trying to write an algorithm which calculate the week (saturday to saturday) of interest based on a range of date.
For example I have this range:
2018-01-04 to 2018-01-13
In this case I have two weeks of interest, it is: "week 1" From 01 to 07 of January and "week 2" From 08 to 14 of the same January.
In this case the algorithm will respond to me that the week of interest is the "Week 2" because the number of days in that week is higher than the number of days in the "week 1".
How can I do this in Carbon?
Assuming you have the start and end date as Carbon objects $s and $e
make sure the difference is less than 14 days
calculate the overlaps:
$s->diffInDays($s->copy()->startOfWeek()) "offset" into first week
$e->copy()->endOfWeek()->diffInDays($e) "remainder" of last week
if $offset > $remainder select $e; else select $s
output $selectedWeek->copy()->startOfWeek() and $selectedWeek->copy()->endOfWeek()
Apparently, startOfWeek() and endOfWeek() alter the object; so make sure to copy() before using these methods!
Implementation:
$s = new Carbon\Carbon('2018-01-04');
$e = new Carbon\Carbon('2018-01-13');
$diff = $e->diffInDays($s);
if ($diff > 14) die("uh-oh!");
$offset = $s->diffInDays($s->copy()->startOfWeek());
$remainder = $e->copy()->endOfWeek()->diffInDays($e);
$selectedWeek = ($offset > $remainder ? $e : $s)->copy()->startOfWeek();
echo "selected week {$selectedWeek->weekOfYear} ({$selectedWeek} - {$selectedWeek->copy()->endOfWeek()})";
Output:
selected week 2 (2018-01-08 00:00:00 - 2018-01-14 23:59:59)
I guess you could do something like this. I haven't tested it but you can get some idea. Also I would like to point to the documentation https://carbon.nesbot.com/docs/
// Create dates
$date1 = Carbon::create(2018, 1, 4, 0, 0, 0);
$date2 = Carbon::create(2018, 1, 13, 0, 0, 0);
// Get The week numbers
$week_date_1 = $date1->weekOfYear;
$week_date_2 = $date2->weekOfYear;
// Count number of days for each week
$number_of_days_week_1 = $date1->diffInDays($date1->startOfWeek);
$number_of_days_week_2 = $date2->diffInDays($date2->startOfWeek);
// Highest number of days per week wins
if($number_of_days_week_1 > $number_of_days_week_2){
return $week_date_1;
} else {
return $week_date_2;
}
Related
Basically, I have a date set to today. From that date, I want to get the next and previous 20th day of the month. Basically, starting with 07/10/2020 (date of the post in d/m/Y format), I should end up with a date/timestamp to 20/09/2020 (previous 20th) and 20/10/2020 (next 20th).
I tried a few things and ended up with:
$ld_next_str = date("d/m/Y", strtotime("20 day this month"));
$ld_next_dt = DateTime::createFromFormat("d/m/Y", $ld_next_str);
$ld_prev_str = date("d/m/Y", strtotime("20 day last month"));
$ld_prev_dt = DateTime::createFromFormat("d/m/Y", $ld_prev_str);
But turns out this : "20 day this month" just adds 20 days to the current day so right now (on the seventh) it returns the 27th.
Knowing this, I figured I could probably just do something like subtracting the current day to that date (27 - 7 = 20) but I feel like there's probably a simpler way to do it, using strtotime without any extra steps.
Thanks in advance!
PS : I know you can get some days using "first" / "second" / "third" etc... but (I think) it doesn't go any higher than "twelfth", because I tried "twentieth" and that didn't work...
The formula for the previous 20th is:
mktime(0, 0, 0, $month - (1 - floor($day / 20)), 20, $year))
The formula for the next 20th is:
mktime(0, 0, 0, $month + floor($day / 20), 20, $year))
As a demo:
for ($month = 1; $month <= 12; $month++) {
for ($day = 1; $day <= 30; $day += 5) {
$prev = mktime(0, 0, 0, $month - (1 - floor($day / 20)), 20, 2020);
$next = mktime(0, 0, 0, $month + floor($day / 20), 20, 2020);
echo "$day/$month/2020: ", date('d/m/Y', $prev), ', ', date('d/m/Y', $next), PHP_EOL;
}
}
Outputs:
1/1/2020: 20/12/2019, 20/01/2020
6/1/2020: 20/12/2019, 20/01/2020
11/1/2020: 20/12/2019, 20/01/2020
16/1/2020: 20/12/2019, 20/01/2020
21/1/2020: 20/01/2020, 20/02/2020
26/1/2020: 20/01/2020, 20/02/2020
1/2/2020: 20/01/2020, 20/02/2020
6/2/2020: 20/01/2020, 20/02/2020
11/2/2020: 20/01/2020, 20/02/2020
16/2/2020: 20/01/2020, 20/02/2020
21/2/2020: 20/02/2020, 20/03/2020
...
Solution with an external class (a DateTime extension) that supports the cron syntax.
The string "0 0 20 * *" specifies the 20th of every month and every weekday at 00:00.
The nextCron() and previousCron() methods are used to determine the previous/next point in time based on a specific date.
$cronStr = "0 0 20 * *"; //every 20th 00:00
//source on https://github.com/jspit-de/dt
$next20 = dt::create('now')->nextCron($cronStr);
echo $next20; //2020-10-20 00:00:00
$prev20 = dt::create('now')->previousCron($cronStr);
echo $prev20; //2020-09-20 00:00:00
Outputs for the execution on October 8, 2020.
Here a better solution by using DateTime
$lastMonth = (new DateTime("last month"))->format("Y-m-20");
$thisMonth = (new DateTime("this month"))->format("Y-m-20");
$nextMonth = (new DateTime("next month"))->format("Y-m-20");
echo "$lastMonth - $thisMonth - $nextMonth";
In the Christian liturgical year, Advent starts things off four weeks before Christmas, and there are three cycles (Year A, B, and C).
In PHP, what would be the most efficient and elegant method to determine what cycle we would be in for any given year.
Here are the rules:
The beginning of the cycle occurs on whatever Sunday falls on or closest to Nov. 30
November 30, 2016 was the beginning of year A.
There are three iterations, being year A, year B, and year C, after which we return to year A.
So if given any year month and day, would it be possible to determine whether that date was in the cycle A, B, or C?
UPDATE: What I've tried
I'm not very good with math, so haven't had much luck in figuring this out.
I've been focusing first on the year and how it relates to A B and C.
So if I equate A to 1 and B to 2 and C to 3, I could get the first three years by subtracting 2015 from the current date - but I don't know what to do after 2018.
To calculate the year, I think this would work:
$year = date('Y');
$year_A = strtotime(date('Y', strtotime('-1 year', strtotime($year))));
if($year % 3 == 0) {
$liturgical_year = "C" ;
} else if ($year_A % 3 == 0) {
$liturgical_year = "A" ;
} else {
$liturgical_year = "B" ;
}
echo $liturgical_year;
Also, to calculate the first date of the Liturgical year you can use St Andrew's feast day and find the closest Sunday:
$St_Andrew = date("Y") . "-11-30";
$St_Andrew = date($St_Andrew);
$last_Sunday = date('Y-m-d', strtotime('last Sunday', strtotime($St_Andrew)));
$next_Sunday = date('Y-m-d', strtotime('next Sunday', strtotime($St_Andrew)));
$days_delta = abs(strtotime($St_Andrew) - strtotime($last_Sunday))/(60 * 60 * 24);
if ($days_delta <= 3) {
$advent = $last_Sunday;
} else {
$advent = $next_Sunday;
}
Thanks to #Qirel for pointing me in the right direction. I've never really used the modulus operator before!
So problem is solved as follows (though my coding might be sloppy / inefficient):
$cycle = array(1=>"A",2=>"B",3=>"C");
for($year=2016;$year<2120;$year++){
$date = strtotime("Nov 30, " . $year);
echo "Sunday closest to " . date("Y-m-d",$date) . " is ";
$date = new DateTime($year . "/11/30 -4 days");
$date->modify("next Sunday");
echo $date->format("Y-d-m");
echo " where we start Year " . $cycle[(($year % 3) + 1)] . "<br/>";
}
Here's what's going on:
$cycle is defined as an array, where numbers 1, 2, and 3 are associated with years A, B, and C.
I then start a for loop to work through the years 2016 to 2120, incrementing the years by 1 on each iteration.
I then create a date of XXXX Nov 30 minus 4 days, where XXXX is the year we're iterating through. The minus four days helps us to be sure we're considering the date closest to the 30th, so that when we modify it in the following line to "next Sunday" it will always be the one closest to the 30th.
We then use the array $cycle, divide the year by 3 and take the remainder (using the modulus operator) which gives us either 0, 1, or 2 as an answer. I add one to it so it's returning 1, 2, or 3.
That isn't really necessary if I assign 0=>A, 1=>B, and 2=>C but...
I wrote a function to return this information:
function returnLiturgicalYear($year){
$cycle = array(0=>"A",1=>"B",2=>"C");
$date = new DateTime($year . "/11/30 -4 days");
$date->modify("next Sunday");
return $cycle[($year % 3)];
}
Hopefully this helps someone else trying to determine the Christian liturgical year for any date.
I'm a newbie in both php and codeigniter.
I have a function which is used to label the x-axis of a chart of weekly sums of kilometers rode on a bicycle. It works (or is supposed to work) by counting back from today's date either zero Sundays or 1, 2, 3, etc as passed by an array.
I am using PHP Version 5.6.19, and this is my code:
In the Model:
public function week_start($date, $counter){
$tstring = strtotime($date);
$wknumber = ($counter + 1);
$start = strtotime("-$wknumber weeks sunday", $tstring);
return array(date('M d', $start));}
In the Controller:
$datenumbers = array(0, 1, 2, 3, 4, 5, 6, 7);
$dateResult = $this->bike_model->week_start(date("Y/m/d"), $datenumber);
In the view:
for ($i = 0; $i < 8; $i++){
echo "<text class = \"axis\" y = \"357\" x = \"";
echo 26 + ($i*70);
echo "\" dy = \".35em\">";
echo $dates_for_table[$i];}
I have had it up for few days and it has been showing Sunday July 3 as the first number on on my x-axis, as it should have. But today, it is Sunday and I expect it to now say Sunday July 10 as my most recent week. However, it is still showing last Sunday!
So my question is, is there a bug here or am I misunderstanding how '- one week Sunday' should work?
My goal is to have my function with zero as the argument return the most recent week start--either the current Sunday if it is Sunday or the past Sunday. I have found a workaround by adding an if else to the function checking if it is Sunday.
if ($tstring == strtotime('this Sunday')){
$wknumber = $counter;}
else{
$wknumber = ($counter + 1);}
However, this feels clunky and I'm looking for a more elegant way. I also want to understand what went wrong with "- one week Sunday" since I thought I had understood how it was working.
Thank you!
Here is how I did something similar using the object oriented DateTime style:
$startDate = new DateTime('NOW'); // can pass date string if not today
# reset start date to last Sunday or today if it is Sunday:
$startDate->sub(new DateInterval('P'.($startDate->format('w')).'D'));
# for debugging, output first Sunday:
echo $startDate->format('Y-m-d H:i:s') . " => full date and time (check for timezone issues) \n\n";
$weeks = 8;
for ($i = 0; $i < $weeks; $i++) {
# don't decrement first pass; sub 1 week on subsequent:
$startDate->sub(new DateInterval('P'. (($i) ? 1 : 0) .'W'));
echo $startDate->format('Y-m-d') . "\n";
};
You should be able to adapt for your purpose.
If you want to get fancy, you can also use DatePeriod instead of using sub within a loop:
$period = new DatePeriod(
$startDate,
DateInterval::createFromDateString('-1 week'),
7
);
foreach ($period as $date) {
echo $date->format('Y-m-d') . "\n";
}
How do I go about getting all the work days (mon-fri) in a given time period (let's say, today till the end of the next month) ?
If you're using PHP 5.2+ you can use the library I wrote in order to handle date recursion in PHP called When.
With the library, the code would be something like:
$r = new When();
$r->recur(<start date here>, 'weekly')
->until(<end date here>)
->wkst('SU')
->byday(array('MO', 'TU', 'WE', 'TH', 'FR'));
while($result = $r->next())
{
echo $result->format('c') . '<br />';
}
This sample does exactly what you need, in an quick and efficient way.
It doesn't do nested loops and uses the totally awesome DateTime object.
$oDateTime = new DateTime();
$oDayIncrease = new DateInterval("P1D");
$aWeekDays = array();
$sStart = $oDateTime->format("m-Y");
while($oDateTime->format("m-Y") == $sStart) {
$iDayInWeek = $oDateTime->format("w");
if ($iDayInWeek > 0 && $iDayInWeek < 6) {
$aWeekDays[] = clone $oDateTime;
}
$oDateTime->add($oDayIncrease);
}
Try it here: http://codepad.org/wuAyAqnF
To use it, simply pass a timestamp to get_weekdays. You'll get back an array of all the weekdays, as timestamps, for the rest of the current month. Optionally, you can pass a $to argument - you will get all weekdays between $from and $to.
function get_weekdays ($from, $to=false) {
if ($to == false)
$to = last_day_of_month($from);
$days = array();
for ($x = $from; $x < $to; $x+=86400 ) {
if (date('w', $x) > 0 && date('w', $x) < 6)
$days[] = $x;
}
return $days;
}
function last_day_of_month($ts=false) {
$m = date('m', $ts);
$y = date('y', $ts);
return mktime(23, 59, 59, ($m+1), 0, $y);
}
I suppose you could loop through the dates and check the day for each one, and increment a counter.
Can't think of anything else off the top of my head.
Pseudocode coming your way:
Calculate the number of days between now and the last day of the month
Get the current day of the week (i.e. Wednesday)
Based on the current day of the week, and the number of days left in the month, it's simple calculation to figure out how many weekend days are left in the month - it's going to be the number of days remaining in the month, minus the number of Sundays/Saturdays left in the month.
I would write a function, something like:
daysLeftInMonth(daysLeftInMonth, startingDayOfWeek, dayOfWeekToCalculate)
where:
daysLeftInMonth is last day of the month (30), minus the current date (15)
startingDayOfWeek is the day of the week you want to start on (for today it would be Wednesday)
dayOfWeekToCalculate is the day of the week you want to count, e.g. Saturday or Sunday. June 2011 currently has 2 Sunday, and 2 Saturdays left 'til the end of the month
So, your algorithm becomes something like:
getWeekdaysLeft(todaysDate)
...getWeekdaysLeft is something like:
sundaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Sunday");
saturdaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Saturday");
return ((lastDayOfMonth - todaysDate) - (sundaysLeft + saturdaysLeft));
This code does at least one part you ask for. Instead of "end of next month" it simply works with a given number of days.
$dfrom = time();
$fourweeks = 7 * 4;
for ($i = 0; $i < $fourweeks; $i ++) {
$stamp = $dfrom + ($i * 24 * 60 * 60);
$weekday = date("D", $stamp);
if (in_array($weekday, array("Mon", "Tue", "Wed", "Thu", "Fri"))) {
print date(DATE_RSS, $stamp) . "\n";
}
}
// Find today's day of the month (i.e. 15)
$today = intval(date('d'));
// Define the array that will hold the work days.
$work_days = array()
// Find this month's last day. (i.e. 30)
$last = intval(date('d', strtotime('last day of this month')));
// Loop through all of the days between today and the last day of the month (i.e. 15 through 30)
for ( $i = $today; $i <= $last; $i++ )
{
// Create a timestamp.
$timestamp = mktime(null, null, null, null, $i);
// If the day of the week is greater than Sunday (0) but less than Saturday (6), add the timestamp to an array.
if ( intval(date('w', $timestamp)) > 0 && intval(date('w', $timestamp)) < 6 )
$work_days[] = mktime($timestamp);
}
The $work_days array will contain timestamps which you could use this way:
echo date('Y-m-d', $work_days[0]);
The code above with work in PHP 4 as well as PHP 5. It does not rely on the functionality of the DateTime class which was not available until PHP 5.2 and does not require the use of "libraries" created by other people.
I need help to create a function to determine the week number based on these 2 parameters:
Starting date
Specified date
For example, if I specify April 7, 2010 as the starting date & passed April 20, 2010 as the date to lookup, I would like the function to return WEEK 2. Another example, if I set March 6, 2010 as starting date and looked up April 5, 2010 then it should return WEEK 6.
I appreciate your time and help.
=================
UPDATE
For instance:
Starting date: 3/6 - week1
Lookup date: 4/5
Week 2 starts from Mar 7-13.
Week 3 (3/14-3/20).
Week 4 (3/21-3/27). Week 5 (3/28-4/3).
So 4/5 falls under Week 6.
An idea is to use the Sunday of the starting date as the NEW* starting date. So instead of looking up 3/6, the function will use 2/28 as the starting date.
You can find the number of days between the two dates, divide that number by 7 and take the ceil of the result:
$start_date = strtotime("2010-04-07");
$specified_date = strtotime("2010-04-20");
$num_of_days = ($specified_date - $start_date)/(60*60*24);
$week_num = ceil($num_of_days/7);
echo $week_num; // prints 2.
What about?
Determine the number of days since the epoch of the start date
ditto, the specified date
subtract
divide by 7
Since I do not know how you are going to receive your dates, I would do something like:
$starting = mktime(parsedStartingDate);
$lookup = mktime(parsedLookupDate);
$result = $lookup - $starting;
$result = (((($result / 60) / 60) / 24) / 7);
return $result;
Since you're not being very clear this may or may not be what you're looking for:
// Example 1 (returns 2)
date('W', strtotime('April 20, 2010')) - date('W', strtotime('April 7, 2010'));
// Example 2 (returns 5)
date('W', strtotime('April 5, 2010')) - date('W', strtotime('March 6, 2010'));
Figured it out.
$start_date = strtotime("2010-03-06"); // returns 1267862400
$start_date_week = strtotime("last sunday",$start_date); // 02-28
$specified_date = strtotime("2010-04-10"); // returns 1270882800
$specified_date_week = strtotime("next sunday",$specified_date); // 4-11 looking up the Next Sunday was also the key!
$num_of_days = ($specified_date_week - $start_date_week)/(60*60*24); // 41.958
$week_num = ceil($num_of_days/7); //6
echo $week_num;