Mhluzi Bhaka
Mhluzi Bhaka

Reputation: 1392

Fullcalendar adding events across timezones

I am clearly missing something completely fundamental regarding creating events in FullCalendar so am seeing if perhaps anyone has a solution or any advice.

Objective: Have a calendar function that counselors can use to let clients know what their opening times are (times are not full day and can be as short as one hour).

The counselors and clients are not necessarily going to be in the same timezone so the events must be created in a way to reflect the time appropriate to the individual time zones.

For example, a counselor (in New Zealand) creates an available event starting at:

Friday, 1 March 2019 at 7:00:00 a.m.

The client in New York should see that event as:

Thursday, 28 February 2019 at 1:00:00 p.m

This is something that I am struggling to achieve and am not sure if it is even possible with FullCalendar

The "timezone" option doesn't seem to make any difference when creating events (and yes, I perfectly accept that I might be doing something completely wrong).

In this calendar fiddle I have the timezone set as follows:

$('#calendar').fullCalendar({
    header: {
      left: 'prev,next today',
      center: 'title',
      right: 'month,agendaWeek,agendaDay'
    },
    timezone: 'Pacific/Auckland'
    ....
});

When I select 7am on March 1, 2019 FullCalendar generates the start date as UTC:

Fri Mar 01 2019 07:00:00 GMT+0000

If I use moment.tz to format that start date as Pacific/Auckland:

console.log(moment.tz(startDate, 'Pacific/Auckland').format());

I get:

2019-03-01T20:00:00+13:00

Which is a correct conversion of the original start date but obviously of no use in this instance as it is not the date expected.

If I drag and drop an event to 7am March 1, 2019 (see same fiddle) and then click on it to write out the event object to the console using:

eventClick: function(event, element) 
{
    console.log("event: ");
    console.log(event);
},

The following is displayed (shortened here for simplicity):

    "_fc3"
    _start 
    Fri Mar 01 2019 07:00:00 GMT+0000 {...}
    _a
    [2019, 2, 1, 4 more...]
    _ambigTime
    false
    _ambigZone
    true
   _d
   Fri Mar 01 2019 20:00:00 GMT+1300 (New Zealand Daylight Time) {}

So _start is Fri Mar 01 2019 07:00:00 GMT+0000 - which (ignoring the offset for now) is the time that it should be (as in, it should be 7am)

and _d is Fri Mar 01 2019 20:00:00 GMT+1300 (New Zealand Daylight Time) .

But I clearly can't save the value of _d as that is obviously not the desired time in this instance.

Also, if I save that _start date as it is to a MySQL datetime field it gets stored as "2019-03-01 07:00:00", (the MySQL server is set to UTC) and if I import that into the calendar it displays as 7am on March 1 2019 and doesn't factor in the user timezone.

As always I have checked SO for possible answers but no answers seem to address this issue.

As I said, I hope that I am just missing something fundamental.

Any suggestions would be greatly appreciated.

EDIT: Updated fiddle

EDIT Added image and explanation

In the fiddle (described in this image):

image of fiddle

  1. Drag this to 7am (for example)
  2. Click the event to access the event object
  3. this shows the time in 4. as UTC/GMT
  4. this shows the time in 6. as UTC/GMT
  5. this shows the time in 8. as New Zealand timezone (for me as I am in NZ)

So I guess what I am missing here is how to get the public start property

What I am looking for/expecting is a start time of:

Sat Mar 02 2019 07:00:00 GMT+1300 (New Zealand Daylight Time)

which is a UTC time of :

Friday, 1 March 2019 at 6:00:00 p.m. UTC

So that UTC Friday time is what I would expect to save in the database.

Note: I am actually in New Zealand so the fiddle will be different for anyone outside of that time zone.

Upvotes: 0

Views: 4714

Answers (1)

Invincible
Invincible

Reputation: 1460

I've done similar project by storing all date times in UTC. This way it was easy to show time in any timezones.

I've achieved above as below:

  • step 1: I created a dropdown to select the timezone.
  • step 2: initialise fullcalendar on the page
  • step 3: with moment.js, guess users timezone.
  • step 4: convert the calendar timezone to UTC
  • step 5: Ajax to save the dropped event with UTC time
  • step 6: when rendering the event, show the default timezone from the dropdown

else show user selected timezone.

  let calendar = $("#calendar").fullCalendar({
        defaultView: "agendaWeek",
        header: {
            left: "prev,next today",
            center: "title",
            right: "month,agendaWeek,agendaDay"
        },
        now: moment().format("Y-MM-DD HH:mm:ss"),
        height: "auto",
        slotDuration: "00:30:00",
        defaultTimedEventDuration: "00:30:00",
        timezone: "local",
        weekends: false,
        dragRevertDuration: 0,
        allDaySlot: false,
        slotEventOverlap: false,
        editable: true,
        eventStartEditable: true,
        eventDurationEditable: true,
        droppable: true,
        eventSources: [
            fcSources.loadEvents,
            fcSources.loadEwsEvents
        ],
        businessHours: [ // highlight working hours
            {
                dow: [ 1, 2, 3, 4, 5], // weekdays
                start: '08:00',
                end: '18:00'
            }
        ],
        drop: function (date) {
            //Call when you drop slot for the first time
            let defaultDuration = moment.duration($("#calendar").fullCalendar("option", "defaultTimedEventDuration"));
            $("input[name='EVENTS[ID]']").val(null);
            $("input[name='EVENTS[TITLE]']").val($(this).data().event.title);
            $("input[name='EVENTS[EVENT_START]']").val($(this).convertTimeToUTC(date));
            $("input[name='EVENTS[EVENT_END]']").val($(this).convertTimeToUTC(date.clone().add(defaultDuration)));
            $(this).addCalendarEvent();
        },
        eventOverlap: function (stillEvent, movingEvent) {
            return stillEvent.allDay && movingEvent.allDay;
        },
        eventDrop: function (event) {
                $("input[name='EVENTS[ID]']").val(event.id);
                $("input[name='EVENTS[TITLE]']").val(event.title);
                $("input[name='EVENTS[EVENT_START]']").val($(this).convertTimeToUTC(event.start));
                $("input[name='EVENTS[EVENT_END]']").val($(this).convertTimeToUTC(event.end));
            //if the call is confirmed or complete then do not move it
                if ($.trim(event.status) !== "COMPLETE" && $.trim(event.status) !== "CONFIRMED") {
                    $(this).updateCalendarEvent();
                }
        }
    });

