kfmfe04
kfmfe04

Reputation: 15337

How do I detect if two dates straddle a weekend?

Problem

Given two datetimes, dt0 and dt1 (could be out of order), what is an algorithm which can determine if there is at least 24 hours worth of weekend (SAT, SUN) between the two dates?

Assume that there is a dayofweek() function that returns 0 for SUN, 1 for MON, etc...

Note: the problem is easy to visualize geometrically in terms of line segments, but the calculation eludes me for the moment.

Solution

The solution below will work for UTC, but it will fail for DST.

James McNellis is right on the mark concerning DST.

Getting this code to work for the general DST case is non-trivial: need to add tz and anywhere you do any kind of date arithmetic requires careful consideration. Additional unit tests will be needed.

Lessons Learned

Conclusion

The current solution is sufficient for my purposes (I will use UTC to avoid DST problems). I will select holygeek's Answer for his suggestion that I draw some ASCII art. In this case, doing so has helped me come up with an algorithm that is easy-to-understand and really, as simple as I can possibly make it. Thanks to all for contributing to the analysis of this problem.

static const size_t ONEDAYINSECS = (24 * 60 * 60); 

DateTime
DateTime::nextSatMorning() const
{
  // 0 is SUN, 6 is SAT
  return *this + (6 - weekdayno()) * ONEDAYINSECS -
    (((hh() * 60) + mm())*60 + ss());
}

DateTime 
DateTime::previousSunNight() const
{
  return *this - ((weekdayno() - 1 + 7)%7) * ONEDAYINSECS -
    (((hh() * 60) + mm())*60 + ss());
}

bool
DateTime::straddles_24HofWeekend_OrMore( const DateTime& newDt ) const
{
  const DateTime& t0 = min( *this, newDt );
  const DateTime& t1 = max( *this, newDt );
  
  // ------------------------------------
  //
  // <--+--F--+--S--+--S--+--M--+-->
  //    t0    ^           ^    t1
  //    +---->+           +<----|
  //          |           |
  //          +<--nSecs-->+
  //        edge0       edge1
  //
  // ------------------------------------

  DateTime edge0 = t0.isWeekday() ? t0.nextSatMorning()   : t0;                       
  DateTime edge1 = t1.isWeekday() ? t1.previousSunNight() : t1;
                   
  return (edge1 - edge0) > ONEDAYINSECS;
}

John Leidegren asked for my unit tests so here there are (using googletest) Note that they pass for the non-DST cases above (running for UTC) - I expect the current implementation to fail for DST cases (haven't added them to the test cases below yet).

TEST( Test_DateTime, tryNextSatMorning )
{
  DateTime mon{     20010108, 81315 };
  DateTime exp_sat{ 20010113, 0ul   };

  EXPECT_EQ( exp_sat, mon.nextSatMorning() );
}

TEST( Test_DateTime, tryPrevSunNight )
{
  DateTime tue{      20010109, 81315 };
  DateTime exp_sun1{ 20010108, 0ul   };

  EXPECT_EQ( exp_sun1, tue.previousSunNight() );

  DateTime sun{      20010107, 81315 };
  DateTime exp_sun2{ 20010101, 0ul   };

  EXPECT_EQ( exp_sun2, sun.previousSunNight() );
}

TEST( Test_DateTime, straddlesWeekend )
{
  DateTime fri{ 20010105, 163125 };
  DateTime sat{ 20010106, 101515 };
  DateTime sun{ 20010107, 201521 };
  DateTime mon{ 20010108,  81315 };
  DateTime tue{ 20010109,  81315 };

  EXPECT_FALSE( fri.straddles_24HofWeekend_OrMore( sat ));
  EXPECT_TRUE(  fri.straddles_24HofWeekend_OrMore( sun ));
  EXPECT_TRUE(  fri.straddles_24HofWeekend_OrMore( mon ));
  EXPECT_TRUE(  sat.straddles_24HofWeekend_OrMore( sun ));
  EXPECT_TRUE(  sat.straddles_24HofWeekend_OrMore( mon ));
  EXPECT_FALSE( sun.straddles_24HofWeekend_OrMore( mon ));
  EXPECT_TRUE(  fri.straddles_24HofWeekend_OrMore( tue ));
  EXPECT_FALSE( sun.straddles_24HofWeekend_OrMore( tue ));
}

Upvotes: 3

Views: 667

Answers (4)

user244343
user244343

Reputation:

Approach it in a structured manner: What are some of the simple/edge cases? What is the average case?

Simple cases I can think of off the top of my head (note, since you've already forced t0 to be the lower value, I'm assuming that below):

  1. If t1 - t0 is less than 1 day, return false
  2. If t1 - t0 is >= 6 days, return true (there's ALWAYS 24 hours of weekend time in any given 6 day block, even if you start on a Sunday).

Then we take dayofweek() for both t0 and t1, and do some checks (this is the average case). We can be extra cheap here now because we know t0 is only up to 5 days earlier than t1.

Edit: Removed my conditions because there were nasty little edge cases I wasn't considering. Anyway, the solution I recommended is still viable, I just won't do it here.

Upvotes: 1

Gregor Brandt
Gregor Brandt

Reputation: 7799

This site calculates business days in C#, does that help? http://www.infopathdev.com/forums/t/7156.aspx

Upvotes: 0

holygeek
holygeek

Reputation: 16205

This diagram may help:

  SAT       SUN
|---------|---------|
a---------b
 a---------b
 ...
          a---------b

Upvotes: 3

daniel
daniel

Reputation: 471

The silliest way I can think of solving this is: copy the smaller datetime value, continuously add to it until it's either larger than the other datetime value (the bigger one) or dayofweek() doesn't equal 0 or 7 any more. Then check if the total value of time you added is less than 24 hours.

A slightly less silly way would be to check its a weekend, add 24 hours of time and then check once to make sure its a weekend and still less than the second datetime.

Daylight savings shouldn't really come into play as long as your function to find what day it is works.

Upvotes: 1

Related Questions