Skip to content
Snippets Groups Projects
Unverified Commit 61849910 authored by Cal Herries's avatar Cal Herries Committed by GitHub
Browse files

CSV uploads: support more date formats (#37078)

parent 52b8d410
No related branches found
No related tags found
No related merge requests found
......@@ -166,10 +166,10 @@
false)))
(defn- date-string? [s]
(does-not-throw? (t/local-date s)))
(does-not-throw? (upload-parsing/parse-local-date s)))
(defn- datetime-string? [s]
(does-not-throw? (upload-parsing/parse-datetime s)))
(does-not-throw? (upload-parsing/parse-local-datetime s)))
(defn- offset-datetime-string? [s]
(does-not-throw? (upload-parsing/parse-offset-datetime s)))
......
......@@ -5,6 +5,8 @@
[metabase.public-settings :as public-settings]
[metabase.util.i18n :refer [tru]])
(:import
(java.time LocalDate)
(java.time.format DateTimeFormatter DateTimeFormatterBuilder ResolverStyle)
(java.text NumberFormat)
(java.util Locale)))
......@@ -29,19 +31,56 @@
:else (throw (IllegalArgumentException.
(tru "''{0}'' is not a recognizable boolean" s)))))
(defn- parse-date
"Parses a date.
(def local-date-patterns
"patterns used to generate the local date formatter. Excludes ISO_LOCAL_DATE (uuuu-MM-dd) because there's
already a built-in DateTimeFormatter for that: [[DateTimeFormatter/ISO_LOCAL_DATE]]"
;; uuuu is like yyyy but is required for strict parsing and also supports negative years for BC dates
;; see https://stackoverflow.com/questions/41103603/issue-with-datetimeparseexception-when-using-strict-resolver-style
;; uuuu is faster than using yyyy and setting a default era
["MMM dd uuuu" ; Jan 30 2000
"MMM dd, uuuu" ; Jan 30, 2000
"dd MMM uuuu" ; 30 Jan 2000
"dd MMM, uuuu" ; 30 Jan, 2000
"MMMM d uuuu" ; January 30 2000
"MMMM d, uuuu" ; January 30, 2000
"d MMMM uuuu" ; 30 January 2000
"d MMMM, uuuu" ; 30 January, 2000
"EEEE, MMMM d uuuu" ; Sunday, January 30 2000
"EEEE, MMMM d, uuuu" ; Sunday, January 30, 2000
])
(def local-date-formatter
"DateTimeFormatter that runs through a set of patterns to parse a variety of local date formats."
(let [builder (-> (DateTimeFormatterBuilder.)
(.parseCaseInsensitive))]
(doseq [pattern local-date-patterns]
(.appendOptional builder (DateTimeFormatter/ofPattern pattern)))
(-> builder
(.appendOptional DateTimeFormatter/ISO_LOCAL_DATE)
(.toFormatter)
(.withResolverStyle ResolverStyle/STRICT))))
(defn parse-local-date
"Parses a local date string.
Supported formats:
- yyyy-MM-dd"
- yyyy-MM-dd
- MMM dd yyyy
- MMM dd, yyyy
- dd MMM yyyy
- dd MMM, yyyy
- MMMM d yyyy
- MMMM d, yyyy
- d MMMM yyyy
- d MMMM, yyyy"
[s]
(try
(t/local-date s)
(LocalDate/parse s local-date-formatter)
(catch Exception _
(throw (IllegalArgumentException.
(tru "''{0}'' is not a recognizable date" s))))))
(defn parse-datetime
(defn parse-local-datetime
"Parses a string representing a local datetime into a LocalDateTime.
Supported formats:
......@@ -55,13 +94,13 @@
(-> 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]]."
"Parses a string `s` as a LocalDateTime. Supports all the formats for [[parse-local-date]] and [[parse-datetime]]."
[s]
(try
(t/local-date-time (parse-date s) (t/local-time "00:00:00"))
(t/local-date-time (parse-local-date s) (t/local-time "00:00:00"))
(catch Exception _
(try
(parse-datetime s)
(parse-local-datetime s)
(catch Exception _
(throw (IllegalArgumentException.
(tru "''{0}'' is not a recognizable datetime" s))))))))
......@@ -161,7 +200,7 @@
(defmethod upload-type->parser :metabase.upload/date
[_ _]
(comp
parse-date
parse-local-date
str/trim))
(defmethod upload-type->parser :metabase.upload/datetime
......
......@@ -167,6 +167,20 @@
["My favorite number is 86" "My favorite number is 86" vchar-type]
;; Date-related
[" 2022-01-01 " #t "2022-01-01" date-type]
[" 2022-02-30 " " 2022-02-30 " vchar-type]
[" -2022-01-01 " #t "-2022-01-01" date-type]
[" Jan 30 2018" #t "2018-01-30" date-type]
[" Jan 30 -2018" #t "-2018-01-30" date-type]
[" Jan 30, 2018" #t "2018-01-30" date-type]
[" Feb 30, 2018" " Feb 30, 2018" vchar-type]
[" 30 Jan 2018" #t "2018-01-30" date-type]
[" 30 Jan, 2018" #t "2018-01-30" date-type]
[" January 30 2018" #t "2018-01-30" date-type]
[" January 30, 2018" #t "2018-01-30" date-type]
[" 30 January 2018" #t "2018-01-30" date-type]
[" 30 January, 2018" #t "2018-01-30" date-type]
[" Sunday, January 30 2000" #t "2000-01-30" date-type]
[" Sunday, January 30, 2000" #t "2000-01-30" 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]
......
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