VB .NET 的 DateAdd 函数存在严重问题,建议不再使用

Posted by NosaLee in .NET Programming on 30-08-2012. Tags: , , , , ,

作者:牧山道人
原文地址:http://www.seeksunslowly.com/vb-net-的-dateadd-函数存在严重问题-建议不再使用
转载请注明出处,谢谢。
_____________________________________

我们先来看 DateAdd 的函数原型

1
2
3
4
Public Function DateAdd(ByVal Interval As Microsoft.VisualBasic.DateInterval, _
                        ByVal Number As Double, ByVal DateValue As Date) As Date

Member of Microsoft.VisualBasic.DateAndTime

注意
DateAndTime 是 Microsoft.VisualBasic 命名空间下的一个模组,原型如下:
Public Module DateAndTime
Member of Microsoft.VisualBasic
所以,我们可以直接使用 DateAdd 函数。

众所周知
1、VB .NET 的 Date 类型最大值为 9999/12/31 23:59:59,最小值为 1/1/1 00:00:00。
2、DateAdd 函数可以由一基准日期(第三个参数)加上一定数值(第二个参数)的指定元素(年、月、日、时、分、秒等,第一个参数)取得一个新日期(Date 类型,返回值)。
3、DateAdd 中指定元素值为 Double 类型(第二个参数)。
4、只要结果日期在 Date 数据类型取值范围内,DateAdd 函数就能正常工作。

今天发现的问题
DateAdd 在允许范围内加任意数值的年、月、日、时都没问题,但加分和秒时,会抛 System.OverflowException 异常(内存溢出),具体消息是
Message=”Arithmetic operation resulted in an overflow.”

DateAdd 示例代码(存在问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

      '' 以下代码正常工作。
      MsgBox("Max Value of Date Data Type: " & Date.MaxValue)
      MsgBox("Min Value of Date Data Type: " & Date.MinValue)
      MsgBox("Add Years: " & DateAdd(DateInterval.Year, 9998, New Date(1, 1, 1)))
      MsgBox("Add Months: " & DateAdd(DateInterval.Month, 9998 * 12, New Date(1, 1, 1)))
      MsgBox("Add Days: " & DateAdd(DateInterval.Day, 9998 * 12 * 30, New Date(1, 1, 1)))
      MsgBox("Add Hours: " & DateAdd(DateInterval.Hour, 9998 * 12 * 30 * 24, New Date(1, 1, 1)))
      ''

      ' 抛内存溢出异常。
      MsgBox("Add Minutes: " & DateAdd(DateInterval.Minute, 9998.0 * 12 * 30 * 24 * 60, New Date(1, 1, 1)))

      ' 正常工作。
      MsgBox("Add Minutes: " & DateAdd(DateInterval.Minute, 2147483647, New Date(1, 1, 1)))

      '' 抛内存溢出异常。
      MsgBox("Add Minutes: " & DateAdd(DateInterval.Minute, 2147483648, New Date(1, 1, 1)))
      MsgBox("Add Seconds: " & DateAdd(DateInterval.Second, 9998.0 * 12 * 30 * 24 * 60 * 60, New Date(1, 1, 1)))
      ''
   End Sub

简单分析一下
首次抛异常行 MsgBox(“Add Minutes: ” & DateAdd(DateInterval.Minute, 9998.0 * 12 * 30 * 24 * 60, New Date(1, 1, 1)))
中的第二参数值为 5,182,963,200,于是本道想到可能是超出了 Integer 数据类型的范围。
一查,Integer 的最大值为 2,147,483,647,于是改加 2,147,483,647,工作正常,接着又改为加上 2,147,483,648,又抛异常了。

很明显,第二个参数实际应为 Integer 类型,而不是 Double 类型。
所以,DateAdd 函数的问题在于:
1、声明式错误:第二个参数类型实际应为 Integer,不是 Double。
2、设计错误:当选择分、秒作为增加元素时,即使第二个参数取 Integer 的最大值,计算结果也不能达到 Date 类型的最大值。

进一步分析
大家知道,Microsoft.VisualBasic 命名空间其实就是从 VB6 直接移值过来的(一些公用常数、模组、函数等)。
VB .NET 的 Integer 类型在 VB6 中对应 Long 类据类型(取值范围相同)。
经测试,在 VB6 中,存在相同的问题(以上两条均存在)。
所以,DateAdd 的两个问题在 VB6 中就存在,而微软在升级语言时并没有进行修正。

这个问题无所谓?
您可能觉得这个问题无所谓:因为即使 2,147,483,647 代表秒数,也有 68 年时间,一般情况够用了。
但是,你要知道你的用户可能会乱操作或者刻意去测一些奇怪的日期;况且,68 年根本不够用啊,连一般人的生日都算不完……
所以,这个问题需要解决。

解决方案
放弃使用 DateAdd,改用 System.DateTime 结构。
这个不仅可以加秒,毫秒都支持了。

System.DateTime 示例代码如下(工作正常)

1
2
3
4
5
6
7
8
9
10
11
   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
     
      ' dt 初始化为公元 1 年 1 月 1 日。
      Dim dt As System.DateTime = New System.DateTime(1, 1, 1)
      ' 尝试增加比 Integer 数据类型最大值还大的数,工作正常。
      MsgBox(dt.AddSeconds(2147483648))
      ' 以秒数来增加 9998 年(近似值),工作正常。
      MsgBox(dt.AddSeconds(9998.0 * 12 * 30 * 24 * 60 * 60))
      ' 以毫秒数来增加 9998 年(近似值),工作正常。
      MsgBox(dt.AddMilliseconds(9998.0 * 12 * 30 * 24 * 60 * 60 * 1000))
   End Sub

好了,至此,DateAdd 函数的问题分析、深层原因的挖掘,以及最终解决方案已全盘给出,希望对您有帮助。

Post a comment