【问题标题】:How do I detect if two dates straddle a weekend?如何检测两个日期是否跨越一个周末?
【发布时间】:2011-12-21 04:24:39
【问题描述】:

问题

给定两个日期时间,dt0dt1(可能出现故障),有什么算法可以确定两者之间是否存在至少 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 问题:一旦我掌握了基本算法,我会注意它们

标签: c++ datetime c++11


【解决方案1】:

此图可能会有所帮助:

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

【讨论】:

  • +1 是的 - 可视化肯定会有所帮助:实际上你给了我一个便宜的技巧 - 如果我将 24 小时添加到 a 并且它小于 b,我就完成了(根据位置进行一些调整一个是)... ...会想多一点 - tyvm!
  • 我选择了 Holygeek 的答案,因为他对解决方案的贡献(使用 ASCII 艺术来可视化问题)。
【解决方案2】:

我能想到的最愚蠢的解决方法是:复制较小的日期时间值,不断添加它,直到它大于另一个日期时间值(较大的值)或 dayofweek() 不等于 0 或 7更多的。然后检查您添加的总时间值是否小于 24 小时。

一个稍微不那么愚蠢的方法是检查它是一个周末,添加 24 小时的时间,然后检查一次以确保它是一个周末并且仍然小于第二个日期时间。

只要您的函数能够确定是哪一天,夏令时就不应该真正发挥作用。

【讨论】:

  • 我也想过,但我正在处理日期时间,而不仅仅是日期,所以它有点棘手 - 另一种可能性是,如果我能检测到工作日和周末之间的所有界限t0 和 t1 并将我的时间线分成工作日、周末、工作日、周末、工作日、周末等......然后,我可以计算工作日段的长度和周末段的长度。我想这可能更接近一点......
  • 您的日期时间应该是一个很大的值,例如自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数。或者如果您自己编造,则同样令人困惑。跨度>
  • 我如何在内部实现 datetime 在这里应该没有什么不同。但是,夏令时是一个问题 - 例如,两个日期可能是 SAT 9AM 和 SUN 9AM,但它们可能仅相隔 23 小时或最多相隔 25 小时 - 所以任何类型的日期时间算术都可以得到棘手 - 解决这个问题的一种方法是要求一切都是 UTC 或在没有 DST 的时区,但这有点逃避
【解决方案3】:

以结构化的方式处理它:有哪些简单/边缘情况?平均情况是多少?

我能想到的简单案例(注意,由于您已经强制 t0 为较低的值,我假设如下):

  1. 如果t1 - t0小于1天,返回false
  2. 如果 t1 - t0 >= 6 天,则返回 true(在任何给定的 6 天区块中总是有 24 小时的周末时间,即使您从星期日开始)。

然后我们为t0t1 取dayofweek(),并做一些检查(这是一般情况)。我们现在在这里可以更便宜,因为我们知道t0 只比t1 提前 5 天。

编辑:删除了我的条件,因为我没有考虑到一些讨厌的小边缘情况。反正我推荐的方案还是可行的,这里就不做了。

【讨论】:

  • PS:如果我遗漏了一个案例或有一个错误,请允许我先纠正它,而不是投反对票:)
  • 在考虑这个问题时,我在想出一个通用的解决方案和像你所做的那样分解所有可能性之间摇摆不定 - 如果你已经打破了 24 似乎肯定可以“推断”几个小时值得,但是你有所有这些 if/else 语句,除非你非常小心,否则这些语句可能容易出错......
  • 不过,从好的方面来说,代码更清楚地表明它在做什么,并且计算量更少(你只做一个初始减法,没有加法,只是比较)。
  • 实际上,如果您这样做,您会发现最终解决方案包含的 if/else 语句要少得多。而且,您对解决方案的迭代越多,它通常变得越不复杂。最终,您应该能够识别出可以在其上构建所有其他功能的通用抽象。那时,您的大部分计算都应该简单明了。那时你就知道你做得很好。我认为这就是@darvids0n 的目的。
  • 你可能会发现换一种方式也很有帮助,也就是说,尝试为代码编写测试用例。这是一种从不同角度解决问题的方法,您无需使用所有 TDD 即可从中获得有用的东西。
【解决方案4】:

这个网站用 C# 计算工作日,这有帮助吗? http://www.infopathdev.com/forums/t/7156.aspx

【讨论】:

  • ty,但这些解决方案可能比我正在寻找的更复杂 - 因为我必须编辑他们的解决方案以适应我的问题,他们的解决方案可能对我来说有点太离谱了鞋拔进入这个解决方案。
猜你喜欢
  • 2016-07-07
  • 1970-01-01
  • 1970-01-01
  • 2020-12-10
  • 2023-03-07
  • 2013-06-11
  • 2014-06-13
  • 1970-01-01
  • 2013-04-11
相关资源
最近更新 更多