Exercise: Calculate # work days between two dates
[1/3] from: edoconnor::gmail::com at: 31-Jan-2007 16:22
I found Sununda's algorithm challenge to be very interesting. I'm often
amazed by the different approaches to a given problem.
In this spirit, I thought I'd offer up a basic exercise based on a
post about Ruby I came across yesterday. See:
*Calculate the number of working days between two dates*
http://www.railsonwave.com/railsonwave/2007/1/30/calculate-the-number-of-working-days-between-two-dates
Here's an approach I took, which embellishes with a refinement:
calc-workdays: func [
"Return number of workdays between two dates, excluding holidays"
date1 [date!] "Start date"
date2 [date!] "End date"
holidays [block!] "Block of dates to exclude (holidays, etc.)"
/non "Return number of non-work days (weekend + holidays) between 2
dates"
/local days day1 day2 diff param
][
days: copy []
set [day1 day2] sort reduce [date1 date2]
diff: day2 - day1
param: pick [[> 5 union][< 6 exclude]] either found? non [1][2]
loop diff [
day1: day1 + 1
if do param/1 day1/weekday param/2 [append days day1]
]
return length? do param/3 days holidays
]
Results:
>> calc-workdays now/date 3-feb-2007 [1-feb-2007]
== 1
>> print calc-workdays/non now/date 3-feb-2007 [1-feb-2007]
== 2
>> calc-workdays now/date 31-dec-2007 [25-dec-2007 1-Jan-2008]
== 237
>> print calc-workdays/non now/date 31-dec-2007 [25-dec-2007 1-Jan-2008]
== 98
Anyone care to share a different or more natural approach? One of the things
I'd like to see in REBOL's future is a guide or "phrase-book" for common
expressions found in problems big and small.
Ed
[2/3] from: SunandaDH::aol::com at: 1-Feb-2007 21:15
Ed:
> I found Sununda's algorithm challenge to be very interesting. I'm often
> amazed by the different approaches to a given problem.
Thanks. I was amazed too at the range of responses.
Here's my offering for your exercise.
My tweaks are:
1. Avoid the sort as we have only two items (I haven't timed that doing it by
hand is faster than letting REBOL set up a sort for just two items -- it may
be worth someone running a benchmark on that one day,
2. Avoid the loop around *all* the days in the period. I reckoned if I could
round the first date down to the nearest Monday while rounding the second date
up to the next nearest Monday, I was half way there. The tricky part was
tracking how many countable days I'd added in by the rounding -- hence the blocks
of obscure negative numbers. This theoretically makes it faster -- especially
for very wide date ranges.
3. That left me needing to run a loop against the extra holiday days,
checking each if was in range and not a weekend. And I had to do that before Step 2
so it was on the original dates, not the rounded ones. This may counteract the
theoretical speed improvements in Step 2, especially if there are many holiday
dates for a narrow date range.
calc-workdays: func [
date1 [date!]
date2 [date!]
holidays [block!]
/non
/local
dates
offset
hol-count
working-days
][
;; Put dates in order
;; ------------------
dates: copy reduce [date1]
either date1 < date2 [append dates date2] [insert dates date2]
;; count holidays in range
;; -----------------------
hol-count: 0
foreach hd holidays [
if all [
hd/weekday <= 5 ;; got to be a weekday to count
hd >= dates/1
hd <= dates/2][
hol-count: hol-count + 1
]
]
;; Round dates to Mondays
;; ----------------------
offset: 0
offset: offset + pick [0 -1 -2 -3 -4 -4 -4] dates/1/weekday
dates/1: dates/1 + 1 - dates/1/weekday
offset: offset + pick [-5 -4 -3 -2 -1 -1 -1] dates/2/weekday
dates/2: dates/2 + 8 - dates/2/weekday
;; Calculate the working / non-working days
;; --------------------------------
working-days: (dates/2 - dates/1) / 7 * 5 + offset - hol-count
if non [return (abs (date1 - date2)) - working-days]
return working-days
]
That was fun -- thanks!
Sunanda.
[3/3] from: edoconnor::gmail::com at: 2-Feb-2007 16:22
Hi Sunuda--
Thanks for your code & explanation.
A few comments:
>> 1. Avoid the sort as we have only two items...
I was attempting to adhere to the philosophy of being liberal in what
arguments I accept, i.e., accept two dates without requiring a particular
order. I see you did the same, but avoided the 'sort. Looking back to my
function, I could probably just accept the dates and use the 'abs function
to coerce a positive result:
diff: abs day2 - day1
which I see you do in the final lines of your function.
>> 2, 3...
Your solution here is quite creative; its not an approach I would have
considered!
Since I consider myself to be an intermediate REBOL programmer, I find it
highly instructive to see solutions expressed at different skill levels:
1. The quick-and-dirty "most direct" solution.
2. A version that has been cleaned-up (for ease of maintenance) and
error checking.
3. A version that has been optimized for speed/scalability or GUI/CGI.
Eg. The difference between:
1 & 2 Generate a list of files and mp3's from a website or RSS feed.(
http://www.rebol.com/docs/quick-start5.html for level 2)
3 Adding download support for large media files such as podcasts. (using
code such as: http://www.rebol.net/cgi-bin/blog.r?view=0281)
Thanks for your code.
Ed
On 2/1/07, SunandaDH-aol.com <SunandaDH-aol.com> wrote: