I'm not sure how to go about this one. I'm building a calendar in PHP and need users to be able to add a repeating event that follows the following rule:
Last [DOW] of the month (so Last [Mon/Tues/Wed/etc] of the week)
I've got the rule itself stored, I'm just not sure how best to extrapolate the last Mon/Tue/Wed of the month in PHP? I fell like i'm making this more complicated than it needs to be.
Assuming you have variables for $month=4, $dow=3 and $year=2011 how would I best do this?
For everything date-related that you can express in proper English but have a hard time expressing using numbers, strtotime is your best friend.
echo strtotime("last Monday of June 2011");
This returns a timestamp that you can use as the second parameter to date and the likes to get a proper, human-readable date. Since it's a built-in function written in C, this solution is also much faster than almost anything else you could come up with written in PHP (though I'm quite sure it wouldn't matter much in a real-world scenario).
So assuming you have $month=4, $dow=3 and $year=2011, you'll need an array mapping $month values to their English textual representations and another array mapping $dow values to their textual representations.
Here's an alternative:
<?
function lastDayOfMonth($month, $year) {
switch ($month) {
case 2:
# if year is divisible by 4 and not divisible by 100
if (($year % 4 == 0) && ($year % 100) > 0)
return 29;
# or if year is divisible by 400
if ($year % 400 == 0)
return 29;
return 28;
case 4:
case 6:
case 9:
case 11:
return 30;
default:
return 31;
}
}
function lastDayOfWeek($month, $year, $dow) {
$d = new DateTime();
#Finding the last day of month
$d = $d->setDate($year, $month, lastDayOfMonth($month, $year));
#Getting the day of week of last day of month
$date_parts = getdate($d->getTimestamp());
$diff = 0;
#if we can't find the $dow in this week... (cause it would lie on next month)
if ($dow > $date_parts['wday']) {
# ...we go back a week.
$diff -= 7;
}
return $date_parts['mday'] + $diff + ($dow - $date_parts['wday']);
}
# checking the algorithm for this month...
for ($i=0; $i < 7; $i++) {
echo lastDayOfWeek(6,2011,$i) . "<br>";
}
?>
I have a generic function for you to calculate the nth day of a month. Hope this could help you to resolve your issue.
function get_Nth_dow($dow, $occurence, $m, $y)
{
$numdays = date('t', mktime(0, 0, 0, $m, 1, $y));
$add = 7 * ($occurence - 1);
$firstdow = date('w', mktime(0, 0, 0, $m, 1, $y));
$diff = $firstdow - $dow;
$day_of_month = 1;
if ($diff > 0)
{
$day_of_month += ($add - $diff);
}
elseif ($diff < $numdays)
{
$day_of_month -= ($diff - $add);
}
return $day_of_month;
}
$DOW = day of the week (0 = Sunday, 6 = Saturday).
$X = occurence (1 = first, 2 = third, etc..). If the given month does
not have the occurrence, then it will return the last. For example, if
you ask for the 7th occurrence of Friday, it will return the last
Friday of the month.
$M = month
$Y = year
Example, get_Nth_DOW(2,3,7,2009) will return the third Tuesday of 7th 2009.
Related
I've been using the following function fine until the other day when the clocks went forward:
function months($month_format="F") {
$array = array();
for ($i = 1; $i <=12; $i++) {
$array[$i]['string'] = date($month_format, mktime(0,0,0,$i));
$array[$i]['int'] = date('m', mktime(0,0,0,$i));
}
return $array;
}
It outputs an array with:
string[]
int[]
Since the other day (like 2 days ago, when the clocks went forward in the UK), the function seems to be showing me 2 march months and 0 Februarys....
Very very strange...
I'm using rethinkdb with their eachPosTime function... not sure if this is causing it.
I tried using a different function, but still using mktime:
function months(){
$start_month = 1;
$end_month = 12;
$start_year = date("Y");
$array = array();
for($m=$start_month; $m<=12; ++$m){
echo $m.'<br>';
$array[$m]['string'] = date('F', mktime(0,0,0,$m));
$array[$m]['int'] = date('m', mktime(0,0,0,$m));
if($start_month == 12 && $m==12 && $end_month < 12)
{
$m = 0;
$start_year = $start_year+1;
}
//echo date('F Y', mktime(0, 0, 0, $m, 1, $start_year)).'<br>';
if($m == $end_month) break;
}
return $array;
}
Still, I am having no luck.
Check the image here, which shows the output of the months() function:
Output of the months() function
This is not to do with the clocks changing, but the time of the month, and the entirely unhelpful signature of the mktime function.
When you leave out parameters from a "mktime" call, they are filled in with today's date. In your case, you specified month without day or year, so when you ask for mktime(0, 0, 0, 2); it will fill in today's day and year, and look for the 29th February 2021 - a day that doesn't exist. Since the time library "overflows" dates, this becomes the 1st March.
The solution is to pass an explicit day to "mktime", as in mktime(0,0,0,$m,1) or to use a less confusing function.
I am building multi-calendar, I have a horizontal looking interface:
I am trying to run the days of the week S,M,T,W,T,F,S
throughout the whole month instead of just the first 7 as in the picture.
the function which draw the calendar:
//our case "SUN"
if(AC_START_DAY=="sun"){
for($k=0; $k<7; $k++){
$weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li class="cal_weekday"> '.$weekday.'</li>';
}
}
//If we chose Monday as start week.
else{
if ($first_week_day == 0) $first_week_day =7;
for($k=1; $k<=7; $k++){
if($k==7) $weekday = mb_substr($lang["day_0"][0],0,1,'UTF-8');
else $weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li title="'.$lang["day_".$k.""].'"> '.$weekday.'</li>';
}
}
The lang file:
$lang["day_0"] = "Sunday";
$lang["day_1"] = "Monday";
$lang["day_2"] = "Tuesday";
$lang["day_3"] = "Wednesday";
$lang["day_4"] = "Thursday";
$lang["day_5"] = "Friday";
$lang["day_6"] = "Saturday";
Already defined
$month=sprintf("%02s",$month);
// define vars
$today_timestamp = mktime(0,0,0,date('m'),date('d'),date('Y')); # current timestamp - used to check if date is in past
$this_month = getDate(mktime(0, 0, 0, $month, 1, $year)); # convert month to timestamp
$first_week_day = $this_month["wday"]; # define first weekday (0-6)
$days_in_this_month = cal_days_in_month(CAL_GREGORIAN,$month,$year); # define number of days in week
$day_counter_tot = 0; # count total number of days showin INCLUDING previous and next months - use to get 6th row of dates
Looks like the $lang["day_".$k.""] is just counting the days from 0 to 6.. how can i make is loop untill the end of the month?
NOTE: I tried increasing the $k<7 just more empty blue boxes appear.
Use the loop to the 30/31 day.
And then change this line
$weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
to
$weekday = mb_substr($lang["day_".$k%7.""],0,1,'UTF-8');
This should give you the day 0 for every sunday.
0 % 7 = 0 (sunday)
1 % 7 = 1 (monday)
...
7 % 7 = 0 (sunday again)
8 % 7 = 1 (monday again)
You can use this code to generate all the days of the current month.
for ($date = strtotime(date('Y-m-01')); $date < strtotime(date('Y-m-t')); $date = strtotime("+1 day", $date)) {
echo date("l-d", $date)."<br>";
}
Will print all the days of the current month as follows.
Thursday-01
Friday-02
Saturday-03
Sunday-04
Monday-05
Tuesday-06
Wednesday-07
Thursday-08
Friday-09
Saturday-10
Sunday-11
Monday-12
Tuesday-13
Wednesday-14
Thursday-15
Friday-16
Saturday-17
Sunday-18
Monday-19
Tuesday-20
Wednesday-21
Thursday-22
Friday-23
Saturday-24
Sunday-25
Monday-26
Tuesday-27
Wednesday-28
Thursday-29
Friday-30
Looks like you almost got it right.
You only need to slightly modify your code to make it work the way you want it to.
You should just change your code to:
$number_of_days_in_the_future = 42; // Here you can put in the number of days for which you want to display the corresponding letter, and based on your screenshot that is 42
//our case "SUN"
if(AC_START_DAY=="sun"){
for($i=0; $i<$number_of_days_in_the_future; $i++){
$k = $i % 7;
$weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li class="cal_weekday"> '.$weekday.'</li>';
}
}
//If we chose Monday as start week.
else{
if ($first_week_day == 0) $first_week_day =7;
for($i=1; $i<=$number_of_days_in_the_future; $i++){
$k = $i % 7;
if($k==7) $weekday = mb_substr($lang["day_0"][0],0,1,'UTF-8');
else $weekday = mb_substr($lang["day_".$k.""],0,1,'UTF-8');
$list_day_titles.='<li title="'.$lang["day_".$k.""].'"> '.$weekday.'</li>';
}
}
Please note that I only tried to fix your code so it works as you expect it to.
There's probably a more elegant solution but I don't really know your full code so I would just be guessing if I tried to offer you another approach.
I hope this will help you.
Cheers
I have a calendar that I want to allow events to be repeated on a week day of the month. Some examples would be:
Repeat every 4th Tuesday of the month
Repeat every 2nd Friday of the month
And so on...
What I need is the ability to find out how many week days (for example Tuesday's) have passed this month so far.
I found some code that returns how many Monday's have passed.
$now=time() + 86400;
if (($dow = date('w', $now)) == 0) $dow = 7;
$begin = $now - (86400 * ($dow-1));
echo "Mondays: ".ceil(date('d', $begin) / 7)."<br/>";
This works well but how do I make it so that I can determine any week day? I cannot seem to get my head around the code to make this work.
strtotime is really useful for this kind of thing. Here are lists of the supported syntax. Using your example of repeat every 2nd Friday of the month I wrote the following simple snippet for you:
<?php
$noOfMonthsFromNow=12;
$dayCondition="Second Friday of";
$months = array();
$years = array();
$currentMonth = (int)date('m');
for($i = $currentMonth; $i < $currentMonth+$noOfMonthsFromNow; $i++) {
$months[] = date('F', mktime(0, 0, 0, $i, 1));
$years[] = date('Y', mktime(0, 0, 0, $i, 1));
}
for ($i=0;$i<count($months);$i++){
$d = date_create($dayCondition.' '.$months[$i].' '.$years[$i]);
if($d instanceof DateTime) echo $d->format('l F d Y H:i:s').'<br>';
}
?>
This can be tested at: http://www.phpfiddle.org/lite/
$beginningOfMonth = strtotime(date('Y-m-01')); // this will give you the timestamp of the beginning of the month
$numTuesdaysPassed = 0;
for ($i = 0; $i <= date('d'); $i ++) { // 'd' == current day of month might need to change to = from <= depending on your needs
if (date('w', $beginningOfMonth + 3600 * $i) == 2) $numTuesdaysPassed ++; // 3600 being seconds in a day, 2 being tuesday from the 'w' (sunday == 0)
}
Not sure if this will work, and there's probably a better way to do it; don't have the means to test it right now but hopefully this puts you on the right track! (I get tripped up on date math a bit too, especially with timezones)
I need to find date x such that it is n working days prior to date y.
I could use something like date("Y-m-d",$def_date." -5 days");, but in that case it wont take into consideration the weekend or off-date. Let's assume my working days would be Monday to Saturday, any idea how I can accomplish this?
Try this
<?php
function businessdays($begin, $end) {
$rbegin = is_string($begin) ? strtotime(strval($begin)) : $begin;
$rend = is_string($end) ? strtotime(strval($end)) : $end;
if ($rbegin < 0 || $rend < 0)
return 0;
$begin = workday($rbegin, TRUE);
$end = workday($rend, FALSE);
if ($end < $begin) {
$end = $begin;
$begin = $end;
}
$difftime = $end - $begin;
$diffdays = floor($difftime / (24 * 60 * 60)) + 1;
if ($diffdays < 7) {
$abegin = getdate($rbegin);
$aend = getdate($rend);
if ($diffdays == 1 && ($astart['wday'] == 0 || $astart['wday'] == 6) && ($aend['wday'] == 0 || $aend['wday'] == 6))
return 0;
$abegin = getdate($begin);
$aend = getdate($end);
$weekends = ($aend['wday'] < $abegin['wday']) ? 1 : 0;
} else
$weekends = floor($diffdays / 7);
return $diffdays - ($weekends * 2);
}
function workday($date, $begindate = TRUE) {
$adate = getdate($date);
$day = 24 * 60 * 60;
if ($adate['wday'] == 0) // Sunday
$date += $begindate ? $day : -($day * 2);
return $date;
}
$def_date="";//define your date here
$preDay='5 days';//no of previous days
date_sub($date, date_interval_create_from_date_string($preDay));
echo businessdays($date, $def_date); //date prior to another date
?>
Modified from PHP.net
Thanks for the help guys, but to solve this particular problem I wrote a simple code:
$sh_padding = 5; //No of working days to count backwards
$temp_sh_padding = 1; //A temporary holder
$end_stamp = strtotime(date("Y-m-d", strtotime($date_format)) . " -1 day"); //The date(timestamp) from which to count backwards
$start_stamp = $end_stamp; //start from same as end day
while($temp_sh_padding<$sh_padding)
{
$sh_day = date('w',$start_stamp);
if($sh_day==0){ //Skip if sunday
}
else
{
$temp_sh_padding++;
}
$start_stamp = strtotime(date("Y-m-d",$start_stamp)." -1 day");
}
$sh_st_dte = date("Y-m-d",$start_stamp); //The required start day
A quick bit of googling got me to this page, which includes a function for calculating the number of working days between two dates.
It should be fairly trivial to adjust that concept to suit your needs.
Your problem, however, is that the concept of "working days" being monday to friday is not universal. If your software is only ever being used in-house, then it's okay to make some assumptions, but if it's intended for use by third parties, then you can't assume that they'll have the same working week as you.
In addition, public holidays will throw a big spanner in the works, by removing arbitrary dates from various working weeks throughout the year.
If you want to cater for these, then the only sensible way of doing it is to store the dates of the year in a calendar (ie a big array), and mark them individually as working or non-working days. And if you're going to do that, then you may as well use the same mechanism for weekends too.
The down-side, of course, is that this would need to be kept up-to-date. But for weekends, at least, that would be trivial (loop through the calendar in advance and mark weekend days where date('w')==0 or date('w')==6).
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.