19

I have an array of random dates (not coming from MySQL). I need to group them by the week as Week1, Week2, and so on upto Week5.

What I have is this:

$dates = array('2015-09-01','2015-09-05','2015-09-06','2015-09-15','2015-09-17');

What I need is a function to get the week number of the month by providing the date.

I know that I can get the weeknumber by doing date('W',strtotime('2015-09-01')); but this week number is the number between year (1-52) but I need the week number of the month only, e.g. in Sep 2015 there are 5 weeks:

  • Week1 = 1st to 5th
  • Week2 = 6th to 12th
  • Week3 = 13th to 19th
  • Week4 = 20th to 26th
  • Week5 = 27th to 30th

I should be able to get the week Week1 by just providing the date e.g.

$weekNumber = getWeekNumber('2015-09-01') //output 1;
$weekNumber = getWeekNumber('2015-09-17') //output 3;
Anders
  • 8,307
  • 9
  • 56
  • 88
Asif Hussain
  • 341
  • 1
  • 2
  • 11

19 Answers19

39

I think this relationship should be true and come in handy:

Week of the month = Week of the year - Week of the year of first day of month + 1

We also need to make sure that "overlapping" weeks from the previous year are handeled correctly - if January 1st is in week 52 or 53, it should be counted as week 0. In a similar fashion, if a day in December is in the first week of the next year, it should be counted as 53. (Previous versions of this answer failed to do this properly.)

<?php

function weekOfMonth($date) {
    //Get the first day of the month.
    $firstOfMonth = strtotime(date("Y-m-01", $date));
    //Apply above formula.
    return weekOfYear($date) - weekOfYear($firstOfMonth) + 1;
}

function weekOfYear($date) {
    $weekOfYear = intval(date("W", $date));
    if (date('n', $date) == "1" && $weekOfYear > 51) {
        // It's the last week of the previos year.
        return 0;
    }
    else if (date('n', $date) == "12" && $weekOfYear == 1) {
        // It's the first week of the next year.
        return 53;
    }
    else {
        // It's a "normal" week.
        return $weekOfYear;
    }
}

// A few test cases.
echo weekOfMonth(strtotime("2020-04-12")) . " "; // 2
echo weekOfMonth(strtotime("2020-12-31")) . " "; // 5
echo weekOfMonth(strtotime("2020-01-02")) . " "; // 1
echo weekOfMonth(strtotime("2021-01-28")) . " "; // 5
echo weekOfMonth(strtotime("2018-12-31")) . " "; // 6

To get weeks that starts with sunday, simply replace date("W", ...) with strftime("%U", ...).

Anders
  • 8,307
  • 9
  • 56
  • 88
  • this is producing a PHP notice: A non well formed numeric value encountered. This however doesn't happen when using strtotime($date) – Daniel Resch Jul 17 '20 at 14:31
  • It doesn't work great when it's January, and January 1st falls in the precedent year. – CiprianIonescu Jan 18 '21 at 11:04
  • @CiprianIonescu Thank you for pointing this out. Indeed, e.g. 2021-01-18 gives the wrong answer. I have added a notice and will try to update the answer with a fix later. Again, thank you! – Anders Jan 18 '21 at 15:45
  • @CiprianIonescu I have now fixed the issue you pointed out. – Anders Jan 25 '21 at 09:23
  • Returns incorrect results for data at the end of a few years: Error at 2018-12-31 No= -46 expected:6 Error at 2019-12-30 No= -46 expected:6 Error at 2019-12-31 No= -46 expected:6 Error at 2024-12-30 No= -46 expected:6 Error at 2024-12-31 No= -46 expected:6 Error at 2025-12-29 No= -47 expected:5 Error at 2025-12-30 No= -47 expected:5 Error at 2025-12-31 No= -47 expected:5 – jspit Jul 07 '21 at 13:20
  • @jspit Thanks for pointing out the error. I have updated the code, hopefully it should solve the issue. At least your test cases pass. Thanks again for bringing this to my attention. – Anders Jul 11 '21 at 20:48
  • 1
    I think this works now. Thanks for the correction. I get now the same results for all days from 1970 to 2038 as for my function. – jspit Jul 13 '21 at 16:27
  • @jspit Great! I have probably caused some copy pasters to introduce strange bugs in their code. You have put a stop to that, and for that I am grateful. :-) – Anders Jul 13 '21 at 20:53
