【发布时间】:2011-12-21 04:24:39
【问题描述】:
问题
给定两个日期时间,dt0 和 dt1(可能出现故障),有什么算法可以确定两者之间是否存在至少 24 小时的周末(SAT、SUN)日期?
假设有一个 dayofweek() 函数返回 0 表示 SUN,1 表示 MON,等等......
注意:这个问题很容易用线段进行几何可视化,但我暂时无法计算。
解决方案
以下解决方案适用于 UTC,但不适用于 DST。
-
weekdayno()未包括实现:SUN==0、MON==1 等... -
isWeekday()也没有显示,但是一旦你有dayofweek()就可以轻松实现 - 二进制
operator-()实现也未显示,但我们只是将两个实例转换为 UNIX 时间(自 Epoch 以来的秒数)并取差值以得出两个DateTimes 之间的秒数 -
hh()mm()ss()只是分别返回小时、分钟和秒的 const 访问器
James McNellis 在 DST 方面是正确的。
让这段代码适用于一般 DST 情况并非易事:需要添加 tz 并且在任何地方进行任何类型的日期运算都需要仔细考虑。需要额外的单元测试。
经验教训
- 查询 stackoverflow 以了解查看问题的各种方法。
- 你永远不会有太多的单元测试:需要它们来清除奇怪的边缘情况
- 如果可能,使用可视化来查看问题
- 当您查看细节(例如 DST)时,看似微不足道的问题实际上可能有点棘手。
- 保持解决方案尽可能简单,因为您的代码很可能会更改:为了修复错误/新测试用例或为了添加新功能(例如使其适用于 DST) .尽可能保持其可读性和易于理解性:更喜欢算法而不是 switch/case。
- 勇敢地尝试一下:不断寻找解决方案,直到出现有效的解决方案。使用单元测试,以便您可以持续重构。编写简单的代码需要做很多工作,但最终还是值得的。
结论
当前的解决方案足以满足我的目的(我将使用 UTC 来避免 DST 问题)。我将选择 holygeek 的答案,因为他建议我画一些 ASCII 艺术。在这种情况下,这样做帮助我想出了一个易于理解的算法,并且真的尽可能简单。感谢大家对这个问题的分析做出贡献。
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 要求我进行单元测试,所以这里有(使用 googletest) 请注意,它们通过了上述非 DST 案例(针对 UTC 运行) - 我预计当前实现对于 DST 案例会失败(尚未将它们添加到下面的测试案例中)。
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 ));
}
【问题讨论】:
-
不 - 它是我正在建设的图书馆的一部分
-
如果没有任何限制,从头开始编写这将是相当乏味的,因为必须考虑不同时区和位置的夏令时,以及其他日历不规则。
-
@JamesMcNellis +1 指出 DST 问题:一旦我掌握了基本算法,我会注意它们