Reputation: 15337
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.
The solution below will work for UTC, but it will fail for DST.
weekdayno()
implementation not included: SUN==0, MON==1, etc...isWeekday()
is also not shown, but is trivial to implement once you have dayofweek()
operator-()
implementation also not shown, but we simply convert both instances to UNIX-time (no. of secs since Epoch) and take the difference to yield the number of seconds between two DateTime
shh()
mm()
ss()
are just const accessors for returning hours, minutes, and seconds, respectivelyJames 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.
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
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):
t1 - t0
is less than 1 day, return falset1 - 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
Reputation: 7799
This site calculates business days in C#, does that help? http://www.infopathdev.com/forums/t/7156.aspx
Upvotes: 0
Reputation: 16205
This diagram may help:
SAT SUN
|---------|---------|
a---------b
a---------b
...
a---------b
Upvotes: 3
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