Sveriges mest populära poddar

Functional Design in Clojure

Ep 016: When 8 - 1 = 6

30 min • 15 februari 2019

Christoph discovers that time creates its own alternate universe.

  • Continuing our exploration of "literate" time logs
  • We want a function to turn the timestamps into minutes.
  • Format: Fri Feb 08 2019 11:30-13:45
  • Keep it simple: extract times with a regex and use math
    • minutes in day = hours * 60 + minutes
    • total minutes = end minutes - starting minutes
  • Problem: What happens when we work past midnight? Negative minutes!
  • We decided to have only one date, so a time before the starting time must span midnight.
  • Format only allows for an activity to be <24 hours.
  • "If we end up doing any activity in our life longer than 24 hours, I think we should that we might have other problems."
  • Easy Solution: If the end time is before start time, add 24 hours.
  • "When I get past any sort of simpler math, I just type it into my connected editor REPL because I know Clojure can do it faster than I can."
  • Now we have a function to get minutes, want to add them all up.
  • Use loop and recur to iterate through the array and track the sum.
  • Oh wait, what about Daylight Savings Time?
  • "We all pretend that time is actually in the future."
  • If it involves dates and times, we can't just do simple math.
  • "If I do this the right way, I now have to open a whole new can of worms."
  • Easy way out: write "doesn't support DST" in the release notes and call it "user error"!
  • "Any time you have to be careful about something, you're probably doing it wrong."
  • Use a time API.
  • "The Clojure library is just a wrapper because nobody wants to reinvent this thing."
  • Java time is immutable, so that works nicely with functional languages. No cloneing!
  • Lots of functions in the time API. Which ones do we need?
  • Our workflow: try out different expressions in a comment block in our connected editor to figure out the specific time functions we need.
  • "Local" dates and times don't have time zones, but "zoned" dates and times do have them.
  • Need to create timestamps for accurate math: date + time + zone
  • In practice, when we use dates and times, they are implicitly connected to a time zone.
  • Your time zone is your alternate universe: it affects the meaning of your dates and times.
  • We added support for DST without changing the function signature.
  • But, how do we add up other totals? Looks like we're going to need to change even more.

Related episodes:

Clojure in this episode:

  • nil
  • re-matches
  • loop, recur

Related projects:

Code sample from this episode:

(ns app.time
  (:require
    [clojure.java.io :as io]
    [java-time :as jt]))

(def timestamp-re #"(\w+\s\w+\s\d+\s\d+)\s+(\d{2}:\d{2})-(\d{2}:\d{2})")

(defn localize [dt tm]
  (jt/zoned-date-time dt tm (jt/zone-id "America/Los_Angeles")))

(defn parse-time [time-str]
  (jt/local-time "HH:mm" time-str))

(defn parse-date [date-str]
  (jt/local-date "EEE MMM dd yyyy" date-str))

(defn parse-for-minutes
  [line]
  (if-let [[whole dt start end] (re-matches timestamp-re line)]
    (let [date (parse-date dt)
          start (localize date (parse-time start))
          end (localize date (parse-time end))]
      (if (jt/before? start end)
        (jt/time-between start end :minutes)
        (jt/time-between start (jt/plus end (jt/days 1)) :minutes)))
    0))

(defn total-time
  [filename]
  (with-open [rdr (io/reader filename)]
    (loop [total 0
           lines (line-seq rdr)]
      (if lines
        (recur (+ total (or (some-> (first lines) parse-for-minutes) 0)) (next lines))
        total))))
Förekommer på
00:00 -00:00