12

You can use the function below, fully commented:

/**
 * Returns the number of week in a month for the specified date.
 *
 * @param string $date
 * @return int
 */
function weekOfMonth($date) {
    // estract date parts
    list($y, $m, $d) = explode('-', date('Y-m-d', strtotime($date)));
    
    // current week, min 1
    $w = 1;
    
    // for each day since the start of the month
    for ($i = 1; $i < $d; ++$i) {
        // if that day was a sunday and is not the first day of month
        if ($i > 1 && date('w', strtotime("$y-$m-$i")) == 0) {
            // increment current week
            ++$w;
        }
    }
    
    // now return
    return $w;
}
Matteo Tassinari
  • 18,121
  • 8
  • 60
  • 81
  • this is also not giving the correct result.. '2015-09-06' should be the second week not first. – Asif Hussain Sep 17 '15 at 07:25
  • 1
    i think change `$i = 1; $i <= $d; ++$i` to `$i = 1; $i < $d; ++$i` to get perfect result.. – Jazuly Apr 20 '20 at 11:06
  • Why would you suggest so? Is there a specific case which fails? – Matteo Tassinari Apr 20 '20 at 11:39
  • I think the suggestion of @Asif is right I was using above formula and I get case where it throws problem it shows week number more than 6 which is never the case for few dates 2021-10-31 , 2022-01-31, max week number is 6 . – swapnil akolkar Jul 18 '21 at 06:19
11

The corect way is

function weekOfMonth($date) {
    $firstOfMonth = date("Y-m-01", strtotime($date));
    return intval(date("W", strtotime($date))) - intval(date("W", strtotime($firstOfMonth)));
}
Goendg
  • 132
  • 2
  • 8
4

I have created this function on my own, which seems to work correctly. In case somebody else have a better way of doing this, please share.. Here is what I have done.

function weekOfMonth($qDate) {
    $dt = strtotime($qDate);
    $day  = date('j',$dt);
    $month = date('m',$dt);
    $year = date('Y',$dt);
    $totalDays = date('t',$dt);
    $weekCnt = 1;
    $retWeek = 0;
    for($i=1;$i<=$totalDays;$i++) {
        $curDay = date("N", mktime(0,0,0,$month,$i,$year));
        if($curDay==7) {
            if($i==$day) {
                $retWeek = $weekCnt+1;
            }
            $weekCnt++;
        } else {
            if($i==$day) {
                $retWeek = $weekCnt;
            }
        }
    }
    return $retWeek;
}


echo weekOfMonth('2015-09-08') // gives me 2;
Asif Hussain
  • 341
  • 1
  • 2
  • 11
3
function getWeekOfMonth(DateTime $date) {
    $firstDayOfMonth = new DateTime($date->format('Y-m-1'));

    return ceil(($firstDayOfMonth->format('N') + $date->format('j') - 1) / 7);
}

Goendg solution does not work for 2016-10-31.

Community
  • 1
  • 1
j4r3k
  • 156
  • 5
2
function weekOfMonth($strDate) {
  $dateArray = explode("-", $strDate);
  $date = new DateTime();
  $date->setDate($dateArray[0], $dateArray[1], $dateArray[2]);
  return floor((date_format($date, 'j') - 1) / 7) + 1;  
}

weekOfMonth ('2015-09-17') // returns 3

Marlon Ingal
  • 71
  • 1
  • 2
  • 3
    Welcome to Stack Overflow! Please don't just throw your source code here. Be nice and try to give a nice description to your answer, so that others will like it and upvote it. See: [How do I write a good answer?](https://stackoverflow.com/help/how-to-answer) – sɐunıɔןɐqɐp Jun 07 '18 at 07:22
