1
\$\begingroup\$

I want to convert strings with the iso date format (2015-08-02) to the default format of our app (2 Aug 2015) in ClojureScript. As I don't need anything else, I don't want to pull in libs like cljs-time.

I'm quite new to Clojure & ClojureScript, so I want to ask for some feedback on this small snippet. Thanks!

(defn format-date [date]
  (let [month-names [nil "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"]
        [year month day] (map #(js/parseInt %) (str/split date #"-0?"))]
    (str day " " (month-names month) " " year)))
\$\endgroup\$
1
  • 2
    \$\begingroup\$ If you're using clojure.string under the alias str, it would be helpful to include that in your code as (require '[clojure.string :as str]). \$\endgroup\$ – Sam Estep Jun 2 '16 at 13:06
3
\$\begingroup\$

Very nice work! My only suggestions are to split your large-ish function into several smaller, simpler pieces, and to give each of those pieces its own docstring:

(require '[clojure.string :as str])

(def month-names
  "A vector of abbreviations for the twelve months, in order."
  ["Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"])

(defn month-name
  "Returns the abbreviation for a month in the range [1..12]."
  [month]
  (get month-names (dec month)))

(defn parse-iso-date
  "Returns a vector of the year, month, and day from an ISO 8601 date string."
  [date]
  (mapv #(js/parseInt %) (str/split date #"-0?")))

(defn format-date
  "Converts an ISO 8601 date string to one of the format \"(D)D Mon YYYY\"."
  [date]
  (let [[year month day] (parse-iso-date date)]
    (str day " " (month-name month) " " year)))

This is considerably longer, but most of that additional length is from the fact that now more things are named and documented. The advantage of this approach is that if you find later that you want to use only one or two of these individual parts, or use them in a different way (e.g. when writing another function for a different date format), you can do so without having to duplicate the functionality in your format-date function.

However, I was a bit confused by your comment about libraries:

As I don't need anything else, I don't want to pull in libs like cljs-time.

One of the greatest features of ClojureScript is that it emits code that is compatible with the advanced mode of the Google Closure optimizing compiler. The impact of this is explained more in the ClojureScript unveiling video (slides are available here). Basically, in ClojureScript, your app can depend on as many libraries as you want, and when you compile it, its final size will only depend on the size of the specific parts of those libraries that you actually end up using.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ I like this answer. I would personally reduce the publicly visible surface area, so for month-name and parse-iso-date I would use defn-, and for month-names I would use ^:private. \$\endgroup\$ – Chris Jester-Young Jun 3 '16 at 17:13
  • \$\begingroup\$ @ChrisJester-Young Good idea. The main reason I didn't mark those vars as private was for simplicity's sake. The only downside to that approach that I can think of is that private functions are a bit messier to test; see this Stack Overflow question for more details. \$\endgroup\$ – Sam Estep Jun 3 '16 at 17:28
  • \$\begingroup\$ True re testability, though I'd like to think the auxiliary functions and definitions we're talking about are simple enough not to require testing directly, and that we're only testing the (publicly visible) format-date. :-) \$\endgroup\$ – Chris Jester-Young Jun 3 '16 at 17:31
  • 1
    \$\begingroup\$ @ChrisJester-Young Fair enough. I'd rather have more test coverage where possible, but I suppose it's a matter of personal preference; this question seems to indicate that both views are commonly held. \$\endgroup\$ – Sam Estep Jun 3 '16 at 17:37
1
\$\begingroup\$

I like Sam Estep's answer, and you should implement their suggestions. Another thing you can do, to make things even clearer, is to make parse-iso-date return a map:

(defn- parse-iso-date
  "Returns a map with keys :year, :month, and :day from the given ISO 8601 date string."
  [date]
  (zipmap [:year :month :day] (map js/parseInt (str/split date #"-0?"))))

Then in format-date, you can consume it like so:

(let [{:keys [day month year]} (parse-iso-date date)]
  (str day " " (month-name month) " " year))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.