2014-11-23 125 views
2

我遇到了以下需求的有趣问题: 测试过程是否在同一天运行,如果不运行该过程。日期存储为DataTimeOffset。DateTimeOffset当天比较

我最初的做法是:

  1. 都转换值UTC,因为可能在不同的时区中创建这些日期,并有不同的偏移。
  2. 查看每个值的日期值。这是在转换为UTC后完成的,因为Date方法会忽略偏移量。

大多数情况下,这工作,但我遇到了一种情况下,逻辑会失败。如果其中一个值的时间接近前一天/第二天,以便转换为UTC时它将更改日期。如果其他值没有时间也转换为前一天/下一天,则日期比较失败。

所以我结束了以下逻辑包括情景:

public static bool SameDate(DateTimeOffset first, DateTimeOffset second) 
{ 
    bool returnValue = false; 
    DateTime firstAdjusted = first.ToUniversalTime().Date; 
    DateTime secondAdjusted = second.ToUniversalTime().Date; 

    // If date is now a day ahead after conversion, than add/deduct a day to other date if that date hasn't advanced 
    if (first.Date < firstAdjusted.Date && second.Date == secondAdjusted.Date) 
     secondAdjusted = secondAdjusted.Date.AddDays(1); 
    if (first.Date > firstAdjusted.Date && second.Date == secondAdjusted.Date) 
     secondAdjusted = secondAdjusted.Date.AddDays(-1); 

    if (second.Date < secondAdjusted.Date && first.Date == firstAdjusted.Date) 
     firstAdjusted = firstAdjusted.Date.AddDays(1); 
    if (second.Date > secondAdjusted.Date && first.Date == firstAdjusted.Date) 
     firstAdjusted = firstAdjusted.Date.AddDays(-1); 

    if (DateTime.Compare(firstAdjusted, secondAdjusted) == 0) 
     returnValue = true; 

    return returnValue; 
} 

这里是被失败的,现在通过单元测试:

[TestMethod()] 
public void SameDateTest() 
{ 
DateTimeOffset current = DateTimeOffset.Now; 
DateTimeOffset first = current; 
DateTimeOffset second = current; 

// 23 hours later, next day, with negative offset (EST) -- First rolls over 
first = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0)); 
second = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0)); 
Assert.IsFalse(Common.SameDate(first, second)); 

// 23 hours earlier, next day, with postive offset -- First rollovers 
first = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0)); 
second = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0)); 
Assert.IsFalse(Common.SameDate(first, second)); 

// 23 hours later, next day, with negative offset (EST) -- Second rolls over 
first = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0)); 
second = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0)); 
Assert.IsFalse(Common.SameDate(first, second)); 

// 23 hours earlier, next day, with postive offset -- Second rolls over 
first = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0)); 
second = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0)); 
Assert.IsFalse(Common.SameDate(first, second)); 
} 

我的直觉是,有比基于其他值的递增/递减更清洁的方法。有更好的方法吗?

的主要标准:

  1. 调整两个日期为具有相同的偏移量。
  2. 只有在第一个和第二个日期都在同一日历日内发生,而不是在24小时内才返回true。
+3

你所做的是一个肯定的迹象,表明方法很差。根据我的经验,处理日期/时间的最佳方法是将它们存储为UTC或具有特定偏移量。任何“数学”工作都会消失,现在您可以使用标准日期时间API转换为UI显示等。 – 2014-11-23 19:18:27

+0

它们与偏移量一起存储,但偏移量不总是相同。例如,我们通常存储东部时区的偏移量,但取决于夏令时,它可以是-4或-5。 – Josh 2014-11-23 19:30:46

+0

在您的示例中,使用带有和不带夏令时的东部时区,实际上应该表示为两个不同的时区:冬季的EST(UTC-0500)和夏季的EDT(UTC-0400)。 – 2014-11-23 19:42:33

回答

3

调整日期在两个日期之差的一个:

public static bool SameDate(DateTimeOffset first, DateTimeOffset second) 
{ 
    bool returnValue = false; 
    DateTime firstAdjusted = first.ToUniversalTime().Date; 
    DateTime secondAdjusted = second.ToUniversalTime().Date; 

    // calculate the total diference between the dates 
    int diff = first.Date.CompareTo(firstAdjusted) - second.Date.CompareTo(secondAdjusted); 
    // the firstAdjusted date is corected for the difference in BOTH dates. 
    firstAdjusted = firstAdjusted.AddDays(diff); 

    if (DateTime.Compare(firstAdjusted, secondAdjusted) == 0) 
     returnValue = true; 

    return returnValue; 
} 

在这个函数中,我asuming的偏移量将不会超过24小时。 IE日期和它的调整日期之间的区别不会是两天或更多天。如果情况并非如此,那么可以使用time span进行比较。

+0

我认为你钉了它。我需要通过几个单元测试来确认,然后我可以接受你的答案。顺便说一句,我不认为偏移量可能会超过24,因为,因为距离UTC的偏移量在-12小时到14小时之间(http://en.wikipedia.org/wiki/List_of_UTC_time_offsets) – Josh 2014-12-01 20:54:42

+0

减去'CompareTo'返回值显然是错误的 - [唯一指定的](http://msdn.microsoft.com/zh-cn/library/5ata5aya.aspx)有关返回值是符号(负/零/正),而不是绝对值。 – Mormegil 2014-12-01 21:59:09

+0

确实如此,但这意味着CompareTo函数不会声明差异的大小,只是存在差异。结果将始终为-1,0或1. – martijn 2014-12-02 08:20:49

3

您描述的一般方法(转换为常见时区,然后比较日期部分)是合理的。这里的问题实际上是决定参照系的一个问题。你有任意选择UTC作为你的参照系。一开始,只要将它们在相同的时区中进行比较,则无关紧要,但正如您发现这可以将它们放在一天的边界的任一侧。

我认为您需要完善您的规范。问问你自己试图确定下列哪一项。

  • 该值是否出现在指定时区的同一天日历天。
  • 值是否不超过12小时(+/- 12小时是24小时)。
  • 这些值是否不超过24小时。

也可能是别的。实施(但被您拒绝)的定义是“值是否出现在同一UTC日历日”。

+0

我正在寻找测试相同的日历日与两个日期具有相同的时区或偏移量。如果转换为ET比UTC更容易,那对我很有用。大多数情况下,取决于夏令时,值将为ET,偏移量为-4或-5。 – Josh 2014-11-26 00:31:00

0

首先,您在UnitTest中出现错误。

[TestMethod()] 
    public void SameDateTest() 
    { 
     DateTimeOffset current = DateTimeOffset.Now; 
     DateTimeOffset first = current; 
     DateTimeOffset second = current; 

     // 23 hours later, next day, with negative offset (EST) -- First rolls over 
     first = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0)); 
     second = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0)); 
     Assert.IsTrue(DateTimeComparison.Program.SameDate(first, second)); 

     // 23 hours earlier, next day, with positive offset -- First rollovers 
     first = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0)); 
     second = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0)); 
     Assert.IsFalse(DateTimeComparison.Program.SameDate(first, second)); 

     // 23 hours later, next day, with negative offset (EST) -- Second rolls over 
     first = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0)); 
     second = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0)); 
     Assert.IsTrue(DateTimeComparison.Program.SameDate(first, second)); 

     // 23 hours earlier, next day, with positive offset -- Second rolls over 
     first = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0)); 
     second = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0)); 
     Assert.IsFalse(DateTimeComparison.Program.SameDate(first, second)); 
    } 

这是校正后的测试。你的第一次测试应该返回“真实”,就像你的第三次测试一样。被比较的那些DateTimeOffset在相同的UTC日期。只有测试用例2和4应该返回“False”,因为这些DateTimeOffset事实上在2个不同的日期。

其次,您可以简化SameDate()功能如下:

public static bool SameDate(DateTimeOffset first, DateTimeOffset second) 
    { 
     bool returnValue = false; 
     DateTime firstAdjusted = first.UtcDateTime; 
     DateTime secondAdjusted = second.UtcDateTime; 

     if(firstAdjusted.Date == secondAdjusted.Date) 
      returnValue = true; 

     return returnValue; 
    } 

因为所有你感兴趣的是,如果first.Datesecond.Date实际上是在同一个UTC日期,这将会把工作不需要额外做投射/转换为UTC。

第三,你可以用这个完整的程序测试你的测试用例:

using System; 

