Skip to content

Instantly share code, notes, and snippets.

@arshaw
Last active June 17, 2016 10:19
Show Gist options
  • Save arshaw/6420506 to your computer and use it in GitHub Desktop.
Save arshaw/6420506 to your computer and use it in GitHub Desktop.

Proposal for a date system overhaul

The Problem

FullCalendar was initially designed without much notion of timezones. By default, it ignores timezone offsets in the dates it receives.

The original assumption was that if you received a date from Brussels, say "2013-09-01T12:00:00+02:00", which is noon, it would display as noon in every timezone.

However, FullCalendar shoehorns this value into a local date. With the same example, if you were in San Francisco, it internally stores the date as "2013-09-01T12:00:00-08:00". This is bad for two reasons:

  1. The underlying JavaScript Date's milliseconds-since-epoch value no longer accurately represents when the date is. As a result, the getUTC*/setUTC* methods are busted.

  2. The date/time values, which can be accessed via the set*/get* methods, might not exist in the browser's timezone because of daylight savings!

Most importantly, all dates everywhere, regardless of the end-user's timezone, are displayed the same. This makes it impossible for people across the world to view dates that are adjusted to their particular local timezones.

The ignoreTimezone option was created to rememdy this (when set to false), but the problems related to shoehorning everything into local dates still exist.

FullCalendar needs a stronger notion of timezones.

FullCalendar also needs better default support for languages / internationalization. The text/time customization options are not enough.

The Solution

Moment.js should be leveraged.

All dates, everywhere in the API, will now be Moment objects.

Encourage use of ISO8601 dates everywhere, because they can contain timezone information (or lackthereof) and time information (or lackthereof).

This release will break API backwards-compatibility in a bunch of places and likely be called version 2.0.

Change the way event dates are parsed

When a date string has no specified timezone designator, like 2013-09-01T12:00:00, and the utc option is false (the default), the date will be parsed as local. If utc is true, the date will be parsed in UTC.

No more ignoring the timezone. We will remove the ignoreTimezone option.

Also, when utc is set to true, all date values in the API will be in UTC-mode.

Change the way dates are sent over AJAX

Event sources currently send unix timestamps. This should change to ISO8601 dates.

By default, no timezone indicator is sent:

$('#calendar').fullCalendar({
    events: 'feed.php'
		// start = 2013-09-01T00:00:00
		//   end = 2013-10-06T00:00:00
});

Setting the transmitTZD options to true will send the current timezone indicator. If in local mode:

$('#calendar').fullCalendar({
	transmitTZD: true
	events: 'feed.php'
		// In in San Francisco, will send:
		// start = 2013-09-01T00:00:00-07:00
		//   end = 2013-10-06T00:00:00-07:00
});

If in UTC mode, it will send Z as the timezone indicator.

$('#calendar').fullCalendar({
	utc: true,
	transmitTZD: true
	events: 'feed.php'
		// In any timezone, will send:
		// start = 2013-09-01T00:00:00Z
		//   end = 2013-10-06T00:00:00Z
});

Basic example timezone configurations

Scenario: Everyone accessing your calendar is in the same timezone. Dates are displayed in each browser's local timezone, with daylight savings applied:

$('#calendar').fullCalendar({
	events: {
		url: 'feed.php'
	}
	// examples request:
	//   start = 2013-09-01T00:00:00
	//     end = 2013-10-06T00:00:00
	//
	// your feed's dates should NOT have timezone indicators.
	// example response:
	// [
	//   {
	//     "title": "my event",
	//     "start": "2013-09-01T12:00:00" // displayed as noon in all timezones
	//   }
	// ]
});

Scenario: All dates are in a "generic" timezone and you don't want the unpredictability of daylight savings. Use UTC instead:

$('#calendar').fullCalendar({
	utc: true,
	events: {
		url: 'feed.php'
	}
	// examples request:
	//   start = 2013-09-01T00:00:00
	//     end = 2013-10-06T00:00:00
	//
	// your feed's dates should have a 'Z' timezone indicator, or none at all.
	// example response:
	// [
	//   {
	//     "title": "my event",
	//     "start": "2013-09-01T12:00:00" // always displayed as noon
	//   }
	// ]
});

Scenario: Your dates are stored in one timezone, but displayed in each browser's individual local timezone. They should appear different across the world:

$('#calendar').fullCalendar({
	transmitTZD: true,
	events: {
		url: 'feed.php'
	}
	// examples request, from San Francisco:
	//   start = 2013-09-01T00:00:00-07:00
	//     end = 2013-10-06T00:00:00-07:00
	//
	// your feed SHOULD return dates with timezone indicators.
	// example response:
	// [
	//   {
	//     "title": "my event",
	//     "start": "2013-09-01T12:00:00Z" // different across the world, but noon in San Fran
	//   }
	// ]
});

Other timezones?

What if you want the calendar to operate in timezones other than UTC or the browser's local timezone?

Say you are living in Brussels, with a browser in the "Europe/Brussels" timezone, but you want the calendar to appear as if it is in San Francisco?

In JavaScript, it's really hard and cumbersome to represent timezones other than the one the browser naturally has configured, so let's just mock the timezone using UTC.

You'll want to do three things:

  1. Turn on UTC mode so you don't experience any daylight-savings quirks from the browser's local timezone

  2. Make sure the feed script is aware of the timezone we want to operate in, via an additional GET parameter ("timezone" in this example)

  3. Send the dates back to FullCalendar in "Europe/Brussels" time, but with no timezone designator, so FullCalendar interprets them as UTC dates

Example:

$('#calendar').fullCalendar({
	utc: true,
	events: {
		url: 'feed.php',
		data: {
			timezone: 'Europe/Brussels'
		}
	}
	// examples request, from San Francisco:
	//   start = 2013-09-01T00:00:00
	//     end = 2013-10-06T00:00:00
	//
	// your feed's dates should NOT have timezone indicators.
	// example response:
	// [
	//   {
	//     "title": "my event",
	//     "start": "2013-09-01T12:00:00" // always displayed as noon
	//   }
	// ]
});

Changing today's date

If we simulate another timezone in the browser, the today date might be different, at most off-by-one. Maybe add an option to set the current todayDate? (issue 593).

Implicit allDay value

When receiving and parsing incoming event data, the allDay can be encoded into the date string:

{
	title: 'my event',
	start: '2013-09-01' // implies allDay=true
}

{
	title: 'my event',
	start: '2013-09-01T00:50:00' // implies allDay=false
}

{
	title: 'my event',
	start: '2013-09-01T00:50:00Z' // implies allDay=false
}

While we're at it: event end dates inclusivity/exclusivity

The end property of the Event Object has a troubled past. Prior to version 1.3, it was always exclusive. Some people, especially ones that only ever wanted all-day events, found this confusing because it is counter to how one verbally communicates the ending of an event.

The other issue was that daylight-savings would sometimes push an event's exclusive end time, which is often 00:00:00 (midnight), into the next day! (00:01:00). This would cause events to appear one day longer, seemingly for no reason!

The solution at the time seemed to be to make end dates inclusive, but only for all-day events. This seemed to cause equal amounts of confusion, as programmers often think about end-indices as exclusive.

While we're redesigning all-things date, it might be worth considering this again. At this point, I personally prefer to have exclusive end times, always. It is more consistent. Also, the iCalendar protocol does it this way too.

As for the DST issue with dates flowing into the next day, I know there is a good solution. Issue 1049 proposes that events should have a certain threshold they must cross before they are considered to be in the next day. Google calendar implements this, and events must end before 10am the next day. I think we should make this a configurable setting, but have a reasonable default (like 10am).

Options that accept times

FullCalendar should accept ISO8601-style strings for times. Like "03:00" or "3:00" or "3am". Similar to what the internal parseTime method already does in the current codebase.

We should try to leverage Moment for this.

minTime
maxTime
scrollTime (formerly firstHour)

Options that accept durations

Leverage Moment's Duration object for this.

slotDuration (formerly slotMinutes)
snapDuration (formerly snapMinutes)
defaultEventDisplayDuration (formerly defaultEventMinutes)
defaultEventDuration (use this to calulate event.end when not specified)

