1

So, I have two dates, which come from the UI:

var start = new Date(self.Start()), end = new Date(self.End());

We need to figure out the number of days between that range, which is easy enough:

var days = Math.max(0, Math.floor((end - start) / ticksInDay) || 0);

We use this number to calculate some sort of total work estimate. However, now the powers that be demand for this work estimate to no longer include weekends. My first attempt was something like this:

var days = Math.max(0, Math.floor((5 / 7) * (end - start) / ticksInDay) || 0);

Basically, if the range has 7 days, we multiply this by (5 / 7) to get 5 actual days. 14 would give us 10 actual days. However, this has some flaws.

(5/7)*1  = 0.714285714285714
(5/7)*2  = 1.42857142857143
(5/7)*3  = 2.14285714285714
(5/7)*4  = 2.85714285714286
(5/7)*5  = 3.57142857142857
(5/7)*6  = 4.28571428571429
(5/7)*7  = 5
(5/7)*8  = 5.71428571428571
(5/7)*9  = 6.42857142857143
(5/7)*10 = 7.14285714285714
(5/7)*11 = 7.85714285714286
(5/7)*12 = 8.57142857142857
(5/7)*13 = 9.28571428571429
(5/7)*14 = 10

Rounding (either way) doesn't really work here since ranges like 3 or 4 days, or 10 or 11 days will be wrong. Anything else I've tried amounts to a ugly string or if...thens or weird conditional statements.

The only way I can think of that would be 100% accurate would be to loop through the range, similar to what this answer recommends. However, this code has to run whenever the UI model changes, and needs to calculate work estimates for a potentially large number of rows with potentially large date ranges. I worry an O(n) solution will be too slow.

Is there a good formula for a date range to exclude 2 out of every 7 whole days?

Community
  • 1
  • 1
Mike Christensen
  • 88,082
  • 50
  • 208
  • 326
  • 2
    Can't you use [.getDay](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay) to figure out what day of the week the start and end date is - then for the remaining full weeks is just `days * 5 / 7` and then add in the remaining days for the partial weeks at the beginning and end. – Matt Burland Feb 26 '14 at 18:11
  • 2
    Just wait until they ask you to leave out holidays, too... – Wooble Feb 26 '14 at 18:13
  • @Wooble - That we've said we will *not* do :) – Mike Christensen Feb 26 '14 at 18:15
  • 1
    Assuming your overall time range is constrained to a relevant human time scale, just make a map keyed by date (yyymmdd) for every day over a span of years, and set all weekdays to a monotonically incrementing count. Set weekends to -1 or something. Then you can pull out the values for any pair of weekdays and do a simple subtraction. – Pointy Feb 26 '14 at 18:20
  • Calculate the days from start to first weekend. Subtract that amount from total days. Calculate the Monday to end. Subtract that difference. Now take the remaining and get your 5's out of that. TADA, no weird calculations. – epascarello Feb 26 '14 at 18:22
  • 1
    Also: *I worry an O(n) solution will be too slow.* shouldn't you profile it first and see if it really is an issue? – Matt Burland Feb 26 '14 at 18:22
  • @Pointy - Yea, this might be doable (especially if I could set a limit for the max date), however since it's JavaScript, I'd have to do this each time the page loads.. Which would increase the startup cost (not sure how fast building that map would be, but this page is already slow enough).. – Mike Christensen Feb 26 '14 at 18:24
  • 1
    @MikeChristensen I suspect it would take only a couple of milliseconds, and you could build the map at application build time, and/or keep it in local storage. The nice thing about that is that you could easily extend it to deal with holidays. – Pointy Feb 26 '14 at 18:26
  • @MikeChristensen [This version takes around 20 milliseconds on my machine.](http://jsfiddle.net/yADwA/1/) – Pointy Feb 26 '14 at 18:38

1 Answers1

5
// Count days from d0 to d1 inclusive, excluding weekends
function countWeekDays( d0, d1 )
{
    var ndays = 1 + Math.round((d1.getTime()-d0.getTime())/(24*3600*1000));
    var nsaturdays = Math.floor((ndays + d0.getDay()) / 7);
    return ndays - 2*nsaturdays - (d0.getDay()==0) + (d1.getDay()==6);
}

Examples for January 2014:

    January 2014
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

countWeekDays(new Date(2014,0,1),new Date(2014,0,1)) // 1
countWeekDays(new Date(2014,0,1),new Date(2014,0,2)) // 2
countWeekDays(new Date(2014,0,1),new Date(2014,0,3)) // 3
countWeekDays(new Date(2014,0,1),new Date(2014,0,4)) // 3
countWeekDays(new Date(2014,0,1),new Date(2014,0,5)) // 3
countWeekDays(new Date(2014,0,1),new Date(2014,0,6)) // 4

N.B. The Date inputs should be at around the same time of the day. If you are creating Date objects based solely on year, month, and day as in the examples above, then you should be fine. As a counter example, 12:01am on Jan 1 to 11:59pm Jan 2 spans only 2 days, but the above function will count 3 if you use those times.

Matt
  • 20,108
  • 1
  • 57
  • 70