namespace DateTimeComparison 
{ 
    public class Program 
    { 
     static void Main(string[] args) 
     { 
      DateTimeOffset current = DateTimeOffset.Now; 
      DateTimeOffset first = current; 
      DateTimeOffset second = current; 

      // 23 hours later, next day, with negative offset (EST) -- First rolls over 
      first = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0)); 
      second = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0)); 
      if(false == SameDate(first, second)) { 
       Console.WriteLine("Different day values!"); 
      } else { 
       Console.WriteLine("Same day value!"); 
      } 

      // --Comment is wrong -- 23 hours earlier, next day, with positive offset -- First rollovers 
      first = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0)); 
      second = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0)); 
      if(false == SameDate(first, second)) { 
       Console.WriteLine("Different day values!"); 
      } else { 
       Console.WriteLine("Same day value!"); 
      } 

      // 23 hours later, next day, with negative offset (EST) -- Second rolls over 
      first = new DateTimeOffset(2014, 1, 2, 18, 0, 0, new TimeSpan(-5, 0, 0)); 
      second = new DateTimeOffset(2014, 1, 1, 19, 0, 0, new TimeSpan(-5, 0, 0)); 
      if(false == SameDate(first, second)) { 
       Console.WriteLine("Different day values!"); 
      } else { 
       Console.WriteLine("Same day value!"); 
      } 


      // --Comment is wrong -- 23 hours earlier, next day, with positive offset -- Second rolls over 
      first = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0)); 
      second = new DateTimeOffset(2014, 1, 1, 4, 0, 0, new TimeSpan(5, 0, 0)); 
      if(false == SameDate(first, second)) { 
       Console.WriteLine("Different day values!"); 
      } else { 
       Console.WriteLine("Same day value!"); 
      } 
     } 

     public static bool SameDate(DateTimeOffset first, DateTimeOffset second) 
     { 
      bool returnValue = false; 
      DateTime firstAdjusted = first.UtcDateTime; 
      DateTime secondAdjusted = second.UtcDateTime; 

      if(firstAdjusted.Date == secondAdjusted.Date) 
       returnValue = true; 

      return returnValue; 
     }   
    } 
} 

设置你喜欢的地方并运行调试这套短节目一个破发点。这会告诉你测试用例2和测试用例4实际上是UTC时间相隔2天以上,因此应该是错误的。此外,它将显示测试用例1和测试用例3在相同的UTC日期,并且应该从正常运行的SameDate()期望真实。

如果你想你的第二个和第四个测试用例分开23小时在同一日期,然后测试情况下,两个,你应该使用:

 // 23 hours earlier, next day, with positive offset -- First rollovers 
     first = new DateTimeOffset(2014, 1, 2, 4, 0, 0, new TimeSpan(5, 0, 0)); 
     second = new DateTimeOffset(2014, 1, 1, 5, 0, 0, new TimeSpan(5, 0, 0)); 
     Assert.IsTrue(DateTimeComparison.Program.SameDate(first, second)); 

而对于测试情况下为四个,你应该使用:

 // 23 hours earlier, next day, with positive offset -- Second rolls over 
     first = new DateTimeOffset(2014, 1, 2, 5, 0, 0, new TimeSpan(5, 0, 0)); 
     second = new DateTimeOffset(2014, 1, 3, 4, 0, 0, new TimeSpan(5, 0, 0)); 
     Assert.IsTrue(DateTimeComparison.Program.SameDate(first, second)); 
+0

您误解了标准,所有四个单元测试应该是错误的。我只希望该方法只在两个日期发生在同一日历日时才返回true,并将两个日期调整为使用相同的偏移量。因为你的简化代码不起作用 – Josh 2014-11-28 15:22:31

+0

但是你的测试用例不在同一天。一个简单的调试运行表明,与您的测试数据一样。正因为如此,你的测试用例不能传递给定的数据。如果您希望所有四个测试用例都通过'Assert.IsFalse()',您必须相应地调整您的测试用例数据。如果这是你的意图,你的测试用例数据会被窃听。修复你的“bug”测试用例数据,你的代码将按需要传递。 – StarPilot 2014-12-01 23:37:27

+0

也就是说,它们在UTC的日期不同。如果你使用它作为你的参考框架,你的单元测试是错误的。如果你想使用不同的参考框架,例如EDT或EST,那么你应该将所有的datetimeoffsets转换为该参考框架,然后进行比较。当你所追求的是两个日期时间的日期时,引用的框架很重要。由于两个日期时间/日期时间偏移之间的一个刻度差异很重要,如果它是午夜与午夜1刻度之间的差异。 – StarPilot 2014-12-01 23:54:33

0

怎么样这样的功能:

public static bool SameDate(DateTimeOffset first, DateTimeOffset second) 
{ 
    return Math.Abs((first - second).TotalDays) < 1; 
} 

可以。减去两个日期(DateTimeOff设置是聪明的,知道时区),它会给你一个范围,一个时间跨度。然后你可以检查这个范围是否为+ - 1天。

+0

Simon,如果两个日期(调整到相同的偏移量)发生在同一日历日,而不是24小时内,该方法应该返回true。 – Josh 2014-11-28 15:21:35

+1

一些绝对的“相同的日历日”的概念没有意义。如果您需要12小时的间隔,请将1更改为0.5。 – 2014-11-28 15:58:31

+0

如果您在Windows任务计划程序中安排任务以在凌晨2点每天运行一个程序,则它将执行计算以查看它是在相同的日历日还是在达到凌晨2点的不同日期。不确定“绝对”是什么意思,但日历日在这方面有意义。这同样适用于Outlook或其他日历程序中的每日日历约会。 – Josh 2014-11-28 16:11:56

1

首先,你需要清楚一些混淆程序应该做什么。对于两个通用时区中的两个通用时间戳(两个DateTimeOffset实例没有具体限制),不存在“日历日”这样的概念。每个时区都有其自己的日历日。例如,我们可以有两个实例DateTimeOffset,名为firstsecond,它们有不同的偏移量。让我们将时间轴可视化,并使用|__|标记DateTimeOffset实例引用的具体时间点与*和相应时区中的日历日(即特定时区中0:00和23:59之间的时间间隔)。它看起来是这样的:(2-3时之间)

first: ........|___________________*__|....... 
second: ...|______*_______________|............ 

当在first的时区,在second活动期间日历天发生的事情。当在second的时区中时,first事件发生在之后日历日(上午1-2点之间)。

所以很明显这个问题需要澄清,可能还有一点限制。那些真的是通用时区,还是他们在同一地点的时区,可能只在夏令时有所不同?在那种情况下,你为什么不忽略时区?例如。 2014年11月2日00:10至23:50之间,UTC偏移量已经发生变化(EDT-> ET),并且两个时刻间隔超过24小时:new DateTimeOffset(2014, 11, 02, 00, 10, 00, new TimeSpan(-4, 0, 0)).Date == new DateTimeOffset(2014, 11, 02, 23, 50, 00, new TimeSpan(-5, 0, 0)).Date并不重要。基本上,这是martijn试图做的,但是以一种非常复杂的方式。当你尝试只是

public static bool SameDateSimple(DateTimeOffset first, DateTimeOffset second) 
{ 
    return first.Date == second.Date; 
} 

它会适用于所有上述单元测试。而且,这也是大多数人在保证两个实例在一个地方提及时间的情况下称为“同一日历日”的情况。或者,如果你真的比较两个“随机”时区,你必须选择你的参考时区。最初尝试时可能是UTC。或者,它可能是从人类的角度来看更符合逻辑使用第一时区为基准(你也可以选择第二个,它会得出不同的结果,但两者的变体“一样好”):

public static bool SameDateGeneral(DateTimeOffset first, DateTimeOffset second) 
{ 
    DateTime secondAdjusted = second.ToOffset(first.Offset).Date; 
    return first.Date == secondAdjusted.Date; 
} 

这对于上述某些测试不起作用,但在某种意义上对于两个随机时区“正确”(在某种意义上)起作用更为普遍:如果尝试first = new DateTimeOffset(2014, 1, 2, 0, 30, 0, new TimeSpan(5, 0, 0)), second = new DateTimeOffset(2014, 1, 1, 23, 30, 0, new TimeSpan(4, 0, 0)),简单SameDateSimple返回false(与martijn's一样),即使这两个实例在时间(都是2014-01-01 19:30: 00Z)中提及SameDateGeneral此处正确返回true

+0

目前,我们在系统中看到的两种情况是两个DateTimeOffset都是东部时间(-4或-5取决于一年中的时间)。另一个是一个日期是ET,另一个是UTC。当我们有一个不在ET的数据中心时,我想要防范未来的情况,这可能会引入不是ET或UTC的偏移量。 – Josh 2014-12-03 15:08:05