为什么没有真正的“仅日期”数据类型?


24

我不得不为真正“只是一天”的数据集使用DateTime值而感到非常荒唐。生日是最常见的例子,但这总是在业务应用程序中出现。

我已经习惯于将“仅日期”记录的“时间”部分设置为“中午”(这避免了无论时区如何更改日期)。这似乎是一种hack,我将永远从初级开发人员那里找到解决此问题的错误。

时间总是相对于固定点的。4PM是子午线或正午后4个小时。太阳的最高传播点是可观察到的,它使我们能够建立坐标系。自1970年1月1日起,中午(安特子午线)前3个小时,中午后2小时(1441899402938毫秒)。对于在笛卡尔世界中成长的人们来说,这是第二自然。

但是我们的日历早于笛卡尔。我的论点是,可以更恰当地将其视为应用了模函数的枚举。星期一跟随星期日,依此类推,直到周日跟随星期六。没有正负,它是模数或绝对值。

同样,重复多年。每隔365天(或大约365天)对我来说有几天特别的日子:生日,周年纪念日,孩子们的生日等。业务调度应用程序每隔7天,每月的第一个星期二等等都有大量的会议示例。我们可以将其映射到浮点数,实际上将其映射到所述浮点数可以解决很多旧方法确实很难解决的问题,但这并不意味着它是唯一的方法。

意识到并理解使用DateTimes存储日期的“圆角方孔”性质使您成为更好的程序员。

在定义Date类时,是否有明确打算用作计划应用程序的应用程序中有值,还是“将所有时间设置为中午”是最佳方法?使用DateTime并将Time组件设置为Noon可能会有什么问题?这样的方法可以解决时区偏移吗?我使用过MomentJS,但我认为那是更好的Date类。


7
我认为这是一个非常有趣的问题。以生日为例,这对人类来说真的很有意义,我生活在一个地方,每个人都在相同的时间起床。一旦您需要用数学方式表达它,它就会变得非常复杂。与诸如轮班开始/结束时间之类的内容一样,它们也没有“时间”,它们是“您工作所在时区中的一天中的小时数”
Ewan 2015年