1

Given the time_t wday (0=Sunday through 6=Saturday) of the first of the month in firstWday, this returns the (Sunday-based) week number within the month:

weekOfMonth = floor((dayOfMonth + firstWday - 1)/7) + 1 

Translated into PHP:

function weekOfMonth($dateString) {
  list($year, $month, $mday) = explode("-", $dateString);
  $firstWday = date("w",strtotime("$year-$month-1"));
  return floor(($mday + $firstWday - 1)/7) + 1;
}
Mark Reed
  • 91,912
  • 16
  • 138
  • 175
1

You can also use this simple formula for finding week of the month

$currentWeek = ceil((date("d",strtotime($today_date)) - date("w",strtotime($today_date)) - 1) / 7) + 1;

ALGORITHM :

Date = '2018-08-08' => Y-m-d

  1. Find out day of the month eg. 08
  2. Find out Numeric representation of the day of the week minus 1 (number of days in week) eg. (3-1)
  3. Take difference and store in result
  4. Subtract 1 from result
  5. Divide it by 7 to result and ceil the value of result
  6. Add 1 to result eg. ceil(( 08 - 3 ) - 1 ) / 7) + 1 = 2
Ajay Tambe
  • 11
  • 3
1

My function. The main idea: we would count amount of weeks passed from the month's first date to current. And the current week number would be the next one. Works on rule: "Week starts from monday" (for sunday-based type we need to transform the increasing algorithm)

function GetWeekNumberOfMonth ($date){
    echo $date -> format('d.m.Y');
    //define current year, month and day in numeric
    $_year = $date -> format('Y');
    $_month = $date -> format('n');
    $_day = $date -> format('j');
    $_week = 0; //count of weeks passed
    for ($i = 1; $i < $_day; $i++){
        echo "\n\n-->";
        $_newDate = mktime(0,0,1, $_month, $i, $_year);
        echo "\n";
        echo date("d.m.Y", $_newDate);
        echo "-->";
        echo date("N", $_newDate);
        //on sunday increasing weeks passed count
        if (date("N", $_newDate) == 7){
            echo "New week";
            $_week += 1;
        }

    }
    return $_week + 1; // as we are counting only passed weeks the current one would be on one higher
}

$date = new DateTime("2019-04-08");
echo "\n\nResult: ". GetWeekNumberOfMonth($date);
1
$month = 6;
$year = 2021;           
$week = date("W", strtotime($year . "-" . $month ."-01"));

$str='';
$str .= date("d-m-Y", strtotime($year . "-" . $month ."-01")) ."to";
$unix = strtotime($year."W".$week ."+1 week");
while(date("m", $unix) == $month){
 $str .= date("d-m-Y", $unix-86400) . "|";
 $str .= date("d-m-Y", $unix) ."to"; 
 $unix = $unix + (86400*7);
}
$str .= date("d-m-Y", strtotime("last day of ".$year . "-" . $month));

$weeks_ar = explode('|',$str);
echo '<pre>'; print_r($weeks_ar);

working fine.

1
    // Current week of the month starts with Sunday
 
    $first_day_of_the_week = 'Sunday';
    $start_of_the_week1    = strtotime("Last $first_day_of_the_week");     
    
    if (strtolower(date('l')) === strtolower($first_day_of_the_week)) {
         $start_of_the_week1 = strtotime('today');
     }
     $end_of_the_week1   = $start_of_the_week1 + (60 * 60 * 24 * 7) - 1;

// Get the date format
print date('Y-m-d', $start_of_the_week1) . ' 00:00:00';
print date('Y-m-d', $end_of_the_week1) . ' 23:59:59';
Balaji
  • 37
  • 1
  • 7
