Full year calendar in pure PHP

Hello

Recently in my hobby project i got to one point where i need to show whole year data in calendar for each employee, those are like vacations, sick leave, some other reason, etc…

All events data from database

array(
   [0] => {
		"date_from" => "2022-06-30"
		"date_to" => "2022-07-02"
		"class" => "text-danger",
		"event_name" => "sick leave"
	},
	[1] => {
		"date_from" => "2022-07-25"
		"date_to" => "2022-07-30"
		"class" => "text-success",
		"event_name" => "vacation"
	}
},

i found some nice code on SO that fits my needs but i don’t know how to implement date range and apply color to <td class=""> for those date range to color all days for each event. I tried to use foreach loop and explode each date by “year,month,day” then search for each day and apply class to it, but problem is that that foreach loop is then inside of those 2 for loops and you know of course what is a result of that, disaster.

That sample code currently makes random dates in different color.

$year = date('Y');

$headings = ["Ned","Pon","Uto","Sri","Čet","Pet","Sub"];

echo '<table class="calendar table table-bordered table-sm text-center">';           // Create the table
echo "<tr><td><b>Mjesec</b></td>";       // Column heading for months
for ($x = 1; $x <= 37; $x++) {           // Column headings for days
    $title = ($headings[($x % 7) ]);
    echo "<td class=day>{$title}</td>";
}
echo "</tr>";

// Cycle through each month of the year
for ($month = 1; $month <= 12; $month++) {
    $thisMonth   = new DateTime("{$year}-{$month}");   // Create date object (defaults to 1st of month)
    $daysInMonth = $thisMonth->format("t");            // Get the number of days in the month
    $monthName   = $thisMonth->format("F");            // Get the month in textual form
    echo "<tr class=month><td class=monthName>{$monthName}</td>";
    $dayOffsetArray = [
        "Monday"    => 0,
        "Tuesday"   => 1,
        "Wednesday" => 2,
        "Thursday"  => 3,
        "Friday"    => 4,
        "Saturday"  => 5,
        "Sunday"    => 6,
    ];


    // Get the number of days to pad the month row with and output blank cells in a loop
    $offset = $dayOffsetArray[$thisMonth->format("l")];
    for ($i = 0; $i < $offset; $i++){
        echo "<td class=day></td>";
    }

    // Output the individual days
    for ($day = 1; $day <= 37  - $offset; $day++) {
        $dayNumber      = ($day <= $daysInMonth) ? $day : "";

        // Logic here is random to simulate data from the DB
        // you would need to alter to do checks against the DB etc.
        $highlightClass = (
            !(random_int(1, 100) % 15) && $dayNumber
                ? "highlightYellow"
                : (
                    !(random_int(1, 100) % 35) && $dayNumber ? "highlightGreen" : ""
                )
            );
        echo "<td class='day {$highlightClass}'>{$dayNumber}</td>";
    }
    echo "</tr>";
}
echo "</table>";

This would be end result of what i want to achieve

If there is a simpler solution than this one i am opened for any solution/direction how to solve this this i need.

I recommend that you expand the date ranges and index the data using the date.

$data["2022-06-30"] = "sick leave";
$data["2022-07-01"] = "sick leave";
$data["2022-07-02"] = "sick leave";

$data["2022-07-25"] = "vacation";
$data["2022-07-26"] = "vacation";
$data["2022-07-27"] = "vacation";
$data["2022-07-28"] = "vacation";
$data["2022-07-29"] = "vacation";
$data["2022-07-30"] = "vacation";

When you are looping to produce the output, build the full date - yyyy-mm-dd, then test if there’s an element set in the expanded data, and get its value.

I managed to make it to work like this with change only few things in code and added loop from db comparing date range with current date

$data = array(
    array(
        "date_from" => "2022-06-30",
        "date_to" => "2022-07-03",
        "class" => "bg-danger text-white",
        "event_name" => "sick leave"
    ),
    array(
        "date_from" => "2022-07-25",
        "date_to" => "2022-07-30",
        "class" => "bg-success text-white",
        "event_name" => "vacation"
    )
);

// Output the individual days
    for ($day = 1; $day <= 37  - $offset; $day++) {
        // added 0 in front of month and day
        $dayNumber = ($day <= $daysInMonth) ? $year.'-'.(strlen($month) == 1 ? '0'.$month : $month).'-'.(strlen($day) == 1 ? '0'.$day : $day) : "";
        
        $highlightClass = '';
        
        // loop through database array
        foreach ($data as $value) {
            $begin = new DateTime($value['date_from']);
            $end = new DateTime($value['date_to']);
            $end = $end->modify( '+1 day' ); 
            $interval = new DateInterval('P1D');

            // get the date range
            $daterange = new DatePeriod($begin, $interval, $end);
            foreach ($daterange as $date) {
                if ($date->format('Y-m-d') == $dayNumber) {
                    $highlightClass = $value['class'].' font-weight-bold';
                }
            }
        }
        echo '<td class="day '.$highlightClass.'">'.$dayNumber.'</td>';
    }

but there is very ugly table right now with full dates which is not nice in order of design

EDIT: changed last line on output to echo '<td class="day '.$highlightClass.'">'.date('d', strtotime($dayNumber)).'</td>'; fixed days output. I took only day from date.

EDIT: issue with populating empty cells on month ends it fills with “01” happens when i format date to days, tried to format date on “j” same thing.

EDIT: issue fixed

if ($dayNumber != '')
      echo '<td class="day '.$highlightClass.'">'.date('d', strtotime($dayNumber)).'</td>';
else
      echo '<td class="day"></td>';

final result

So, you are looping over the $data 365 times. The code you have that’s looping over it now, if moved above the start of the main loop, can be used to expand the date ranges into individual entries. Then, you only need an isset() statement inside the main loop to test, then get any entry matching the current date.

1 Like