Simple Events Calendar With PHP MySQL – Free Code Download

Welcome to a tutorial on how to build an AJAX-driven events calendar with PHP and MySQL. Looking to add some organizational features to your project? Too many calendars out there that are too complicated?

A simple PHP MySQL calendar only consists of a few key components:

  • A database table to store the events.
  • A PHP class library to manage the events.
  • Lastly, an HTML page to show the calendar of events.

But just how is this done exactly? Let us walk through a step-by-step example in this guide – Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes PHP Calendar Useful Bits & Links
The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Create a database and import 1-database.sql.
  • Change the database settings in 2-cal-core.php to your own.
  • Want to start the week on Monday instead? Set $sunFirst = false in 4-ajax.php.
  • That’s all – Launch 3-calendar.php in your browser.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

 

PHP MYSQL EVENTS CALENDAR

All right, let us now get into the details of building a PHP MYSQL calendar.

 

PART 1) EVENTS DATABASE TABLE

1-database.sql
CREATE TABLE `events` (
  `evt_id` bigint(20) NOT NULL,
  `evt_start` datetime NOT NULL,
  `evt_end` datetime NOT NULL,
  `evt_text` text NOT NULL,
  `evt_color` varchar(7) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
ALTER TABLE `events`
  ADD PRIMARY KEY (`evt_id`),
  ADD KEY `evt_start` (`evt_start`),
  ADD KEY `evt_end` (`evt_end`);
 
ALTER TABLE `events`
  MODIFY `evt_id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;

Let us start by dealing with the foundation of the system – The database. Not to worry, this is nothing but a very simple table with only 5 fields.

Field Description
evt_id Primary key, the event ID.
evt_start Event start date.
evt_end Event end date.
evt_text Details of the event.
evt_color Color to show on the calendar.

 

 

PART 2) PHP EVENTS CLASS LIBRARY

2-cal-core.php
<?php
class Calendar {
  // (A) CONSTRUCTOR - CONNECT TO DATABASE
  private $pdo = null;
  private $stmt = null;
  public $error = "";
  function __construct () {
    try {
      $this->pdo = new PDO(
        "mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET, 
        DB_USER, DB_PASSWORD, [
          PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
          PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
      );
    } catch (Exception $ex) { exit($ex->getMessage()); }
  }

  // (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
  function __destruct () {
    if ($this->stmt!==null) { $this->stmt = null; }
    if ($this->pdo!==null) { $this->pdo = null; }
  }
 
  // (C) HELPER - EXECUTE SQL QUERY
  function exec ($sql, $data=null) {
    try {
      $this->stmt = $this->pdo->prepare($sql);
      $this->stmt->execute($data);
      return true;
    } catch (Exception $ex) {
      $this->error = $ex->getMessage();
      return false;
    }
  }
 
  // (D) SAVE EVENT
  function save ($start, $end, $txt, $color, $id=null) {
    // (C1) START & END DATE QUICK CHECK 
    $uStart = strtotime($start);
    $uEnd = strtotime($end);
    if ($uEnd < $uStart) { $this->error = "End date cannot be earlier than start date";
      return false;
    }

    // (D2) SQL - INSERT OR UPDATE
    if ($id==null) {
      $sql = "INSERT INTO `events` (`evt_start`, `evt_end`, `evt_text`, `evt_color`) VALUES (?,?,?,?)";
      $data = [$start, $end, $txt, $color];
    } else {
      $sql = "UPDATE `events` SET `evt_start`=?, `evt_end`=?, `evt_text`=?, `evt_color`=? WHERE `evt_id`=?";
      $data = [$start, $end, $txt, $color, $id];
    }

    // (D3) EXECUTE
    return $this->exec($sql, $data);
  }

  // (E) DELETE EVENT
  function del ($id) {
    return $this->exec("DELETE FROM `events` WHERE `evt_id`=?", [$id]);
  }

  // (F) GET EVENTS FOR SELECTED MONTH
  function get ($month, $year) {
    // (F1) FIST & LAST DAY OF MONTH
    $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
    $dayFirst = "{$year}-{$month}-01 00:00:00";
    $dayLast = "{$year}-{$month}-{$daysInMonth} 23:59:59";

    // (F2) GET EVENTS
    if (!$this->exec(
      "SELECT * FROM `events` WHERE (
        (`evt_start` BETWEEN ? AND ?)
        OR (`evt_end` BETWEEN ? AND ?)
        OR (`evt_start` <= ? AND `evt_end` >= ?)
      )", [$dayFirst, $dayLast, $dayFirst, $dayLast, $dayFirst, $dayLast]
    )) { return false; }
    
    // $events = [
    // "e" => [ EVENT ID => [DATA], EVENT ID => [DATA], ... ],
    // "d" => [ DAY => [EVENT IDS], DAY => [EVENT IDS], ... ]
    // ]
 
    $events = ["e" => [], "d" => []];
    while ($row = $this->stmt->fetch()) {
      $eStartMonth = substr($row["evt_start"], 5, 2);
      $eEndMonth = substr($row["evt_end"], 5, 2);
      $eStartDay = $eStartMonth==$month 
                 ? (int)substr($row["evt_start"], 8, 2) : 1 ;
      $eEndDay = $eEndMonth==$month 
               ? (int)substr($row["evt_end"], 8, 2) : $daysInMonth ;
      for ($d=$eStartDay; $d<=$eEndDay; $d++) {
        if (!isset($events["d"][$d])) { $events["d"][$d] = []; }
        $events["d"][$d][] = $row["evt_id"];
      }
      $events["e"][$row["evt_id"]] = $row;
      $events["e"][$row["evt_id"]]["first"] = $eStartDay;
    }
    return $events;
  }
}

// (G) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8");
define("DB_USER", "root");
define("DB_PASSWORD", "");

// (H) NEW CALENDAR OBJECT
$_CAL = new Calendar();

This library looks massive at first, but keep calm and look carefully.

  • (A & B) When the $_CAL = new Calendar() object is created, the constructor connects to the database; The destructor closes the connection.
  • (C) A helper function to execute an SQL query.
  • (D To F) There are only 3 key functions here, and all they do are literally SQL queries!
    • function save() Saves an event (insert or update).
    • function del() Deletes an event.
    • function get() Gets all events for a selected month. This is probably the most complicated, just spend some time to familiarize with how the events data is formatted – This will be used to draw the calendar in 4-ajax.php.
  • (G & H) Self-explanatory. Doh.

 

 

PART 3) CALENDAR HTML PAGE

3-calendar.php
<!-- (A) PERIOD SELECTOR -->
<div id="calPeriod"><?php
  // (A1) MONTH SELECTOR
  // NOTE: DEFAULT TO CURRENT SERVER MONTH YEAR
  $months = [
    1 => "January", 2 => "Febuary", 3 => "March", 4 => "April",
    5 => "May", 6 => "June", 7 => "July", 8 => "August",
    9 => "September", 10 => "October", 11 => "November", 12 => "December"
  ];
  $monthNow = date("m");
  echo "<select id='calmonth'>";
  foreach ($months as $m=>$mth) {
    printf("<option value='%s'%s>%s</option>", 
      $m, $m==$monthNow?" selected":"", $mth
    );
  }
  echo "</select>";

  // (A2) YEAR SELECTOR
  echo "<input type='number' id='calyear' value='".date("Y")."'/>";
?></div>
 
<!-- (B) CALENDAR WRAPPER -->
<div id="calwrap"></div>
 
<!-- (C) EVENT FORM -->
<div id="calblock"><form id="calform">
  <input type="hidden" name="req" value="save"/>
  <input type="hidden" id="evtid" name="eid"/>
  <label for="start">Date Start</label>
  <input type="datetime-local" id="evtstart" name="start" required/>
  <label for="end">Date End</label>
  <input type="datetime-local" id="evtend" name="end" required/>
  <label for="txt">Event</label>
  <textarea id="evttxt" name="txt" required></textarea>
  <label for="color">Color</label>
  <input type="color" id="evtcolor" name="color" value="#e4edff" required/>
  <input type="submit" id="calformsave" value="Save"/>
  <input type="button" id="calformdel" value="Delete"/>
  <input type="button" id="calformcx" value="Cancel"/>
</form></div>

There is quite a bit of code to the landing page but look carefully again. There are only 3 main parts:

  1. <div id="calPeriod"> The month and year selector.
  2. <div id="calwrap"> The calendar itself – This will be loaded via AJAX.
  3. <form id="calform"> A hidden popup form to add/update an event.

Take note, I am using native <input type="datetime-local"> and <input type="color"> here. Feel free to implement and use your own library if you want – React, Angular, Vue, jQuery, etc…

 

 

PART 4) CALENDAR AJAX HANDLER

4-ajax.php
<?php
// (A) INVALID AJAX REQUEST
if (!isset($_POST["req"])) { exit("INVALID REQUEST"); }
require "2-cal-core.php";
switch ($_POST["req"]) {
  // (B) DRAW CALENDAR FOR MONTH
  case "draw":
    // (B1) DATE RANGE CALCULATIONS
    // NUMBER OF DAYS IN MONTH
    $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $_POST["month"], $_POST["year"]);
    // FIRST & LAST DAY OF MONTH
    $dateFirst = "{$_POST["year"]}-{$_POST["month"]}-01";
    $dateLast = "{$_POST["year"]}-{$_POST["month"]}-{$daysInMonth}";
    // DAY OF WEEK - NOTE 0 IS SUNDAY
    $dayFirst = (new DateTime($dateFirst))->format("w");
    $dayLast = (new DateTime($dateLast))->format("w");

    // (B2) DAY NAMES
    $sunFirst = true; // CHANGE THIS IF YOU WANT MON FIRST
    $days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    if ($sunFirst) { array_unshift($days, "Sun"); }
    else { $days[] = "Sun"; }
    foreach ($days as $d) { echo "<div class='calsq head'>$d</div>"; }
    unset($days);
 
    // (B3) PAD EMPTY SQUARES BEFORE FIRST DAY OF MONTH
    if ($sunFirst) { $pad = $dayFirst; }
    else { $pad = $dayFirst==0 ? 6 : $dayFirst-1 ; }
    for ($i=0; $i<$pad; $i++) { echo "<div class='calsq blank'></div>"; }
 
    // (B4) DRAW DAYS IN MONTH
    $events = $_CAL->get($_POST["month"], $_POST["year"]);
    $nowMonth = date("n");
    $nowYear = date("Y");
    $nowDay = ($nowMonth==$_POST["month"] && $nowYear==$_POST["year"]) ? date("j") : 0 ;
    for ($day=1; $day<=$daysInMonth; $day++) { ?>
    <div class="calsq day<?=$day==$nowDay?" today":""?>" data-day="<?=$day?>">
      <div class="calnum"><?=$day?></div>
        <?php if (isset($events["d"][$day])) { foreach ($events["d"][$day] as $eid) { ?>
        <div class="calevt" data-eid="<?=$eid?>"
             style="background:<?=$events["e"][$eid]["evt_color"]?>">
          <?=$events["e"][$eid]["evt_text"]?>
        </div>
        <?php if ($day == $events["e"][$eid]["first"]) {
          echo "<div id='evt$eid' class='calninja'>".json_encode($events['e'][$eid])."</div>";
        }}} ?>
      </div>
    <?php }
 
    // (B5) PAD EMPTY SQUARES AFTER LAST DAY OF MONTH
    if ($sunFirst) { $pad = $dayLast==0 ? 6 : 6-$dayLast ; }
    else { $pad = $dayLast==0 ? 0 : 7-$dayLast ; }
    for ($i=0; $i<$pad; $i++) { echo "<div class='calsq blank'></div>"; }
    break;
 
  // (C) SAVE EVENT
  case "save":
    if (!is_numeric($_POST["eid"])) { $_POST["eid"] = null; }
    echo $_CAL->save(
      $_POST["start"], $_POST["end"], $_POST["txt"], $_POST["color"],
      isset($_POST["eid"]) ? $_POST["eid"] : null
    ) ? "OK" : $_CAL->error ;
    break;
 
  // (D) DELETE EVENT
  case "del":
    echo $_CAL->del($_POST["eid"]) ? "OK" : $_CAL->error ;
    break;
}

This AJAX handler fills in the “functional parts” using the class library. Just send a $_POST["req"] followed by the required parameters to this script.

  • (B) $_POST = ["req"=>"draw", "month"=>M, "year"=>Y] Generate the calendar for the given month. Just take your time to trace through.
  • (C) $_POST = ["req"=>"save", "start"=>START DATE, "end"=>END DATE, "TXT"=>DETAILS, "color"=>HEX, "eid"=>EVENT ID] Save the given event. Will update if eid is provided, add a new event if not.
  • (D) $_POST = ["req"=>"del", "eid"=>EVENT ID] Delete specified event.

 

 

PART 5) THE JAVASCRIPT

5A) INITIALIZE CALENDAR

5-calendar.js
// (A) INIT CALENDAR
hMth:null, hYr:null, // MONTH & YEAR
hWrap:null, // DAYS WRAPPER
// EVENT FORM
hBlock:null, hForm:null, hFormDel:null, hFormCX:null,
hID:null, hStart:null, hEnd:null, hTxt:null, hColor:null,
init : () => {
  // (A1) GET HTML ELEMENTS
  cal.hMth = document.getElementById("calmonth");
  cal.hYr = document.getElementById("calyear");
  cal.hWrap = document.getElementById("calwrap");
  cal.hBlock = document.getElementById("calblock");
  cal.hForm = document.getElementById("calform");
  cal.hFormDel = document.getElementById("calformdel");
  cal.hFormCX = document.getElementById("calformcx");
  cal.hID = document.getElementById("evtid");
  cal.hStart = document.getElementById("evtstart");
  cal.hEnd = document.getElementById("evtend");
  cal.hTxt = document.getElementById("evttxt");
  cal.hColor = document.getElementById("evtcolor");
 
  // (A2) ATTACH CONTROLS
  cal.hMth.onchange = cal.draw;
  cal.hYr.onchange = cal.draw;
  cal.hForm.onsubmit = cal.save;
  cal.hFormDel.onclick = cal.del;
  cal.hFormCX.onclick = cal.hide;
 
  // (A3) DRAW CURRENT MONTH/YEAR
  cal.draw();
}
window.addEventListener("DOMContentLoaded", cal.init);

Finally, we have some stinky long Javascript. This first section should be self-explanatory – cal.init() runs on window load, it simply gets all the HTML elements and attaches the calendar controls.

 

5B) AJAX FECH

5-calendar.js
// (B) SUPPORT FUNCTION - AJAX FETCH
ajax : (data, load) => {
  fetch("4-ajax.php", { method:"POST", body:data })
  .then(res=>res.text()).then(load);
}

Next, a self-explanatory support function. Do a fetch() call to the above PHP AJAX handler.

 

5C) DRAW CALENDAR

5-calendar.js
 // (C) DRAW CALENDAR
draw : () => {
  // (C1) FORM DATA
  let data = new FormData();
  data.append("req", "draw");
  data.append("month", cal.hMth.value);
  data.append("year", cal.hYr.value);
 
  // (C2) AJAX LOAD SELECTED MONTH
  cal.ajax(data, (res) => {
    // (C2-1) ATTACH CALENDAR TO WRAPPER
    cal.hWrap.innerHTML = res;
 
    // (C2-2) CLICK DAY CELLS TO ADD EVENT
    for (let day of cal.hWrap.getElementsByClassName("day")) {
      day.onclick = () => { cal.show(day); };
    }
 
    // (C2-3) CLICK EVENT TO EDIT
    for (let evt of cal.hWrap.getElementsByClassName("calevt")) {
      evt.onclick = (e) => { cal.show(evt); e.stopPropagation(); };
    }
  });
}

Captain Obvious – This does an AJAX call to 4-ajax.php to fetch the calendar for the selected month and year. Then, attaches “click on date/event” to show the “edit event form”.

 

5D) SHOW/HIDE EVENT FORM

5-calendar.js
// (D) SHOW EVENT FORM
show : (cell) => {
  let eid = cell.getAttribute("data-eid");
 
  // (D1) ADD NEW EVENT
  if (eid === null) {
    let y = cal.hYr.value, m = cal.hMth.value, d = cell.dataset.day;
    if (m.length==1) { m = "0" + m; }
    if (d.length==1) { d = "0" + d; }
    let ymd = `${y}-${m}-${d}T00:00:00`; // RFC 3339
    cal.hForm.reset();
    cal.hID.value = "";
    cal.hStart.value = ymd;
    cal.hEnd.value = ymd;
    cal.hFormDel.style.display = "none";
  }
 
  // (D2) EDIT EVENT
  else {
    let edata = JSON.parse(document.getElementById("evt"+eid).innerHTML);
    cal.hID.value = eid;
    cal.hStart.value = edata["evt_start"].replaceAll(" ", "T");
    cal.hEnd.value = edata["evt_end"].replaceAll(" ", "T");
    cal.hTxt.value = edata["evt_text"];
    cal.hColor.value = edata["evt_color"];
    cal.hFormDel.style.display = "block";
  }
 
  // (D3) SHOW EVENT FORM
  cal.hBlock.classList.add("show");
},
 
// (E) HIDE EVENT FORM
hide : () => { cal.hBlock.classList.remove("show"); },{

Just in case you have forgotten already – There is hidden <form id="calform">. When the user clicks on a date/event, cal.show() will update the form and show it… cal.hide() is self-explanatory.

 

5E) SAVE & DELETE EVENT

5-calendar.js
// (F) SAVE EVENT
save : () => {
  cal.ajax(new FormData(cal.hForm), (res) => {
    if (res=="OK") { cal.hide(); cal.draw(); }
    else { alert(res); }
  });
  return false;
},
 
// (G) DELETE EVENT
del : () => { if (confirm("Delete Event?")) {
  // (G1) FORM DATA
  let data = new FormData();
  data.append("req", "del");
  data.append("eid", cal.hID.value);
 
  // (G2) AJAX DELETE
  cal.ajax(data, (res) => {
    if (res=="OK") { cal.hide(); cal.draw(); }
    else { alert(res); }
  });
}}

Lastly, cal.save() and cal.del() does exactly what it says… Process “save event” and “delete event”.

 

JAVASCRIPT FUNCTIONS SUMMARY

Function Description
init() Called when the page is fully loaded, gets the HTML elements and attach onclick onchange calendar controls.
ajax() A helper function. Does an AJAX call to 4-ajax.php.
draw() Draws the calendar for the selected month (do an AJAX call to the server).
show() When the user clicks on an empty day or existing event – Show the add/edit event form.
hide() Hide the add/edit event form.
save() Save the event.
del() Delete an event.

 

USEFUL BITS & LINKS

That’s it for the tutorial and here are some small extras that you may find useful.

 

LIMITATIONS

Yes, this is a fully functioning simple events calendar system. But there are plenty of improvements that you can make, plus some limitations to take note of:

  • The system is open to all users – You will want to integrate some form of user login.
  • There is no security, some encryption may be good if sensitive information is involved.
  • Not backward compatible with ancient browsers – Arrow Function | Template Literals | CSS Flexbox | Viewport Units

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you to build a better project, and if you have anything to share with this guide, please feel free to comment below. Good luck and happy coding!

32 thoughts on “Simple Events Calendar With PHP MySQL – Free Code Download”

    1. Quick fix in 2-cal-core.php (F) –
      $dayFirst = "{$year}-{$month}-01 00:00:00";
      $dayLast = "{$year}-{$month}-{$daysInMonth} 23:59:59";

  1. nice…. its working..for those cant connect….

    in — 2-cal-core.php
    look at the end of the page.. edit data as per your server requirement..

    thanks dev…awesome

    EDIT: ADDITIONAL NOTES BY WS – Also make sure that your PDO extension is enabled in php.ini.

  2. Hello, I left a comment before but it didn’t post so I’m going to try again.
    I want to be able to display the Time alongside the Event name on the calendar, so I’ve added it to the html and made it a required input type on the form.
    = CODE TRUNCATED =
    Please help, I’m stuck.

  3. Hi admin , may i ask that how i adjust the calender size because I want to add some widgets at the side of the calendar?

  4. : Uncaught PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘test.events’ doesn’t exist in C:\xampps\htdocs\caland\2-cal-core.php:77 Stack trace: #0 C:\xampps\htdocs\caland\2-cal-core.php(77): PDOStatement->execute(Array) #1 C:\xampps\htdocs\caland\3d-ajax.php(32): Calendar->get(‘8’, ‘2021’) #2 {main} thrown in
    C:\xampps\htdocs\caland\2-cal-core.php
    on line
    77
    whay this errore ?

    1. No worries. This is a very common beginner problem, usually caused by:

      1) Cannot be bothered to read the instructions.
      2) Prefers to waste time debugging, ignore the instructions.
      3) Did not even import the SQL and/or change the database settings.

      At least go through “Quick Notes” above. Good luck.

      https://code-boxx.com/faq/#no-db

Leave a Comment

Your email address will not be published. Required fields are marked *