diff --git a/README.md b/README.md index 741534c..44676af 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,33 @@ zero-dependency, cross-platform Clojure time library -*Status* - pre-alpha. +*Status* - pre-alpha. -* The underlying [Javascript platform time API](https://github.com/tc39/proposal-temporal) has semi-stabilized at ecma `stage 3` - meaning implementors -can still suggest changes. When it has reached `stage 4`, application developers targeting the browser will need to include their own -script to bring in a polyfill if the end-user's browser does not yet have the platform API required. +* The underlying [Javascript platform time API](https://github.com/tc39/proposal-temporal) has semi-stabilized at + ecma `stage 3` - meaning implementors + can still suggest changes. When it has reached `stage 4`, application developers targeting the browser will need to + include their own + script to bring in a polyfill if the end-user's browser does not yet have the platform API required. ## About -* Zero dependency => platform APIs only - see [comparison of java.time vs temporal](https://widdindustries.com/ecma-temporal-vs-java-time/) +* Zero dependency => platform APIs only - + see [comparison of java.time vs temporal](https://widdindustries.com/ecma-temporal-vs-java-time/) * platform+performance friendly - max DCE-ability for cljs, reflection-free on jvm. * explicitly named conversions from type to type. no keyword arguments in functions * no implicit/contextual use of ambient zone or clock * API based on mnemonics * small feature set - aim for 80% of everyday date/time use cases. -* assume ISO8601 calendar. +* assume ISO8601 calendar. * data-literals - same ones as [time-literals](https://github.com/henryw374/time-literals) ### java.time vs Temporal -java.time and Temporal have some shared concepts and some unshared. Naming is also partially shared. [See here for a brief introduction and overview](https://widdindustries.com/blog/ecma-temporal-vs-java-time.html) +java.time and Temporal have some shared concepts and some unshared. Naming is also partially +shared. [See here for a brief introduction and overview](https://widdindustries.com/blog/ecma-temporal-vs-java-time.html) -The below graph shows the entities in Temporal. If you know java.time and you squint a bit, it will look familiar to you. +The below graph shows the entities in Temporal. If you know java.time and you squint a bit, it will look familiar to +you. ![graph of entities in Temporal](https://tc39.es/proposal-temporal/docs/object-model.svg) @@ -36,14 +41,15 @@ Tempo tries to find obvious common ground between java.time and Temporal. Follow *just java.time* -* parsing non-iso strings ([Temporal may have this in the future](https://github.com/js-temporal/proposal-temporal-v2/issues/2)) +* parsing non-iso + strings ([Temporal may have this in the future](https://github.com/js-temporal/proposal-temporal-v2/issues/2)) * 2 types to represent temporal-amounts: `Duration` and `Period` * clojure `=` and `hash` work - so these are added to Temporal objects in Tempo * fixed & offset clocks - so these are added in cljs Tempo -* OffsetDateTime, OffsetTime, Month, Year and DayOfWeek entities - * Tempo adds DayOfWeek to cljs, so there is e.g. `t/weekday-saturday` - * OffsetDateTime & OffsetTime are not in Tempo - * Month and Year are just represented by integers in Tempo +* OffsetDateTime, OffsetTime, Month, Year and DayOfWeek entities + * Tempo adds DayOfWeek to cljs, so there is e.g. `t/weekday-saturday` + * OffsetDateTime & OffsetTime are not in Tempo + * Month and Year are just represented by integers in Tempo *just temporal* @@ -58,56 +64,74 @@ Tempo tries to find obvious common ground between java.time and Temporal. Follow ## Rationale Since it was introduced in Java 8, use of the java.time API has become more and more widespread because: - * it improves on the legacy `java.util.Date` API - * it is a platform API - developers and library authors can be confident that other developers will know the API and be happy to use it. + +* it improves on the legacy `java.util.Date` API +* it is a platform API - developers and library authors can be confident that other developers will know the API and be + happy to use it. The same benefits will apply to the Temporal API when it is widely available in browsers. -Cross-platform date/time APIs for Clojure have already proven popular. It seems logical that one should exist targeting both java.time and Temporal. +Cross-platform date/time APIs for Clojure have already proven popular. It seems logical that one should exist targeting +both java.time and Temporal. -However, as stated above, although there is not a 1-1 correspondance between java.time and Temporal, there is sufficient overlap for a cross platform API that covers the majority of everyday use-cases. +However, as stated above, although there is not a 1-1 correspondance between java.time and Temporal, there is sufficient +overlap for a cross platform API that covers the majority of everyday use-cases. ### Why not 'fill in the gaps' to make Temporal like java.time? -There are some obvious benefits to be had if this were done. +There are some obvious benefits to be had if this were done. -However, aside from being a lot of work to do this, Temporal is a different API from java.time. The Temporal authors have designed it from scratch very deliberately and in so doing have made some different choices from java.time. +However, aside from being a lot of work to do this, Temporal is a different API from java.time. The Temporal authors +have designed it from scratch very deliberately and in so doing have made some different choices from java.time. -Where Temporal and java.time overlap, there is obvious scope for a common API. Where they differ, application developers can decide on a case by case basis how to tackle that. +Where Temporal and java.time overlap, there is obvious scope for a common API. Where they differ, application developers +can decide on a case by case basis how to tackle that. ### What about Existing Cross-platform date/time APIs? -[Tick](https://github.com/juxt/tick) (which I help maintain) is great for application developers who want a +[Tick](https://github.com/juxt/tick) (which I help maintain) is great for application developers who want a cross-platform date-time library based on the java.time API. Tick provides much useful functionality -on top of java.time, but users know they can always drop to [cljc.java-time](https://github.com/henryw374/cljc.java-time), +on top of java.time, but users know they can always drop +to [cljc.java-time](https://github.com/henryw374/cljc.java-time), to access the full java.time API directly when needed. -Even when Temporal is widely available, I would imagine many Clojure developers will want to keep using Tick because -* It is based on the same java.time API in both JVM and Javascript environments - so the full capability of java.time is available as required. -* The additional build size of Tick in Javascript [does not degrade application performance](https://widdindustries.com/blog/clojurescript-datetime-lib-comparison.html) +Even when Temporal is widely available, I would imagine many Clojure developers will want to keep using Tick because + +* It is based on the same java.time API in both JVM and Javascript environments - so the full capability of java.time is + available as required. +* The additional build size of Tick in + Javascript [does not degrade application performance](https://widdindustries.com/blog/clojurescript-datetime-lib-comparison.html) * Switching away from it will require significant time investment -Since `tick` is based on `java.time`, in its entirety it is incompatible with Temporal. Having said that a `tempo.tick` namespace exists which contains a subset of the functions from `tick.core` which are compatible. +Since `tick` is based on `java.time`, in its entirety it is incompatible with Temporal. Having said that a `tempo.tick` +namespace exists which contains a subset of the functions from `tick.core` which are compatible. ## Usage -### Depend +### Depend There is no tempo maven artifact atm. Depend on tempo via deps.edn: ```clojure -{:deps {com.widdindustries/tempo {:git/url "https://github.com/henryw374/tempo.git" - :sha "abc" - }}} +{:deps {com.widdindustries/tempo + {:git/url "https://github.com/henryw374/tempo.git" + :sha "abc"} + ; to get data-literals for java.time and Temporal, also add... + com.widdindustries/time-literals-tempo {:mvn/version "0.1.10"}}} ``` ### Setup ```clojure (ns my.cljc.namespace - (:require [com.widdindustries.tempo :as t])) + (:require [com.widdindustries.tempo :as t] + [time-literals.read-write])) + +; optionally, print objects as data-literals +(time-literals.read-write/print-time-literals-clj!) +(time-literals.read-write/print-time-literals-cljs!) ;optional - make clojure.core fns =,sort,compare etc work for all js/Temporal entities (t/extend-all-cljs-protocols) @@ -150,7 +174,8 @@ a clock is then passed as arg to all `now` functions, for example: (t/timezone-now clock) ``` -Where a timezone is accessed from an object, or passed into an object, only the string representation is used, referred to as `timezone_id`. Call `str` on a timezone to get its id. +Where a timezone is accessed from an object, or passed into an object, only the string representation is used, referred +to as `timezone_id`. Call `str` on a timezone to get its id. ```clojure (t/zdt->timezone_id zdt) @@ -186,7 +211,7 @@ Where a timezone is accessed from an object, or passed into an object, only the ``` -#### Manipulation +#### Manipulation aka construction a new temporal from one of the same type @@ -213,22 +238,25 @@ Consider the following: (t/<< 1 t/months-property))) ``` -If you shift a date forward by an amount, then back by that amount then one might think the output would be equal to the input. In some cases that would happen, but not in the case shown above. +If you shift a date forward by an amount, then back by that amount then one might think the output would be equal to the +input. In some cases that would happen, but not in the case shown above. Here's a similar example: ```clojure (let [start (t/date-parse "2020-02-29")] - (-> start + (-> start (t/with 2021 t/years-property) (t/with 2020 t/years-property))) ``` We increment the year, then decrement it, but the output is not the same as the input. -Both java.time and Temporal work this way and in my experience it is a source of bugs. For this reason, shifting `>>/<<` and `with` do not work in Tempo if the property is years or months. +Both java.time and Temporal work this way and in my experience it is a source of bugs. For this reason, shifting `>>/<<` +and `with` do not work in Tempo if the property is years or months. -As a safer alternative, I suggest getting the year-month from a temporal first, doing whatever with/shift operations you like then setting the remaining fields. +As a safer alternative, I suggest getting the year-month from a temporal first, doing whatever with/shift operations you +like then setting the remaining fields. If you do not wish to have this guardrail, set `t/*block-non-commutative-operations*` to false @@ -280,7 +308,7 @@ It is preferred to use numbers and properties for example (t/>> a-date 1 t/days-property) ``` -## Dev +## Dev see dev.clj for instructions diff --git a/deps.edn b/deps.edn index da9fde3..86a5d15 100644 --- a/deps.edn +++ b/deps.edn @@ -1,16 +1,22 @@ ; this is not the deps for a lib user to depend on. find those under 'gen-out' {:paths ["src"] - :aliases {:dev {:extra-paths ["dev" "web-target" "test"] - :extra-deps {;ring/ring-core {:mvn/version "1.8.1"} - henryw374/defoclock {:mvn/version "0.1.3"} - backtick/backtick {:mvn/version "0.3.4"} - camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"} - medley/medley {:mvn/version "1.4.0"} - com.widdindustries/tiado-cljs2 {:git/url "https://github.com/henryw374/tiado-cljs2.git" :git/sha "3229283d825a393008a1f1c408603dff775e87c2"} - org.clojure/tools.namespace {:mvn/version "1.4.4"} - babashka/fs {:mvn/version "0.5.20"} + :aliases {:dev {:extra-paths ["dev" "web-target" "test"] + :extra-deps {;ring/ring-core {:mvn/version "1.8.1"} + henryw374/defoclock {:mvn/version "0.1.3"} + backtick/backtick {:mvn/version "0.3.4"} + camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"} + medley/medley {:mvn/version "1.4.0"} + com.widdindustries/tiado-cljs2 {:git/url "https://github.com/henryw374/tiado-cljs2.git" :git/sha "3229283d825a393008a1f1c408603dff775e87c2"} + org.clojure/tools.namespace {:mvn/version "1.4.4"} + babashka/fs {:mvn/version "0.5.20"} + com.widdindustries/time-literals-tempo {:mvn/version "0.1.10"} + + } } - }} - :release {:extra-deps {applied-science/deps-library {:mvn/version "RELEASE"}} - :main-opts ["-m" "applied-science.deps-library"]} + + + :release {:extra-deps {applied-science/deps-library {:mvn/version "RELEASE"}} + :main-opts ["-m" "applied-science.deps-library"]} + } + } \ No newline at end of file diff --git a/dev/dev.clj b/dev/dev.clj index e4100ca..d7c7484 100644 --- a/dev/dev.clj +++ b/dev/dev.clj @@ -28,7 +28,11 @@ - + '); + } + diff --git a/dev/user.clj b/dev/user.clj index f30e469..bdc3a1c 100644 --- a/dev/user.clj +++ b/dev/user.clj @@ -6,4 +6,5 @@ (comment (dev) + ) diff --git a/test/com/widdindustries/demo.cljc b/test/com/widdindustries/demo.cljc index ac04802..138e5ca 100644 --- a/test/com/widdindustries/demo.cljc +++ b/test/com/widdindustries/demo.cljc @@ -1,64 +1,10 @@ (ns com.widdindustries.demo (:require [com.widdindustries.tempo :as t] ;[com.widdindustries.tempo.cljs-protocols :as cljs-protocols] - ; [com.widdindustries.tempo.js-temporal-entities :as entities] - ; [com.widdindustries.tempo.js-temporal-methods :as methods] - ; [com.widdindustries.tempo.clock :as clock] + )) (comment - (t/date-now) - ;(t/date-now clock) - (t/date-parse "2020-02-02") ;iso strings only - ;(t/date-from {:year 2020 :month 2 :day-of-month 2}) - ; the -from functions accept a map of components which is sufficient to build the entity - (t/datetime-from {:date (t/date-parse "2020-02-02") :time (t/time-now)}) - ; or equivalently - (t/datetime-from {:year 2020 :month 2 :day 2 :time (t/time-now)}) - ; with -from, you can use smaller or larger components. - ; larger ones take precedence. below, the :year is ignored, because the :date took precedence (being larger) - (t/datetime-from {:year 2021 :date (t/date-parse "2020-02-02") :time (t/time-now)}) - - ; to get parts of an entity, start with the subject and add -> - ;(t/date->yearmonth (t/date-now)) - (t/date->month (t/date-now)) - (t/zdt->nano (t/zdt-now)) - ;(-> (t/instant-now) (t/instant->epochmillis)) - - - ;(t/period->days (t/period-parse "P3Y5M3D")) ; > 3 - - ;(t/duration->as-minutes (t/duration-parse "PT3H")) ; > 180 - - ; following won't exist bc years and months are variable length - ;(t/period->as-days (t/period-parse "P3Y5M3D")) - - - ;(t/+ (t/duration-parse "PT3H") (t/duration-parse "PT3S")) - - - ;; move date forward 3 days - ;(t/>> (t/date-now) (t/period-parse "P3D")) - - ;(-> (t/date-now) (t/with {:year 2021 :month 7})) - ;(-> (t/date-now) (t/with-year 3030)) - - ; todo - is this easily doable with platform api?? - ;(-> (t/date-now) (t/truncate-to-month)) - ;(-> (t/instant-now) (t/truncate-to-month)) - - - ;only entities of the same type can be compared - - (t/>= (t/date-parse "2020-05-05") (t/date-parse "2021-05-05")) - - - (t/max (t/date-parse "2020-05-05") (t/date-parse "2021-05-05") (t/date-parse "1920-05-05")) - - ; you must specify unit - ;(t/until a b :minutes) - - - + ) diff --git a/test/com/widdindustries/tempo_test.cljc b/test/com/widdindustries/tempo_test.cljc index 3270b03..2037389 100644 --- a/test/com/widdindustries/tempo_test.cljc +++ b/test/com/widdindustries/tempo_test.cljc @@ -1,19 +1,30 @@ (ns com.widdindustries.tempo-test (:require [clojure.test :refer [deftest is testing]] [com.widdindustries.tempo :as t] - [com.widdindustries.tempo.duration-alpha :as d]) + [com.widdindustries.tempo.duration-alpha :as d] + [time-literals.read-write]) #?(:clj (:import [java.util Date]))) +(time-literals.read-write/print-time-literals-clj!) +(time-literals.read-write/print-time-literals-cljs!) + (comment (remove-ns (.name *ns*)) (remove-ns 'com.widdindustries.tempo) (require '[com.widdindustries.tempo] :reload-all) + (require '[time-literals.read-write]) + #time/date "2020-02-02" ) (t/extend-all-cljs-protocols) ; (deftest construction-from-parts-test + + (testing "" + t/monthday-from + t/yearmonth-from + ) (testing "level 0" (let [nanos 789 micros 456 @@ -40,8 +51,7 @@ zdt (t/zdt-from {:datetime datetime :timezone_id timezone})] (is (t/zdt? zdt)) (is (= datetime (t/zdt->datetime zdt))) - (is (= timezone (t/zdt->timezone_id zdt))) - )) + (is (= timezone (t/zdt->timezone_id zdt))))) (testing "level 2" (let [date (t/date-now (t/clock-system-default-zone)) time (t/time-now (t/clock-system-default-zone)) @@ -71,7 +81,13 @@ (let [d #?(:clj (Date.) :cljs (js/Date.)) i (t/instant-from {:legacydate d})] (= (.getTime d) (t/instant->epochmilli i)) - )))) + )) + (testing "zdt with offset" + (is (= "+05:50" + (-> + (t/zdt-from {:instant (t/instant-now (t/clock-system-default-zone)) + :timezone_id "+05:50"}) + (t/zdt->timezone_id))))))) (deftest parsing-duration (is (t/duration? (d/duration-parse "PT1S")))) @@ -111,7 +127,8 @@ (is (not (t/period? (t/date-now (t/clock-system-default-zone)))))) (deftest parsing-test - (is (t/timezone? (t/timezone-parse "Europe/London")))) + (is (t/timezone? (t/timezone-parse "Europe/London"))) + (is (t/timezone? (t/timezone-parse "-12:15")))) (deftest equals-hash (is (= (t/timezone-parse "Europe/London") (t/timezone-parse "Europe/London"))) @@ -257,12 +274,16 @@ ;todo (deftest guardrails-test - (is (thrown? #?(:clj Throwable :cljs js/Error) (t/>> (t/date-parse "2020-02-02") 1 t/years-property)))) + (is (thrown? #?(:clj Throwable :cljs js/Error) (t/>> (t/date-parse "2020-02-02") 1 t/years-property))) + (binding [t/*block-non-commutative-operations* false] + (is (t/>> (t/date-parse "2020-02-02") 1 t/years-property)))) + +(deftest comparison-test + ;todo + t/max + t/min + t/>= + t/coincident? + ) -t/monthday-from -t/yearmonth-from -t/max -t/min -t/>= -t/coincident? \ No newline at end of file