Repository: getredash/redash

Issue:
Add time zone support
Right now re:dash assumes that all timestamps are in the local time zone. However, Redshift (and many other databases, depending on the configuration) default to UTC, and thus re:dash parses the data incorrectly. What's confusing is that in Highcharts also formats the data in local time, leading to this awkward display:

![image](https://cloud.githubusercontent.com/assets/963826/7282512/4c9a1d38-e8e5-11e4-9bbf-eb52707d71ff.png)

Notice how the data is highlighted at the beginning of April 23 (which hasn't happened yet in GMT), but the date is showing April 22 4:00 pm (which hasn't happened yet in Pacific Standard Time).

I temporarily worked around this by patching ng_highchart.js:

``` diff
         if (moment.isMoment(this.x)) {
-          var s = '<b>' + this.x.toDate().toLocaleString() + '</b>',
+          var s = '<b>' + this.x.toDate().toISOString() + '</b>',
               pointsCount = this.points.length;
```

resources.js:

``` diff
             if (angular.isNumber(v)) {
               columnTypes[k] = 'float';
             } else if (_.isString(v) && v.match(/^\d{4}-\d{2}-\d{2}T/)) {
-              row[k] = moment(v);
+              row[k] = moment.utc(v);
               columnTypes[k] = 'datetime';
             } else if (_.isString(v) && v.match(/^\d{4}-\d{2}-\d{2}/)) {
-              row[k] = moment(v);
+              row[k] = moment.utc(v);
               columnTypes[k] = 'date';
             } else if (typeof(v) == 'object' && v !== null) {
               row[k] = JSON.stringify(v);
```

I'd imagine there will be cases where some users have their database configured to output a specific standard time and others will use UTC. What's the best way to support this feature? Options:
1. Store the UTC offset in the `query_result` (so moment will parse the right value)
2. Allow configuration option in re:dash for time zone (e.g. UTC, America/Los_Angeles) 

What do you think, @arikfr?



Discussion:
Oh, I see 267c32b39003241368b34f47dfc8975fe58d8ae3 at least fixed the time to be stored UTC internally, but it didn't fix my use case.

We still need to consider how the time is displayed in the chart.

Actually, there is no assumption about the timezone of the data, and the plan was to show the dates/times as they are returned from the database to avoid such issues and let the querying user control the timezone. About a month ago, I started using the `toLocaleString` method to print dates and times, to make sure that dates are presented with correct formatting (MM/DD for US, DD/MM for Israel for example).

But looks like this resulted in two bugs:
1. Inconsistency between how the time presented in the the chart axis (which handled by HighCharts) and the rest of the system (tooltips, table).
2. In some configurations, it will show the wrong time. From what I can see, as long as the data is UTC and your machine has correct time zone it should actually work.

I think that the best thing to do, is to revert to previous method of handling this (just print the time as is) and find a way to correctly print the dates without using `toLocaleString`.

But just to verify my theory in #2 above, can you check what time zone you have configured on your machine?

@arikfr, my browser is configured for Pacific Standard Time (UTC-0700), and the database usually uses UTC. `moment()` assumes parsing of the local time of the browser if no time zone information is available, which can happen quite frequently when you do a regular SQL query (e.g. extracting a day via  `SELECT round(extract('epoch' FROM timestamp) / 3600`). Requiring that the SQL output the time zone information in all cases seems unrealistic; either re:dash needs to determine this automatically or allow the user to specify it.

> Requiring that the SQL output the time zone information in all cases seems unrealistic

I agree. 

I think that we should make as a little assumptions about the data as possible and just show the data as is, regardless of the time zone. Does this sound right to you?

@arikfr I like the idea, but I'd imagine this may still lead to confusion if a UTC offset or time zone isn't shown somewhere. Also, Moment.js will enforce either UTC or local time zone. Is it really practical to create a timestamp that has no time zone associated with it?

Maybe there should be a configuration option of default timezone? And we will apply this to timestamps without timezone? (using UTC by default)

Yes, definitely.

I was looking into finally fixing this because this is an annoying bug. I think it would be easier to fix this issue if this comment were addressed:

``` javascript
// TODO: we should stop manipulating incoming data, and switch to relaying on the column type set by the backend.
// This logic is prone to errors, and better be removed. Kept for now, for backward compatability.
```

That way we can tell if a value is a TIMESTAMP type and parse accordingly. Should we fix this by storing the column type mappings as a field in the `query_result` table?

Oh, it looks like `data_type` is available in the query result.


Resolving patch:
diff --git a/redash/handlers/static.py b/redash/handlers/static.py
--- a/redash/handlers/static.py
+++ b/redash/handlers/static.py
@@ -36,14 +36,16 @@ def index(**kwargs):
         'permissions': current_user.permissions
     }
 
-    features = {
+    client_config = {
         'clientSideMetrics': settings.CLIENT_SIDE_METRICS,
         'allowScriptsInUserInput': settings.ALLOW_SCRIPTS_IN_USER_INPUT,
-        'highChartsTurboThreshold': settings.HIGHCHARTS_TURBO_THRESHOLD
+        'highChartsTurboThreshold': settings.HIGHCHARTS_TURBO_THRESHOLD,
+        'dateFormat': settings.DATE_FORMAT,
+        'dateTimeFormat': "{0} HH:mm".format(settings.DATE_FORMAT)
     }
 
     return render_template("index.html", user=json.dumps(user), name=settings.NAME,
-                           features=json.dumps(features),
+                           client_config=json.dumps(client_config),
                            analytics=settings.ANALYTICS)
 
 
diff --git a/redash/settings.py b/redash/settings.py
--- a/redash/settings.py
+++ b/redash/settings.py
@@ -143,6 +143,7 @@ def all_settings():
 CLIENT_SIDE_METRICS = parse_boolean(os.environ.get("REDASH_CLIENT_SIDE_METRICS", "false"))
 # http://api.highcharts.com/highcharts#plotOptions.series.turboThreshold
 HIGHCHARTS_TURBO_THRESHOLD = int(os.environ.get("REDASH_HIGHCHARTS_TURBO_THRESHOLD", "1000"))
+DATE_FORMAT = os.environ.get("REDASH_DATE_FORMAT", "DD/MM/YY")
 
 # Features:
 FEATURE_ALLOW_ALL_TO_EDIT_QUERIES = parse_boolean(os.environ.get("REDASH_FEATURE_ALLOW_ALL_TO_EDIT", "true"))


Test patch:
diff --git a/rd_ui/test/mocks/redash_mocks.js b/rd_ui/test/mocks/redash_mocks.js
--- a/rd_ui/test/mocks/redash_mocks.js
+++ b/rd_ui/test/mocks/redash_mocks.js
@@ -1,4 +1,4 @@
-featureFlags = [];
+clientConfig = {};
 currentUser = {
   id: 1,
   name: 'John Mock',