阅读主题
第 1 章:时间不是一个 datetime

时间把过去、现在与未来串成一条路,却不能把不同的时间含义合并成一个值。
开发一个功能时,时间往往只是需求中的一小部分:记录付款时间,填写出生日期,设置有效期限,安排每周上课。它们出现在不同的页面和字段里,我们通常把它们统称为“时间”。
到了实现阶段,这些要求需要变成具体的类型和操作。付款时间要能够比较先后,有效期限要参与权限判断,上课安排要生成未来的日程。我们开始选择数据库字段、查找日期时间 API,也开始决定怎样解析、转换和计算这些值。
这些决定依赖于我们怎样理解原来的需求。出生日期没有说明出生的具体时刻,但对于记录生日来说,信息已经完整;“一个月后”需要从某个起点沿日历计算;“每周一上午九点”还需要明确按哪里的时间安排。有些信息应当按原样保留,有些条件需要和产品进一步约定。把它们都写进一个“时间字段”,并没有消除这些区别。
即使业务规则已经明确,实现中仍然有选择要做。某个日期时间类型能否表达我们需要的信息?库函数所采用的计算规则是否符合需求?一个值经过接口传递、数据库保存,再被读出来使用时,是否仍然保持原来的含义?这些问题会影响字段设计,也会影响后续的计算与判断。
例如,同一次付款可以在不同时区显示为不同的日期和钟点,记录的仍然应是同一次事件发生的时刻。出生日期的要求却不同:用户换个地方打开个人资料,原先填写的出生日期不应随之改变。两者都需要保存和显示,但转换时需要保持的东西并不相同。
因此,选择类型与函数以前,我们需要先说明这个值表达什么、将被用来回答什么问题。随后才能确定计算需要哪些条件、哪些信息必须保存,以及怎样验证结果。时间的含义、运算规则和技术表示,需要在设计中彼此对应。 数据库和编程语言提供了实现这些设计的工具,理解工具的能力与约定,也是这项工作的一部分。
本章会逐步建立这些区别及其关系。读者不必先记住一套时间类型的分类;我们先从一个基本的设计问题开始:产品给出了会员的最后有效日期,程序应当怎样据此判断会员是否仍然有效?
本章阅读路径
本章先从日期与截止边界入手,建立时间的语义模型;随后讨论日历运算、业务日、日程和存储;再进入到期规则、时钟来源和误差;最后用跨时区课程把这些问题串起来。日历示例采用公历,代码主要用 Java 表达,重点是类型和算法背后的含义。
需要按主题查阅时,可以从下面几个入口进入: