"Apply truncation / extraction to a date field or value for SQLite.
See also the [SQLite Date and Time Functions Reference](http://www.sqlite.org/lang_datefunc.html)."
[_unitexpr]
[unitexpr]
;; Convert Timestamps to ISO 8601 strings before passing to SQLite, otherwise they don't seem to work correctly
(let[v(if(instance?java.sql.Timestampexpr)
(kx/literal(u/date->iso-8601expr))
expr)]
(caseunit
:default(->datetimev)
:second(->datetime(strftime"%Y-%m-%d %H:%M:%S"v))
:minute(->datetime(strftime"%Y-%m-%d %H:%M"v))
:minute-of-hour(kx/->integer(strftime"%M"v))
:hour(->datetime(strftime"%Y-%m-%d %H:00"v))
...
...
@@ -78,26 +79,28 @@
3)
:year(kx/->integer(strftime"%Y"v)))))
(def^:privatedatetime(partialk/sqlfn*:DATETIME))
(defn-date-interval[unitamount]
(let[[multipliersqlite-unit](caseunit
:second[1"seconds"]
:minute[1"minutes"]
:hour[1"hours"]
:day[1"days"]
:week[7"days"]
:month[1"months"]
:quarter[3"months"]
:year[1"years"])]
;; Make a string like DATETIME(DATE('now', 'start of month'), '-1 month')
;; The date bucketing will end up being done twice since `date` is called on the results of `date-interval` automatically. This shouldn't be a big deal because it's used for relative dates and only needs to be done once in a given query.
;; It's important to call `date` on 'now' to apply bucketing *before* adding/subtracting dates to handle certain edge cases as discussed in issue #2275 (https://github.com/metabase/metabase/issues/2275).
;; Basically, March 30th minus one month becomes Feb 30th in SQLite, which becomes March 2nd. DATE(DATETIME('2016-03-30', '-1 month'), 'start of month') is thus March 1st.
;; The SQL we produce instead (for "last month") ends up looking something like: DATE(DATETIME(DATE('2015-03-30', 'start of month'), '-1 month'), 'start of month'). It's a little verbose, but gives us the correct answer (Feb 1st).