3
NodaTime(日期和时间的C#库)具有一种日期类型。
CodesInChaos

5
将您的时间存储为UTC,没有日期问题,没有时区问题。
罗伦·佩希特尔

3
“但是天哪,这似乎是一种黑客手段,我将永远从初级开发人员那里找到解决此问题的错误。” -为什么不创建自己的DateOnly类或其他任何东西而将其命名为“ day”。
布兰登2015年

5
@MichaelBlackburn我很认真。您的解释完全符合强制执行业务规则的数据类型的情况(仅日期,例如,您可以将其内部存储为“ DateTime”,并将时间部分设置为正午),但随后仅公开您想要的部分(月,天等年/无年)。
布兰丁2015年

Answers:


15

首先,让我们摆脱一件事:生日是一回事,生日是另一回事。一个生日是外来的数据类型,因为它缺少的小时,分钟等不仅组成部分,但它也缺乏年份组成。如果您真的想应对生日,我建议您发明一种自己的数据类型,该数据类型只包含月号和天号,并且与任何内置的日期时间数据类型都不相关。

另一方面,如果您还想跟踪出生年份,那么您所拥有的不是生日,而是生日。因此,现在的问题变成了为什么没有仅日期的数据类型,以便您可以方便地表示出生日期,而流行的语言似乎迫使您使用还包含时间成分的某种类型。

让我简短地提及一下,并非所有的编程语言都只提供包含时间成分的时态数据类型,这是不正确的。我在RDBMSes及其对应的SQL方言中遇到了仅日期数据类型。但这无关紧要:这些数据类型存在并不意味着它们是一件好事,RDBMS具有将存储与表示混淆的悠久历史。

您将了解为什么在意识到时间是坐标的那一刻就拥有此类仅日期的数据类型是一个坏主意。大多数人对时间是一个非常模糊的概念,并且这个概念包含了一些神秘的文化概念,例如年,月和日,而没有意识到这些概念仅是代表性的:它们仅用于表示人类的时间并接受时间作为人类的投入。在实际时间输入GUI控件下方的任何层上,时间应该并且通常以时间坐标表示,时间坐标是自某个起点起的单个时间单位。

例如,在DateTimeMicrosoft Dotnet 的数据类型中,时间单位为100纳秒,时间的起点为0001 CE 1月1日午夜。

奥术的唯一示例性表示法是使用度,度的分钟和度的秒的角度测量。当然,为了完成任何有用的计算,您必须在内部使用弧度,并且如果需要的话,在与人类用户进行交互时可以与度进行相互转换。

因此,请勿将人类可读的度量表示与度量的实际性质混淆。实现测量的理想方法通常与测量的性质最接近,通常与人们易于理解的测量方法大不相同。

鉴于所有这些,您对仅表示日期的时间数据类型的请求类似于对仅能表示度的角度数据类型的请求,从而明确地阻止了更高的精度。这样的数据类型将非常有限,最终将无用,因为您必须无论如何都要将其转换为弧度或从弧度转换为完成任何有用的操作。

您的出生日期问题是您的时间坐标不准确:该人当然是在特定时间出生的,但是医院没有记录小时和分钟,或者我们没有关心他们。因此,实际上发生的是,您的出生日期和时间坐标有误差范围,公差不确定性(如果您愿意的话),最好这样对待:将其准确地放在中间并考虑隐含的+12 -12小时不确定性。这正是您直观地想到的解决方案。


4
对于“天”而言,情况并非如此,因为“天”的概念甚至早于“时间”。最恰当地将它们视为枚举或模数。周日跟随星期一,依此类推,直到我们回到周日跟随的星期日。真的是另一回事。
迈克尔·布莱克本

10
那么当然医院写下出生的时候-这是在大多数医院的标准做法。这根本不是问题。问题在于,出于纪念事件(生日,假期,周年纪念日等)的目的,人们不使用或希望分辨率不超过1天。去问问你见到的第一个人,他们的生日是什么时候,他们会给你一个月零一天的时间,但不会给你一个小时和一分钟的时间。另外:我们一直使用包括刻度的单位:毫米,千克,兆瓦,甚至微法拉。
Caleb 2015年

4
-1:并非所有时间的使用都代表一个时刻。生日是一个不同的想法,在公历中以一个月和一天表示。问“今天是凯文的生日吗?”很有道理。答案取决于位置。如果我在悉尼,那可能是我的生日,但是如果我在檀香山,那将是几个小时了。
凯文·克莱恩

6
@MikeNakis结果将是微安,而不是胡说八道。更为重要的是:当您使用日期而不是一秒或毫秒时,通常是在谈论整天-一个时间 -而不是特定时间,并且取决于上下文,这可能是重复事件。
Caleb 2015年

4
OP谈论的是生日,确切地由java.util.MonthDay表示。生日不是度量标准,而是人类每年重复的日子的观念。人类的许多时间观念没有用单个时间点来表示。
凯文·克莱恩

9

日期和时间取决于上下文,有很多不同的东西,您需要许多单独的类型来涵盖所有用例。

DateTime多种语言显示的类型表示精确的时间点(“瞬时时间”)。除此之外,我们还有许多相对的或“人类的”时间和时间概念,例如日历天,重复日期,月份,年等,在许多情况下是模棱两可的并且取决于上下文。这些类型并不是通用的,但在特定的应用程序领域(如日历,计划工具和其他与人类时间概念交互的应用程序)中是必需的。

如果您正在编写类似日历应用程序的内容,那么一定会受益于使用像Joda-time这样的库,该库提供了更丰富的时间类型。例如LocalDate,没有时间的日期。这DateTime与将时间部分设置为零的普通语言具有不同的语义,因为它DateTime仍然指示特定的时间点(特定时区中的午夜),而LocalDate指示整日,并且不与特定时区相关。这也意味着您不能直接将一个翻译成另一个。

LocalDate比肯定要简单得多,DateTime因为它不必考虑时区,但是您应该意识到其他问题,例如,当您越过时区时,当前日期实际上可能会倒退,并且同一时刻可能对应于不同时区中的不同日期。如果您在网络应用程序或Web应用程序中使用本地日期,则应特别注意这些问题。从日期中删除时间部分并不能解决时区的根本问题!如果考虑到历史日期和不同的文化,它会变得更加棘手,因为在儒略历与公历之间,相同的日期可能对应于时间上千差万别的实例。

现在,您问为什么语言没有LocalDate 内置的东西。首先,某些语言(例如SQL和Visual Basic)的日期类型没有时间部分。并且Java LocalDate在最新版本中也添加了一个。但是.Net等其他平台则没有。只有语言设计人员才能真正回答为什么标准库中未包含此内容,但我的猜测是“瞬时时间”在概念上很简单且普遍有用,而其他时间概念仅对特定的应用程序领域(如日历等)有用。 )。因此,让应用程序开发人员编写自定义类型来处理更复杂的用例,或者由第三方库(如Joda-time)来处理它,是很有意义的。


5

我不得不为真正“只是一天”的数据集使用DateTime值而感到非常荒唐。生日是最常见的例子,但这总是在业务应用程序中出现。

这可能是因为日历很复杂并且以许多不同的方式使用,以至于没有人能够找到一个既简单又通用的类,足以在许多领域中使用。

在编程语言中常见的日期类型可用于在计算机系统中准确地为交易记录日期。其他用例可能需要自定义库。

以下是有关日历的事实的简短列表,表明了日历的复杂性-其中大多数是历史记录,因此,如果将注意力集中在1970年1月1日之后,则不会受到影响。但是,如果您的应用程序需要处理19世纪末之前的日期,那么这些事实就很重要。可能的用例是各种历史数据库(书籍,家谱),但还有今天仍在活动中的大型公司或组织的资产。

所有这些事实均来自Julien Signolles为OCaml的日历库中提供的出色的FAQ

  1. 朱利安历法由尤利乌斯·凯撒(Julius Caesar)于公元前45年引入。直到1500年代,各国开始使用公历(2.2节)时,它才被普遍使用。但是,一些国家(例如希腊和俄罗斯)在1900年代使用了它,俄罗斯的东正教教堂和其他一些东正教教堂一样,仍然使用它。

  2. 从儒略历到公历的转换并没有统一发生,并且根据更改年份的不同,已删除了10到13天。例如在法国1582年12月9日,之后是1582年12月20日,在希腊,1924年3月9日,然后是1924年3月23日。

  3. 即使在现代时代,也使用许多不同的日历(格里高利历,东正教,伊斯兰教和中文)来引用一些日历,它们都使用不同的方式来计算年和周年纪念日或宗教庆典的日期。

现在,您希望将日期类型与对一般业务操作有用的操作捆绑在一起。我想没有一般的业务运作这样的事情。例如,在金融界,我们需要计算:

  1. 年分数(例如“ 6个月”对应于“ 0.5”),与利率一起用于计算给定期限内贷款的实际利息。有6到10个配方可以计算这些分数,每个配方在处理a年的长度,相对于2月最后一天的时间段的位置以及一个月的持续时间方面都不同。

  2. 日期滚动,在计算周年纪念日时,我们使用业务日历和规则(从一组超过6种不同的规则中选取)将周年纪念日从假期更改为工作日。

对于从事金融业的人来说,任何没有实现所有这些功能和规则的日历类型都是没有用的。许多其他行业可能还有其他类型的习惯和约定,需要在日历上进行自定义计算。

在定义Date类时,是否有明确打算用作计划应用程序的应用程序中有值,还是“将所有时间设置为中午”是最佳方法?使用DateTime并将Time组件设置为Noon可能会有什么问题?这样的方法可以解决时区偏移吗?我使用过MomentJS,但我认为那是更好的Date类。

如果需要跟踪单个日历日,最好的方法可能是使用一个大整数表示该日历日的儒略日。从儒略日到日历日来回转换的算法(用年,月和日历描述)是众所周知的,并且经过了全面测试,因此您可以轻松地在应用程序中实现它们,并找出与您相关的任何规则计算2月29日某个事件的周年纪念日的案例。


2

我认为,迈克·纳基斯(Mike Nakis)在上面的回答中所做的工作比我能更好地解释,时间通常是绝对测量的坐标,而任何其他通信,假定状态或该时间坐标的持久性只是该时间坐标的抽象表示。

当将“星期几”仅指实际时间点的某种模数表示时,您就是在说这种表示。实际上,它要复杂得多。如果您的任务是编写一个将返回给定时间点的“星期几”的函数,请考虑将以下信息用作此类算法的输入。您将需要时间点,日历,要考虑的时区(请记住,时区会在所有时间发生变化,因此您需要知道该有效时区何时开始以及在特定时间坐标中何时结束。朝鲜最近例如,如果更改夏令时!),并且如果实行夏令时,则时间也会随着时间而改变。现在考虑是否为您提供了本地时区的DateTime,

您可以看到这个看似简单的问题确实有多复杂。

我知道您正在经历的痛苦,一次就解决了由经验不足的开发人员编写的产品的访问计划应用程序中的所有错误。整个事情必须报废。

时间确实是一个坐标,但是除了单纯的日期外,还考虑其他可能需要时间的数据,例如:

持续时间:可能发生的毫秒跨度,表示没有指定时间坐标的时间长度或经过的时间。一个用例可能是

作为用户,我希望每隔一个星期三午夜作业完成后15秒执行一次此任务。

时间间隔:两个特定时间坐标之间的时间范围。您可能需要考虑间隔的用例。

作为用户,我需要查看每月的每一天完全包含在指定的时间间隔内。

我想提的另一个要点是,您对基于时间的数据的浮点数发表了评论,我建议不要这样做。浮点运算不可避免地会导致舍入误差,可能无法为您提供所需的精确测量时间。

因此,总而言之,所有这些信息不可避免地导致以下设计注意事项:

  • 特定的时间点或时间范围应保留在UTC或某些包含足够信息的数据类型中,以便在需要时可以轻松地抽象回UTC
  • 应编写应用程序逻辑,以将UTC格式或UTC坐标范围格式化为代表性数据状态,以使最终用户更容易理解。
  • 重要的持续时间应以毫秒为单位
  • 任何有关时间的数据的显示在特定于区域设置或最终用户的首选项应保留为其他数据,并应视为显示选项。(例如信息亭A,中央时区,军事时间格式或用户B首选项,特拉维夫标准时间(GMT + 7:00)时区,等等。)
  • 避免FP编号

1
这些都不是“不可避免的”。对于许多应用程序可能很常见,但是大型强子对撞机处理纳秒级的事件。系统时间已移至微秒,因为毫秒的精度不足。日历应用程序需要具有在本地日历日发生的日期的概念,无论您是否与创建活动的时区相同。
艾伦·舒特科

1
@AlanShutko是的,需要的“附加数据”是诸如本地时区和天数之类的东西,以及必须捕获的任何其他重要数据。即使时间点对您的算法并不重要,但这些都只是一个时间点的抽象。就纳秒甚至微秒级而言,我的答案更多地面向Web和LOB类型的软件。我怀疑选择的LHC语言是C#还是Javascript。
maple_shaft

1
毫秒非常适合可重复的物理过程。对于人类活动,有时适当的单位是标称天数,例如,如果在当天结束时装运,则三天后可以在目的地使用。
凯文·克莱恩

过去,我曾考虑将64位浮点数用于时间坐标,更重要的是用于时间跨度,因为它们使您能够以极高的精度表达微小的数量,以及在精度损失并不重要的情况下能够表达大量的数量。 。(那么,如果恐龙在6500万年前死了,该怎么办?要花几年?)那么,为什么你说应该避免它们呢?
Mike Nakis 2015年

迈克,我认为他的关注点在于FP数学不可避免地会四舍五入,因为它正在从十进制转换为二进制值。例如,当以二进制FP编号表示时,0.3是重复的十进制,因此无法准确表示。不是因为您缺少容量,而是因为您缺少精度。
Michael Blackburn

2

简而言之,因为大多数基于计算机的时间类型都集中在正确处理时间和时区问题上。

通常的方法不能很好地解决2种边缘情况。使用当地时间设置夏令时另一侧的时间点,然后通过较低的抽象层将其转换为UTC,然后使您提前1小时/晚1小时参加会议。

另一个是(根据问题)对任意日期信息进行建模,例如记录一个人的出生日期。假设有两个人同时出生,一个人在新西兰,另一个在夏威夷。他们的护照上可能有不同的出生日期,并且如果在夏威夷出生的人移居新西兰,尽管他们确实居住了相同的时间,但他们会被认为比新西兰出生的人大一天。

问题中的建议是将日期设置为中午,UTC将正常运行,几乎在任何地方都有效。UTC偏移量的范围是-12至+14,因此在太平洋中有一些地方这种方法会失败。我倾向于将这些数据类型视为yyyymmdd格式的字符串,如果我必须在两个日期之间进行比较计算,则可以安全地将其作为字符串比较来完成。在进行增量比较时(例如,在日期和现在之间,或者直到它们达到X年龄需要多长时间),您需要确保所有日期都在相同的UTC偏移量中创建,然后可以使用标准时间函数进行工作。


3
我倾向于将这些数据类型视为yyyymmdd格式的字符串,如果我必须在两个日期之间进行比较计算,则可以安全地将其作为字符串比较来完成。 ”确保听起来像是Date数据类型,只是随身携带aString
罗斯·帕特森

就像您说的那样,它是日期的字符串表示形式,并且与原始发布者不同,该日期不会因为处于UTC + 13时区而被更改。
Michael Shaw 2015年

2

为什么没有真正的“仅日期”数据类型?

基于相同的原因,我认为,DateTime值通常在UTC中指定:简单性可靠性。DateTime值的点是指定不受时区,夏令时,日历和其他本地调整影响的单个时间点。DateTime值指定一个瞬间(不超过类型分辨率的极限),而不是一个时间段或一组时间。这些限制使以可靠,可预测,简单的方式比较DateTime值成为可能。

尝试使用DateTime值指定日期就像尝试使用点来指定区域。您可以通过使用约定来完成这项工作,例如“此点代表半径为100m的圆的中心”,但是那里存在很多问题:每个人都需要使用相同的约定,您需要写一些支持代码以减少使用错误类型的麻烦,并且可以保证在某些时候您需要指定一个比常规区域更大或更小的区域。日期也是如此:您可以使用“ noon”作为指定日期的常规时间,但随后进入时区,因为人们希望在当地时间而不是UTC指定日期。即使您想出一种令人满意的方式来使用DateTime来指定日期,您也将需要更多信息来了解它是绝对日期还是相对日期:是7月4日,1776年还是每年7月4日?如果您想重复使用其他时间怎么办?日历有各种各样的疯狂问题:有些月份比另一些月份长,有些年份比另一些年份长,有些日子甚至比另一些日子更长,而且有些日历也有差距。您可能不想一整天就解决这些问题,因为相同的问题会在更短的时间内出现:您可能希望能够编写表示“每4小时服用一粒药”的代码,就像“小组在每个第三个星期五开会。”

因此,在处理日期时会涉及很多复杂性。提供一个指定时间点并像处理数字一样使用它是相对容易的(没有双关语),但是要提供一个可以解决所有日期使用方式的类型则非常困难。

正如其他人指出的那样,有一些语言和库可以很好地支持日期,并且使用它们是一个好主意,因为要正确地获取与日期相关的代码非常困难。


2
“很难提供一种可解决日期使用所有方式的类型。” 没错,这就是为什么尝试提供单个类型来完成所有操作这是一个可怕的想法。最初的Java版本仅提供java.util.Date,结果令人震惊。然后他们添加了一个复杂的java.util.Calendar层次结构,并没有得到更好的改善。
凯文·克莱恩

1
@kevincline完全同意。OP没有指定语言,所以我的目标是通用性。
Caleb 2015年

出于同样的原因,我认为,DateTime值通常是在UTC中指定的。 ”“哦,那是真的。现在是2015年,仍然有很多代码使用作者的时区编写:-(
Ross Patterson

1
我确实想知道为什么没有围绕日期的普遍接受的约定。您的“用点代表区域”完美地表达了我的挫败感。
Michael Blackburn 2015年

2

为什么没有真正的“仅日期”数据类型?

在各种库中,针对各种语言的类型很多。您的当前语言几乎可以肯定有一种。Java util程序包具有用于计算时间的可怕API,但是java.time包的引入使生活变得更好。请参阅java.time.LocalDate,其中包含年月日值;或者java.time.MonthDay,仅包含月和日数。


1

日历处理是计算中最难理解的方面之一。 整本书都写了关于这个主题的书。@MichealBlackburn要求使用仅日期的数据类型是绝对正确的,该类型不能解析到时间轴上的某个点,需要重新解释。历史上,关于日期的含义存在合理的争议。除了采用公历之外,人们别无所求。此外,即使在西欧及其殖民地,年份也不总是始于1月1日(例如,英国和英属美洲始于3月25日)。


1
这是正确的。我在答案中引用的日历常见问题解答是一个极好的资源,可以发现秘密操作的复杂性和微妙之处
Michael Le

-2

回答:

好吧,我很想知道为什么几乎每种语言都只有一种数据类型。

最常见的原因可能是“因为没有必要”。如果您想要的日期时间不关心小时,分钟,秒等,那么您可以像这样初始化它:

date = new DateTime(year, month, day, 0, 0, 0);

如果需要,可以自己扩展DateTime:

public class Date extends DateTime {
    ...
    public Date(int year, int month, int day) {
        this(year, month, day, 0, 0, 0);
    }
}

注意:我故意忽略默认时间和时区。只要它们对所有Dates 都是相同的,将它们设置为什么并不重要。您可以为UTC辩护。您可以提出一个理由来使用服务器所在的时区,无论哪种方式,我都不认为代表这样的不精确值很重要Date。与默认时间相同-您可以将其设置为0,也可以将其设置为中午。没关系 如果Facebook在00:01向我发送生日通知,但我在23:59出生,则我不在乎,也不会因为他们的休息时间超过12小时而受到冒犯。

上面的代码是用Java编写的,但是在具有DateTime和继承的任何语言中都可以类似地工作。Java实际上有多种方法可以解决此问题,不推荐使用以上方法(现在希望您使用Calendar)。但是,正如其他人在评论中发布的那样,某些语言实际上确实提供了一个Date类,大概就是因为这个原因。

很有可能,每种Date实现都可能只是该语言的包装,DateTime并且使时间归零。否则,您将需要重复的代码来解决诸如两个Date / DateTimes之间的天数,或者两个Dates是否相等的问题(2月29日和3月1日如何?)。这些问题通常在DateTime课堂上解决。为重复使用相同的代码是有意义的Date


4
-1:如果只需要Year + month + day,请使用LocalDate。为此目的使用DateTime会导致一些其他程序员看到DateTime并假定它代表某个时间时会产生错误。
凯文·克莱恩

@kevincline我认为这不是必需的,因为该问题与语言无关。我还认为,使用标记为“已弃用”的代码显然是一种自行使用的场景。更重要的是,我只用了,作为东西的例子,你可以在一个场景中做其中一门语言具有LocalDateDate类型的类,你必须“滚你自己”。
Shaz 2015年

1
但是对于涉及时间(而不是物理时间)的业务规则的清晰,简洁,正确的代码,“有必要”。如果该库未提供标称时间的类型,那么程序员将有必要发明它们,而这样做的方式并非以“ DateTime”开头。
凯文·克莱恩

@kevincline我看不出代码的清晰程度如何。从程序员的角度来看,new Date(2000, 1, 1);和之间有什么区别new Date(2000, 1, 1);?您能分辨出哪个人的小时,分​​钟和秒在下面吗?如果您担心会出现额外的功能(例如setMinutes()),则可以采用自动Date换行DateTime而不是继承的方式,然后只公开setYear,setMonth,setDay等。比您使用的库的DateTime更正确。
沙兹(Shaz)2015年

我的背景主要是C#,没有类似于LocalDate的构造。显然,我们只是DateTime并弄混了。
Michael Blackburn 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.