From 92f61a3b69eafc16ba157cfa3bc1cf99bb633bd9 Mon Sep 17 00:00:00 2001 From: Chris Hapgood Date: Thu, 6 Jun 2024 15:38:31 -0400 Subject: [PATCH] Create initial project structure with tests and reader --- .gitignore | 1 + .tool-versions | 7 ++++++ deps.edn | 8 +++++++ src/challenge/core.clj | 6 +++++ src/challenge/readers.clj | 34 ++++++++++++++++++++++++++++ src/data_readers.clj | 1 + src/data_readers.edn | 0 test/challenge_test.clj | 47 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 104 insertions(+) create mode 100644 .gitignore create mode 100755 .tool-versions create mode 100644 deps.edn create mode 100644 src/challenge/core.clj create mode 100644 src/challenge/readers.clj create mode 100644 src/data_readers.clj create mode 100644 src/data_readers.edn create mode 100644 test/challenge_test.clj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..304db9f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.cpcache diff --git a/.tool-versions b/.tool-versions new file mode 100755 index 0000000..828d55f --- /dev/null +++ b/.tool-versions @@ -0,0 +1,7 @@ +# https://asdf-vm.com/ + +# Java: +java temurin-17.0.7+7 + +# Clojure: +clojure 1.11.1.1342 diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..aa4bcf9 --- /dev/null +++ b/deps.edn @@ -0,0 +1,8 @@ +{:deps {org.clojure/clojure {:mvn/version "1.11.2"} + org.threeten/threeten-extra {:mvn/version "1.8.0"}} + :paths ["src"] + :aliases {:test {:extra-paths ["test"] + :extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git" + :git/sha "dfb30dd" + :git/tag "v0.5.1"}} + :exec-fn cognitect.test-runner.api/test}}} diff --git a/src/challenge/core.clj b/src/challenge/core.clj new file mode 100644 index 0000000..e1f4927 --- /dev/null +++ b/src/challenge/core.clj @@ -0,0 +1,6 @@ +(ns challenge.core + (:require [clojure.set :as set]) + (:import (java.time LocalDate Period) + (org.threeten.extra LocalDateRange))) + +(defn difference [& input]) diff --git a/src/challenge/readers.clj b/src/challenge/readers.clj new file mode 100644 index 0000000..9dc355f --- /dev/null +++ b/src/challenge/readers.clj @@ -0,0 +1,34 @@ +(ns challenge.readers + (:import (org.threeten.extra LocalDateRange))) + +(defn local-date-range [s] (LocalDateRange/parse s)) + +(defn local-date-range-as-iso-interval + "Print the given LocalDateRange `ldr` as an ISO 8601-1:2019 interval in + / format." + [ldr] + {:pre [(instance? LocalDateRange ldr)]} + (let [start (.getStart ldr) + period (.toPeriod ldr)] + ;; NB: when the start date is the first day of the month and the duration is + ;; one month, consider printing the interval as "YYYY-MM/P1M". This has the + ;; advantage of naturally segregating month intervals from day intervals in + ;; lexical sorts but has the disadvantage of not being parseable by the + ;; LocalDateRange class nor by its sibling Interval class. + (str start "/" period))) + +;; We elect to print a LocalDateRange using the ISO 8601-1:2019 / +;; representation. This is a compact and standardized representation that high- +;; lights our expected use case of specific periods (one day and one month). +;; The use of a LocalDate to represent the date-only start point is not well +;; publicized, probably because the relevant ISO standard (ISO 8601-2:2019) is +;; aggressively copyrighted and the use cases are few compared to other elements +;; of the standard. +(defmethod print-dup LocalDateRange + [o ^java.io.Writer w] + (doto w + (.write "#st/local-date-range \"") + (.write (local-date-range-as-iso-interval o)) + (.write "\""))) + +(defmethod print-method LocalDateRange [o ^java.io.Writer w] (print-dup o w)) diff --git a/src/data_readers.clj b/src/data_readers.clj new file mode 100644 index 0000000..bb4e1e2 --- /dev/null +++ b/src/data_readers.clj @@ -0,0 +1 @@ +{st/local-date-range challenge.readers/local-date-range} diff --git a/src/data_readers.edn b/src/data_readers.edn new file mode 100644 index 0000000..e69de29 diff --git a/test/challenge_test.clj b/test/challenge_test.clj new file mode 100644 index 0000000..f778690 --- /dev/null +++ b/test/challenge_test.clj @@ -0,0 +1,47 @@ +(ns challenge-test + (:require [challenge.core :refer [difference]] + [challenge.readers] + [clojure.test :refer [deftest is testing]])) + +(def week0 #st/local-date-range "2024-01-01/P7D") +(def week1 #st/local-date-range "2024-01-08/P7D") +(def week2 #st/local-date-range "2024-01-15/P7D") +(def week3 #st/local-date-range "2024-01-22/P7D") +(def extra #st/local-date-range "2024-01-29/P3D") +(def month0 #st/local-date-range "2024-01-01/P1M") + +(deftest unary + (is (= #{week0} + (difference #{week0}))) + (testing "compresses results" + (is (= #{month0} + (difference #{week0 week1 week2 week3 extra}))))) + +(deftest binary + (testing "equals" + (is (= #{} + (difference #{week0} #{week0}))) + (is (= #{} + (difference #{week0 week1} #{week0 week1})))) + (testing "overlapped by" + (is (= #{#st/local-date-range "2024-01-01/P4D"} + (difference #{week0} + #{#st/local-date-range "2024-01-05/P10D"}))) + (is (= #{#st/local-date-range "2024-01-01/P4D"} + (difference #{week0 week1} + #{#st/local-date-range "2024-01-05/P10D"})))) + (testing "met by" + (is (= #{week0} + (difference #{week0} #{week1})))) + (testing "contains" + (is (= #{week0 #st/local-date-range "2024-01-15/P17D"} + (difference #{month0} #{week1})))) + (testing "during" + (is (= #{} + (difference #{week1} #{month0}))))) + +(deftest variadic + (is (= #{week0 extra} + (difference #{month0} + #{week1} + #{week2 week3}))))