Repository: streamlit/streamlit
Pull request: Display area/bar/line chart time values in UTC, rather than browser-local time
By default, vega-lite displays time data in the browser's local time zone, regardless of which time zone the data specifies. (See https://vega.github.io/vega-lite/docs/timeunit.html#output for more details.) This leads to unexpected results - see #485 for an example.

With this PR, for generated altair charts (`st.area`, `bar`, `line`), we now explicitly set the X and Y axes' scale to `type="utc"` if they contain `datetime.date` values. This is meaningful only for date/time values, but it means that time data will be shown in UTC, rather the user's local time zone.

Fixes #485 


Problem statement:
Passing a date in index to st.line_chart defaults to UTC time zone
# Summary
This may not be a Streamlit bug - it may be a Python issue where I need to declare a datetime. When I pass a date through as the index to st.line_chart my graph is then shifted 7 hours back in time from my intended time (e.g. I would want to show value 50 on the graph for midnight on 2019-08-10 and it shows as 5pm for 2019-08-09). I think what is happening is somehow it's doing a UTC offset by default. Would like to

# Steps to reproduce
df = pd.DataFrame({
  'index': [date(2019, 8, 9), date(2019, 8, 10), date(2019, 8, 11), date(2019, 8, 12)],
  'numbers': [10, 50, 30, 40]
})

df

st.line_chart(df.set_index('index'))



## Expected behavior:
I would expect it to plot in the date that I passed through but it's converting it for some reason. Also happens when I use pd.to_datetime() on a dataset. 

## Actual behavior:
Shifts it back in time by 7 hours.

## Is this a regression?
No

# Debug info
Streamlit, version 0.47.4
Python 3.7.2

Hints from issue discussion:
By default, Vegalite outputs time data in local time: https://vega.github.io/vega-lite/docs/timeunit.html#output

We can fix this by setting `scale: {type: "utc"}` in the chart spec:

```python
chart = (
    getattr(alt.Chart(data), "mark_" + chart_type)()
    .encode(
        alt.X("index", title="", scale=alt.Scale(type="utc")),  # <-- set "scale" to "utc"
        alt.Y("value", title=""),
        alt.Color("variable", title="", type="nominal"),
        alt.Tooltip(["index", "value", "variable"]),
        opacity=opacity,
    )
    .interactive()
)
```

(We'd only want to do this for temporal data, obviously.) But I'm not familiar with altair/vega-lite, so I'm not sure if this is the right solution.
Maybe `st.line_chart` and friends take a new `datetime_display` param that defaults to "local" but can be set to "utc"?

Commit message: chart_utc_time snapshot tests

Patch to review:
diff --git a/e2e/scripts/chart_utc_time.py b/e2e/scripts/chart_utc_time.py
new file mode 100644
index 000000000000..b024c12c1e20
--- /dev/null
+++ b/e2e/scripts/chart_utc_time.py
@@ -0,0 +1,27 @@
+from datetime import date
+
+import pandas as pd
+import streamlit as st
+
+df = pd.DataFrame(
+    {
+        "index": [
+            date(2019, 8, 9),
+            date(2019, 8, 10),
+            date(2019, 8, 11),
+            date(2019, 8, 12),
+        ],
+        "numbers": [10, 50, 30, 40],
+    }
+)
+
+df.set_index("index", inplace=True)
+
+# st.area/bar/line_chart all use Altair/Vega-Lite under the hood.
+# By default, Vega-Lite displays time values in the browser's local
+# time zone. In `altair.generate_chart`, we explicitly set the time
+# display to UTC, so that our results are consistent. This test verifies
+# that change!
+st.area_chart(df)
+st.bar_chart(df)
+st.line_chart(df)
diff --git a/e2e/specs/chart_utc_time.ts b/e2e/specs/chart_utc_time.ts
new file mode 100644
index 000000000000..72cf1e003899
--- /dev/null
+++ b/e2e/specs/chart_utc_time.ts
@@ -0,0 +1,35 @@
+/**
+ * @license
+ * Copyright 2018-2019 Streamlit Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/// <reference types="cypress" />
+
+describe("st.area, bar, and line charts", () => {
+  before(() => {
+    cy.visit("http://localhost:3000/");
+
+    // Make the ribbon decoration line disappear
+    cy.get(".decoration").invoke("css", "display", "none");
+  });
+
+  it("display times in UTC", () => {
+    cy.get(".element-container .stVegaLiteChart")
+      .find("canvas")
+      .each((el, i) => {
+        return cy.get(el).matchImageSnapshot(`chartUTCTime-${i}`);
+      });
+  });
+});
diff --git a/lib/streamlit/elements/altair.py b/lib/streamlit/elements/altair.py
index c2534538f67e..ae8b0a79b6c5 100644
--- a/lib/streamlit/elements/altair.py
+++ b/lib/streamlit/elements/altair.py
@@ -34,7 +34,6 @@ def generate_chart(chart_type, data):
     if not isinstance(data, pd.DataFrame):
         data = convert_anything_to_df(data)
 
-    n_cols = len(data.columns)
     data = pd.melt(data.reset_index(), id_vars=["index"])
 
     if chart_type == "area":
@@ -45,8 +44,14 @@ def generate_chart(chart_type, data):
     chart = (
         getattr(alt.Chart(data), "mark_" + chart_type)()
         .encode(
-            alt.X("index", title=""),
-            alt.Y("value", title=""),
+            # Set the X and Y axes' scale to `type="utc"`. This is meaningful
+            # only for date/time values, but it means that time data will be
+            # shown in UTC, rather the user's local time zone. (By default,
+            # vega-lite displays time data in the browser's local time zone,
+            # regardless of which time zone the data specifies:
+            # https://vega.github.io/vega-lite/docs/timeunit.html#output).
+            alt.X("index", title="", scale=alt.Scale(type="utc")),
+            alt.Y("value", title="", scale=alt.Scale(type="utc")),
             alt.Color("variable", title="", type="nominal"),
             alt.Tooltip(["index", "value", "variable"]),
             opacity=opacity,


Code review comments:
diff_hunk: @@ -45,8 +44,14 @@ def generate_chart(chart_type, data):
     chart = (
         getattr(alt.Chart(data), "mark_" + chart_type)()
         .encode(
-            alt.X("index", title=""),
-            alt.Y("value", title=""),
+            # Set the X and Y axes' scale to `type="utc"`. This is meaningful
+            # only for date/time values, but it means that time data will be
+            # shown in UTC, rather the user's local time zone. (By default,
+            # vega-lite displays time data in the browser's local time zone,
+            # regardless of which time zone the data specifies:
+            # https://vega.github.io/vega-lite/docs/timeunit.html#output).
+            alt.X("index", title="", scale=alt.Scale(type="utc")),
path: lib/streamlit/elements/altair.py
text: @author:
We should set the X, Y  scales only if the data is time-based