tags:

views:

1484

answers:

7

I'm trying to write a calendar function like this

function get_date($month, $year, $week, $day, $direction)
{
    ....
}

$week is a an integer (1, 2, 3...), $day is a day (Sun, Mon, ...) or number, whichever is easier. The direction is a little confusing, because it does a different calculation.

For an example, let's call

get_date(5, 2009, 1, 'Sun', 'forward');

It uses the default, and gets the first Sunday in May ie 2009-05-03. If we call

get_date(5, 2009, 2, 'Sun', 'backward');

, it returns the second last Sunday in May ie 2009-05-24.

+2  A: 

You can use mktime to retrieve the unix timestamp of the first day in the month:

$firstOfMonth = mktime(0, 0, 0, $month, 1, $year);

When you have the date of the first day of a certain month it's easy to retrieve the weekday for that date using date:

$weekday = date("N", $firstOfMonth);

From there it's rather easy to just step forward to get the date you're after.

Emil H
+8  A: 

The language-agnostic version:

To get the first particular day of the month, start with the first day of the month: yyyy-mm-01. Use whatever function is available to give a number corresponding to the day of the week. Subtract that number from the day you are looking for; for example, if the first day of the month is Wednesday (2) and you're looking for Friday (4), subtract 2 from 4, leaving 2. If the answer is negative, add 7. Finally add that to the first of the month; for my example, the first Friday would be the 3rd.

To get the last Friday of the month, find the first Friday of the next month and subtract 7 days.

Mark Ransom
A: 

Just find out what the first and last day of the month in question is (i.e. May 1, 2009 is a Friday and May 31, 2009 is a Sunday) I believe most PHP functions use Monday=0, Sunday=6, thus Friday=4, so you know that Sunday (6) - Friday (4) = 2, then 31-2 = 29, meaning the last friday of this month is on the 29th. For the first Friday, if the number is negative, add 7, if the number is 0, the month starts on Friday.

Shadow
+5  A: 

Perhaps it can be made quicker...
This was VERY interesting to code.
Please note that $direction is 1 for forward and -1 for backward to ease things up :)

function get_date($month, $year, $week, $day, $direction) {
  if($direction > 0)
    $startday = 1;
  else
    $startday = date('t', mktime(0, 0, 0, $month, 1, $year));

  $start = mktime(0, 0, 0, $month, $startday, $year);
  $weekday = date('N', $start);

  if($direction * $day >= $direction * $weekday)
    $offset = -$direction * 7;
  else
    $offset = 0;

  $offset += $direction * ($week * 7) + ($day - $weekday);
  return mktime(0, 0, 0, $month, $startday + $offset, $year);
}

I've tested it with a few examples and seems to work always, be sure to double-check it though ;)

MartinodF
Oh and also note that Monday = 1, Sunday = 7
MartinodF
+4  A: 

strtotime() can help you. e.g.

<?php
$tsFirst = strtotime('2009-04-00 next friday');
$tsLast = strtotime('2009-05-01 last friday');
echo date(DATE_RFC850, $tsFirst), " | ", date(DATE_RFC850, $tsLast);
prints
Friday, 03-Apr-09 00:00:00 CEST | Friday, 24-Apr-09 00:00:00 CEST

VolkerK
I already checked, it doesn't support "second last" or similar, and it's way slower.
MartinodF
+2  A: 

No need for calculations or loops - this is very easy to do with PHPs date functions:

Find the the Nth or Last occurrence of a particular day in a month as follows:

/////////////////////////////////////////////////////////////////
// Setup
/////////////////////////////////////////////////////////////////

// Specify the month of which we are interested.
// You can use any timestamp inside that month.  
// I'm using the current time as an example, so it will use the current month.
$DateTS = time();

// The day of interest, ie: Friday.  
// It can be 0=Sunday through 6=Saturday (Like 'w' from date()).
// I'll use Monday for example.
$Day = 1;

// The occurance of this day in which we are interested.  
// It can be 1, 2, 3, 4 for the first, second, third, and fourth occurence of the day in question in the month in question.
// You can also use -1 to fine the LAST occurence.  That will return the fifth occurrence if there is one, else the 4th.
// I will use the 2nd occurrence as example.
$Ord = 2;

////////////////////////////////////////////////////////////////
// We now have all the specific values we need.
// The example values above specify the 2nd Monday in the current month
////////////////////////////////////////////////////////////////

// We need the day name that corresponds with our day number to pass to strtotime().
// We could just specify the string in the first place, but for date calcs, you are more likely to have the day number than the string itself.
$Names = array( 0=>"Sun", 1=>"Mon", 2=>"Tue", 3=>"Wed", 4=>"Thu", 5=>"Fri", 6=>"Sat" );

// Calculate the the first of the month in question, relative to $DateTS.  It will be the timestamp of midnight (00:00).
$ThisMonthTS = strtotime( date("Y-m-01", $DateTS ) );

// Calculate the first of the FOLLOWING month, relative to $DateTS.  Also timestamp of midnight.
// This will be used if we specify -1 for last occurrence.
$NextMonthTS = strtotime( date("Y-m-01", strtotime("next month", $DateTS) ) );

// Now we just format the values a bit and pass them to strtotime().
// To find the 1,2,3,4th occurrence, we work from the first of the month forward.
// For the last (-1) occurence,work we work back from the first of the following month.
$DateOfInterest = (-1 == $Ord) ?
    strtotime( "last ".$Names[$Day], $NextMonthTS ) : // The last occurrence of the day in this month.  Calculated as "last dayname" from the first of next month, which will be the last one in this month. 
    strtotime( $Names[$Day]." + ".($Ord-1)." weeks", $ThisMonthTS ); // From the first of this month, move to "next dayname" which will be the first occurrence, and then move ahead a week for as many additional occurrences as you need.


// You can view the result with this code.  Just change $DateTS, $Day, and $Ord to your desired values and run it.
echo( "Month: ".date("Y-m", $DateTS)."<br />");
echo( "Ord: ".$Ord."<br />");
echo( "Day: ".$Names[$Day]."<br /><br />");
echo("Date of Interest: ".date("l, F jS, Y", $DateOfInterest));
Eli
A: 
function get_date($month, $year, $week, $day) {
    # $month, $year: current month to search in
    # $week: 0=1st, 1=2nd, 2=3rd, 3=4th, -1=last
    # $day:  0=mon, 1=tue, ..., 6=sun

    $startday=1; $delta=0;
    if ($week < 0) {
        $startday = date('t', mktime(0, 0, 0, $month, 1, $year)); # 28..31
        $delta=1;
    }
    $start  = mktime(0, 0, 0, $month, $startday, $year);
    $dstart = date('w', $start)-1; # last of the month falls on 0=mon,6=sun
    $offset=$day-$dstart; if ($offset<$delta){$offset+=7;}
    $newday=$startday+$offset+($week*7);
    return mktime(0, 0, 0, $month, $newday, $year);
}

This works for me, and based on the language-agnostic version :-) Only too bad, I needed to do that delta-thing (for if the last day of the month is the wanted week-day, we do not need to subtract 7)

Steven de Brouwer