Soy un gran admirador de fullcalendar.io, la última versión es bastante diferente de las versiones anteriores, en este tutorial trataré cómo integrar fullcalendar en una base de datos con MySQL y cargar los eventos desde PHP.
Manifestación
 
Descargar archivos fuente
He descargado archivos de  https://fullcalendar.io/docs/getting-started y  también he agregado lo siguiente:
Bootstrap v4.4.1
jQuery v3.2.1
jQuery UI v1.10.3
Datepicker
v1.6.3 Colorpicker
He colocado todos los paquetes descargados y las versiones enumeradas anteriormente en una carpeta llamada paquetes. También tengo una carpeta llamada api donde vivirán todos los archivos de la base de datos.
Voy a usar un PDO Wrapper para las interacciones de la base de datos para instalarlo, crear un archivo llamado composer.json y agregar:
{
    "require": {
        "daveismyname/pdo-wrapper": "^1.1"
    }
}
Ahora ejecute `composer install` en una terminal para instalar el contenedor. Se creará una carpeta de proveedor.
Ahora cree un archivo config.php y agregue:
<?php
require('vendor/autoload.php');

use Daveismyname\PdoWrapper\Database;

$host = "localhost";
$database = "calendar";
$username = "root";
$password = "";

$db = Database::get($username, $password, $database, $host);
$dir = "./";
Primero, incluimos el autocargador para que se puedan cargar los archivos del compositor.
Cree una instancia del contenedor de la base de datos y establezca las credenciales de la base de datos.
Finalmente establezca la ruta de la carpeta $ dir = "./" apunta a la carpeta actual, puede hacer esto absoluto si lo prefiere.
Cree una tabla llamada eventos con la siguiente estructura:
CREATE TABLE `events` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` text,
  `start_event` datetime NOT NULL,
  `end_event` datetime NOT NULL,
  `color` varchar(191) DEFAULT NULL,
  `text_color` varchar(191) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Puede importar el archivo db.sql proporcionado en mi repositorio de GitHub o ejecutarlo en su programa MySQL de elección.
Cree un archivo llamado index.php, aquí es donde se utilizará el calendario.
Incluya la configuración y los paquetes:
<?php require('config.php');?>
<!DOCTYPE html>
<html>
<head>
    <title>Calandar</title>
    
    <link href='<?=$dir;?>packages/core/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/daygrid/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/timegrid/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/list/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/bootstrap/css/bootstrap.css' rel='stylesheet' />
    <link href="<?=$dir;?>packages/jqueryui/custom-theme/jquery-ui-1.10.4.custom.min.css" rel="stylesheet">
    <link href='<?=$dir;?>packages/datepicker/datepicker.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/colorpicker/bootstrap-colorpicker.min.css' rel='stylesheet' />
    <link href='<?=$dir;?>style.css' rel='stylesheet' />

    <script src='<?=$dir;?>packages/core/main.js'></script>
    <script src='<?=$dir;?>packages/daygrid/main.js'></script>
    <script src='<?=$dir;?>packages/timegrid/main.js'></script>
    <script src='<?=$dir;?>packages/list/main.js'></script>
    <script src='<?=$dir;?>packages/interaction/main.js'></script>
    <script src='<?=$dir;?>packages/jquery/jquery.js'></script>
    <script src='<?=$dir;?>packages/jqueryui/jqueryui.min.js'></script>
    <script src='<?=$dir;?>packages/bootstrap/js/bootstrap.js'></script>
    <script src='<?=$dir;?>packages/datepicker/datepicker.js'></script>
    <script src='<?=$dir;?>packages/colorpicker/bootstrap-colorpicker.min.js'></script>
    <script src='<?=$dir;?>calendar.js'></script>
</head>
<body>
El calendar.js es donde irán todas las funciones del calendario.
Ahora agregue el marcado del calendario:
<div class="container">

    <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addeventmodal">
      Add Event
    </button>

    <div id="calendar"></div>
</div>

</body>
</html>
Por defecto, el calendario se adjuntará a un elemento por su id.
Además, como estamos usando bootstrap, podemos usar sus modales para cargar ventanas emergentes para agregar y editar eventos.
el margen de beneficio para estos:
<div class="modal fade" id="addeventmodal" tabindex="-1" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">

            <div class="modal-header">
                <h5 class="modal-title">Add Event</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>

            <div class="modal-body">

                <div class="container-fluid">

                    <form id="createEvent" class="form-horizontal">

                    <div class="row">

                        <div class="col-md-6">

                            <div id="title-group" class="form-group">
                                <label class="control-label" for="title">Title</label>
                                <input type="text" class="form-control" name="title">
                                <!-- errors will go here -->
                            </div>

                            <div id="startdate-group" class="form-group">
                                <label class="control-label" for="startDate">Start Date</label>
                                <input type="text" class="form-control datetimepicker" id="startDate" name="startDate">
                                <!-- errors will go here -->
                            </div>

                            <div id="enddate-group" class="form-group">
                                <label class="control-label" for="endDate">End Date</label>
                                <input type="text" class="form-control datetimepicker" id="endDate" name="endDate">
                                <!-- errors will go here -->
                            </div>

                        </div>

                        <div class="col-md-6">

                            <div id="color-group" class="form-group">
                                <label class="control-label" for="color">Colour</label>
                                <input type="text" class="form-control colorpicker" name="color" value="#6453e9">
                                <!-- errors will go here -->
                            </div>

                            <div id="textcolor-group" class="form-group">
                                <label class="control-label" for="textcolor">Text Colour</label>
                                <input type="text" class="form-control colorpicker" name="text_color" value="#ffffff">
                                <!-- errors will go here -->
                            </div>

                        </div>

                    </div>

                    

                </div>

            </div>

            <div class="modal-footer">
              <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
              <button type="submit" class="btn btn-primary">Save changes</button>
            </div>

            </form>

        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

<div class="modal fade" id="editeventmodal" tabindex="-1" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">

            <div class="modal-header">
                <h5 class="modal-title">Update Event</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>

            <div class="modal-body">

                <div class="container-fluid">

                    <form id="editEvent" class="form-horizontal">
                    <input type="hidden" id="editEventId" name="editEventId" value="">

                    <div class="row">

                        <div class="col-md-6">

                            <div id="edit-title-group" class="form-group">
                                <label class="control-label" for="editEventTitle">Title</label>
                                <input type="text" class="form-control" id="editEventTitle" name="editEventTitle">
                                <!-- errors will go here -->
                            </div>

                            <div id="edit-startdate-group" class="form-group">
                                <label class="control-label" for="editStartDate">Start Date</label>
                                <input type="text" class="form-control datetimepicker" id="editStartDate" name="editStartDate">
                                <!-- errors will go here -->
                            </div>

                            <div id="edit-enddate-group" class="form-group">
                                <label class="control-label" for="editEndDate">End Date</label>
                                <input type="text" class="form-control datetimepicker" id="editEndDate" name="editEndDate">
                                <!-- errors will go here -->
                            </div>

                        </div>

                        <div class="col-md-6">

                            <div id="edit-color-group" class="form-group">
                                <label class="control-label" for="editColor">Colour</label>
                                <input type="text" class="form-control colorpicker" id="editColor" name="editColor" value="#6453e9">
                                <!-- errors will go here -->
                            </div>

                            <div id="edit-textcolor-group" class="form-group">
                                <label class="control-label" for="editTextColor">Text Colour</label>
                                <input type="text" class="form-control colorpicker" id="editTextColor" name="editTextColor" value="#ffffff">
                                <!-- errors will go here -->
                            </div>

                        </div>

                    </div>

                </div>

            </div>

            <div class="modal-footer">
              <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
              <button type="submit" class="btn btn-primary">Save changes</button>
              <button type="button" class="btn btn-danger" id="deleteEvent" data-id>Delete</button>
            </div>

            </form>

        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->
Ambos tienen formularios para agregar y editar el evento.
Ahora pasemos a JS para crear un archivo llamado calendar.js
agregue un detector de eventos para que el código solo se ejecute una vez que la página esté completamente cargada:
document.addEventListener('DOMContentLoaded', function() {

    var url ='/';
Ahora configuraremos una clase para que el selector de fechas se adjunte a
$('body').on('click', '.datetimepicker', function() {
    $(this).not('.hasDateTimePicker').datetimepicker({
        controlType: 'select',
        changeMonth: true,
        changeYear: true,
        dateFormat: "dd-mm-yy",
        timeFormat: 'HH:mm:ss',
        yearRange: "1900:+10",
        showOn:'focus',
        firstDay: 1
    }).focus();
});
Y el selector de color
$(".colorpicker").colorpicker();
Ahora configure una variable para que el calendario se adjunte a un div con una identificación de calendario
var calendarEl = document.getElementById('calendar');
Ahora para el calendario completo 
var calendar = new FullCalendar.Calendar(calendarEl, {
Dentro de FullCalendar coloque las opciones de calendario:
plugins: ['interaction', 'dayGrid', 'timeGrid', 'list'],
header: {
    left: 'prev,next today',
    center: 'title',
    right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
navLinks: true, // can click day/week names to navigate views
businessHours: true, // display business hours
editable: true,
para cargar los eventos necesitamos llamar eventos:
events: url+'api/load.php',
Esto envía una solicitud a load.php que devolverá una matriz JSON de todos los eventos, primero cubriré las funciones JS y luego revisaré el código PHP.
Ahora puede llamar a 3 opciones adicionales, eventDrop, eventResize y eventClick
eventDrop se activa cuando un evento se arrastra y suelta a otra fecha u hora.
Recopilamos las fechas de inicio y finalización y luego enviamos una solicitud ajax a update.php y pasamos la identificación del evento y la hora de inicio y finalización.
eventDrop: function(arg) {
    var start = arg.event.start.toDateString()+' '+arg.event.start.getHours()+':'+arg.event.start.getMinutes()+':'+arg.event.start.getSeconds();
    if (arg.event.end == null) {
        end = start;
    } else {
        var end = arg.event.end.toDateString()+' '+arg.event.end.getHours()+':'+arg.event.end.getMinutes()+':'+arg.event.end.getSeconds();
    }

    $.ajax({
      url:url+"api/update.php",
      type:"POST",
      data:{id:arg.event.id, start:start, end:end},
    });
}
eventReize es muy similar, esto se activa cuando un evento se redimensiona manualmente desde una vista de semana o día.
eventResize: function(arg) {
    var start = arg.event.start.toDateString()+' '+arg.event.start.getHours()+':'+arg.event.start.getMinutes()+':'+arg.event.start.getSeconds();
    var end = arg.event.end.toDateString()+' '+arg.event.end.getHours()+':'+arg.event.end.getMinutes()+':'+arg.event.end.getSeconds();

    $.ajax({
      url:url+"api/update.php",
      type:"POST",
      data:{id:arg.event.id, start:start, end:end},
    });
}
eventClick se activa cuando se hace clic en un evento, en este punto queremos recopilar la identificación del evento y luego hacer una llamada ajax para obtener todos los detalles del evento, agregarlos a un modelo y mostrar el modal. Compruebe también si se ha presionado un botón Eliminar en el modal y, de ser así, envíe un evento ajax para eliminar la entrada.
eventClick: function(arg) {
    var id = arg.event.id;
    
    $('#editEventId').val(id);
    $('#deleteEvent').attr('data-id', id); 

    $.ajax({
      url:url+"api/getevent.php",
      type:"POST",
      dataType: 'json',
      data:{id:id},
      success: function(data) {
            $('#editEventTitle').val(data.title);
            $('#editStartDate').val(data.start);
            $('#editEndDate').val(data.end);
            $('#editColor').val(data.color);
            $('#editTextColor').val(data.textColor);
            $('#editeventmodal').modal();
        }
    });

    $('body').on('click', '#deleteEvent', function() {
        if(confirm("Are you sure you want to remove it?")) {
            $.ajax({
                url:url+"api/delete.php",
                type:"POST",
                data:{id:arg.event.id},
            }); 

            //close model
            $('#editeventmodal').modal('hide');

            //refresh calendar
            calendar.refetchEvents();         
        }
    });
    
    calendar.refetchEvents();
}
En el modelo para agregar un evento, necesitamos enviar los datos del formulario a PHP y procesar la respuesta, lo hacemos enviando una llamada ajax:
$('#createEvent').submit(function(event) {

    // stop the form refreshing the page
    event.preventDefault();

    $('.form-group').removeClass('has-error'); // remove the error class
    $('.help-block').remove(); // remove the error text

    // process the form
    $.ajax({
        type        : "POST",
        url         : url+'api/insert.php',
        data        : $(this).serialize(),
        dataType    : 'json',
        encode      : true
    }).done(function(data) {

        // insert worked
        if (data.success) {
            
            //remove any form data
            $('#createEvent').trigger("reset");

            //close model
            $('#addeventmodal').modal('hide');

            //refresh calendar
            calendar.refetchEvents();

        } else {

            //if error exists update html
            if (data.errors.date) {
                $('#date-group').addClass('has-error');
                $('#date-group').append('<div class="help-block">' + data.errors.date + '</div>');
            }

            if (data.errors.title) {
                $('#title-group').addClass('has-error');
                $('#title-group').append('<div class="help-block">' + data.errors.title + '</div>');
            }

        }

    });
});
El proceso para editar un evento es similar:
$('#editEvent').submit(function(event) {

    // stop the form refreshing the page
    event.preventDefault();

    $('.form-group').removeClass('has-error'); // remove the error class
    $('.help-block').remove(); // remove the error text

    //form data
    var id = $('#editEventId').val();
    var title = $('#editEventTitle').val();
    var start = $('#editStartDate').val();
    var end = $('#editEndDate').val();
    var color = $('#editColor').val();
    var textColor = $('#editTextColor').val();

    // process the form
    $.ajax({
        type        : "POST",
        url         : url+'api/update.php',
        data        : {
            id:id, 
            title:title, 
            start:start,
            end:end,
            color:color,
            text_color:textColor
        },
        dataType    : 'json',
        encode      : true
    }).done(function(data) {

        // insert worked
        if (data.success) {
            
            //remove any form data
            $('#editEvent').trigger("reset");

            //close model
            $('#editeventmodal').modal('hide');

            //refresh calendar
            calendar.refetchEvents();

        } else {

            //if error exists update html
            if (data.errors.date) {
                $('#date-group').addClass('has-error');
                $('#date-group').append('<div class="help-block">' + data.errors.date + '</div>');
            }

            if (data.errors.title) {
                $('#title-group').addClass('has-error');
                $('#title-group').append('<div class="help-block">' + data.errors.title + '</div>');
            }

        }

    });
});
En este punto tenemos el calendario y todas las llamadas ajax a continuación necesitamos PHP para procesar las solicitudes.
Cree una carpeta llamada api y los siguientes archivos:

delete.php
getevent.php
insert.php
load.php
update.php

delete.php
Compruebe si existe una identificación en la solicitud POST y, si lo hace, intente eliminar el evento donde la identificación publicada coincide con una identificación de la tabla de eventos.
include("../config.php");

if (isset($_POST["id"])) {
    $db->delete('events', ['id' => $_POST['id']]);
}
getevent
Compruebe si existe una identificación en la solicitud POST, cargue el evento que coincida con la identificación y luego cree una matriz de datos que coincida con la estructura que necesita el calendario y finalmente devuelva la matriz como JSON.
<?php
include("../config.php");

if (isset($_POST['id'])) {
    $row = $db->find("* FROM events where id=?", [$_POST['id']]);
    $data = [
        'id'        => $row->id,
        'title'     => $row->title,
        'start'     => date('d-m-Y H:i:s', strtotime($row->start_event)),
        'end'       => date('d-m-Y H:i:s', strtotime($row->end_event)),
        'color'     => $row->color,
        'textColor' => $row->text_color
    ];

    echo json_encode($data);
}
insert.php
aquí validamos que los datos cumplan con nuestros requisitos asegurándonos de que el título, el inicio y el final no estén vacíos y luego formateemos los datos antes de insertarlos. Siempre devolvemos datos JSON al usuario, se pueden mostrar errores de validación o cerrar el modelo en caso de éxito.
<?php
include("../config.php");

if (isset($_POST['title'])) {

    //collect data
    $error      = null;
    $title      = $_POST['title'];
    $start      = $_POST['startDate'];
    $end        = $_POST['startDate'];
    $color      = $_POST['color'];
    $text_color = $_POST['text_color'];

    //validation
    if ($title == '') {
        $error['title'] = 'Title is required';
    }

    if ($start == '') {
        $error['start'] = 'Start date is required';
    }

    if ($end == '') {
        $error['end'] = 'End date is required';
    }

    //if there are no errors, carry on
    if (! isset($error)) {

        //format date
        $start = date('Y-m-d H:i:s', strtotime($start));
        $end = date('Y-m-d H:i:s', strtotime($end));
        
        $data['success'] = true;
        $data['message'] = 'Success!';

        //store
        $insert = [
            'title'       => $title,
            'start_event' => $start,
            'end_event'   => $end,
            'color'       => $color,
            'text_color'  => $text_color
        ];
        $db->insert('events', $insert);
      
    } else {

        $data['success'] = false;
        $data['errors'] = $error;
    }

    echo json_encode($data);
}
load.php
este archivo carga todos los eventos para que el calendario los lea
<?php
include("../config.php");
$data = [];

$result = $db->select("* FROM events ORDER BY id");
foreach($result as $row) {
    $data[] = [
        'id'              => $row->id,
        'title'           => $row->title,
        'start'           => $row->start_event,
        'end'             => $row->end_event,
        'backgroundColor' => $row->color,
        'textColor'       => $row->text_color
    ];
}

echo json_encode($data);
update.php
esto nuevamente validará todos los campos obligatorios que se hayan ingresado y luego realizará una actualización 
<?php
include("../config.php");

if (isset($_POST['id'])) {

    //collect data
    $error      = null;
    $id         = $_POST['id'];
    $start      = $_POST['start'];
    $end        = $_POST['end'];

    //optional fields
    $title      = isset($_POST['title']) ? $_POST['title']: '';
    $color      = isset($_POST['color']) ? $_POST['color']: '';
    $text_color = isset($_POST['text_color']) ? $_POST['text_color']: '';

    //validation
    if ($start == '') {
        $error['start'] = 'Start date is required';
    }

    if ($end == '') {
        $error['end'] = 'End date is required';
    }

    //if there are no errors, carry on
    if (! isset($error)) {

        //reformat date
        $start = date('Y-m-d H:i:s', strtotime($start));
        $end = date('Y-m-d H:i:s', strtotime($end));
        
        $data['success'] = true;
        $data['message'] = 'Success!';

        //set core update array
        $update = [
            'start_event' => date('Y-m-d H:i:s', strtotime($_POST['start'])),
            'end_event' => date('Y-m-d H:i:s', strtotime($_POST['end']))
        ];

        //check for additional fields, and add to $update array if they exist
        if ($title !='') {
            $update['title'] = $title;
        }

        if ($color !='') {
            $update['color'] = $color;
        }

        if ($text_color !='') {
            $update['text_color'] = $text_color;
        }

        //set the where condition ie where id = 2
        $where = ['id' => $_POST['id']];

        //update database
        $db->update('events', $update, $where);
      
    } else {

        $data['success'] = false;
        $data['errors'] = $error;
    }

    echo json_encode($data);
}
En este punto, los eventos se pueden agregar, editar, eliminar y arrastrar a otros días.