Spark 3.0 对于 DATE 和 TIMESTAMP 的改进

原文链接:
https://databricks.com/blog/2020/07/22/a-comprehensive-look-at-dates-and-timestamps-in-apache-spark-3-0.html

翻译:彭慧波,FreeWheel 基础架构大数据开发工程师,Apache Spark 中文社区志愿者。


Spark是一个当下较为热门的,能同时处理结构化数据和非结构化数据的工具。Spark能够支持诸如integer, long, double, string等在内的基本数据类型,同时也支持包括DATE和TIMESTAMP在内的复杂的数据类型。这些复杂的数据类型需要开发人员花费大量的时间来理解和使用它们。本文将会深入介绍DATE和TIMESTAMP,力图使读者对其有一个深入的了解,避免在使用的过程中犯错。本文将分为以下四个部分:

  1. DATE的定义及其使用的日历,这也将包括Spark3.0对所使用日历的变化;

  2. TIMESTAMP的定义及其与时区的联系,TIMESTAMP如何通过时区偏移来描述一个具体的时间点,以及Java8和Spark3.0中所使用的新的时间API的变化;

  3. Spark中如何通过API来构建DATE和TIMESTAMP值;

  4. Spark driver收集DATE和TIMESTAMP对象的最佳实践和常见误区。

DATE和日历

对于DATE的定义非常简单,Date是由年,月,日组合而成的一个字段,比如2012年12月31日。但是,年,月,日有各自的取值范围,比如,月的取值范围必须是从1到12,日的取值范围必须根据年和月的不同可以取值为1到28/29/30/31。因此Date值代表的是真实存在的一天。

年、月、日的约束条件和取值范围可能是由许多不同的日历定义的。这些日历有不同的应用场景,有些日历只在特定的地区使用,比如农历;有些日历只在历史上使用,比如儒略历;而国际上和人们日常生活中常用的标准是公历。公历诞生于1582年,后来其纪年日期也被扩展到1582年之前。这种扩展的日历也被称作Proleptic Gregorian公历。

Proleptic Gregorian日历目前已经被pandas,R,Apache Arrow等多个数据处理框架所使用, Spark从3.0版本开始使用Proleptic Gregorian公历。在3.0之前的版本中,Spark同时使用了儒略历和普通公历,对于1582年之前的日期使用儒略历,对于1582年之后的日期使用公历。Spark对于日历的这种使用方式是调用Java 8 之前版本中java.sql.Date API造成的,在Java8及其之后的版本中java.time.LocalDate API废弃了原先使用两种日历的模式,转而使用Proleptic Gregorian公历。
当然,DATE类型并不与时区相关。

TIMESTAMP和时区

TIMESTAMP采用新的字段扩展了DATE类型:小时,分钟,秒(可能拥有小数部分)以及一个session范围内的时区。TIMESTAMP定义了地球上一个具体的时间点。比如,2012年12月31日23时59分59.123456秒,session时区是UTC+01:00。当将TimeStamp值写入非文本数据源(如Parquet)时,这些值只是没有时区信息的点(如UTC中的TimeStamp)。如果使用不同的session时区写入和读取TimeStamp值,可能会看到不同的小时/分钟/秒字段值,但它们实际上对应的是相同的时间点。

当然,小时、分钟和秒也有着各自的取值范围,小时是0-23,分钟和秒是0-59,Spark支持最高到微秒的精度,而微秒的有效范围是0到999,999微秒。

在任何时间点,我们都可以根据所在时区的不同,观察到时钟的不同显示值。同样地,一个时钟的显示值也可以根据所在时区的不同代表许多不同的时间点。时区偏移允许明确地将当前的TimeStamp值绑定到一个具体的时间点。时区偏移被定义为格林尼治标准时间(GMT)或协调世界时(UTC+0)的小时偏移。这样的时区信息表示消除了歧义,但对普通用户来说却是比较不方便。用户更喜欢指出全球的某个具体位置,譬如美国/洛杉矶或欧洲/巴黎等。

如果用具体位置来代替具体的时区偏移信息并与TimeStamp进行绑定的话,将会带来一些额外的问题。譬如,我们必须维护一个特殊的时区数据库,以将时区名称映射到具体的偏移量。由于Spark是运行在JVM上的,因此它将时区到具体偏移量的映射委托给了Java标准库,该库从Internet分配号码授权机构的时区数据库(IANA TZDB)加载数据。此外,Java标准库中的映射机制在某些方面会影响Spark的行为。我们在下面重点介绍其中存在的一些问题。

从Java 8开始,JDK公布了用于操作DATE和TIMESTAMP的新API,Spark从3.0版本也迁移到这些的API中。尽管Java 8和Java7对于时区名称到偏移量的映射使用了相同的源数据库IANA TZDB,但是二者实现的方式还是有所不同的。

举例来说,让我们看一下1883年之前的美国/洛杉矶时区的一个TimeStamp:1883-11-10 00:00:00。这个时间点之所以与众不同是因为在1883年11月18日当天,所有北美铁路都切换到了一个新的标准时间系统来管理其时间表。使用Java 7的时间API,我们可以获得本地TimeStamp为-08:00的时区偏移量:

scala> java.time.ZoneId.systemDefault
res0: java.time.ZoneId = America/Los_Angeles
scala> java.sql.Timestamp.valueOf("1883-11-10 00:00:00").getTimezoneOffset / 60.0
res1: Double = 8.0

Java 8 API却返回了不同的结果:

scala> java.time.ZoneId.of("America/Los_Angeles")
.getRules.getOffset(java.time.LocalDateTime.parse("1883-11-10T00:00:00"))
res2: java.time.ZoneOffset = -07:52:58

在1883年11月18日之前,时间并不是全球统一的,大多数城镇都各自使用某种形式的本地统一时间,该时间由著名的时钟维护(例如,在教堂的尖顶上或在珠宝商的窗户中) 。这就是为什么我们会看到如此奇怪的时区偏移。

该示例说明了Java 8时间函数更加精确,并考虑了IANA TZDB的历史数据。切换到Java 8时间API后,Spark 3.0也从这一改进中受益,并在解决时区偏移方面变得更加精确。

正如我们前面提到的,Spark 3.0也将DATE切换为Proleptic Gregorian日历。TIMESTAMP也是如此。Spark 3.0完全符合 ISO SQL:2016提出的标准,TIMESTAMP的有效范围在0001-01-01 00:00:00到9999-12-31 23:59:59.999999之间。并支持该范围内的所有TimeStamp。而Spark 2.4和更早版本却存在以下几个问题:

  1. 0001-01-01 00:00:00至1582-10-03 23:59:59.999999这一范围内的时间, Spark 2.4使用儒略历,不符合ISO SQL:2016的标准。Spark 3.0则使用了Proleptic Gregorian公历来获取年,月,日等信息。由于采用日历不同,Spark 2.4中存在某些Spark3.0中不存在的日期,譬如,1000-02-29不是有效日期,因为公历中的1000年不是一个闰年。同时,Spark 2.4在这TimeStamp范围内也将时区名称解析到了错误的时区偏移上。

    <
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值