Last active
March 16, 2018 07:31
-
-
Save spirinvladimir/42e2fefee338131fa87c3157a8a585ad to your computer and use it in GitHub Desktop.
Date and time calculations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
3rd revision has fix for case with ~6 days interval (without full weeks) and start and end week day are same.
Example:
Both days are same week day (Saturday).
There are two cases:
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
6 days interval is full week without hours in one day
Last solution is more than 2x faster.