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:
-
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. -
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.
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.
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.
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
});
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
// }
// ]
});
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:
-
Turn on UTC mode so you don't experience any daylight-savings quirks from the browser's local timezone
-
Make sure the feed script is aware of the timezone we want to operate in, via an additional GET parameter ("timezone" in this example)
-
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
// }
// ]
});
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).
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
}
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).
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)
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)
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
.
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.
Hi Adam,
First, thanks for taking this seriously. I know it's difficult for any project to consider such a drastic change this far along. Clearly this would be a breaking change for full calendar 2.0, and I'm sure that some of your audience might not fully appreciate the changes. But I think it's a move in the right direction.
With regards to moment.js - I am a big fan. It's an excellent library, and has a lot of respect and momentum (pun intended). And I know I suggested this to you almost a year ago, but I've learned a lot about moment since then, and date/time in JavaScript in general.
One thing I've learned is that there is not currently any one library that addresses all JavaScript date/time concerns. Moment addresses many items, but not all of them. Occasionally, you need a more elaborate parser, like Sugar, or you have to go deep and do your own string manipulation from
Date
parts.Your XDate library is fairly decent, but I see that you are moving on from that. (I'm glad to see you doing it proactively though, there are too many people who still think DateJS is a good idea, even though it's clearly been abandoned. The authors never said goodbye, they just left!)
Moment is probably one of the most comprehensive libraries available, and it is certainly well tested. But it does have some deficiencies. Let me share some of my thoughts about how those might affect FullCalendar.
First, you should read through my comments in issue #961. Basically, the concern is that certain functions in moment allow for approximate answers. The various places this can happen are not very well documented.
Moment is first and foremost about parsing and formatting, which it does an excellent job of. But when it comes to manipulation and calculation, it places flexibility first. Sometimes this comes with the cost of not necessarily being 100% accurate. There are more discussions about this in issue #854 and issue #345.
If you do use moment, the main point would be to be absolutely sure you are performing an operation that results in a precise answer. For example, don't attempt to measure a duration in terms of "months" and use the the result in an algorithm.
I have pitched an idea for a separate library that places a higher priority on accuracy, over flexibility. It would be loosely based on the same API that Noda Time uses - which in turn is very similar to Joda Time and the upcoming JSR-310. The key differences from moment would be:
If you are at all interested in collaborating on this, I'd be very happy to discuss it further. It may be a better fit than moment for certain features of the new FullCalendar. I would still suggest using moment for formatting and parsing, and especially for internationalization.
Even if you don't decide to go down this route, you might want to consider representing calendar items with numeric values instead of with
Date
ormoment
values. You can always craft aDate
or amoment
when needed, but I don't think using them internally is a great idea. Mostly because of my next point...Ultimately, moment encapsulates a JS
Date
and uses it for much of it's internals. So you still get some of the quirks that go along with that. One that is particularly annoying is what I wrote up in issue #831 and discussed on my blog.Basically, the problem is that ECMAScript 5 required that whatever daylight saving time rules that are currently in effect are applied as if they were always in effect. Even if the host environment has a full TZDB implementation, it wasn't allowed to use it. This is changing for ES6, but very few browsers honor that part of it yet. This presents a problem for FullCalendar, in that you cannot reliably show calendars for past years, or even for the current year if a transition change has already been taken into effect.
For example, there's a change for Israel this year, in that they are extending DST by a month. Once a computer has this update (either in IANA TZDB 2013d or the Windows August 2013 DST Update), then the JS
Date
object in most browsers will behave as if DST in Israel always ended on the last Sunday in October.Using moment.js doesn't avoid this bug. Even if you use the moment-timezone plugin, it currently doesn't help. There are still problems, as noted in issue #28.
This is another reason I feel like a new library is called for. By not relying on the
Date
class, it won't inherit these problems. It does mean that it would need access to TZDB data, either from moment-timezone, or one of the other libraries that are out there.Next, I'm not sure if trying to represent time zones accurately on a calendar is a good plan. I've found through experience (and from others such as Jon Skeet and Stephen Colbourne, who run Noda and Joda respectively) that Instantaneous (Global/Universal) Time is a distinctly different concept from Calendar (Local/Civil) Time. This is described in part here.
Since you are clearly working with calendars, it might be more appropriate to not consider the instantaneous timeline at all. How a calendar date aligns with an instantaneous moment is the realm of Time Zones. You're currently relying on the
Date
class to do this for you with the "local" time zone. Using moment would do the same. But it would be much better if real time zone data was used. This could fall back to the user/developer, but more likely you might still want to handle this concern to a certain degree. Since the average developer isn't necessarily familiar with these concepts, it could be a point of contention if you just punted.My suggestion would be to implement some sort of provider or plugin model. The interface would define the necessary functionality, while the plugin would handle the details. You might ship with a "default plugin" that understands UTC and "local" time using just
Date
and/ormoment
, which would inherit all of the behavior of the host environment, for better or for worse. But for those that want higher accuracy, you could have a plugin that uses moment-timezone, or bigeasy/timezone, or any other provider. The user could even write their own plugin if they had some special rules to follow. For example, some people actually like the Windows time zone database (not me). They might want to write a plugin that re-implements some of those time zones in JavaScript.Lastly, since the main reason people use FullCalendar is for actually rendering a physical calendar, then being time zone aware means that you will have some funky drawing to do around DST transitions. I can see that you might block out an hour of invalid time (for spring-forward transitions), but I'm not sure how you would draw a period of repeated time (for fall-back transitions).
The month and day views would be fairly straightforward, but how would you present it on the week view? I'm racking my brain, and the only design I can think of is one that shows the repeated hour for the entire week but blocks out the days that it doesn't apply. That would work, but I'm not sure if folks would understand that or not.
You might also want the ability to show a time zone abbreviation along with the time-of-day on the day or week views. While there's no agreed upon standard for these, you could still make that part of your interface and let the plugin decide what to use. Plugins that use TZDB data can use the abbreviations from that database.
Wow, this was a long post. Sorry about that!!! If you'd like to discuss further here, that is fine. I can also be available via skype or regular old telephone if you like. Email me at mj1856 at hotmail if you would like to chat.
-Matt