Fullcalendar and timezones. Help, I'm doing it wrong

Mike picture Mike · Apr 20, 2011 · Viewed 11.9k times · Source

I'm doing it wrong somehow. I'm getting tripped up on timezones with Fullcalendar. I've tried setting ignoreTimezone to true and false, but it doesn't seem to matter. It's in the code below in two places because I wasn't sure from the doc where it goes.

My data source is a hidden form field. Data that goes out of FullCalendar is adjusted by adding 5 hours (CDT). Data that goes in to FullCalendar isn't adjusted by removing 5 hours.

On the back-end, I'm just saving and returning the JSON string without processing it (or even decoding it)

Page Load:
  Data In: Empty, no data
  Data Edit: drag from noon to 2pm (CDT), then submit form
  Data Out: Use clientEvent to get data, and JSON.stringify to put into form field.
    [{"id":6844,"title":"Open","start":"2011-04-19T17:00:00.000Z","end":"2011-04-19T19:00:00.000Z","allDay":false}]

Page Load (after submitting form):
  Data In: Use JSON.parse to load data from hidden form field.  This is the incoming data, but the event is shifted to 5pm (CDT) in the control.  
    [{"id":6844,"title":"Open","start":"2011-04-19T17:00:00.000Z","end":"2011-04-19T19:00:00.000Z","allDay":false}]
  Data Out:  Without changing the control, it's now:
    [{"id":6844,"title":"Open","start":"2011-04-19T22:00:00.000Z","end":"2011-04-20T00:00:00.000Z","allDay":false}]

I setup the Fullcalendar like this:

// Fullcalendar for business hours page

jQuery(document).ready(function() {

  jQuery('#edit-submit').bind("click", business_hours_set);
  jQuery('#edit-preview').bind("click", business_hours_set);

  jQuery('#calendar').fullCalendar({

    // configure display
    header: {
      left: '',
      center: '',
      right: ''
    },
    ignoreTimezone: false,
    defaultView: 'agendaWeek',
    allDaySlot: false,
    firstHour: 8,

    // configure selection for event creation
    selectable: true,
    selectHelper: true,
    select: business_hours_add,

    // configure data source
    editable: true,
    eventSources: [
    {
      events: jQuery.parseJSON(jQuery('#fullcalendar_data').val()),
      color: '#992B0A',
      textColor: 'white',
      ignoreTimezone: false
    }
    ],

    // configure editing
    eventClick: function(calEvent) {
      business_hours_delete(calEvent.id);
    }
  });
  alert(jQuery('#fullcalendar_data').val());
});

function business_hours_add(startDate, endDate) {
  var calendar = jQuery('#calendar');
  var newid = Math.ceil(Math.random()*64000);
  calendar.fullCalendar('renderEvent',
  {
    id: newid,
    title: "Open",
    start: startDate,
    end: endDate,
    allDay: false
  },
  true // make the event "stick"
  );
  calendar.fullCalendar('unselect');
}

var business_hours_selectedId = -1;
function business_hours_delete(id) {

  business_hours_selectedId = id;

  jQuery( "#dialog-confirm" ).dialog({
    resizable: false,
    height:160,
    modal: true,
    buttons: {
      "Yes, delete!": function() {
        calendar = jQuery('#calendar');
        calendar.fullCalendar( 'removeEvents', business_hours_selectedId);
        jQuery( this ).dialog( "close" );
      },
      Cancel: function() {
        jQuery( this ).dialog( "close" );
      }
    }
  }, id);
}

function business_hours_set() {
  var data = jQuery('#calendar').fullCalendar( 'clientEvents' );

  // data is cyclical.  Create a new data structure to stringify.
  var ret = [];
  for(var i=0; i<data.length; i++) {
    var datum = {
      id: data[i].id,
      title: data[i].title,
      start: data[i].start,
      end: data[i].end,
      allDay: data[i].allDay
    }
    ret[i] = datum;
  }
  // stringify and return
  jQuery('#fullcalendar_data').val(JSON.stringify(ret));
  alert(JSON.stringify(ret));
}

What am I doing wrong?

Thanks in advance, Mike

Answer

Borgar picture Borgar · Apr 20, 2011

You are serializing CDT-adjusted dates as UTC dates (thus getting a 5 hour shift) so when they are read back in they get re-adjusted to CDT, and so on..

Because there isn't a way to set a timezone on JS date objects, Fullcalendar represents them internally as UTC dates, but adjusts for timezone offset on input time.

$.fullCalendar.parseISO8601('2011-04-19T17:00:00.000-05:00');
// Tue Apr 19 2011 22:00:00 GMT+0000 (GMT)  <-- note time shift

This is why, when you serialize to JSON, you get a string with the "Zulu" (UTC) timezone:

var dt = $.fullCalendar.parseISO8601('2011-04-19T17:00:00.000-05:00');
JSON.stringify( dt ); // "2011-04-19T22:00:00.000Z"

You need the date back to your timezone. It doesn't look like Fullcalendar has this so you'll need to to the work:

// detect local timezone offset
var localoffset = (new Date()).getTimezoneOffset();
// "unadjust" date
ret = new Date( ret.valueOf() + (localoffset * 60 * 1000) );

// serialize
function pad (n) { return String(n).replace(/^(-?)(\d)$/,'$10$2'); }
JSON.stringify( ret )
     // replace Z timezone with current
     .replace('Z', pad(Math.floor(localoffset / 60))+':'+ pad(localoffset % 60));

// should result in something like: "2011-04-21T19:00:00.000-05:00"

There may be a better way of solving this using Fullcalendar but I am not familiar with it.

Code is untested: I live in GMT with no-DST and don't really want to mess with my system just to see it work (YMMW). :-)