热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java时区处理初学者指南

基本时间观念大多数Web应用程序必须支持不同的时区,而正确处理时区绝非易事。更糟糕的是,您必须确保各种编程语言(例如,前端J

基本时间观念

大多数Web应用程序必须支持不同的时区,而正确处理时区绝非易事。 更糟糕的是,您必须确保各种编程语言(例如,前端Javascript,中间件中的Java和作为数据存储库的MongoDB)之间的时间戳是一致的。 这篇文章旨在解释绝对时间和相对时间的基本概念。

时代

纪元是绝对时间基准。 大多数编程语言(例如Java,Javascript,Python)使用Unix纪元(1970年1月1日午夜)来表示给定的时间戳,即自固定时间点引用以来经过的毫秒数。

相对数字时间戳

相对数字时间戳表示为从纪元以来经过的毫秒数。

时区

协调世界时(UTC)是最常见的时间标准。 UTC时区(相当于GMT )表示所有其他时区涉及的时间参考(通过正/负偏移量)。

UTC时区通常称为Zulu时间(Z)或UTC + 0。 日本时区为UTC + 9,而檀香山时区为UTC-10。 在Unix时代(1970年1月1日UTC时区),东京为1970年1月1日,檀香山为1969年12月31日14:00。

ISO 8601

ISO 8601是最广泛的日期/时间表示标准,它使用以下日期/时间格式:

时区 符号
世界标准时间 1970-01-01T00:00:00.000 + 00:00
UTC祖鲁时间 1970-01-01T00:00:00.000 + Z
时雄 1970-01-01T00:00:00.000 + 09:00
火奴鲁鲁 1969-12-31T14:00:00.000-10:00

Java时间基础

java.util.Date

java.util.Date绝对是最常见的时间相关类。 它表示一个固定的时间点,表示为自历元以来经过的相对毫秒数。 java.util.Date是与时区无关的 ,除了toString方法使用本地时区生成String表示形式。

java.util.Calendar

java.util.Calendar既是日期/时间工厂,也是时区感知定时实例。 它是最不友好的Java API类之一,我们可以在以下示例中进行演示:

@Test
public void testTimeZonesWithCalendar() throws ParseException {assertEquals(0L, newCalendarInstanceMillis("GMT").getTimeInMillis());assertEquals(TimeUnit.HOURS.toMillis(-9), newCalendarInstanceMillis("Japan").getTimeInMillis());assertEquals(TimeUnit.HOURS.toMillis(10), newCalendarInstanceMillis("Pacific/Honolulu").getTimeInMillis());Calendar epoch = newCalendarInstanceMillis("GMT");epoch.setTimeZone(TimeZone.getTimeZone("Japan"));assertEquals(TimeUnit.HOURS.toMillis(-9), epoch.getTimeInMillis());
}private Calendar newCalendarInstance(String timeZoneId) {Calendar calendar = new GregorianCalendar();calendar.set(Calendar.YEAR, 1970);calendar.set(Calendar.MONTH, 0);calendar.set(Calendar.DAY_OF_MONTH, 1);calendar.set(Calendar.HOUR_OF_DAY, 0);calendar.set(Calendar.MINUTE, 0);calendar.set(Calendar.SECOND, 0);calendar.set(Calendar.MILLISECOND, 0);calendar.setTimeZone(TimeZone.getTimeZone(timeZoneId));return calendar;
}

在Unix时代(UTC时区),东京时间提前了9个小时,而檀香山却落后了10个小时。

更改日历时区会在偏移时区偏移时保留实际时间。 相对时间戳随日历时区偏移量而变化。

Joda-Time和Java 8 Date Time API只是使java.util.Calandar过时,因此您不必再使用此古怪的API。

org.joda.time.DateTime

Joda-Time旨在通过提供以下服务来修复旧版Date / Time API:

  • 不变和可变的日期结构
  • 流利的API
  • 更好地支持ISO 8601标准

使用Joda-Time,这就是我们之前的测试用例的样子:

@Test
public void testTimeZonesWithDateTime() throws ParseException {assertEquals(0L, newDateTimeMillis("GMT").toDate().getTime());assertEquals(TimeUnit.HOURS.toMillis(-9), newDateTimeMillis("Japan").toDate().getTime());assertEquals(TimeUnit.HOURS.toMillis(10), newDateTimeMillis("Pacific/Honolulu").toDate().getTime());DateTime epoch = newDateTimeMillis("GMT");assertEquals("1970-01-01T00:00:00.000Z", epoch.toString());epoch = epoch.toDateTime(DateTimeZone.forID("Japan"));assertEquals(0, epoch.toDate().getTime());assertEquals("1970-01-01T09:00:00.000+09:00", epoch.toString());MutableDateTime mutableDateTime = epoch.toMutableDateTime();mutableDateTime.setChronology(ISOChronology.getInstance().withZone(DateTimeZone.forID("Japan")));assertEquals("1970-01-01T09:00:00.000+09:00", epoch.toString());
}private DateTime newDateTimeMillis(String timeZoneId) {return new DateTime(DateTimeZone.forID(timeZoneId)).withYear(1970).withMonthOfYear(1).withDayOfMonth(1).withTimeAtStartOfDay();
}

DateTime流利的API比java.util.Calendar#set易于使用。 DateTime是不可变的,但如果适合当前的用例,我们可以轻松地切换到MutableDateTime 。

与我们的Calendar测试用例相比,当更改时区时,相对时间戳不会改变,因此保留了相同的原始时间点。

只是人类的时间感知发生了变化( 1970-01-01T00:00:00.000Z1970-01-01T09:00:00.000 + 09:00指向相同的绝对时间)。

相对时间与绝对时间实例

当支持时区时,基本上有两个主要选择:相对时间戳和绝对时间信息。

相对时间戳

时间戳的数字表示形式(自纪元以来的毫秒数)是相对信息。 该值是针对UTC时代给出的,但是您仍然需要一个时区来正确表示特定区域上的实际时间。

作为一个长值,它是最紧凑的时间表示形式,是交换大量数据时的理想选择。

如果您不知道原始事件的时区,则可能会显示与当前本地时区相对的时间戳,这并不总是可取的。

绝对时间戳

绝对时间戳包含相对时间以及时区信息。 在其ISO 8601字符串表示中表示时间戳是很常见的。

与数字形式(64位长)相比,字符串表示的紧凑性较低,它最多可包含25个字符(UTF-8编码为200位)。

ISO 8601在XML文件中非常常见,因为XML模式使用的是受ISO 8601标准启发的词汇格式 。

当我们想针对原始时区重构时间实例时,绝对时间表示会更加方便。 电子邮件客户端可能希望使用发件人的时区显示电子邮件创建日期,而这只能使用绝对时间戳来实现。

谜题

以下练习旨在说明使用古老的java.text.DateFormat实用程序正确处理符合ISO 8601的日期/时间结构有多么困难。

java.text.SimpleDateFormat

首先,我们将使用以下测试逻辑来测试java.text.SimpleDateFormat解析功能:

/*** DateFormat parsing utility* @param pattern date/time pattern* @param dateTimeString date/time string value* @param expectedNumericTimestamp expected millis since epoch */
private void dateFormatParse(String pattern, String dateTimeString, long expectedNumericTimestamp) {try {Date utcDate = new SimpleDateFormat(pattern).parse(dateTimeString);if(expectedNumericTimestamp != utcDate.getTime()) {LOGGER.warn("Pattern: {}, date: {} actual epoch {} while expected epoch: {}", new Object[]{pattern, dateTimeString, utcDate.getTime(), expectedNumericTimestamp});}} catch (ParseException e) {LOGGER.warn("Pattern: {}, date: {} threw {}", new Object[]{pattern, dateTimeString, e.getClass().getSimpleName()});}
}

用例1

让我们看看各种ISO 8601模式如何针对第一个解析器表现:

dateFormatParse("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "1970-01-01T00:00:00.200Z", 200L);

产生以下结果:

Pattern: yyyy-MM-dd'T'HH:mm:ss.SSS'Z', date: 1970-01-01T00:00:00.200Z actual epoch -7199800 while expected epoch: 200

此模式不符合ISO 8601。 单引号字符是一个转义序列,因此最后的“ Z”符号不会被视为时间指令(例如Zulu时间)。 解析后,我们将仅获取本地时区的Date参考。

该测试是使用我当前的系统默认欧洲/雅典时区运行的,截至撰写本文时,它比UTC提前两个小时。

用例2

根据java.util.SimpleDateFormat文档,以下模式: yyyy-MM-dd'T'HH:mm:ss.SSSZ应该匹配ISO 8601日期/时间字符串值:

dateFormatParse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "1970-01-01T00:00:00.200Z", 200L);

但是相反,我们得到了以下异常:

Pattern: yyyy-MM-dd'T'HH:mm:ss.SSSZ, date: 1970-01-01T00:00:00.200Z threw ParseException

因此,此模式似乎无法解析Zulu时间UTC字符串值。

用例3

以下模式对于显式偏移量非常适用:

dateFormatParse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "1970-01-01T00:00:00.200+0000", 200L);

用例4

此模式还与其他时区偏移量兼容:

dateFormatParse("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "1970-01-01T00:00:00.200+0100", 200L - 1000 * 60 * 60);

用例5

为了匹配祖鲁语时间符号,我们需要使用以下模式:

dateFormatParse("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", "1970-01-01T00:00:00.200Z", 200L);

用例6

不幸的是,最后一个模式与明确的时区偏移量不兼容:

dateFormatParse("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", "1970-01-01T00:00:00.200+0000", 200L);

最后出现以下异常:

Pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX, date: 1970-01-01T00:00:00.200+0000 threw ParseException

org.joda.time.DateTime

java.text.SimpleDateFormat相反, Joda-Time与任何ISO 8601模式兼容。 以下测试用例将用于即将推出的测试用例:

/*** Joda-Time parsing utility* @param dateTimeString date/time string value* @param expectedNumericTimestamp expected millis since epoch*/
private void jodaTimeParse(String dateTimeString, long expectedNumericTimestamp) {Date utcDate = DateTime.parse(dateTimeString).toDate();if(expectedNumericTimestamp != utcDate.getTime()) {LOGGER.warn("date: {} actual epoch {} while expected epoch: {}", new Object[]{dateTimeString, utcDate.getTime(), expectedNumericTimestamp});}
}

Joda-Time与所有标准ISO 8601日期/时间格式兼容:

jodaTimeParse("1970-01-01T00:00:00.200Z", 200L);
jodaTimeParse("1970-01-01T00:00:00.200+0000", 200L);
jodaTimeParse("1970-01-01T00:00:00.200+0100", 200L - 1000 * 60 * 60);

结论

如您所见,古老的Java Date / Time实用程序不容易使用。 Joda-Time是更好的选择,提供更好的时间处理功能。

如果您碰巧使用Java 8,则值得切换到Java 8 Date / Time API ,该API是从头开始设计的,但深受Joda-Time启发 。

  • 代码可在GitHub上获得 。

翻译自: https://www.javacodegeeks.com/2014/11/a-beginners-guide-to-java-time-zone-handling.html



推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
author-avatar
我就是人家
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有