0
// self::DAYS_IN_WEEK = 7;
function getWeeksNumberOfMonth(): int
{
    $currentDate            = new \DateTime();
    $dayNumberInMonth       = (int) $currentDate->format('j');
    $dayNumberInWeek        = (int) $currentDate->format('N');
    $dayNumberToLastSunday  = $dayNumberInMonth - $dayNumberInWeek;
    $daysCountInFirstWeek   = $dayNumberToLastSunday % self::DAYS_IN_WEEK;
    $weeksCountToLastSunday = ($dayNumberToLastSunday - $daysCountInFirstWeek) / self::DAYS_IN_WEEK;

    $weeks = [];
    array_push($weeks, $daysCountInFirstWeek);
    for ($i = 0; $i < $weeksCountToLastSunday; $i++) {
        array_push($weeks, self::DAYS_IN_WEEK);
    }
    array_push($weeks, $dayNumberInWeek);

    if (array_sum($weeks) !== $dayNumberInMonth) {
        throw new Exception('Logic is not valid');
    }

    return count($weeks);
}

Short variant:

(int) (new \DateTime())->format('W') - (int) (new \DateTime('first day of this month'))->format('W') + 1;
Lionser
  • 1
  • 1
0

There is a many solutions but here is one my solution that working well in the most cases.

function current_week ($date = NULL) {
    if($date) {
        if(is_numeric($date) && ctype_digit($date) && strtotime(date('Y-m-d H:i:s',$date)) === (int)$date)
            $unix_timestamp = $date;
        else
            $unix_timestamp = strtotime($date);
    } else $unix_timestamp = time();

    return (ceil((date('d', $unix_timestamp) - date('w', $unix_timestamp) - 1) / 7) + 1);
}

It accept unix timestamp, normal date or return current week from the time() if you not pass any value.

Enjoy!

Ivijan Stefan Stipić
  • 6,249
  • 6
  • 45
  • 78
0

I know this an old post but i have an idea!

$datetime0 = date_create("1970-01-01");
$datetime1 = date_create(date("Y-m-d",mktime(0,0,0,$m,"01",$Y)));
$datetime2 = date_create(date("Y-m-d",mktime(0,0,0,$m,$d,$Y)));

$interval1 = date_diff($datetime0, $datetime1);
$daysdiff1= $interval1->format('%a');

$interval2 = date_diff($datetime0, $datetime2);
$daysdiff2= $interval2->format('%a');

$week1=round($daysdiff1/7);
$week2=round($daysdiff2/7);

$WeekOfMonth=$week2-$week1+1;
0
$date = new DateTime('first Monday of this month');
$thisMonth = $date->format('m');
$mondays_arr = [];

// Get all the Mondays in the current month and store in array
while ($date->format('m') === $thisMonth) {
    //echo $date->format('Y-m-d'), "\n";
    $mondays_arr[] = $date->format('d');
    $date->modify('next Monday');
}

// Get the day of the week (1-7 from monday to sunday)
$day_of_week = date('N') - 1;

// Get the day of month (1 to 31) 
$current_week_monday_date = date('j') - $day_of_week;

/*$day_of_week = date('N',mktime(0, 0, 0, 2, 11, 2020)) - 1;
$current_week_monday_date = date('j',mktime(0, 0, 0, 2, 11, 2020)) - $day_of_week;*/

$week_no = array_search($current_week_monday_date,$mondays_arr) + 1;
echo "Week No: ". $week_no;
danvin
  • 1
0

How about this function making use of PHP's relative dates? This function assumes the week ends on Saturday. But this can be changed easily.

function get_weekNumMonth($date) {
    $CI = &get_instance();
    $strtotimedate = strtotime($date);
    $firstweekEnd = date('j', strtotime("FIRST SATURDAY OF " . date("F", $strtotimedate) . " " . date("Y", $strtotimedate)));
    $cutoff = date('j', strtotime($date));
    $weekcount = 1;
    while ($cutoff > $firstweekEnd) {
        $weekcount++;
        $firstweekEnd += 7; // move to next week
    }
    return $weekcount;
}
Chin. Udara
  • 694
  • 4
  • 19
0

This function returns the integer week number of the current month. Weeks always start on Monday and counting always starts with 1.

function weekOfmonth(DateTime $date)
{
  $dayFirstMonday = date_create('first monday of '.$date->format('F Y'))->format('j');
  return (int)(($date->format('j') - $dayFirstMonday +7)/7) + ($dayFirstMonday == 1 ? 0 : 1);
}

Example of use

echo weekOfmonth(new DateTime("2020-04-12"));  //2

A test for all days from 1900-2038 with the accepted solution from @Anders as a reference:

//reference functions
//integer $date (Timestamp) 
function weekOfMonthAnders($date) {
    //Get the first day of the month.
    $firstOfMonth = strtotime(date("Y-m-01", $date));
    //Apply above formula.
    return weekOfYear($date) - weekOfYear($firstOfMonth) + 1;
}

function weekOfYear($date) {
    $weekOfYear = intval(date("W", $date));
    if (date('n', $date) == "1" && $weekOfYear > 51) {
        // It's the last week of the previos year.
        return 0;
    }
    else if (date('n', $date) == "12" && $weekOfYear == 1) {
        // It's the first week of the next year.
        return 53;
    }
    else {
        // It's a "normal" week.
        return $weekOfYear;
    }
}

//this function
function weekOfmonth(DateTime $date)
{
  $dayFirstMonday = date_create('first monday of '.$date->format('F Y'))->format('j');
  return (int)(($date->format('j') - $dayFirstMonday +7)/7) + ($dayFirstMonday == 1 ? 0 : 1);
}

$dt = date_create('1900-01-01');
$end = date_create('2038-01-02');
$countOk = 0;
$countError = 0;
for(;$dt < $end; $dt->modify('+1 Day')){
    $ts = $dt->getTimestamp();
    if(weekOfmonth($dt) === weekOfMonthAnders($ts)){
       ++$countOk; 
    }
    else {
       ++$countError;
    }
}
echo $countOk.' compare ok, '.$countError.' errors';

Result: 50405 compare ok, 0 errors

jspit
  • 7,276
  • 1
  • 9
  • 17
0

I took the visual approach (like how we do it in the real world). Instead of using formulas or what not, I solved it (or at least I think I did) by visualizing a literal calendar and then putting the dates in a multidimensional array. The first dimension corresponds to the week.

I hope someone can check if it stands your tests. Or help someone out with a different approach.

# date in this format 2021-08-03
# week_start is either Sunday or Monday
function getWeekOfMonth($date, $week_start = "Sunday"){

    list($year, $month, $day) = explode("-", $date);

    $dates = array();
    $current_week = 1;

    $new_week_signal = $week_start == "Sunday" ? 6 : 0;

    for($i = 1; $i <= date("t", strtotime($date)); $i++){
        $current_date = strtotime("{$year}-{$month}-".$i);
        $dates[$current_week][] = $i;
        if(date('w', $current_date) == $new_week_signal){
            $current_week++;
        }
    }

    foreach($dates as $week => $days){
        if(in_array(intval($day), $days)){
            return $week;
       }
    }

    return false;

}
Paolo
  • 390
  • 2
  • 5
  • 20
  • Great idea but seems a bit invalid. Each month has 4 weeks and extra days are not counted as a week. With your solution if you check "2021-12-27" for example it will return "5" which is not a valid "week". – Mecanik Nov 24 '21 at 06:46
  • @Mecanik: Yes you're right if you put it that way. But in my case and in the OP's case, week 5 is valid. – Paolo Nov 24 '21 at 08:18
-2
//It's easy, no need to use php function
//Let's say your date is 2017-07-02

$Date = explode("-","2017-07-02");
$DateNo = $Date[2];
$WeekNo = $DateNo / 7; // devide it with 7
if(is_float($WeekNo) == true)
{
   $WeekNo = ceil($WeekNo); //So answer will be 1
}  

//If value is not float then ,you got your answer directly
Anders
  • 8,307
  • 9
  • 56
  • 88