Commit 84a7a273 authored by olau@iola.dk's avatar olau@iola.dk

Display dates according to UTC instead of according to the time zone of the visitor

git-svn-id: https://flot.googlecode.com/svn/trunk@67 1e0a6537-2640-0410-bfb7-f154510ff394
parent 2d6aa6e9
...@@ -260,20 +260,45 @@ an example of a custom formatter: ...@@ -260,20 +260,45 @@ an example of a custom formatter:
Time series data Time series data
================ ================
Time series are a bit more difficult than scalar data because
calendars don't follow a simple base 10 system. For many cases, Flot
abstracts most of this away, but it can still be a bit difficult to
get the data into Flot. So we'll first discuss the data format.
The time series support in Flot is based on Javascript timestamps, The time series support in Flot is based on Javascript timestamps,
i.e. everywhere a time value is expected or handed over, a Javascript i.e. everywhere a time value is expected or handed over, a Javascript
timestamp number is used. This is not the same as a Date object. A timestamp number is used. This is a number, not a Date object. A
Javascript timestamp is the number of milliseconds since January 1, Javascript timestamp is the number of milliseconds since January 1,
1970 00:00:00. This is almost the same as Unix timestamps, except it's 1970 00:00:00. This is almost the same as Unix timestamps, except it's
in milliseconds, so remember to multiply by 1000! in milliseconds, so remember to multiply by 1000!
You can see a timestamp by outputting You can see a timestamp like this
alert((new Date()).getTime())
Normally you want the timestamps to be displayed according to a
certain time zone, usually the time zone in which the data has been
produced. However, Flot always displays timestamps according to UTC.
It has to as the only alternative with core Javascript is to interpret
the timestamps according to the time zone that the visitor is in,
which means that the ticks will shift unpredictably with the time
zone and daylight savings of each visitor.
(new Date()).getTime() So given that there's no good support for custom time zones in
Javascript, you'll have to take care of this server-side.
The easiest way to think about it is to pretend that the data
production time zone is UTC, even if it isn't. So if you have a
datapoint at 2002-02-20 08:00, you can generate a timestamp for eight
o'clock UTC even if it really happened eight o'clock UTC+0200. If
you've already got the real UTC timestamp, it's too late to pretend -
but you can fix it up by adding the time zone offset, e.g. for
UTC+0200 you would add 2 hours to the timestamp. Then it'll look right
on the plot.
In PHP you can get an appropriate timestamp with In PHP you can get an appropriate timestamp with
'strtotime("2002-02-20") * 1000', in Python with 'strtotime("2002-02-20 UTC") * 1000', in Python with
'time.mktime(datetime_object.timetuple()) * 1000', in .NET with 'calendar.timegm(datetime_object.timetuple()) * 1000', in .NET with
something like: something like:
public static int GetJavascriptTimestamp(System.DateTime input) public static int GetJavascriptTimestamp(System.DateTime input)
...@@ -283,13 +308,17 @@ something like: ...@@ -283,13 +308,17 @@ something like:
return (int)(time.Ticks / 10000); return (int)(time.Ticks / 10000);
} }
Javascript also has some support for parsing date strings, so it is
possible to generate the timestamps client-side if you need to.
Once you've got the timestamps into the data and specified "time" as Once you've got the timestamps into the data and specified "time" as
the axis mode, Flot will automatically generate relevant ticks and the axis mode, Flot will automatically generate relevant ticks and
format them. As always, you can tweak the ticks via the "ticks" format them. As always, you can tweak the ticks via the "ticks" option
option. Again the values should be timestamps, not Date objects! - just remember that the values should be timestamps, not Date
objects.
Tick generation and formatting is controlled separately through the Tick generation and formatting can also be controlled separately
following axis options: through the following axis options:
xaxis, yaxis: { xaxis, yaxis: {
minTickSize minTickSize
...@@ -328,7 +357,7 @@ which will format December 24 as 24/12: ...@@ -328,7 +357,7 @@ which will format December 24 as 24/12:
tickFormatter: function (val, axis) { tickFormatter: function (val, axis) {
var d = new Date(val); var d = new Date(val);
return d.getDate() + "/" + (d.getMonth() + 1); return d.getUTCDate() + "/" + (d.getUTCMonth() + 1);
} }
Note that for the time mode "tickSize" and "minTickSize" are a bit Note that for the time mode "tickSize" and "minTickSize" are a bit
......
Flot x.x Flot x.x
-------- --------
API changes: timestamps in time mode are now displayed according to
UTC instead of the time zone of the visitor. This affects the way the
timestamps should be input; you'll probably have to offset the
timestamps according to your local time zone. It also affects any
custom date handling code (which basically now should use the
equivalent UTC date mehods, e.g. .setUTCMonth() instead of
.setMonth().
Added support for specifying the size of tick labels (axis.labelWidth, Added support for specifying the size of tick labels (axis.labelWidth,
axis.labelHeight). Useful for specifying a max label size to keep axis.labelHeight). Useful for specifying a max label size to keep
multiple plots aligned. multiple plots aligned.
......
...@@ -23,10 +23,16 @@ ...@@ -23,10 +23,16 @@
<button id="nineties">1990-2000</button> <button id="nineties">1990-2000</button>
<button id="ninetynine">1999</button></p> <button id="ninetynine">1999</button></p>
<p>The timestamps must be specified as Javascript <p>The timestamps must be specified as Javascript timestamps, as
timestamps, as milliseconds since January 1, 1970 00:00. This is milliseconds since January 1, 1970 00:00. This is like Unix
like Unix timestamps, but in milliseconds instead of seconds timestamps, but in milliseconds instead of seconds (remember to
(remember to multiply with 1000!).</p> multiply with 1000!).</p>
<p>As an extra caveat, the timestamps are interpreted according to
UTC to avoid having the graph shift with each visitor's local
time zone. So you might have to add your local time zone offset
to the timestamps or simply pretend that the data was produced
in UTC instead of your local time zone.</p>
<script id="source" language="javascript" type="text/javascript"> <script id="source" language="javascript" type="text/javascript">
$(function () { $(function () {
......
...@@ -22,15 +22,21 @@ ...@@ -22,15 +22,21 @@
$(function () { $(function () {
var d = [[1196463600000, 0], [1196550000000, 0], [1196636400000, 0], [1196722800000, 77], [1196809200000, 3636], [1196895600000, 3575], [1196982000000, 2736], [1197068400000, 1086], [1197154800000, 676], [1197241200000, 1205], [1197327600000, 906], [1197414000000, 710], [1197500400000, 639], [1197586800000, 540], [1197673200000, 435], [1197759600000, 301], [1197846000000, 575], [1197932400000, 481], [1198018800000, 591], [1198105200000, 608], [1198191600000, 459], [1198278000000, 234], [1198364400000, 1352], [1198450800000, 686], [1198537200000, 279], [1198623600000, 449], [1198710000000, 468], [1198796400000, 392], [1198882800000, 282], [1198969200000, 208], [1199055600000, 229], [1199142000000, 177], [1199228400000, 374], [1199314800000, 436], [1199401200000, 404], [1199487600000, 253], [1199574000000, 218], [1199660400000, 476], [1199746800000, 462], [1199833200000, 448], [1199919600000, 442], [1200006000000, 403], [1200092400000, 204], [1200178800000, 194], [1200265200000, 327], [1200351600000, 374], [1200438000000, 507], [1200524400000, 546], [1200610800000, 482], [1200697200000, 283], [1200783600000, 221], [1200870000000, 483], [1200956400000, 523], [1201042800000, 528], [1201129200000, 483], [1201215600000, 452], [1201302000000, 270], [1201388400000, 222], [1201474800000, 439], [1201561200000, 559], [1201647600000, 521], [1201734000000, 477], [1201820400000, 442], [1201906800000, 252], [1201993200000, 236], [1202079600000, 525], [1202166000000, 477], [1202252400000, 386], [1202338800000, 409], [1202425200000, 408], [1202511600000, 237], [1202598000000, 193], [1202684400000, 357], [1202770800000, 414], [1202857200000, 393], [1202943600000, 353], [1203030000000, 364], [1203116400000, 215], [1203202800000, 214], [1203289200000, 356], [1203375600000, 399], [1203462000000, 334], [1203548400000, 348], [1203634800000, 243], [1203721200000, 126], [1203807600000, 157], [1203894000000, 288]]; var d = [[1196463600000, 0], [1196550000000, 0], [1196636400000, 0], [1196722800000, 77], [1196809200000, 3636], [1196895600000, 3575], [1196982000000, 2736], [1197068400000, 1086], [1197154800000, 676], [1197241200000, 1205], [1197327600000, 906], [1197414000000, 710], [1197500400000, 639], [1197586800000, 540], [1197673200000, 435], [1197759600000, 301], [1197846000000, 575], [1197932400000, 481], [1198018800000, 591], [1198105200000, 608], [1198191600000, 459], [1198278000000, 234], [1198364400000, 1352], [1198450800000, 686], [1198537200000, 279], [1198623600000, 449], [1198710000000, 468], [1198796400000, 392], [1198882800000, 282], [1198969200000, 208], [1199055600000, 229], [1199142000000, 177], [1199228400000, 374], [1199314800000, 436], [1199401200000, 404], [1199487600000, 253], [1199574000000, 218], [1199660400000, 476], [1199746800000, 462], [1199833200000, 448], [1199919600000, 442], [1200006000000, 403], [1200092400000, 204], [1200178800000, 194], [1200265200000, 327], [1200351600000, 374], [1200438000000, 507], [1200524400000, 546], [1200610800000, 482], [1200697200000, 283], [1200783600000, 221], [1200870000000, 483], [1200956400000, 523], [1201042800000, 528], [1201129200000, 483], [1201215600000, 452], [1201302000000, 270], [1201388400000, 222], [1201474800000, 439], [1201561200000, 559], [1201647600000, 521], [1201734000000, 477], [1201820400000, 442], [1201906800000, 252], [1201993200000, 236], [1202079600000, 525], [1202166000000, 477], [1202252400000, 386], [1202338800000, 409], [1202425200000, 408], [1202511600000, 237], [1202598000000, 193], [1202684400000, 357], [1202770800000, 414], [1202857200000, 393], [1202943600000, 353], [1203030000000, 364], [1203116400000, 215], [1203202800000, 214], [1203289200000, 356], [1203375600000, 399], [1203462000000, 334], [1203548400000, 348], [1203634800000, 243], [1203721200000, 126], [1203807600000, 157], [1203894000000, 288]];
// first correct the timestamps - they are recorded as the daily
// midnights in UTC+0100, but Flot always displays dates in UTC
// so we have to add one hour to hit the midnights in the plot
for (var i = 0; i < d.length; ++i)
d[i][0] += 60 * 60 * 1000;
// helper for returning the weekends in a period // helper for returning the weekends in a period
function weekendAreas(plotarea) { function weekendAreas(plotarea) {
var areas = []; var areas = [];
var d = new Date(plotarea.xmin); var d = new Date(plotarea.xmin);
// go to the first Saturday // go to the first Saturday
d.setDate(d.getDate() - ((d.getDay() + 1) % 7)) d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
d.setSeconds(0); d.setUTCSeconds(0);
d.setMinutes(0); d.setUTCMinutes(0);
d.setHours(0); d.setUTCHours(0);
var i = d.getTime(); var i = d.getTime();
do { do {
// when we don't set y1 and y2 the rectangle // when we don't set y1 and y2 the rectangle
......
...@@ -386,14 +386,14 @@ ...@@ -386,14 +386,14 @@
if (escape) { if (escape) {
switch (c) { switch (c) {
case 'h': c = "" + d.getHours(); break; case 'h': c = "" + d.getUTCHours(); break;
case 'H': c = leftPad(d.getHours()); break; case 'H': c = leftPad(d.getUTCHours()); break;
case 'M': c = leftPad(d.getMinutes()); break; case 'M': c = leftPad(d.getUTCMinutes()); break;
case 'S': c = leftPad(d.getSeconds()); break; case 'S': c = leftPad(d.getUTCSeconds()); break;
case 'd': c = "" + d.getDate(); break; case 'd': c = "" + d.getUTCDate(); break;
case 'm': c = "" + (d.getMonth() + 1); break; case 'm': c = "" + (d.getUTCMonth() + 1); break;
case 'y': c = "" + d.getFullYear(); break; case 'y': c = "" + d.getUTCFullYear(); break;
case 'b': c = "" + monthNames[d.getMonth()]; break; case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
} }
r.push(c); r.push(c);
escape = false; escape = false;
...@@ -480,28 +480,28 @@ ...@@ -480,28 +480,28 @@
var step = tickSize * timeUnitSize[unit]; var step = tickSize * timeUnitSize[unit];
if (unit == "second") if (unit == "second")
d.setSeconds(floorInBase(d.getSeconds(), tickSize)); d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
if (unit == "minute") if (unit == "minute")
d.setMinutes(floorInBase(d.getMinutes(), tickSize)); d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
if (unit == "hour") if (unit == "hour")
d.setHours(floorInBase(d.getHours(), tickSize)); d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
if (unit == "month") if (unit == "month")
d.setMonth(floorInBase(d.getMonth(), tickSize)); d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
if (unit == "year") if (unit == "year")
d.setFullYear(floorInBase(d.getFullYear(), tickSize)); d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
// reset smaller components // reset smaller components
d.setMilliseconds(0); d.setUTCMilliseconds(0);
if (step >= timeUnitSize.minute) if (step >= timeUnitSize.minute)
d.setSeconds(0); d.setUTCSeconds(0);
if (step >= timeUnitSize.hour) if (step >= timeUnitSize.hour)
d.setMinutes(0); d.setUTCMinutes(0);
if (step >= timeUnitSize.day) if (step >= timeUnitSize.day)
d.setHours(0); d.setUTCHours(0);
if (step >= timeUnitSize.day * 4) if (step >= timeUnitSize.day * 4)
d.setDate(1); d.setUTCDate(1);
if (step >= timeUnitSize.year) if (step >= timeUnitSize.year)
d.setMonth(0); d.setUTCMonth(0);
var carry = 0, v = Number.NaN, prev; var carry = 0, v = Number.NaN, prev;
...@@ -514,19 +514,19 @@ ...@@ -514,19 +514,19 @@
// a bit complicated - we'll divide the month // a bit complicated - we'll divide the month
// up but we need to take care of fractions // up but we need to take care of fractions
// so we don't end up in the middle of a day // so we don't end up in the middle of a day
d.setDate(1); d.setUTCDate(1);
var start = d.getTime(); var start = d.getTime();
d.setMonth(d.getMonth() + 1); d.setUTCMonth(d.getUTCMonth() + 1);
var end = d.getTime(); var end = d.getTime();
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
carry = d.getHours(); carry = d.getUTCHours();
d.setHours(0); d.setUTCHours(0);
} }
else else
d.setMonth(d.getMonth() + tickSize); d.setUTCMonth(d.getUTCMonth() + tickSize);
} }
else if (unit == "year") { else if (unit == "year") {
d.setFullYear(d.getFullYear() + tickSize); d.setUTCFullYear(d.getUTCFullYear() + tickSize);
} }
else else
d.setTime(v + step); d.setTime(v + step);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment