Skip to content

Instantly share code, notes, and snippets.

@spirinvladimir
Last active March 16, 2018 07:31
Show Gist options
  • Save spirinvladimir/42e2fefee338131fa87c3157a8a585ad to your computer and use it in GitHub Desktop.
Save spirinvladimir/42e2fefee338131fa87c3157a8a585ad to your computer and use it in GitHub Desktop.
Date and time calculations
(require '[clj-time.core :as t])
(require '[clj-time.predicates :as pr])
(require '[clj-time.coerce :as c])
(defn add_hours_for_day_in_week [week weekday hours]
"Update day in a week by adding new business hours."
(assoc
week
(dec weekday)
(clojure.set/union
hours
(nth week (dec weekday)))))
(defn add-row-from-schedule [week row]
"Add hours from every record in schedule to corresponding day."
(let [weekdays (:schedule/weekdays row)
hours (:schedule/hours row)]
(reduce
#(add_hours_for_day_in_week %1 %2 hours)
week
weekdays)))
(defn schedule-to-week [schedule]
"Create a week map by schedule. Week is vector of 7 days. Each day is a set of hours."
(let [empty-week (vec (take 7 (repeat #{})))]
(reduce
add-row-from-schedule
empty-week
schedule)))
(defn get-weekday [date]
"Map function between weekdays and numbers 0-6."
(cond
(pr/monday? date) 0
(pr/tuesday? date) 1
(pr/wednesday? date) 2
(pr/thursday? date) 3
(pr/friday? date) 4
(pr/saturday? date) 5
(pr/sunday? date) 6))
(defn get-day [week weekday]
"Get set of buissnes hours for a weekday. Weekday should be number from 0 to 6."
(nth week weekday))
(defn hours-in-full-days [week from to]
"Get hours for interval of full working days (without boundaries or 00:00 -> 24:00)."
(let [full-first (rem (inc from) 7)
full-last (if (= 0 to) 6 (dec to))]
(loop [weekday full-first
hours 0]
(if (= weekday full-last)
(+ hours (count (get-day week weekday)))
(recur
(rem (inc weekday) 7)
(+ hours (count (get-day week weekday))))))))
(defn hours-in-part-of-day [hours from to]
"Counting business hours of a current day in boundaries [from, to)."
(reduce
(fn [sum hour]
(if (and (>= hour from) (< hour to))
(inc sum)
sum))
0
hours))
(defn days-between [weekday-from weekday-to]
"Counting days between to weekdays. Example: Friday -> Monday = 4 days."
(loop [weekday weekday-from
days 0]
(if (= weekday weekday-to)
days
(recur
(rem (inc weekday) 7)
(inc days)))))
(defn hours-in-rest-of-week [from to week hours-in-week]
"Calculate hours in interval less then one week. Time interval could be calculated by one of three cases: less then 1 day or less then 2 days or bigger."
(let [weekday-from (get-weekday from)
weekday-to (get-weekday to)
weekday-delta (days-between weekday-from weekday-to)
day-from (get-day week weekday-from)
day-to (get-day week weekday-to)
hour-from (t/hour from)
hour-to (t/hour to)]
(case weekday-delta
0 (if (< hour-from hour-to)
(hours-in-part-of-day day-from hour-from hour-to)
(-
hours-in-week
(hours-in-part-of-day day-from hour-to hour-from)))
1 (+
(hours-in-part-of-day day-from hour-from 24)
(hours-in-part-of-day day-to 0 hour-to))
(+
(hours-in-part-of-day day-from hour-from 24)
(hours-in-full-days week weekday-from weekday-to)
(hours-in-part-of-day day-to 0 hour-to)))))
(defn calculate-business-hours-clj-time [schedule start-date end-date]
"Every week have same business hours. Total hours is sum hours from full weeks and part weeks."
(let [week (schedule-to-week schedule)
hours (map count week)
hours-in-week (reduce + hours)
delta-days (t/in-days (t/interval start-date end-date))
full-weeks (/
(-
delta-days
(rem
delta-days
7))
7)]
(+
(*
full-weeks
hours-in-week)
(hours-in-rest-of-week start-date end-date week hours-in-week))))
(defn calculate-business-hours [schedule start-date end-date]
"Convert date and time from java.util.Date to DateTime(clj-time lib)."
(calculate-business-hours-clj-time
schedule
(c/from-date start-date)
(c/from-date end-date)))
@spirinvladimir
Copy link
Author

spirinvladimir commented Mar 16, 2018

3rd revision has fix for case with ~6 days interval (without full weeks) and start and end week day are same.

Example:

start-date end-date
2000-01-01T03:00 2000-01-08T00:00
Saturday Saturday
3 am 0 am

Both days are same week day (Saturday).

There are two cases:

  • start time < end time: time interval is less then 1 day
  • start time >= end time: time interval is greater then 6 days (3 am >= 0 am)

Reason of bug: second case was not implemented in code.

There are two ways to calculate working hours at interval of 6 days:

Like calculate any interval < 7 days and > 1 day

(+
  (hours-in-part-of-day day-from hour-from 24)
  (hours-in-full-days week weekday-from weekday-to)
  (hours-in-part-of-day day-to 0 hour-to)))))

6 days interval is full week without hours in one day

(-
  hours-in-week
  (hours-in-part-of-day day-from hour-to hour-from)))

Last solution is more than 2x faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment