时间间隔(TimeSpan)是否是不必要的?

17

编辑于2009年11月4日

好的,距离我最初发布这个问题已经有一段时间了。在我看来,许多最初的回答者没有真正理解我的意思 - 常见的回答是"你说的没任何意义"之类的变体 - 所以我做了一些方便的图表来真正说明我的观点。

当我们谈论数字时,我们通常是指所谓的数轴上的点,这是小学生学习的内容:

The number line

现在,当我们学习算术时,我们的思维会对这个概念进行非常有趣的转化。例如,评估表达式1 + 0.5,如果我们仅仅应用"数轴思维",就需要我们想出一种方法来解释这个式子:

Adding two points on the number line

这很难真正说明,因为很难“想象”出来:“添加”两个点。这就是许多回答者在添加日期的概念上挣扎(或仅仅将其视为荒谬)的原因,因为他们认为日期是点。
但是,表达式1 + 0.5对我们来说是有意义的,因为当我们思考它时,我们真正想象的是这样的情况:

Adding a number (point) and a magnitude (vector)

换句话说,数字或点1,加上向量0.5(vector),结果得到点1.5。

或者,我们可以这样想象:

Adding two vectors

也就是说,向量1加上向量0.5,得到向量1.5。

换句话说,在处理数字时,我们可以互换地对待点和向量。但是日期呢?毕竟,日期基本上也是数字。如果你不相信我,将这行与上面的数轴进行比较:

A timeline

注意时间轴和数轴之间的对应关系吗?这就是我的观点:如果我们使用数字进行上述转换,那么我们也应该能够使用日期进行转换。因此,应用“时间轴思维”,表达式0001-Jan-02 00:00:00 + 0001-Jan-01 12:00:00没有太多意义,正如许多回答者所指出的那样:

Adding two points on a timeline

但是,如果我们在脑海中进行与每次加减数字时执行的相同的概念转换,我们可以轻松地将上述内容“重新思考”为以下形式:

Adding a point in time and a time vector

很明显,一个DateTime和一个TimeSpan之间的区别就像一个点和一个向量之间存在的区别一样。我认为导致很多人对我的建议作出负面反应的原因是,以这种方式将日期视为大小感觉非常不自然。但我不同意没有明显的参考点可以用作零点的论点。有一个明显的参考点,我会给你一个提示:大约在2010年前后。不要误解:我并不质疑在概念上区分DateTime和TimeSpan的有用性。实际上,我的问题一直应该是(正如ChrisW间接建议的那样),当处理常规数字类型时,为什么我们要将数字和向量互换使用?(或者说,为什么我们只有一个int类型,而不是int和intspan?)这是一个很大的区别,然而我们直到初中或高中才真正开始思考它。然后它被视为这个新的数学概念,而实际上它是我们自从学会用手指数数加法以来一直在利用的东西。
最终,最好的答案来自Strilanc,他指出使用DateTimeTimeSpan实际上是仿射空间的一种实现,这种实现具有方便的特性,即不需要参考点作为原点处理。所以感谢Strilanc。然而,我将采纳ChrisW的答案,因为他首先提出了向量和点的概念,这真正触及了问题的核心。

原始问题(供后人参考)

我当然不是编程万事通,但我知道PHP和.NET都有一个TimeSpan类,除了DateTime类(或.NET中的结构),我猜这在许多其他语言和框架中也是如此(尽管我主要是参考.NET结构写的)。这可能看起来是个奇怪的问题,但TimeSpan不是多余的吗?

如果你认为答案很明显(“DateTime是时间上的一个绝对点,而TimeSpan是一段时间范围——就这么简单!”),请考虑以下:整数可以被概念化为绝对值(数轴上的点)或值之间的距离,我们不需要为这些不同的概念化定义两种不同的数据类型。我仍然可以毫无歧义地写出5 + 6。

只要有一个一致的零点参考,我认为没有理由需要一个TimeSpan对象来执行DateTime对象的算术运算,或者获取它们之间的距离。

我错过了什么?为什么不能将TimeSpan结构的独特方法和属性简单地合并到DateTime中?

(免责声明:我并不是很热衷于这个问题;我一直都在使用DateTimeTimeSpan对象,就像它们被设计的那样。我只是在问一个问题。)

编辑:为了阐述我的观点,这里提供一个过于简单的例子:

考虑方程式10 - 5 = 5。我们可以将其理解为“从10(值)开始,向左移动5个单位(跨度),最终到达5(值)”。

假设为了方便,我们将1900年1月1日定义为零点,并且我们仅以天数来定义TimeSpan对象。

那么,以DateTime术语讲,10 - 5 = 5可以被理解为1900年1月11日 - 1900年1月6日 = 1900年1月6日。这是正确的,因为根据我们的定义,1月11日就是“10”,1月6日就是“5”。我们之所以将10视为,第一个5视为跨度,最后一个5又视为,仅仅是为了帮助我们更好地理解概念。我的观点只是:唯一的区别在于你如何看待这个数字,而不是它实际上是什么。这就是为什么我们没有针对整数值和整数跨度分别设计结构的原因——一个普通的整数就足以涵盖所有情况。

我的意思清楚吗?


2
@Henk:哈哈,不要假装那不会很棒。 - Dan Tao
在10天的时间段内有多少个星期二? - recursive
@recursive:正如大家一直在说的那样,这取决于你的零点,而.NET的零点是公元0001年1月1日星期一(DateTime.MinValue)——因此答案将是2。但是看起来你试图指出我的问题中有些荒谬的东西;因此,我将问你一个几乎相同的后续问题,以说明我的观点:在8的跨度内有多少个5的倍数? - Dan Tao
1
首先,时间是一个维度,数字是用来描述这个维度的单位。这个问题没有意义,BCL是广泛适用的通用库。如果某些东西对你没用,那并不意味着它对所有人都是浪费。我相信有数百万人正在使用TimeSpan,并且他们正在正确地使用它,我就是其中之一。如果你有一些简单的替代方案,那并不意味着每个人都应该使用它。 - Akash Kava
实际上,我们确实需要不同的int和intspan概念,因为它可以消除整个类别的错误。这就是最初的匈牙利标记法的含义,直到它被破坏了。 - Alexander Abramov
很抱歉,添加标量和向量是一种未定义的操作。在单个维度(数轴)中,向量本质上是一个标量,因此我可以理解混淆的原因。但是,您可以添加两个向量或添加两个标量,但不能混合标量+向量。顺便说一句,我真的很喜欢你的图表:P - Seth
9个回答

12

(作为一名数学家)这是因为对“日期”的算术运算不封闭或者说没有明确定义,需要额外的结构来处理。

例如,2000年1月1日减去1999年12月1日等于...?我们知道它们之间相差31天,但如果将其解释为日期,则答案为纪元(即零)+31天。这不再是有效的“日期”。

同样地,所有整数的算术运算也没有被明确定义(1/2在整数中没有答案... 在整数运算中返回零,但0*2等于0,而不是你所期望的1)。这需要一个额外的结构来处理,我们称之为分数。


就像你说的一样,2000年1月1日和1999年12月1日之间有31天。那么答案应该是31天,如果你只想知道距离,那么DateTime应该有一个TotalDays属性,你应该能够写出(Date1 - Date2).TotalDays。对吗?为什么Epoch + 31天会被判定为"无效日期"呢? - Dan Tao
2
由于各种原因,我最喜欢的原因是在格里高利日历于1528年左右引入之前,“一天”的长度与现在不同。因此,如果您有一个DateTime实现,它知道这些事情,(Date1 - Date2) + Date2!= Date1。( .NET实现相当不错,但我不知道它是否会为您滑动日历 :)。 - Seth
Seth,我认为你的评论真正抓住了问题的症结,我认为你应该将其纳入你的答案中。 - Logan Capaldo
Seth,你确定公历改变了一天的长度吗?它确实改变了一年的平均长度。而且.NET通过CultureInfo类支持儒略日历/公历等。我需要测试一下它是否会在转换日期周围进行调整。 - H H

12
考虑一下:整数可以被看作是绝对值(在数字线上的点)或值之间的距离。
按照你的逻辑,不必要的不是TimeSpan,而是DateTime不必要,可以被TimeSpan(从零开始的持续时间)所取代。
此外,整数有一个明显的零,而日期却没有一个明显的零;但是,如果您想用“距离/跨度从零/原点”来替换“数字线上的位置”,那么拥有一个明显的零是必要的。
编辑:
点(平面上的位置)与向量不同。
它们看起来相似...
- 向量(距离原点)可以表示一个点 - 点(相对于原点)可以表示一个向量
...但是,为了表示给定点所需的向量的值会在原点改变时发生变化。
始终将两个(相对)向量相加是有意义的,但是除了将这些点转换为向量并添加向量之外,将两个点相加是毫无意义的。
两个向量的和不受原点变化的影响,但是如果通过将它们转换为向量并添加向量来汇总它们,则两个点的和会受到原点变化的影响(因为更改原点会影响这些向量的值)。
[在上述论点中将“点”替换为DateTime,将“向量”替换为TimeSpan。]
我认为绝对值和相对值之间存在真正的差异。我不知道为什么这种差异在算术中不太明显,即为什么“数字”似乎可以交替表示绝对值和相对值。

1
我不同意:按照丹的逻辑,DateTime和TimeSpan之间实际上没有任何区别。它们是同一概念的不同实例。因此,我认为说其中一个使另一个过时是不正确的。 - Joren
此外,整数仅有一个明显的零,因为我们是任意定义的。任何坐标或索引系统都是相对的。 - Joren
2
@Joren 如果你能找到另一个整数 new_zero,使得对于所有整数 x,new_zero * x = new_zero 都成立,那么你可以说零是相对的。 - Pete Kirkham
@Pete 你给出了一个“任意定义”的零。顺便说一下,零的规范定义是:它是任何数x加上它等于x的数字...根据那个定义,它可以说是相对的(它相对于x)。 - ChrisW
+1 对于点与向量比较(如果我可以的话,+10)。这正是你不想这样做的原因。有趣的是,在二维空间中,一个点和一个向量都可以用一个二元组表示,因此它们中每个都有相同数量的“信息”。只有语义意义不同,类型系统是捕获数据语义意义的好方法。 - Daniel Pryden
显示剩余3条评论

5
只因你能够定义一个操作,不代表你应该这么做。例如,除以零未定义的原因之一是因为定义它将需要牺牲算术的一些非常有用的属性(例如结合律等)。
时间跨度和日期之间的区别在于加法。添加两个时间跨度是有意义的,但是添加两个日期除非你有一个任意的参考日期,否则没有意义。通过不允许添加日期,您可以抽象掉那个任意的参考日期。我不知道.Net中的日期“0”是什么,我也从来不需要知道。这不是很好吗?
添加两个日期几乎总是一个错误(严肃地说,试着想想这在数理数字外还有什么意义)。通过引入时间跨度(创建仿射空间),您消除了整个错误类别。

4

一个原因是将类型分开可以避免一类错误,即您认为您拥有相对时间,但实际上拥有绝对时间,反之亦然。例如,如果两种类型是分开的,则加法操作两个绝对时间可能会被标记为编译器错误。

此外,当成员数量较少时,IntelliSense(和新手发现)的效果更好--通过在两种类型之间分割方法,使用每种方法变得更容易。


2
反过来问:削弱类型系统在这方面的好处是什么?
这完全是成本与效益的问题,DateTime 的巨大优势在于通过禁止此类操作来减少由于不合逻辑的日期/时间计算而导致的错误。 DateTime 存在的原因非常类似于严格的类型检查系统存在的原因:使代码中的语义错误产生编译时消息,通知程序员其代码中的错误。
相反,使用 DateTime 的成本为零。
现在考虑放弃 DateTime。我们会得到什么?
直接回答您的问题:“TimeSpan 不是多余的吗?” 绝对不是,它可以减少错误。 对我来说绝对有用。

2

从概念上考虑。如果我告诉你,我将在7天后举办一场派对,那么这个句子中的“7天”是一个日期吗?我能否只说我的聚会在“7天”?当然不行,因为7天不是一个日期。面向对象编程的一个关键思想是将这样的概念表示为类型在系统中。虽然我们可以将所有东西表示为整数(事实上,许多人已经这样做了),但在面向对象编程中,我们有物品类型、它们的行为和属性的概念,在这种意义上,有一个表达这一点的对象是有意义的。


0

日期有很多复杂性,例如:

  • 闰年
  • 闰秒
  • 1582年改革的公历
  • 不存在0年这样的事实
  • 月份长度的差异

将日期和时间跨度视为不同的事物意味着在实践中这些问题不太可能使您感到困惑。


0

我认为你可以反过来说,DateTime是多余的,我们只需要TimeSpan :)

说真的,所有的日期都只是时间跨度。它们都相对于某个起点。从技术上讲,在基督教日历中没有“零年”(因为你实际上不能有一个“我们主的第零年”),但如果我们将公元前0001年1月1日12:00 A.M.作为“零点”,那么每个在此之后(或之前)的日期都可以被视为相对于该日期的时间跨度。因此,2009年9月19日上午12:00的时间跨度为734033天。

因此,数学上DateTimeTimeSpan是多余的。但是当我们编写代码时,我们试图传达的远不止抽象的数学构造。任何给定的DateTime实例实际上可能只是相对于某个任意零点的时间跨度,但对于大多数阅读您代码的人来说,它将暗示特定的日历日期。同样,TimeSpan暗示了日历上两个时间点之间的间隔。

在这种情况下,微软选择了明确而不是吝啬。我不反对这个决定。

如果您只有一个相对值TimeSpan,那么如何表示输出日期,因为您需要基于时代的时间点,它与任何事物都无关。 - Pete Kirkham
@Pete,不确定我是否理解您的问题。我们每天都这样做。今天是09/19/09。这是公元前1年九个月、十九天和2009年之后的时间跨度。绝对日期只是一个时间跨度。 - devuxer

-1

它是糖,不多也不少...


7
DateTimeTimeSpan都是由数字内部表示的,但它们不是实际的数字。如果TimeSpan被称为“语法糖”,那么int[]char[]string也只是byte[]的语法糖。然而这样说会把“语法糖”的含义淡化到毫无意义的程度,因此我们可以认为把TimeSpan称作语法糖是不恰当的。 - Sam Harwell
虽然答案有点简洁,但仍然是正确的。在这里进行负投票只是表示“我不理解他在说什么”,而不是“这是错误的”。实际上,如果您愿意使用“自日期X时间Y以来的秒数”计算日期,那么DateTime和TimeSpan都是不必要的。 - Lasse V. Karlsen
2
有人可能会认为类型系统的整个目的就是为值添加语义含义,并强制执行关于如何使用这些值的规则。并非每个位和字节上的抽象都只是“糖衣”——否则,你会在哪里结束呢?位和字节只是电气状态变化的抽象。计算机语言的整个重点是推理行为,而不是实现。 - Daniel Pryden
你真的认为我给出需要讨论的答案就需要给我点踩吗? - Kai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接