Skip to content
Snippets Groups Projects
Unverified Commit 05b33c2e authored by metamben's avatar metamben Committed by GitHub
Browse files

Set timezone when truncating dates (#24247)

Breaking date values into parts should happen in the time zone expected by the user and the construction of the
truncated date should happen in the same timezone. This is especially important when the difference between the
expected timezone and UTC is not an integer number of hours and the truncation resolution is hour. (See #11149).
parent f2d61bf3
No related branches found
No related tags found
No related merge requests found
...@@ -34,8 +34,8 @@ describe("issue 16170", () => { ...@@ -34,8 +34,8 @@ describe("issue 16170", () => {
cy.get(".dot").eq(-2).trigger("mousemove", { force: true }); cy.get(".dot").eq(-2).trigger("mousemove", { force: true });
popover().within(() => { popover().within(() => {
testPairedTooltipValues("Created At", "2018"); testPairedTooltipValues("Created At", "2019");
testPairedTooltipValues("Count", "6,578"); testPairedTooltipValues("Count", "6,524");
}); });
}); });
}); });
......
...@@ -123,13 +123,13 @@ ...@@ -123,13 +123,13 @@
(let [field-name (str \$ (field->name field "."))] (let [field-name (str \$ (field->name field "."))]
(cond (cond
(isa? coercion :Coercion/UNIXMicroSeconds->DateTime) (isa? coercion :Coercion/UNIXMicroSeconds->DateTime)
{:$dateFromParts {:millisecond {$divide [field-name 1000]}, :year 1970}} {:$dateFromParts {:millisecond {$divide [field-name 1000]}, :year 1970, :timezone "UTC"}}
(isa? coercion :Coercion/UNIXMilliSeconds->DateTime) (isa? coercion :Coercion/UNIXMilliSeconds->DateTime)
{:$dateFromParts {:millisecond field-name, :year 1970}} {:$dateFromParts {:millisecond field-name, :year 1970, :timezone "UTC"}}
(isa? coercion :Coercion/UNIXSeconds->DateTime) (isa? coercion :Coercion/UNIXSeconds->DateTime)
{:$dateFromParts {:second field-name, :year 1970}} {:$dateFromParts {:second field-name, :year 1970, :timezone "UTC"}}
(isa? coercion :Coercion/YYYYMMDDHHMMSSString->Temporal) (isa? coercion :Coercion/YYYYMMDDHHMMSSString->Temporal)
{"$dateFromString" {:dateString field-name {"$dateFromString" {:dateString field-name
...@@ -181,11 +181,13 @@ ...@@ -181,11 +181,13 @@
(* 24 60 60 1000)]}]}) (* 24 60 60 1000)]}]})
(defn- truncate-to-resolution [column resolution] (defn- truncate-to-resolution [column resolution]
(mongo-let [parts {:$dateToParts {:date column}}] (mongo-let [parts {:$dateToParts {:timezone (qp.timezone/results-timezone-id)
{:$dateFromParts (into {} (for [part (concat (take-while (partial not= resolution) :date column}}]
[:year :month :day :hour :minute :second :millisecond]) {:$dateFromParts (into {:timezone (qp.timezone/results-timezone-id)}
[resolution])] (for [part (concat (take-while (partial not= resolution)
[part (str (name parts) \. (name part))]))})) [:year :month :day :hour :minute :second :millisecond])
[resolution])]
[part (str (name parts) \. (name part))]))}))
(defn- with-rvalue-temporal-bucketing (defn- with-rvalue-temporal-bucketing
[field unit] [field unit]
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
[metabase.driver.mongo.query-processor :as mongo.qp] [metabase.driver.mongo.query-processor :as mongo.qp]
[metabase.models :refer [Field Table]] [metabase.models :refer [Field Table]]
[metabase.query-processor :as qp] [metabase.query-processor :as qp]
[metabase.query-processor.timezone :as qp.timezone]
[metabase.test :as mt] [metabase.test :as mt]
[metabase.util :as u] [metabase.util :as u]
[schema.core :as s] [schema.core :as s]
...@@ -87,16 +88,22 @@ ...@@ -87,16 +88,22 @@
:query [{"$match" :query [{"$match"
{:$expr {:$expr
{"$eq" {"$eq"
[{:$let {:vars {:parts {:$dateToParts {:date "$datetime"}}} [{:$let {:vars {:parts {:$dateToParts {:date "$datetime"
:in {:$dateFromParts {:year "$$parts.year", :month "$$parts.month"}}}} :timezone (qp.timezone/results-timezone-id :mongo mt/db)}}}
:in {:$dateFromParts {:year "$$parts.year", :month "$$parts.month"
:timezone (qp.timezone/results-timezone-id :mongo mt/db)}}}}
{:$dateFromString {:dateString "2021-01-01T00:00Z"}}]}}} {:$dateFromString {:dateString "2021-01-01T00:00Z"}}]}}}
{"$group" {"_id" {"datetime~~~month" {:$let {:vars {:parts {:$dateToParts {:date "$datetime"}}} {"$group" {"_id" {"datetime~~~month" {:$let {:vars {:parts {:$dateToParts {:date "$datetime"
:timezone (qp.timezone/results-timezone-id :mongo mt/db)}}}
:in {:$dateFromParts {:year "$$parts.year" :in {:$dateFromParts {:year "$$parts.year"
:month "$$parts.month"}}}}, :month "$$parts.month"
"datetime~~~day" {:$let {:vars {:parts {:$dateToParts {:date "$datetime"}}} :timezone (qp.timezone/results-timezone-id :mongo mt/db)}}}},
"datetime~~~day" {:$let {:vars {:parts {:$dateToParts {:date "$datetime"
:timezone (qp.timezone/results-timezone-id :mongo mt/db)}}}
:in {:$dateFromParts {:year "$$parts.year" :in {:$dateFromParts {:year "$$parts.year"
:month "$$parts.month" :month "$$parts.month"
:day "$$parts.day"}}}}} :day "$$parts.day"
:timezone (qp.timezone/results-timezone-id :mongo mt/db)}}}}}
"count" {"$sum" 1}}} "count" {"$sum" 1}}}
{"$sort" {"_id" 1}} {"$sort" {"_id" 1}}
{"$project" {"_id" false {"$project" {"_id" false
...@@ -111,6 +118,33 @@ ...@@ -111,6 +118,33 @@
s/Keyword s/Any} s/Keyword s/Any}
(qp/process-query (mt/native-query query))))))))))))) (qp/process-query (mt/native-query query)))))))))))))
(deftest grouping-with-timezone-test
(mt/test-driver :mongo
(testing "Result timezone is respected when grouping by hour (#11149)"
(mt/dataset attempted-murders
(testing "Querying in UTC works"
(mt/with-system-timezone-id "UTC"
(is (= [["2019-11-20T20:00:00Z" 1]
["2019-11-19T00:00:00Z" 1]
["2019-11-18T20:00:00Z" 1]
["2019-11-17T14:00:00Z" 1]]
(mt/rows (mt/run-mbql-query attempts
{:aggregation [[:count]]
:breakout [[:field %datetime {:temporal-unit :hour}]]
:order-by [[:desc [:field %datetime {:temporal-unit :hour}]]]
:limit 4}))))))
(testing "Querying in Kathmandu works"
(mt/with-system-timezone-id "Asia/Kathmandu"
(is (= [["2019-11-21T01:00:00+05:45" 1]
["2019-11-19T06:00:00+05:45" 1]
["2019-11-19T02:00:00+05:45" 1]
["2019-11-17T19:00:00+05:45" 1]]
(mt/rows (mt/run-mbql-query attempts
{:aggregation [[:count]]
:breakout [[:field %datetime {:temporal-unit :hour}]]
:order-by [[:desc [:field %datetime {:temporal-unit :hour}]]]
:limit 4}))))))))))
(deftest nested-columns-test (deftest nested-columns-test
(mt/test-driver :mongo (mt/test-driver :mongo
(testing "Should generate correct queries against nested columns" (testing "Should generate correct queries against nested columns"
...@@ -262,8 +296,10 @@ ...@@ -262,8 +296,10 @@
{"_id" {"_id"
{"date~~~day" {"date~~~day"
{:$let {:$let
{:vars {:parts {:$dateToParts {:date "$date"}}}, {:vars {:parts {:$dateToParts {:date "$date"
:in {:$dateFromParts {:year "$$parts.year", :month "$$parts.month", :day "$$parts.day"}}}}}}} :timezone (qp.timezone/results-timezone-id :mongo mt/db)}}},
:in {:$dateFromParts {:year "$$parts.year", :month "$$parts.month", :day "$$parts.day"
:timezone (qp.timezone/results-timezone-id :mongo mt/db)}}}}}}}
{"$sort" {"_id" 1}} {"$sort" {"_id" 1}}
{"$project" {"_id" false, "date~~~day" "$_id.date~~~day"}} {"$project" {"_id" false, "date~~~day" "$_id.date~~~day"}}
{"$sort" {"date~~~day" 1}} {"$sort" {"date~~~day" 1}}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment