不同编程语言的库如何处理日期时间、时间戳和持续时间、闰秒和年份、夏令时和时区等?
有没有一个标准的机构或者具体的规范,说明时间相关的东西应该怎么在实际中实现(就像ICU处理Unicode相关任务那样)?还是说现在这方面的实现都是“尽力而为”,主要看语言和库的开发者愿意花多少时间、精力和钱?
有没有一个具体而完整的实现,可以作为时间相关内容处理的示例?
你认为现有的哪些库是糟糕的、还不错的或者好的例子?
6 个回答
时间和日期(日历)是两回事
第一个问题是,日期并不是和时间直接相关,而是和地球、月亮等天体的位置,以及人类活动的规律性有关。时间也是主观的、相对的,甚至是相对论的,可以通过天文或原子来测量。
时间和日期/日历的标准
国际标准化组织(ISO) [4] 发布了
- “ISO 8601 数据元素和交换格式——信息交换——日期和时间的表示” [4a]
这个标准像其他国际标准一样,是一种推荐,基于已经建立的实践。
它(主观上)仅基于公历 [5] 和前瞻性公历(向前推算到它实际被发明之前,所以在处理历史日期时用途有限) [5a]。
世界日历协会 [1d] 从2012年开始推动新的世界日历的引入 [1b-1d],这将使现有的日期库变得没用。同样,主要问题还是在于后面会提到的。
我见过的最全面的与日期时间相关的IT系统比较是 [2],涉及BIG8数据库管理系统(IBM DB2、Informix、Ingres、InterBase、Microsoft SQL Server、MySQL、Oracle和Sybase)。
这些调查显示,即使是相同的公历时间/日期,在所有系统之间以及同一平台内(不同产品和版本之间)的处理方式也不同,见例如 [3]。
所有日期/时间库的主要问题在于所有系统和框架中,它们的日期/时间数据类型不允许包含地理和日历信息。
没有这些信息,它们主要是半无用的——在SQL Server的datetime2值中,毫秒有什么意义呢?在7世纪的时候,甚至没有时钟能精确到分钟(例如,伽利略在实验中用心跳来测量时间间隔),而且公历还没有被发明。
因此,很多日期时间类型的空间被误用,未能提供与日期相关的最重要的灵活性,即将地理和/或日历信息与日期关联起来。
简单举几个例子:
- 现代俄罗斯使用公历,而俄罗斯东正教使用儒略历,因此俄罗斯的许多国家假期是根据儒略历来确定的(例如,俄罗斯的圣诞节在1月7日,旧新年在1月14日,而其他宗教假期的日期则相对于公历是浮动的)。
- 在1917年之前,作为波兰一部分的俄罗斯使用公历,而其他地区的俄罗斯使用儒略历(在“同一”时区之间有13到18天的浮动差异) [5b];
- 在MS Windows中双击时钟(或打开控制面板 --> 日期和时间) --> 时区选项卡 --> 在下拉框中查看时区。你会看到从GMT-12:00到GMT+13:00之间有25个小时,超过一百个时区,还有像GMT+5:00、GMT+5:30、GMT+5:45等带小数的时区。
==== 引用:
[1] 新世界日历
[1a]更新:抱歉,不要阅读[1a],作者混淆了日历,写了错误的信息
世界日历2012:每个月35天
http://www.panorama.am/en/society/2010/01/29/newcalendar
[1b] http://en.wikipedia.org/wiki/World_Calendar
[1c] http://www.theworldcalendarin2012.org/Index2.htm
[1d] http://www.theworldcalendar.org/TWCA.htm
[2] Peter Gulutzan, Trudy Pelzer. SQL性能调优:SQL中的日期
http://www.informit.com/articles/printerfriendly.aspx?p=30939
[3] SqlDateTime.MinValue != C# DateTime.MinValue,为什么?
SqlDateTime.MinValue != DateTime.MinValue,为什么?
[4]
国际标准化组织
http://en.wikipedia.org/wiki/International_Organization_for_Standardization
[4a] ISO 8601 数据元素和交换格式——信息交换——日期和时间的表示
http://en.wikipedia.org/wiki/ISO_8601
[5]
公历
http://en.wikipedia.org/wiki/Gregorian_calendar
[5a] 前瞻性公历
http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar
[5b] 公历采用
http://en.wikipedia.org/wiki/Gregorian_calendar#Adoption
[6]
http://en.wikipedia.org/wiki/Galileo_Galilei
我觉得现在没有一个统一的标准来处理这些事情,不过有几个标准可以参考,比如ISO 8601。
ICU是一个跨语言(C/C++和Java)和多平台的日期/时间处理库。
它内部处理日期和时间,通常使用UDate(C/C++)或java.util.Date/long(Java),这些都是从1970年1月1日开始的毫秒数,或者使用特定日历类型的Calendar对象(比如公历和伊斯兰历等)。它还提供了持续时间的格式化功能。闰年会根据日历系统来计算,而闰秒则由底层操作系统来处理。夏令时和时区的数据会通过一个叫“tz数据库”的东西保持更新,这个数据库有时也用它作者的姓氏Olson来称呼。
希望这些信息能对你了解ICU有所帮助。
我会尝试用可能会成为Java 7一部分的Java库来回答第二和第三个问题。
javax.time.* (JSR 310)
这些类是对JodaTime的全面重写,旨在修复util.Date
和util.Time
以及JodaTime的设计缺陷。
JSR 310试图提供一个全面的日期和时间模型,这个模型是类型安全的,并且自我说明。它可以与现有的类互操作,同时也考虑了基于XML和数据库的使用场景。类是最终的、不可变的、线程安全的,构造后不能修改。实例是通过一系列丰富的工厂方法创建的,这些方法可以在后台缓存数据。
LocalDate dateToday = LocalDate.of(2010, 9, 14);
LocalDate oneMonthLater = dateToday.with(OCTOBER);
LocalDate oneYearLater = dateToday.withYear(2011);
这个API有一些“机器导向”的类和一些“人类导向”的类:
机器导向
Instant
表示一个时间点,可以与Unix或Java时间戳相比较。实际上有Instant
、TAIInstant
和UTCInstant
,让人们可以精确选择他们需要的时间定义,比如“基于天的”、“线性的,没有闰秒”等等。
Duration
表示一个时间范围,不一定与特定的日期或日历相关联。
人类导向
有丰富的类来处理不同的使用场景,比如仅日期、仅时间、日期和时间,带时区和不带时区,带夏令时和不带夏令时。
DateProvider
OffsetDate
、LocalDate
(兼容java.sql.Date
)
TimeProvider
OffsetTime
、LocalTime
(兼容java.sql.Time
)
DateTimeProvider
ZonedDateTime
、OffsetDateTime
、LocalDateTime
(兼容java.util.GregorianCalendar
)
InstantProvider
Instant
、ZonedDateTime
、OffsetDateTime
(兼容java.util.Date
)
Period
表示一个时间段,比如“5天”,可以加到或减去某个日期/时间。
Matcher
可以用来查询,比如“这个日期是在2006年吗?”或者“这一天是这一年的最后一天吗?”
Adjuster
如果你想做更复杂的修改,比如“给我这个月的最后一天!”或者“圣诞节后的第二个星期二,请!”时,调整器就派上用场了。
Resolver
解析器允许用户定义如果某个日期无效时应该发生什么,比如2010年2月31日:
DateResolver previous = DateResolvers.previousValid();
LocalDate date = date(2010, 2, 30, previous);
// date = 2010-02-28
处理时区和夏令时数据
可以序列化这些类,并使用当前时区数据或它们被序列化时的时区数据进行反序列化。此外,可以比较不同时区的规则:可以找出夏令时规则是否发生了变化,比如在伦敦或莫斯科的2010e和2010f版本之间,并决定如果时间在间隙或重叠中应该怎么处理。
日历系统
虽然一切都是基于ISO-8601,但也提供了简单的希伯来历、伊斯兰历、日本历、泰国佛历等日历系统。
格式化和解析
toString()
返回ISO8601格式,支持像SimpleDateFormat
中的模式以及更高级的格式。
集成
- 数据库
- JodaTime
- 遗留的JDK类(
java.util.*
) - XML
参考资料: