热门标签 | 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



推荐阅读
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 简化报表生成:EasyReport工具的全面解析
    本文详细介绍了EasyReport,一个易于使用的开源Web报表工具。该工具支持Hadoop、HBase及多种关系型数据库,能够将SQL查询结果转换为HTML表格,并提供Excel导出、图表显示和表头冻结等功能。 ... [详细]
  • 深入理解Vue.js:从入门到精通
    本文详细介绍了Vue.js的基础知识、安装方法、核心概念及实战案例,帮助开发者全面掌握这一流行的前端框架。 ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • ABBYY FineReader:高效PDF转换、精准OCR识别与文档对比工具
    在处理PDF转换和OCR识别时,您是否遇到过格式混乱、识别率低或图表无法正常识别的问题?ABBYY FineReader以其强大的功能和高精度的识别技术,完美解决这些问题,帮助您轻松找到最终版文档。 ... [详细]
  • 使用PHP实现网站访客计数器的完整指南
    本文详细介绍了如何利用PHP构建一个简易的网站访客统计系统。通过具体的代码示例和详细的解释,帮助开发者理解和实现这一功能,适用于初学者和有一定经验的开发人员。 ... [详细]
  • 本文详细介绍了 Java 中 org.geotools.data.shapefile.ShapefileDataStore 类的 getCurrentTypeName() 方法,并提供了多个代码示例,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 本文详细介绍了一种通过MySQL弱口令漏洞在Windows操作系统上获取SYSTEM权限的方法。该方法涉及使用自定义UDF DLL文件来执行任意命令,从而实现对远程服务器的完全控制。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 全面解析运维监控:白盒与黑盒监控及四大黄金指标
    本文深入探讨了白盒和黑盒监控的概念,以及它们在系统监控中的应用。通过详细分析基础监控和业务监控的不同采集方法,结合四个黄金指标的解读,帮助读者更好地理解和实施有效的监控策略。 ... [详细]
  • 本教程将详细介绍Python中的包、模块、类和函数的概念,探讨它们在程序中的作用及相互关系,帮助读者更好地理解Python的结构化编程。 ... [详细]
  • Linux环境下C语言实现定时向文件写入当前时间
    本文介绍如何在Linux系统中使用C语言编程,实现在每秒钟向指定文件中写入当前时间戳。通过此示例,读者可以了解基本的文件操作、时间处理以及循环控制。 ... [详细]
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社区 版权所有