2016-05-20 11:20:48 +00:00
|
|
|
(ns status-im.utils.datetime
|
2018-09-06 10:04:12 +00:00
|
|
|
(:require [re-frame.core :as re-frame]
|
2019-03-05 12:17:11 +00:00
|
|
|
[cljs-time.core :as t :refer [date-time plus minus days hours before?]]
|
2016-10-06 13:15:57 +00:00
|
|
|
[cljs-time.coerce :refer [from-long to-long from-date]]
|
2016-08-04 15:36:13 +00:00
|
|
|
[cljs-time.format :refer [formatters
|
|
|
|
formatter
|
|
|
|
unparse]]
|
2016-06-30 16:00:44 +00:00
|
|
|
[status-im.i18n :refer [label label-pluralize]]
|
2018-08-03 16:43:37 +00:00
|
|
|
[status-im.native-module.core :as status]
|
2016-06-30 16:00:44 +00:00
|
|
|
[goog.string :as gstring]
|
2018-01-04 01:13:12 +00:00
|
|
|
goog.string.format
|
|
|
|
goog.i18n.DateTimeFormat
|
|
|
|
[clojure.string :as s]
|
|
|
|
[goog.object :refer [get]]))
|
2016-08-04 15:36:13 +00:00
|
|
|
|
2017-03-17 14:37:54 +00:00
|
|
|
(defn now []
|
|
|
|
(t/now))
|
|
|
|
|
2016-08-04 15:36:13 +00:00
|
|
|
(def hour (* 1000 60 60))
|
|
|
|
(def day (* hour 24))
|
|
|
|
(def week (* 7 day))
|
2016-06-30 16:00:44 +00:00
|
|
|
(def units [{:name :t/datetime-second :limit 60 :in-second 1}
|
|
|
|
{:name :t/datetime-minute :limit 3600 :in-second 60}
|
|
|
|
{:name :t/datetime-hour :limit 86400 :in-second 3600}
|
|
|
|
{:name :t/datetime-day :limit nil :in-second 86400}])
|
2016-05-20 09:41:55 +00:00
|
|
|
|
|
|
|
(def time-zone-offset (hours (- (/ (.getTimezoneOffset (js/Date.)) 60))))
|
|
|
|
|
2018-01-04 01:13:12 +00:00
|
|
|
;; xx-YY locale, xx locale or en fallback
|
|
|
|
(defn- locale-symbols [locale-name]
|
2019-05-29 16:52:41 +00:00
|
|
|
(if-let [loc (get goog/i18n (str "DateTimeSymbols_" locale-name))]
|
2018-01-04 01:13:12 +00:00
|
|
|
loc
|
|
|
|
(let [name-first (s/replace (or locale-name "") #"-.*$" "")
|
2019-05-29 16:52:41 +00:00
|
|
|
loc (get goog/i18n (str "DateTimeSymbols_" name-first))]
|
|
|
|
(or loc goog/i18n.DateTimeSymbols_en))))
|
2018-01-04 01:13:12 +00:00
|
|
|
|
2018-07-30 10:16:12 +00:00
|
|
|
;; detects if given locale symbols timeformat generates AM/PM ("a")
|
|
|
|
(defn- is24Hour-locsym [locsym]
|
|
|
|
(not (s/includes?
|
2019-05-29 16:52:41 +00:00
|
|
|
(nth (.-TIMEFORMATS locsym) 2)
|
2018-07-30 10:16:12 +00:00
|
|
|
"a")))
|
2018-01-04 01:13:12 +00:00
|
|
|
|
2018-07-30 10:16:12 +00:00
|
|
|
;; returns is24Hour from device or from given locale symbols
|
2018-08-03 16:43:37 +00:00
|
|
|
;; whenever we get non-nil value use it, else calculate it from the given locale symbol
|
2018-07-30 10:16:12 +00:00
|
|
|
(defn- is24Hour [locsym]
|
2018-08-03 16:43:37 +00:00
|
|
|
(if-some [fromdev (status/is24Hour)]
|
|
|
|
fromdev
|
|
|
|
(is24Hour-locsym locsym)))
|
2018-07-30 10:16:12 +00:00
|
|
|
|
2018-09-06 10:04:12 +00:00
|
|
|
;; time formats
|
2018-07-30 10:16:12 +00:00
|
|
|
(defn- short-time-format [locsym] (if (is24Hour locsym) "HH:mm" "h:mm a"))
|
|
|
|
(defn- time-format [locsym] (if (is24Hour locsym) "HH:mm:ss" "h:mm:ss a"))
|
|
|
|
|
|
|
|
;; date formats
|
|
|
|
(defn- short-date-format [locsym] "dd MMM")
|
2019-05-29 16:52:41 +00:00
|
|
|
(defn- medium-date-format [locsym] (nth (.-DATEFORMATS locsym) 2)) ; get medium format from current locale symbols
|
2018-07-30 10:16:12 +00:00
|
|
|
|
|
|
|
;; date-time formats
|
2019-05-29 16:52:41 +00:00
|
|
|
(defn- medium-date-time-format [locsym]
|
|
|
|
(str (medium-date-format locsym) ", " (time-format locsym)))
|
2018-07-30 10:16:12 +00:00
|
|
|
|
|
|
|
;; get formatter for current locale symbols and format function
|
|
|
|
(defn- mk-fmt [locale format-fn]
|
|
|
|
(let [locsym (locale-symbols locale)]
|
2019-05-29 16:52:41 +00:00
|
|
|
(goog/i18n.DateTimeFormat. (format-fn locsym) locsym)))
|
2018-07-30 10:16:12 +00:00
|
|
|
|
|
|
|
;; generate formatters for different formats
|
2018-01-04 01:13:12 +00:00
|
|
|
(def date-time-fmt
|
2018-04-13 13:06:52 +00:00
|
|
|
(mk-fmt status-im.i18n/locale medium-date-time-format))
|
2018-01-04 01:13:12 +00:00
|
|
|
(def date-fmt
|
2018-04-13 13:06:52 +00:00
|
|
|
(mk-fmt status-im.i18n/locale medium-date-format))
|
|
|
|
(def time-fmt
|
|
|
|
(mk-fmt status-im.i18n/locale short-time-format))
|
2018-07-30 10:16:12 +00:00
|
|
|
(def short-date-fmt
|
|
|
|
(mk-fmt status-im.i18n/locale short-date-format))
|
|
|
|
|
|
|
|
;;
|
|
|
|
;; functions which apply formats for the given timestamp
|
|
|
|
;;
|
2018-01-04 01:13:12 +00:00
|
|
|
|
|
|
|
(defn- to-str [ms old-fmt-fn yesterday-fmt-fn today-fmt-fn]
|
|
|
|
(let [date (from-long ms)
|
2018-04-13 13:06:52 +00:00
|
|
|
local (plus date time-zone-offset) ; this is wrong, it uses the current timezone offset, regardless of DST
|
2019-03-05 12:17:11 +00:00
|
|
|
today (minus (t/today-at-midnight) time-zone-offset)
|
2018-01-04 01:13:12 +00:00
|
|
|
yesterday (plus today (days -1))]
|
|
|
|
(cond
|
|
|
|
(before? date yesterday) (old-fmt-fn local)
|
|
|
|
(before? date today) (yesterday-fmt-fn local)
|
|
|
|
:else (today-fmt-fn local))))
|
|
|
|
|
|
|
|
(defn to-short-str [ms]
|
|
|
|
(to-str ms
|
|
|
|
#(.format date-fmt %)
|
|
|
|
#(label :t/datetime-yesterday)
|
2018-04-13 13:06:52 +00:00
|
|
|
#(.format time-fmt %)))
|
2018-01-04 01:13:12 +00:00
|
|
|
|
|
|
|
(defn day-relative [ms]
|
|
|
|
(to-str ms
|
2018-03-20 18:23:08 +00:00
|
|
|
#(.format date-fmt %)
|
2018-01-04 01:13:12 +00:00
|
|
|
#(label :t/datetime-yesterday)
|
|
|
|
#(label :t/datetime-today)))
|
2016-10-04 20:58:33 +00:00
|
|
|
|
2017-09-11 12:04:11 +00:00
|
|
|
(defn timestamp->mini-date [ms]
|
2018-07-30 10:16:12 +00:00
|
|
|
(.format short-date-fmt (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset))))
|
2017-09-05 22:37:43 +00:00
|
|
|
|
2017-10-16 14:11:56 +00:00
|
|
|
(defn timestamp->time [ms]
|
2018-04-13 13:06:52 +00:00
|
|
|
(.format time-fmt (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset))))
|
2017-10-16 14:11:56 +00:00
|
|
|
|
2017-09-11 12:04:11 +00:00
|
|
|
(defn timestamp->date-key [ms]
|
|
|
|
(keyword (unparse (formatter "YYYYMMDD") (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset)))))
|
|
|
|
|
2017-09-12 02:03:59 +00:00
|
|
|
(defn timestamp->long-date [ms]
|
2018-07-30 10:16:12 +00:00
|
|
|
(.format date-time-fmt (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset))))
|
2017-09-12 02:03:59 +00:00
|
|
|
|
2016-06-30 16:00:44 +00:00
|
|
|
(defn format-time-ago [diff unit]
|
|
|
|
(let [name (label-pluralize diff (:name unit))]
|
2017-03-16 15:11:09 +00:00
|
|
|
(label :t/datetime-ago-format {:ago (label :t/datetime-ago)
|
|
|
|
:number diff
|
|
|
|
:time-intervals name})))
|
2018-10-19 15:50:35 +00:00
|
|
|
(defn seconds-ago [time]
|
|
|
|
(t/in-seconds (t/interval time (t/now))))
|
2016-06-30 16:00:44 +00:00
|
|
|
|
2016-08-04 15:36:13 +00:00
|
|
|
(defn time-ago [time]
|
2018-10-19 15:50:35 +00:00
|
|
|
(let [diff (seconds-ago time)]
|
2016-08-04 15:36:13 +00:00
|
|
|
(if (< diff 60)
|
|
|
|
(label :t/active-online)
|
2016-10-11 14:24:52 +00:00
|
|
|
(let [unit (first (drop-while #(and (>= diff (:limit %))
|
|
|
|
(:limit %))
|
2016-08-04 15:36:13 +00:00
|
|
|
units))]
|
|
|
|
(-> (/ diff (:in-second unit))
|
|
|
|
Math/floor
|
|
|
|
int
|
2016-06-30 16:00:44 +00:00
|
|
|
(format-time-ago unit))))))
|
2016-08-04 15:36:13 +00:00
|
|
|
|
|
|
|
(defn to-date [ms]
|
|
|
|
(from-long ms))
|
|
|
|
|
2018-02-20 14:47:56 +00:00
|
|
|
(defn timestamp []
|
|
|
|
(inst-ms (js/Date.)))
|
2016-10-06 13:15:57 +00:00
|
|
|
|
2018-12-25 14:57:19 +00:00
|
|
|
(defn timestamp->year-month-day-date [ms]
|
|
|
|
(unparse (:year-month-day formatters) (to-date ms)))
|
|
|
|
|
2018-09-06 10:04:12 +00:00
|
|
|
(re-frame/reg-cofx
|
|
|
|
:now
|
|
|
|
(fn [coeffects _]
|
|
|
|
(assoc coeffects :now (timestamp))))
|
|
|
|
|
2016-10-06 13:15:57 +00:00
|
|
|
(defn format-date [format date]
|
|
|
|
(let [local (plus (from-date date) time-zone-offset)]
|
|
|
|
(unparse (formatter format) local)))
|
|
|
|
|
|
|
|
(defn get-ordinal-date [date]
|
|
|
|
(let [local (plus (from-date date) time-zone-offset)
|
|
|
|
day (js/parseInt (unparse (formatter "d") local))
|
|
|
|
s {0 "th"
|
|
|
|
1 "st"
|
|
|
|
2 "nd"
|
|
|
|
3 "rd"}
|
|
|
|
m (mod day 100)]
|
|
|
|
(str day (or (s (mod (- m 20) 10))
|
|
|
|
(s m)
|
|
|
|
(s 0)))))
|