2016-05-20 14:20:48 +03:00
|
|
|
(ns status-im.utils.datetime
|
2018-09-06 12:04:12 +02:00
|
|
|
(:require [re-frame.core :as re-frame]
|
2019-09-07 14:57:22 +02:00
|
|
|
[cljs-time.core :as t :refer [plus minus days hours before?]]
|
2022-09-19 11:08:21 -03:00
|
|
|
[cljs-time.coerce :refer [from-long]]
|
2016-08-04 18:36:13 +03:00
|
|
|
[cljs-time.format :refer [formatters
|
|
|
|
formatter
|
|
|
|
unparse]]
|
2021-02-12 15:58:43 +01:00
|
|
|
[status-im.i18n.i18n :refer [label label-pluralize]]
|
2018-08-03 18:43:37 +02:00
|
|
|
[status-im.native-module.core :as status]
|
2018-01-04 02:13:12 +01:00
|
|
|
[clojure.string :as s]
|
2019-09-07 14:57:22 +02:00
|
|
|
[status-im.goog.i18n :as goog.18n]))
|
2016-08-04 18:36:13 +03:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
;;;; Datetime constants
|
|
|
|
|
2017-03-17 17:37:54 +03:00
|
|
|
(defn now []
|
|
|
|
(t/now))
|
|
|
|
|
2020-12-10 18:02:45 +02:00
|
|
|
(def one-second 1000)
|
|
|
|
(def minute (* 60 one-second))
|
|
|
|
(defn minutes [m]
|
|
|
|
(* m minute))
|
|
|
|
(def hour (* 60 minute))
|
|
|
|
(def day (* 24 hour))
|
2016-08-04 18:36:13 +03:00
|
|
|
(def week (* 7 day))
|
2021-10-29 21:55:01 +05:30
|
|
|
(defn weeks [w]
|
|
|
|
(* w week))
|
2020-10-02 11:53:02 +02:00
|
|
|
(def units [{:name :t/datetime-second-short :limit 60 :in-second 1}
|
|
|
|
{:name :t/datetime-minute-short :limit 3600 :in-second 60}
|
|
|
|
{:name :t/datetime-hour-short :limit 86400 :in-second 3600}
|
|
|
|
{:name :t/datetime-day-short :limit nil :in-second 86400}])
|
2016-05-20 12:41:55 +03:00
|
|
|
|
2019-09-07 14:57:22 +02:00
|
|
|
(def time-zone-offset (hours (- (/ (.getTimezoneOffset ^js (js/Date.)) 60))))
|
2016-05-20 12:41:55 +03:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
;;;; Utilities
|
|
|
|
|
|
|
|
(defn- is24Hour-locsym
|
|
|
|
"Detects if given locale symbols timeformat generates AM/PM ('a')."
|
|
|
|
[^js locsym]
|
2018-07-30 12:16:12 +02:00
|
|
|
(not (s/includes?
|
2019-05-29 19:52:41 +03:00
|
|
|
(nth (.-TIMEFORMATS locsym) 2)
|
2018-07-30 12:16:12 +02:00
|
|
|
"a")))
|
2018-01-04 02:13:12 +01:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
(defn- is24Hour
|
|
|
|
"Returns is24Hour from device or from given locale symbols. Whenever we get
|
|
|
|
non-nil value use it, else calculate it from the given locale symbol."
|
|
|
|
[^js locsym]
|
2018-08-03 18:43:37 +02:00
|
|
|
(if-some [fromdev (status/is24Hour)]
|
|
|
|
fromdev
|
|
|
|
(is24Hour-locsym locsym)))
|
2018-07-30 12:16:12 +02:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
;;;; Time formats
|
|
|
|
|
|
|
|
(defn- short-time-format
|
|
|
|
[^js locsym]
|
|
|
|
(if (is24Hour locsym)
|
|
|
|
"HH:mm"
|
|
|
|
"h:mm a"))
|
|
|
|
|
|
|
|
(defn- time-format
|
|
|
|
[^js locsym]
|
|
|
|
(if (is24Hour locsym)
|
|
|
|
"HH:mm:ss"
|
|
|
|
"h:mm:ss a"))
|
|
|
|
|
|
|
|
;;;; Date formats
|
2018-07-30 12:16:12 +02:00
|
|
|
|
2019-09-07 14:57:22 +02:00
|
|
|
(defn- short-date-format [_] "dd MMM")
|
2018-07-30 12:16:12 +02:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
(defn- datetime-within-one-week-format
|
|
|
|
[^js locsym]
|
|
|
|
(if (is24Hour locsym)
|
|
|
|
"E HH:mm"
|
|
|
|
"E h:mm a"))
|
|
|
|
|
|
|
|
(defn- medium-date-format
|
|
|
|
"Get medium format from current locale symbols."
|
|
|
|
[^js locsym]
|
|
|
|
(nth (.-DATEFORMATS locsym) 2))
|
|
|
|
|
|
|
|
;;;; Datetime formats
|
|
|
|
|
2019-05-29 19:52:41 +03:00
|
|
|
(defn- medium-date-time-format [locsym]
|
|
|
|
(str (medium-date-format locsym) ", " (time-format locsym)))
|
2018-07-30 12:16:12 +02:00
|
|
|
|
2019-06-04 18:20:03 +03:00
|
|
|
(defn get-formatter-fn [format]
|
|
|
|
(let [formatter (atom nil)]
|
|
|
|
(fn []
|
|
|
|
(or @formatter
|
|
|
|
(reset! formatter
|
2021-02-12 15:58:43 +01:00
|
|
|
(goog.18n/mk-fmt status-im.i18n.i18n/locale format))))))
|
2018-07-30 12:16:12 +02:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
(def date-time-fmt (get-formatter-fn medium-date-time-format))
|
|
|
|
(def date-fmt (get-formatter-fn medium-date-format))
|
|
|
|
(def time-fmt (get-formatter-fn short-time-format))
|
|
|
|
(def short-date-fmt (get-formatter-fn short-date-format))
|
|
|
|
(def datetime-within-one-week-fmt (get-formatter-fn datetime-within-one-week-format))
|
|
|
|
|
|
|
|
;;;; Utilities
|
2018-07-30 12:16:12 +02:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
(defn previous-years?
|
|
|
|
[datetime]
|
|
|
|
(< (t/year datetime) (t/year (t/now))))
|
|
|
|
|
|
|
|
(defn current-year?
|
|
|
|
[datetime]
|
|
|
|
(= (t/year datetime) (t/year (t/now))))
|
|
|
|
|
|
|
|
(defn today?
|
|
|
|
[datetime]
|
|
|
|
(let [now (t/now)]
|
|
|
|
(and (= (t/year now) (t/year datetime))
|
|
|
|
(= (t/month now) (t/month datetime))
|
|
|
|
(= (t/day now) (t/day datetime)))))
|
|
|
|
|
|
|
|
(defn within-last-n-days?
|
|
|
|
"Returns true if `datetime` is within last `n` days (inclusive on both ends)."
|
|
|
|
[datetime n]
|
|
|
|
(let [now (t/now)
|
|
|
|
start (t/at-midnight (t/minus now (t/days n)))
|
|
|
|
end (t/plus now (t/millis 1))]
|
|
|
|
(t/within? start end datetime)))
|
|
|
|
|
|
|
|
;;;; Timestamp formatters
|
2018-01-04 02:13:12 +01:00
|
|
|
|
|
|
|
(defn- to-str [ms old-fmt-fn yesterday-fmt-fn today-fmt-fn]
|
|
|
|
(let [date (from-long ms)
|
2018-04-13 14:06:52 +01:00
|
|
|
local (plus date time-zone-offset) ; this is wrong, it uses the current timezone offset, regardless of DST
|
2019-03-05 13:17:11 +01:00
|
|
|
today (minus (t/today-at-midnight) time-zone-offset)
|
2018-01-04 02:13:12 +01: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
|
2019-09-07 14:57:22 +02:00
|
|
|
#(.format ^js (date-fmt) %)
|
2018-01-04 02:13:12 +01:00
|
|
|
#(label :t/datetime-yesterday)
|
2019-09-07 14:57:22 +02:00
|
|
|
#(.format ^js (time-fmt) %)))
|
2018-01-04 02:13:12 +01:00
|
|
|
|
|
|
|
(defn day-relative [ms]
|
|
|
|
(to-str ms
|
2019-09-07 14:57:22 +02:00
|
|
|
#(.format ^js (date-fmt) %)
|
2018-01-04 02:13:12 +01:00
|
|
|
#(label :t/datetime-yesterday)
|
|
|
|
#(label :t/datetime-today)))
|
2016-10-04 23:58:33 +03:00
|
|
|
|
2022-09-19 11:08:21 -03:00
|
|
|
(defn timestamp->relative [ms]
|
|
|
|
(let [datetime (from-long ms)]
|
|
|
|
(cond
|
|
|
|
(today? datetime)
|
|
|
|
(.format ^js (time-fmt) datetime)
|
|
|
|
|
|
|
|
(within-last-n-days? datetime 1)
|
|
|
|
(str (s/capitalize (label :t/datetime-yesterday))
|
|
|
|
" "
|
|
|
|
(.format ^js (time-fmt) datetime))
|
|
|
|
|
|
|
|
(within-last-n-days? datetime 6)
|
|
|
|
(.format ^js (datetime-within-one-week-fmt) datetime)
|
|
|
|
|
|
|
|
(current-year? datetime)
|
|
|
|
(.format ^js (short-date-fmt) datetime)
|
|
|
|
|
|
|
|
(previous-years? datetime)
|
|
|
|
(.format ^js (date-fmt) datetime))))
|
|
|
|
|
2017-09-11 14:04:11 +02:00
|
|
|
(defn timestamp->mini-date [ms]
|
2019-09-07 14:57:22 +02:00
|
|
|
(.format ^js (short-date-fmt) (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset))))
|
2017-09-06 00:37:43 +02:00
|
|
|
|
2017-10-16 16:11:56 +02:00
|
|
|
(defn timestamp->time [ms]
|
2019-09-07 14:57:22 +02:00
|
|
|
(.format ^js (time-fmt) (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset))))
|
2017-10-16 16:11:56 +02:00
|
|
|
|
2017-09-11 14:04:11 +02:00
|
|
|
(defn timestamp->date-key [ms]
|
|
|
|
(keyword (unparse (formatter "YYYYMMDD") (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset)))))
|
|
|
|
|
2017-09-12 04:03:59 +02:00
|
|
|
(defn timestamp->long-date [ms]
|
2019-09-07 14:57:22 +02:00
|
|
|
(.format ^js (date-time-fmt) (-> ms
|
|
|
|
from-long
|
|
|
|
(plus time-zone-offset))))
|
2017-09-12 04:03:59 +02:00
|
|
|
|
2016-06-30 19:00:44 +03:00
|
|
|
(defn format-time-ago [diff unit]
|
|
|
|
(let [name (label-pluralize diff (:name unit))]
|
2020-10-02 11:53:02 +02:00
|
|
|
(if (= :t/datetime-second-short (:name unit))
|
|
|
|
(label :t/now)
|
|
|
|
(label :t/datetime-ago-format-short {:ago (label :t/datetime-ago)
|
|
|
|
:number diff
|
|
|
|
:time-intervals name}))))
|
2018-10-19 18:50:35 +03:00
|
|
|
(defn seconds-ago [time]
|
2020-10-29 14:59:58 +01:00
|
|
|
(let [now (t/now)]
|
|
|
|
(if (<= (.getTime ^js time) (.getTime ^js now))
|
|
|
|
(t/in-seconds (t/interval time now))
|
|
|
|
0)))
|
2016-06-30 19:00:44 +03:00
|
|
|
|
2016-08-04 18:36:13 +03:00
|
|
|
(defn time-ago [time]
|
2020-10-02 11:53:02 +02:00
|
|
|
(let [diff (seconds-ago time)
|
|
|
|
unit (first (drop-while #(and (>= diff (:limit %))
|
|
|
|
(:limit %))
|
|
|
|
units))]
|
|
|
|
(-> (/ diff (:in-second unit))
|
|
|
|
Math/floor
|
|
|
|
int
|
|
|
|
(format-time-ago unit))))
|
2016-08-04 18:36:13 +03:00
|
|
|
|
2021-10-25 09:46:33 +01:00
|
|
|
(defn time-ago-long [time]
|
|
|
|
(let [seconds-ago (seconds-ago time)
|
|
|
|
unit (first (drop-while #(and (>= seconds-ago (:limit %))
|
|
|
|
(:limit %))
|
|
|
|
units))
|
|
|
|
diff (-> (/ seconds-ago (:in-second unit))
|
|
|
|
Math/floor
|
|
|
|
int)
|
|
|
|
|
|
|
|
name (label-pluralize diff (:name unit))]
|
|
|
|
(label :t/datetime-ago-format {:ago (label :t/datetime-ago)
|
|
|
|
:number diff
|
|
|
|
:time-intervals name})))
|
|
|
|
|
2016-08-04 18:36:13 +03:00
|
|
|
(defn to-date [ms]
|
|
|
|
(from-long ms))
|
|
|
|
|
2018-02-20 23:47:56 +09:00
|
|
|
(defn timestamp []
|
|
|
|
(inst-ms (js/Date.)))
|
2016-10-06 16:15:57 +03:00
|
|
|
|
2021-10-29 21:55:01 +05:30
|
|
|
(defn timestamp-sec []
|
|
|
|
(int (/ (timestamp) 1000)))
|
|
|
|
|
2018-12-25 17:57:19 +03:00
|
|
|
(defn timestamp->year-month-day-date [ms]
|
|
|
|
(unparse (:year-month-day formatters) (to-date ms)))
|
|
|
|
|
2018-09-06 12:04:12 +02:00
|
|
|
(re-frame/reg-cofx
|
|
|
|
:now
|
|
|
|
(fn [coeffects _]
|
|
|
|
(assoc coeffects :now (timestamp))))
|
|
|
|
|
2021-10-29 21:55:01 +05:30
|
|
|
(defn to-ms [sec]
|
|
|
|
(* 1000 sec))
|