Date formatting options

All formatting-related options, like timeFormat and titleFormat, should now use Moment's formatting codes.

Date range formatting will no longer be explicitly specified with formatting strings (like h:mm{ - h:mm}), but rather, a formatting string for a single date will be provided and FullCalendar will be smart about inserting a dash between the two dates, like Sep 2 - 9 2013.

i18n

FullCalendar will leverge Moment's existing translations, as well as jQuery UI datepicker's translations. Neither single library alone has enough info, se we'll have to package them as one and also provide some additional strings.

<script src='/js/fullcalendar/lang/fr.js'></script>

See what fr.js might look like.

@vaab
Copy link

vaab commented Sep 16, 2013

Sorry for this late comment...

Adopting Moment.js is a decision that should be carefully concidered. While "moment.js" is a good library, I don't feel there's a javascript library that hits all target right now. And as the pull request I've provided, I'm more concerned about the flexibility of fullcalendar code to be plugged to various library.

This is not concerns against including Moment.js, but against an architecture that would too heavily rely on one library and as a consequence wouldn't allow some external tweaks.

For example, I feel that the formatting/internationalizing options of Moments lack some elements that I would like to override ponctualy.

I guess that both can be achieved : using Moment.js and keep clean overridable API.

That would be great.

@MuraliM
Copy link

MuraliM commented Oct 23, 2013

@arshaw When this will be ready? I am waiting to enhance my old code with this i18n.

So far I was handled in this way for swedish

var myCulture = {
titleFormat: {
month: 'MMMM yyyy',
week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
day: 'dddd, MMM d, yyyy',
resourceMonth: 'MMMM yyyy',
resourceWeek: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
resourceNextWeeks: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
resourceDay: 'dddd, MMM d, yyyy'
},
columnFormat: {
month: 'ddd',
week: 'ddd M/d',
day: 'dddd M/d',
resourceDay: 'H:mm',
resourceMonth: 'd/M',
resourceWeek: 'ddd d/M',
resourceNextWeeks: 'ddd d/M'
},

        firstDay: 1,
        monthNames: ['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni',
    'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
        monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun',
    'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
        dayNames: ['Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag'],
        dayNamesShort: ['Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör'],
        buttonText: {
            prev: '&nbsp;&#9668;&nbsp;',
            next: '&nbsp;&#9658;&nbsp;',
            prevYear: '&nbsp;&lt;&lt;&nbsp;',
            nextYear: '&nbsp;&gt;&gt;&nbsp;',
            today: 'Idag',
            month: 'månad',
            week: 'vecka',
            day: 'dag',
            resourceDay: 'dag',
            resourceWeek: 'vecka',
            resourceNextWeeks: 'nästa vecka',
            resourceMonth: 'månad'
        },
        weekPrefix: 'vecka'
    };

and

    $.fullCalendar.setDefaults(myCulture);

but now i get error. setDefaults is not supported

@arshaw
Copy link
Author

arshaw commented Jan 25, 2014

@andyl
Copy link

andyl commented Apr 23, 2014

Now that delta parameters have been removed from 'eventDrop' callback, what is the best way to drag/resize recurring events? I think I need a way to calculate 'dayDelta' and 'minuteDelta'...

@arshaw
Copy link
Author

arshaw commented Jun 4, 2014

Hey @andyl, I removed them, but I forget my rational as to why. I think there should be a param for this. A delta argument that is a MomentJS Duration object. Please star this issue and it will very likely be fixed in the next minor release:
https://code.google.com/p/fullcalendar/issues/detail?id=2156

@arshaw
Copy link
Author

arshaw commented Jun 4, 2014

NOTE TO ALL: FullCalendar 2.0 has been release with all these features (or some alternate form of them) and THIS THREAD WILL NOT BE CHECKED FOR FUTURE COMMENTS. Please continue discussions elsewhere, whether on the Issue Tracker, blog post comment, etc

@VinayKumarSarda
Copy link

Hi Arshaw,

I have requirement of adding repeating event that is event which occurs on same day every year. How can i achieve or how should i do it in full calendar?

Regards
Vinay

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment