diff --git a/src/metabase/upload.clj b/src/metabase/upload.clj index 6c9f9c0d43f06bec4b3edf9f6bd7c0271fbde8cf..2fabdabbac7d69f4fcbe789e935374d9b864a8fb 100644 --- a/src/metabase/upload.clj +++ b/src/metabase/upload.clj @@ -12,7 +12,6 @@ [metabase.mbql.util :as mbql.u] [metabase.models :refer [Database]] [metabase.public-settings :as public-settings] - [metabase.search.util :as search.util] [metabase.upload.parsing :as upload-parsing] [metabase.util :as u] [metabase.util.i18n :refer [tru]] @@ -90,13 +89,13 @@ false))) (defn- datetime-string? [s] - (try (t/local-date-time s) + (try (upload-parsing/parse-datetime s) true (catch Exception _ false))) (defn- offset-datetime-string? [s] - (try (t/offset-date-time s) + (try (upload-parsing/parse-offset-datetime s) true (catch Exception _ false))) @@ -165,7 +164,7 @@ (defn- row->types [row] - (map (comp value->type search.util/normalize) row)) + (map value->type row)) (defn- lowest-common-member [[x & xs :as all-xs] ys] (cond diff --git a/src/metabase/upload/parsing.clj b/src/metabase/upload/parsing.clj index 2e5ac17416f8cbbbc4ba1cc47108d07beccac3da..0b65542c1947b80e2a50e38a1a66cadfb2ff7fda 100644 --- a/src/metabase/upload/parsing.clj +++ b/src/metabase/upload/parsing.clj @@ -27,27 +27,57 @@ (tru "{0} is not a recognizable boolean" s))))) (defn parse-date - "Parses a date." + "Parses a date. + + Supported formats: + - yyyy-MM-dd" [s] (t/local-date s)) (defn parse-datetime - "Parses a datetime (without timezone)." + "Parses a string representing a local datetime into a LocalDateTime. + + Supported formats: + - yyyy-MM-dd'T'HH:mm + - yyyy-MM-dd'T'HH:mm:ss + - yyyy-MM-dd'T'HH:mm:ss.SSS (and any other number of S's) + - the above formats, with a space instead of a 'T' + + Parsing is case-insensitive." + [s] + (-> s (str/replace \space \T) t/local-date-time)) + +(defn parse-as-datetime + "Parses a string `s` as a LocalDateTime. Supports all the formats for [[parse-date]] and [[parse-datetime]]." [s] (try - (t/local-date-time (t/local-date s) (t/local-time "00:00:00")) + (t/local-date-time (parse-date s) (t/local-time "00:00:00")) (catch Exception _ (try - (t/local-date-time s) + (parse-datetime s) (catch Exception _ (throw (IllegalArgumentException. (tru "{0} is not a recognizable datetime" s)))))))) (defn parse-offset-datetime - "Parses a datetime (with offset)." + "Parses a string representing an offset datetime into an OffsetDateTime. + + The format consists of: + 1) The a date and time, with the formats: + - yyyy-MM-dd'T'HH:mm + - yyyy-MM-dd'T'HH:mm:ss + - yyyy-MM-dd'T'HH:mm:ss.SSS (and any other number of S's) + - the above formats, with a space instead of a 'T' + 2) An offset, with the formats: + - Z (for UTC) + - +HH or -HH + - +HH:mm or -HH:mm + - +HH:mm:ss or -HH:mm:ss + + Parsing is case-insensitive." [s] (try - (t/offset-date-time s) + (-> s (str/replace \space \T) t/offset-date-time) (catch Exception e (throw (IllegalArgumentException. (tru "{0} is not a recognizable zoned datetime" s) e))))) @@ -135,7 +165,7 @@ (defmethod upload-type->parser :metabase.upload/datetime [_] (comp - parse-datetime + parse-as-datetime str/trim)) (defmethod upload-type->parser :metabase.upload/offset-datetime diff --git a/test/metabase/upload_test.clj b/test/metabase/upload_test.clj index e5ba5cd7bb00c4ec22d165b5cdc55019220cce90..6372e6d9be3691fc318bd38100336ac1f3e8b799 100644 --- a/test/metabase/upload_test.clj +++ b/test/metabase/upload_test.clj @@ -5,6 +5,7 @@ [clojure.java.jdbc :as jdbc] [clojure.string :as str] [clojure.test :refer :all] + [java-time.api :as t] [metabase.driver :as driver] [metabase.driver.mysql :as mysql] [metabase.driver.sql-jdbc.connection :as sql-jdbc.conn] @@ -124,10 +125,30 @@ ["My favorite number is 86" "My favorite number is 86" vchar-type] ;; Date-related [" 2022-01-01 " #t "2022-01-01" date-type] + [" 2022-01-01T01:00 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01t01:00 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01 01:00 " #t "2022-01-01T01:00" datetime-type] [" 2022-01-01T01:00:00 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01t01:00:00 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01 01:00:00 " #t "2022-01-01T01:00" datetime-type] [" 2022-01-01T01:00:00.00 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01t01:00:00.00 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01 01:00:00.00 " #t "2022-01-01T01:00" datetime-type] [" 2022-01-01T01:00:00.000000000 " #t "2022-01-01T01:00" datetime-type] - [" 2022-01-01T01:00:00.00-07:00 " #t "2022-01-01T01:00-07:00" offset-dt-type]]] + [" 2022-01-01t01:00:00.000000000 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01 01:00:00.000000000 " #t "2022-01-01T01:00" datetime-type] + [" 2022-01-01T01:00:00.00-07 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01t01:00:00.00-07 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01 01:00:00.00-07 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01T01:00:00.00-07:00 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01t01:00:00.00-07:00 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01 01:00:00.00-07:00 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01T01:00:00.00-07:00 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01t01:00:00.00-07:00 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01 01:00:00.00-07:00 " #t "2022-01-01T01:00-07:00" offset-dt-type] + [" 2022-01-01T01:00:00.00Z " (t/offset-date-time "2022-01-01T01:00+00:00") offset-dt-type] + [" 2022-01-01t01:00:00.00Z " (t/offset-date-time "2022-01-01T01:00+00:00") offset-dt-type] + [" 2022-01-01 01:00:00.00Z " (t/offset-date-time "2022-01-01T01:00+00:00") offset-dt-type]]] (mt/with-temporary-setting-values [custom-formatting (when seps {:type/Number {:number_separators seps}})] (let [type (upload/value->type string-value) parser (#'upload-parsing/upload-type->parser type)] @@ -431,7 +452,9 @@ "upload_test" (csv-file-with ["datetime" "2022-01-01" - "2022-01-01T00:00:00"]))) + "2022-01-01 00:00" + "2022-01-01T00:00:00" + "2022-01-01T00:00"]))) (testing "Fields exists after sync" (sync/sync-database! (mt/db)) (let [table (t2/select-one Table :db_id (mt/id))]