Получить количество недель в месяце и соответствующие дни

Я почти уверен, что слишком усложняю это, но я понятия не имею, как сделать это по-другому.

Я хочу создать массив, который содержит номер недели в месяце в качестве ключа и дни этой недели в качестве значения.

Я использую эту функцию, чтобы получить все дни недели:

public function getDaysOfWeek($year, $month, $day)
{
    $firstMondayThisWeek = new DateTime($year . '/' . $month . '/' . $day, new DateTimeZone("Europe/Berlin"));
    $firstMondayThisWeek->modify('tomorrow');
    $firstMondayThisWeek->modify('last Monday');

    $nextSevenDays = new DatePeriod(
        $firstMondayThisWeek,
        DateInterval::createFromDateString('+1 day'),
        6
    );

    return $nextSevenDays;
}

И эта функция для построения массива:

public function getWeekAndDays($year, $month)
{
    $weeksInMonth = Carbon::createFromDate($year, $month)->endOfMonth()->weekOfMonth;   
    $weekBegin = Carbon::createFromDate($year, $month)->startOfMonth();

    $weeks = [];

    for($i=1; $i<=$weeksInMonth; $i++)
    {
        $collection = new \stdClass();
        $collection->week = $i;
        $collection->days = $this->getDaysOfWeek($year, $month, $weekBegin->day);

        $weekBegin->addWeek(0);

        $weeks[] = $collection;
    }

    return $weeks;
}

За все месяцы, кроме февраля, я получаю 5 недель. На февраль у меня есть 4 недели, поэтому я не могу сохранить перекрывающиеся дни месяца.

Я здесь совсем не прав? Каков возможный путь решения этой задачи?


person Michael Kambeck    schedule 08.08.2014    source источник


Ответы (2)


Мой друг, я чувствую твою боль, работа с данными календаря раздражает. Вот функция, которую я написал некоторое время назад, которая создает массив недель и дней, разделенных месяцами. Это не самый чистый код, но он работает.

Он начнется с даты, которую вы передаете в $today как "Y-m-d" (или по умолчанию к текущей дате), затем вернется к первой неделе текущего месяца, начнется там, а затем продолжится в течение $scheduleMonths месяцев, создавая массив, проиндексированный первым по месяцам, а затем по неделям.

Это немного сложно объяснить здесь, но это автономно, поэтому вы можете просто скопировать/вставить его в свой код, а затем dd() вывести результат, чтобы увидеть, как он выглядит и работает ли он для вас, и изменить его оттуда. Возможно, вам придется изменить форматирование, поскольку оно было сделано таким образом для моего конкретного случая использования, но бизнес-логика должна быть разумной.

Вы должны иметь возможность извлечь количество недель в данном месяце из вывода (поэтому, если это все, что вам нужно, вы можете упростить это, как только подтвердите, что он работает).

public function getWeeks($today = null, $scheduleMonths = 6) {

    $today = !is_null($today) ? Carbon::createFromFormat('Y-m-d',$today) : Carbon::now();

    $startDate = Carbon::instance($today)->startOfMonth()->startOfWeek()->subDay(); // start on Sunday
    $endDate = Carbon::instance($startDate)->addMonths($scheduleMonths)->endOfMonth();
    $endDate->addDays(6 - $endDate->dayOfWeek);

    $epoch = Carbon::createFromTimestamp(0);
    $firstDay = $epoch->diffInDays($startDate);
    $lastDay = $epoch->diffInDays($endDate);

    $week=0;
    $monthNum = $today->month;
    $yearNum = $today->year;
    $prevDay = null;
    $theDay = $startDate;
    $prevMonth = $monthNum;

    $data = array();

    while ($firstDay < $lastDay) {

        if (($theDay->dayOfWeek == Carbon::SUNDAY) && (($theDay->month > $monthNum) || ($theDay->month == 1))) $monthNum = $theDay->month;
        if ($prevMonth > $monthNum) $yearNum++;

        $theMonth = Carbon::createFromFormat("Y-m-d",$yearNum."-".$monthNum."-01")->format('F Y');

        if (!array_key_exists($theMonth,$data)) $data[$theMonth] = array();
        if (!array_key_exists($week,$data[$theMonth])) $data[$theMonth][$week] = array(
            'day_range' => '',
        );

        if ($theDay->dayOfWeek == Carbon::SUNDAY) $data[$theMonth][$week]['day_range'] = sprintf("%d-",$theDay->day);
        if ($theDay->dayOfWeek == Carbon::SATURDAY) $data[$theMonth][$week]['day_range'] .= sprintf("%d",$theDay->day);

        $firstDay++;
        if ($theDay->dayOfWeek == Carbon::SATURDAY) $week++;
        $theDay = $theDay->copy()->addDay();
        $prevMonth = $monthNum;
    }

    $totalWeeks = $week;

    return array(
        'startDate' => $startDate,
        'endDate' => $endDate,
        'totalWeeks' => $totalWeeks,
        'schedule' => $data,
    );

}

Надеюсь, это поможет вам хотя бы начать!

person Seb Barre    schedule 08.08.2014
comment
Прости за мой запоздалый ответ. Код мне очень помог. Не только за внедрение кода в мой проект, но и за лучшее понимание странного и запутанного мира форматирования даты :D Спасибо! - person Michael Kambeck; 12.09.2014
comment
Я бы... но для этого мне нужно еще 4 очка репутации. - person Michael Kambeck; 28.09.2014
comment
какие-нибудь указания о том, как исключить выходные? (Суббота и воскресенье) - person Amir Asyraf; 14.10.2019
comment
@AmirAsyraf в цикле while вы можете добавить счетчик $weekdays и использовать свойство dayOfWeek() для проверки субботы/воскресенья и пропустить увеличение этого счетчика, поскольку пример эффективно перебирает все дни. См. строку, где я проверяю Carbon::SATURDAY, чтобы увеличить счетчик $week для аналогичного примера. - person Seb Barre; 15.10.2019

Что-то вроде этого?

function weekOfMonth($date) {

    $firstOfMonth = strtotime(date("Y-m-01", $date));
    $lastWeekNumber = (int)date("W", $date);
    $firstWeekNumber = (int)date("W", $firstOfMonth);
    if (12 === (int)date("m", $date)) {
        if (1 == $lastWeekNumber) {
            $lastWeekNumber = (int)date("W", ($date - (7*24*60*60))) + 1;
        }
    } elseif (1 === (int)date("m", $date) and 1 < $firstWeekNumber) {
        $firstWeekNumber = 0;
    }
    return $lastWeekNumber - $firstWeekNumber + 1;
}

function weeks($month, $year){
    $lastday = date("t", mktime(0, 0, 0, $month, 1, $year));
    return weekOfMonth(strtotime($year.'-'.$month.'-'.$lastday));
}

$result = [];

for ($year = 2017; $year < 2020; $year++){
    for ($month = 1; $month < 13; $month++) {
        $numOfWeeks = weeks($month, $year);
        $result[$year][$month]['numOfWeeks'] = $numOfWeeks;
        $daysInFirstWeek = 8 - date('N', strtotime($year.'-'.$month.'-01'));
        $result[$year][$month]['daysPerWeek']['week_1'] = $daysInFirstWeek;
        $startDay = date('d', strtotime('next Monday', strtotime($year.'-'.$month.'-01')));
        for ($i = 1; $i < ($numOfWeeks - 1); $i++) {
            $startDay += 7;
            $result[$year][$month]['daysPerWeek']['week_'.($i+1)] = 7;
        }

        //last week
        $result[$year][$month]['daysPerWeek']['week_'.($i+1)] = date('t', strtotime($year.'-'.$month.'-01')) - $startDay + 1;
    }
}

echo json_encode($result)."\n";
person Oleksiy Tikh    schedule 06.09.2017