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.

@sw360cab
Copy link

sw360cab commented Sep 3, 2013

In general I agree with this proposal (based on the fact that I've raised the issue).
I think that the key points are that:

  • adopting an external library is a strong decision for the future roadmap of this project, even if Moment.js is a pretty common an stable one.
  • the main point to highlight (and I think you mentioned that) is that date info should always go UTC/Epoch in the client/server communication (Date.getTime()), giving timezone information should be an optional and warmly advised parameter to be sent (Date.getTimezoneOffset). But I think that for the sake of flexibility, they should remain separated.

If there is an agreemente on that and we can just iterate to improve a little bit the proposal/specification here, I will be really happy to contribute to this kind of refactoring.

Copy link

ghost commented Sep 3, 2013

So if I understand, my server would need to send a formatted date rather than sending a timestamp ? I'm not sure I like that idea. I think most REST servers are using timestamps and you would have to reconfigure the server... Wouldn't it be easier to have a parameter that sets the server timezone and then apply then delay between the client and server, if needed ?

@espen
Copy link

espen commented Sep 3, 2013

Sounds great! +1 moment.js, ISO8601, allDay, iCalendar exclusive times.

As @sw360cab says it is a big decision to include a external library in this way. But date handling in JavaScript isn't all that great and moment.js solves it and timezones very well.

When you say 'jQuery UI datepickers translations' you just mean have a default config for this, not include jQuery UI in the project?

@arshaw
Copy link
Author

arshaw commented Sep 4, 2013

@MiroHibler, globalize.js looks like it would solve the problem too, but people have also been craving a more powerful date manipulation library, and Moment kills two birds with one stone.

@arshaw
Copy link
Author

arshaw commented Sep 4, 2013

@sw360cab, on your second point, I'm in agreement, except for when dates are sent to the server (this would happen when sending a start/end interval for which to fetch events). Obviously the basic date parts (like 2013-09-03T14:00:00) are essential, and the offset info (like -07:00) is important in order to normalize the date to UTC, but I'm not too sure the offset is valuable all by itself.

You might think the offset will tell the server the current timezone, and the server will use that info to adjust the returned dates. But in reality, this is not enough info because there is more to a timezone than just a single offset value. Daylight-savings is the killer. Say your start date has an offset of -07:00 and your end date has an offset of -08:00. That means a daylight savings switchover happens at some point in between. How will the server know when?

The actual timezone identifier (like America/Chicago) is a more important thing for the server to know. The server can use it to determine all the offsets and intricacies of DST.

In summary, I'm in agreement except for sending the timezone offsets to the server separately.

@arshaw
Copy link
Author

arshaw commented Sep 4, 2013

@joel-lord, a formatted ISO8601 date string is simply another way of expressing a timestamp. Your server code should be able to convert between the two.

FullCalendar will still continue to support receiving raw integer timestamps, by the way.

@arshaw
Copy link
Author

arshaw commented Sep 4, 2013

@espen, right, there won't be any required dependency on the jQuery UI project. FullCalendar will try to use datepicker's translations if present however. Conversely, if FullCalendar's translation files are loaded, it will try to initialize datepicker's translations if the plugin is on the page.

@sw360cab
Copy link

sw360cab commented Sep 4, 2013

@arshaw you are right. Even if I already faced the problem, I always forget about daylight saving (we are normally GMT+1, but now we are GMT+2), and that this information is always hidden (if client is in daylight or not).
What I wanted to be clear was that whether having different tecnolgies between client server, using Epoch/timestamps is always the best and most flexible solution to exchange time information. And fullcalendar may not always be sync starting from client side time.

@arshaw
Copy link
Author

arshaw commented Sep 4, 2013

@sw360cab, yes, storing dates as time-since-epoch is always the best way. Nobody should every be storing individual Y/M/D/h/m/s values. This should happen on both client and server.

Once the date is converted to ISO8601, choosing to transmit or not to transmit the timezone offset will yield the different desired behaviors I outline in the proposal.

@sw360cab
Copy link

sw360cab commented Sep 4, 2013

I think we agreed on the main points. As I said before if you like to share code, I will be happy to help..

@mattjohnsonpint
Copy link

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:

    • Focused on calculation over formatting - so very little string manipulation if any.
    • Would use numeric types internally, rather then the Date type.
    • All objects would be treated as immutable types, rather than the mutable approach Moment takes.

    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 or moment values. You can always craft a Date or a moment 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/or moment, 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

@arshaw
Copy link
Author

arshaw commented Sep 6, 2013

Thank you very much for the in-depth response @mj1856. You sound extremely well versed on the topic and I'll make sure to keep your email. I'll likely move ahead with the Moment implementation, and I'll be mindful of the date-math gotchas you warn of. More and more I am considering leveraging Moment-timezone (as an optional dependency) and favoring it over the mock-local UTC technique I describe above. If someone wants an alternate timezone implementation, they'd just have to make an adapter for Moment and override the moment.tz and moment.fn.tz methods. Anything else is probably over-engineering. Also, I will further consider the UX of dealing with DST on the calendar. Thanks again for the comment.

@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