//then select dropdown

  //self initialising function for timezones dropdown
    (function(window, document, $, undefined){
        const _t = (s) => {
            if (i18n !== void 0 && i18n[s]) {
                return i18n[s];
            }
            return s;
        };
        const timezones = [
            "Etc/GMT+12",
            "Pacific/Midway",
            "Pacific/Honolulu",
            "America/Juneau",
            "America/Dawson",
            "America/Boise",
            "America/Chihuahua",
            "America/Phoenix",
            "America/Chicago",
            "America/Regina",
            "America/Mexico_City",
            "America/Belize",
            "America/Detroit",
            "America/Indiana/Indianapolis",
            "America/Bogota",
            "America/Glace_Bay",
            "America/Caracas",
            "America/Santiago",
            "America/St_Johns",
            "America/Sao_Paulo",
            "America/Argentina/Buenos_Aires",
            "America/Godthab",
            "Etc/GMT+2",
            "Atlantic/Azores",
            "Atlantic/Cape_Verde",
            "GMT",
            "Africa/Casablanca",
            "Atlantic/Canary",
            "Europe/Belgrade",
            "Europe/Sarajevo",
            "Europe/Brussels",
            "Europe/Amsterdam",
            "Africa/Algiers",
            "Europe/Bucharest",
            "Africa/Cairo",
            "Europe/London",
            "Europe/Helsinki",
            "Europe/Athens",
            "Asia/Jerusalem",
            "Africa/Harare",
            "Europe/Moscow",
            "Asia/Kuwait",
            "Africa/Nairobi",
            "Asia/Baghdad",
            "Asia/Tehran",
            "Asia/Dubai",
            "Asia/Baku",
            "Asia/Kabul",
            "Asia/Yekaterinburg",
            "Asia/Karachi",
            "Asia/Kolkata",
            "Asia/Kathmandu",
            "Asia/Dhaka",
            "Asia/Colombo",
            "Asia/Almaty",
            "Asia/Rangoon",
            "Asia/Bangkok",
            "Asia/Krasnoyarsk",
            "Asia/Shanghai",
            "Asia/Kuala_Lumpur",
            "Asia/Taipei",
            "Australia/Perth",
            "Asia/Irkutsk",
            "Asia/Seoul",
            "Asia/Tokyo",
            "Asia/Yakutsk",
            "Australia/Darwin",
            "Australia/Adelaide",
            "Australia/Sydney",
            "Australia/Brisbane",
            "Australia/Hobart",
            "Asia/Vladivostok",
            "Pacific/Guam",
            "Asia/Magadan",
            "Pacific/Fiji",
            "Pacific/Auckland",
            "Pacific/Tongatapu"
        ];
        const i18n = {
            "Etc/GMT+12": "International Date Line West",
            "Pacific/Midway": "Midway Island, Samoa",
            "Pacific/Honolulu": "Hawaii",
            "America/Juneau": "Alaska",
            "America/Dawson": "Pacific Time (US and Canada); Tijuana",
            "America/Boise": "Mountain Time (US and Canada)",
            "America/Chihuahua": "Chihuahua, La Paz, Mazatlan",
            "America/Phoenix": "Arizona",
            "America/Chicago": "Central Time (US and Canada)",
            "America/Regina": "Saskatchewan",
            "America/Mexico_City": "Guadalajara, Mexico City, Monterrey",
            "America/Belize": "Central America",
            "America/Detroit": "Eastern Time (US and Canada)",
            "America/Indiana/Indianapolis": "Indiana (East)",
            "America/Bogota": "Bogota, Lima, Quito",
            "America/Glace_Bay": "Atlantic Time (Canada)",
            "America/Caracas": "Caracas, La Paz",
            "America/Santiago": "Santiago",
            "America/St_Johns": "Newfoundland and Labrador",
            "America/Sao_Paulo": "Brasilia",
            "America/Argentina/Buenos_Aires": "Buenos Aires, Georgetown",
            "America/Godthab": "Greenland",
            "Etc/GMT+2": "Mid-Atlantic",
            "Atlantic/Azores": "Azores",
            "Atlantic/Cape_Verde": "Cape Verde Islands",
            "Europe/London": "Dublin, Edinburgh, Lisbon, London",
            "Africa/Casablanca": "Casablanca, Monrovia",
            "Atlantic/Canary": "Canary Islands",
            "Europe/Belgrade": "Belgrade, Bratislava, Budapest, Ljubljana, Prague",
            "Europe/Sarajevo": "Sarajevo, Skopje, Warsaw, Zagreb",
            "Europe/Brussels": "Brussels, Copenhagen, Madrid, Paris",
            "Europe/Amsterdam": "Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna",
            "Africa/Algiers": "West Central Africa",
            "Europe/Bucharest": "Bucharest",
            "Africa/Cairo": "Cairo",
            "Europe/Helsinki": "Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius",
            "Europe/Athens": "Athens, Istanbul, Minsk",
            "Asia/Jerusalem": "Jerusalem",
            "Africa/Harare": "Harare, Pretoria",
            "Europe/Moscow": "Moscow, St. Petersburg, Volgograd",
            "Asia/Kuwait": "Kuwait, Riyadh",
            "Africa/Nairobi": "Nairobi",
            "Asia/Baghdad": "Baghdad",
            "Asia/Tehran": "Tehran",
            "Asia/Dubai": "Abu Dhabi, Muscat",
            "Asia/Baku": "Baku, Tbilisi, Yerevan",
            "Asia/Kabul": "Kabul",
            "Asia/Yekaterinburg": "Ekaterinburg",
            "Asia/Karachi": "Islamabad, Karachi, Tashkent",
            "Asia/Kolkata": "Chennai, Kolkata, Mumbai, New Delhi",
            "Asia/Kathmandu": "Kathmandu",
            "Asia/Dhaka": "Astana, Dhaka",
            "Asia/Colombo": "Sri Jayawardenepura",
            "Asia/Almaty": "Almaty, Novosibirsk",
            "Asia/Rangoon": "Yangon Rangoon",
            "Asia/Bangkok": "Bangkok, Hanoi, Jakarta",
            "Asia/Krasnoyarsk": "Krasnoyarsk",
            "Asia/Shanghai": "Beijing, Chongqing, Hong Kong SAR, Urumqi",
            "Asia/Kuala_Lumpur": "Kuala Lumpur, Singapore",
            "Asia/Taipei": "Taipei",
            "Australia/Perth": "Perth",
            "Asia/Irkutsk": "Irkutsk, Ulaanbaatar",
            "Asia/Seoul": "Seoul",
            "Asia/Tokyo": "Osaka, Sapporo, Tokyo",
            "Asia/Yakutsk": "Yakutsk",
            "Australia/Darwin": "Darwin",
            "Australia/Adelaide": "Adelaide",
            "Australia/Sydney": "Canberra, Melbourne, Sydney",
            "Australia/Brisbane": "Brisbane",
            "Australia/Hobart": "Hobart",
            "Asia/Vladivostok": "Vladivostok",
            "Pacific/Guam": "Guam, Port Moresby",
            "Asia/Magadan": "Magadan, Solomon Islands, New Caledonia",
            "Pacific/Fiji": "Fiji Islands, Kamchatka, Marshall Islands",
            "Pacific/Auckland": "Auckland, Wellington",
            "Pacific/Tongatapu": "Nuku'alofa"
        };
        //get current time
        const dateTimeUtc = moment().utc();
        document.querySelector(".js-TimeUtc").innerHTML = dateTimeUtc.format("ddd, DD MMM YYYY HH:mm:ss");
        const dateTimeLocal = moment().utc(moment.tz.guess());
        document.querySelector(".js-TimeLocal").innerHTML = dateTimeLocal.format("ddd, DD MMM YYYY HH:mm:ss");
        const selectorOptions = moment.tz.names()
            .filter(tz => {
                return timezones.includes(tz)
            })
            .reduce((memo, tz) => {
                memo.push({
                    name: tz,
                    offset: moment.tz(tz).utcOffset()
                });

                return memo;
            }, [])
            .sort((a, b) => {
                return a.offset - b.offset
            })
            .reduce((memo, tz) => {
                const timezone = tz.offset ? moment.tz(tz.name).format('Z') : '';
                return memo.concat(`<option value="${tz.name}">${_t(tz.name)}</option>`);
            }, "");

        document.querySelector(".js-Selector").innerHTML = selectorOptions;

        $(".js-Selector").on("change", e => {
            const timestamp = dateTimeUtc.unix();
            const offset = moment.tz(e.target.value).utcOffset() * 60;
            const dateTimeSelected = moment.unix(timestamp + offset).utc();
            document.querySelector(".js-TimeSelected").innerHTML = dateTimeSelected.format("ddd, DD MMM YYYY HH:mm:ss");
            timeZone = $(".js-Selector").val();
            if(timeZone !=  moment.tz.guess()) {
                let timezoneWarningMessage = '<div class="alert alert-warning timezone-warning" role="alert" class="btn btn-indigo btn-sm ml-0 waves-effect waves-light bg-warning timezone-warning">' +
                    'Your timezone has been changed to <b>' + timeZone + '</b></div>';

                if($(".timezone-warning").length > 0) {
                    $(".timezone-warning").replaceWith(timezoneWarningMessage);
                } else {
                    $(".fc-view-container").prepend(timezoneWarningMessage);
                }
            } else {
                $(".timezone-warning").replaceWith('');
            }
            $('#calendar').fullCalendar('option', 'timezone', $(".js-Selector").val() || false);
        });

        document.querySelector(".js-Selector").value = "Europe/London";
        const event = new Event("change");
        document.querySelector(".js-Selector").dispatchEvent(event);
        $(".js-Selector").chosen();
    })(window, document, jQuery);

//now pass the selected timezone to eventsource

     let loadEvents: {
                url: "", //your url,
                type: "GET",
                cache: true,
                dataType: "json",
                success: function (data) {
                    //based on the dropdown changetimezone of each event 

                    let updatedTime = [];
                    $.each(data.events, function( k, v ) {
                        v.start = moment.tz(v.start, timeZone);
                        v.end = moment.tz(v.end, timeZone);
                        updatedTime[k] = v ;
                    });
                    return updatedTime;
                }
            }

 //function to convert fullcalendar time to utc
    $.fn.convertTimeToUTC = function (convertTime) {
       if($(this).isObject(convertTime)) {
            return moment.tz(convertTime.format("Y-MM-DD HH:mm:ss"), moment.tz.guess()).utc().format("Y-MM-DD HH:mm:ss");
        }
    };
    // Returns if a value is an object
    $.fn.isObject =  function(value) {
        return value && typeof value === 'object';
    };

Upvotes: 1

Related Questions