日期/时间的国际化,不仅涉及到地理位置(Locale,比如星期、月份等日历本地化表示),还涉及到时区(TimeZone,针对UTC/GMT的偏移量)。时区不仅是地理位置规定,更是政治规定,比如中国从地理位置上跨5个时区,但只使用一个统一时区(id=Shanghai/Asia)。

用户locale/timezone的获取

猜测:根据IP、HTTP Header(Accept-Language),js脚本等方式猜测,不准确。

客户请求参数。有一个入口让用户选择locale/timezone,同时也可让用户选择date/time format偏好。常见于后台管理系统。

用户locale/timezone的存储

cookie

用户profile管理系统

客户端输出

优先使用用户确认的locale/timezone/format。否则使用应用系统默认,必须显示时区,比如amazon的限时销售显示为PST时区。

客户端输入

相对时间:比如一周内,1天前等。直接转换为应用系统相对时间。

绝对时间:允许客户端输入具体日期/时间的系统,有用户确认timezone的,需转换为应用系统默认timezone再处理。比如:

DateFormat df = new SimpleDateFormat(pattern, userLocale);

df.setTimeZone(userTimeZone);

Date userInputDate = df.parse(inputDate);

服务系统时区

同一服务系统内所有主机的操作系统、数据库、JVM,原则上应该使用相同时区。

系统交互

同一服务系统跨时区服务的,日期/时间数据必须带有时区信息。服务系统之间交换日期/时间数据的,必须带有时区信息。

操作系统时区设定

同一服务系统内,数据库服务器按照其服务的地理位置和时区设置,应用服务器参照数据库服务器设置。数据库软件系统和JVM默认时区不做调整,均采用主机操作系统时区。

所有主机开启NTP,应用服务器向数据库服务器请求时间同步。

数据库日期/时间字段类型存储

mysql(5.5)

字段类型

字节

精度

范围

说明

date

3

'1000-01-01' to '9999-12-31'

日期

datetime

8

'1000-01-01 00:00:00' to '9999-12-31 23:59:59'

日期和时间混合

timestamp

4

'1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.

日期和时间混合,转换为UTC时区的时间后存储;insert/update时,由数据库自动更新。

year

1

1901 to 2155, or 0000

年份

time

3

'-838:59:59' to '838:59:59'

时间值或持续时间

说明:

datetime/timestamp可接受微妙级的时间,但是存储时只保留到秒级别。需要存储毫秒级别的,可以使用bigint类型字段,在应用程序级别进行long型的epoch毫秒和Date类型转换。

只有timestamp类型在存储时,会将值从数据库时区转换成UTC时区存储;检索时,从UTC时区转换为数据库时区。中途改变数据库时区,timestamp类型的值将不会正确转换。

timestamp使用条件限制:一个表最多只有一个timestamp类型字段会被正确处理。

oracle(10g)

字段类型

字节

精度

范围

说明

date

7

-4712-01-01 00:00:00 to 9999-12-31 23:59:59

日期和时间混合

timestamp(n)

7-11

秒~纳秒

秒范围同上

日期和时间混合,小数秒位数(0-9,相当于秒-纳秒)可设置

timestamp WITH TIME ZONE

9-13

秒~纳秒

秒范围同上

日期和时间混合,同时存储时区。若输入不指定时区,则使用数据库时区

timestamp WITH LOCAL TIME ZONE

7-11

秒~纳秒

秒范围同上

转换为数据库时区后存储,不存储时区

interval year to month

5

N/A

N/A

存储年或月指定的时间段

interval day to second

11

N/A

N/A

存储天,小时,分钟,秒指定的时间段

说明:

timestamp WITH LOCAL TIME ZONE不存储时区,因此不能中途改变数据库时区。

sqlserver(2008)

字段类型

字节

精度

范围

说明

smalldatetime

4

1900-01-1 00:00:00 to 2079-06-06 23:59:00

日期和时间混合

datetime

8

3.33毫秒

1753-01-01 00:00:00.000 to 9999-12-31 23:59:59.998

日期和时间混合

datetime2(n)

6-8

100ns

0001-01-01 00:00:00.0000000 to 9999-12-31 23:59:59.9999999

日期和时间混合。n为小数秒精度,取值范围为0-7,表示1秒-100ns

date

3

001-01-01 to 9999-12-31

日期

time(n)

3-5

100ns

00:00:00.0000000 to 23:59:59.9999999

时间。n为小数秒精度,取值范围为0-7,表示1秒-100ns

datetimeoffset

8-10

100ns

0001-01-01 00:00:00.0000000 to 9999-12-31 23:59:59.9999999

日期和时间混合。n为小数秒精度,取值范围为0-7,表示1秒-100ns。

数据库时区与UTC的时区偏移量(精确到分钟)被存储。

小结

数据库系统时区默认取自操作系统的时区设置。因此必须正确设定操作系统的时区。

字段中存储时区信息的只有sqlserver2008(datetimeoffset)和oracle(timestamp WITH TIME ZONE),可以安全的跨时区进行数据库级别导出导入/复制。当然相应的存储空间也会加大。

mysql和oracle均有一种字段类型支持存储前进行时区转换。数据库时区一旦设定,均不应该更改。

大多数日期/时间类型字段都不进行时区转换存储。因此应用程序应该使用与数据库一致的时区。相对使用数据库sysdate(),now()等时间函数而言,优先使用应用程序传入时间。

对于精确到秒的日期/时间类型字段,不应该作为乐观锁和版本管理用途,而应优先使用int4类型。

JDK6日期时间处理相关类

java.util.Date的fastTime域和Calendar的time域,存储特定的瞬间,精确到毫秒。初始值是系统时间距1970-01-01 00:00:00.000 UTC(epoch time)以来的毫秒数。因此可以认为这两个类本身是带有时区信息的。

Calendar通过改变timezone,将时间在不同时区转换表示。相对java.util.Date,Calendar提供了人可读的日历字段:年月日小时星期等,还提供了通过修改这些日历字段来改变时间值的方法。

TimeZone主要提供距UTC时区的偏移毫秒数、夏令时规定。

DateFormat及其子类,用来格式化calendar域存储的时间,或将字符串表示解析成java.util.Date。pattern格式化字串和locale属性提供更灵活的本地化表示能力。也可通过设置timezone,格式化成不同时区的时间表示。

通过-Duser.language、-Duser.country、-Duser.timezone设定与操作系统不一致的地理位置和时区,不推荐。

小结

java.util.Date是目前JDK中存储时间的标准。Calendar提供了获取和修改时间值的方便方式。

Locale和TimeZone是日期/时间的国际化处理的核心,分别起着不同的作用。

Calendar默认为宽松模式(lenient=true),使用DateFormat解析字串时,可设置为严格模式,避免将"2012-01-32"解析成2012-02-01。

除Locale是线程安全的不可变类外,其他都是可变类,非线程安全。

JDBC

jdbc使用java.util.Date的三个子类(见上图),负责与数据库系统交互。java.sql.Date只包含日期,java.sql.Time只包含时间,java.sql.Timestamp包含日期和时间混合,精确到纳秒。java type, jdbc sql type和数据库字段类型对应关系如下:

java.sql

java.sql.Types

mysql

sqlserver

oracle

Date

DATE

date

date

date

Time

TIME

time

time

date

Timestamp

TIMESTAMP

datetime

timestamp

datetime

datetime2

datetimeoffset

date

timestamp [with ....]

String

VARCHAR

timestamp WITH TIME ZONE

timestamp WITH LOCAL TIME ZONE:

当检索列时,返回给用户的值被转换成 TIME_ZONE 会话参数指定的时区(JVM报告的当前时区:TimeZone.getDefault(),来自-Duser.timezone的设定或主机操作系统时区,或设置TimeZone.setDefault(timezone))。

当设置列时(PreparedStatement.setTimestamp):设置的值将被转换成 TIME_ZONE 会话参数指定的时区。

对于timestamp WITH TIME ZONE来说,默认映射为JDBC VARCHAR,时区信息将以字符串返回。

jdbc规范中的ps.setXXX(index, datetime, calendar)和ps.getXXX(index, calendar)不是所有的jdbc驱动都正确实现。见:http://wenku.baidu.com/view/6a06501ffc4ffe473368ab6b.html

小结

java应用程序应该始终使用java.util.Date作为领域对象的日期/时间属性类型。

大部分的数据库日期/时间字段不保留时区信息,传递给数据库的值依赖于JVM时区。

注意jdbc sql type和数据库字段类型的映射关系。

JVM时区尽量保持与数据库系统时区一致。当应用系统JVM与数据库时区不同时,读写时需要参照数据库时区转换。这个工作应该由框架来完成,对开发透明。

mybatis

mybatis关于日期/时间处理的地方,主要是使用TypeHandler:

register(Date.class, new DateTypeHandler());

register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());

register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());

register(JdbcType.TIMESTAMP, new DateTypeHandler());

register(JdbcType.DATE, new DateOnlyTypeHandler());

register(JdbcType.TIME, new TimeOnlyTypeHandler());

register(java.sql.Date.class, new SqlDateTypeHandler());

register(java.sql.Time.class, new SqlTimeTypeHandler());

register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

各个TypeHandler主要处理java.util.Date类型和jdbc sql type的映射关系和转换,屏蔽java.sql.Date/Time/Timestamp的差异。比如:

select ...

from ...

where ...

and createTime>= #{start,jdbcType=TIMESTAMP} and createTime

]]>

小结

考虑到数据库日期/时间字段的精度问题,按时间段查询时,start(含)、end(不含)应该剔除时间信息,只保留到日期。

可以编写具备时区转换的,或者将Date映射成int8的TypeHandler,覆盖mybatis的默认处理器。

MVC框架

struts2/spring3 mvc针对日期/时间处理和国际化方面,主要涉及到拦截器设置locale上下文环境、日期/时间字符串输入解析、验证、国际化显示这几个方面。

设置locale context

struts2:I18nInterceptor。支持从请求参数、session中获取用户设置的locale。

spring3 mvc:LocaleResolver的几个实现。支持从请求参数、session、http header中获取。

两者均将获取到的locale暴露到request scope context中,供解析、国际化使用。两者均缺乏TimeZone相关的拦截器。如果单点登录系统增加了用户profile管理,则还可以增加基于profile的拦截器实现。

输入解析

两个框架都利用DateFormat的parse方法进行解析,支持locale/timezone转换。

struts2:Converter接口和XWorkBasicConverter,将字符串按照一些上下文locale相关的规范格式、rfc3399等进行fallback解析,使用严格模式(lenient=false)。当用户输入不符合这些规范格式时,将出现错误。

spring3 mvc:spring3新的Converter框架众多转换器中,唯独缺少日期/时间转换相关实现,而是迫使开发者在DateBinder中注册自定义的CustomDateEditor(提供特定的DateFormat,设置宽松模式或严格模式)。

在无法获知用户locale/timezone的情况下,或者用户输入格式随意,通过猜测来解析用户的时间含义,是不够准确的。spring框架把这个决定权交给开发者,很明智。因此,在涉及到用户输入时,必须明确规范用户的输入格式、协商用户时区。

验证

在输入解析时进行了日期/时间格式合法性验证后,两者的验证框架,均有针对日期/时间的可否为空、范围验证。

国际化显示

均可以在两者支持的模板系统中添加相关的宏或tag。实际格式化由暴露在view context中的DateFormat帮助类,根据request scope context中的locale/timezone或缺省设置来处理。

小结

两种框架针对日期/时间的输入、输出都有成熟和规范的解决方案,正确使用即可,缺乏的功能可扩展。

必须明确用户的输入格式,使用严格模式解析。协商用户时区。

xml/json针对日期/时间的处理

xml/json也被MVC框架用来作为视图层。更多的时候,用作两个系统进行信息交互(比如webservice和rest)的一种中间格式。

XML

JAXB规范中的日期/时间格式,遵循XML中的日期/时间规范(ISO8601),JDK内部实现中格式如下:

日期格式:2012-03-08+08:00

时间格式:22:18:13.453+08:00

日期时间格式:2012-03-08T22:18:13.453+08:00

JSON

JSON规范并没有定义如何序列化日期时间。json框架主要处理方式如下:

long型:自1970-01-01 00:00:00以来的毫秒数。java和javascript中的Date类型内部都这样表示时间。jackson json框架考虑到性能,默认以这种方式序列化日期类型。

String型:常见的是ISO8601标准的表示,如上。

小结

遵循标准规范。在系统交互时,必须传递时区信息;在无法确定用户时区时,显示时必须带有时区信息。

总结

规范

所有主机开启NTP。同一服务系统,原则上使用同一时区。数据库系统、JVM,使用主机默认时区。

如果应用系统与数据库系统时区不一致,读写时应参照数据库时区转换。

系统间交互,日期/时间信息必须带有时区信息。

能确定用户locale/timezone的,使用用户时区进行显示。否则必须显示应用系统的时区。

有用户输入的,必须明确规范用户输入格式,使用严格模式。

格式化字串以常量定义,避免typo。

理解并遵循各层规范标准和成熟实现(JDBC/MVC/XML/JSON等)。

公共类库开发原则

不重复造轮子,尽量使用广泛成熟的工具类,比如apache.commons.lang,jodatime等,最多加一个门面。

尽量使用mybatis/mvc等框架的标准实现,整理最佳实践文档;适当扩展mybatis/mvc框架中部分实现。

mysql 时间国际化_日期时间处理和国际化相关相关推荐

  1. calendar类计算时间距离_日期时间--JAVA成长之路

    Java中为处理日期和时间提供了大量的API,确实有把一件简单的事情搞复杂的嫌疑,各种类:Date Time Timestamp Calendar...,但是如果能够看到时间处理的本质就可以轻松hol ...

  2. 复习Object类_日期时间类_System类_StringBuilder_包装类以及各类的细节

    Object类_日期时间类_System类_StringBuilder_包装类以及各类的细节 主要内容 Object类 Date类 DateFormat类 Calendar类 System类 Stri ...

  3. 将UTC日期时间转换为本地日期时间

    本文翻译自:Convert UTC date time to local date time From the server I get a datetime variable in this for ...

  4. vue 日期时间选择器_Vue日期时间选择器

    vue 日期时间选择器 Vue日期时间选择器 (Vue Datetime picker) Vue Datetime picker BY Vladyslav Shchepotin. Vue日期时间选择器 ...

  5. Matlab:提取或分配日期时间数组的日期时间分量

    Matlab:提取或分配日期时间数组的日期时间分量 访问属性以检索日期时间分量 使用函数检索日期时间分量 获取多个日期时间分量 修改日期时间分量 此示例演示了从现有日期时间数组中提取日期时间分量的两种 ...

  6. 软件工程方法学要素含义_日期时间数据的要素工程

    软件工程方法学要素含义 According to Wikipedia, feature engineering refers to the process of using domain knowle ...

  7. java 解析日期格式_日期/时间格式/解析,Java 8样式

    java 解析日期格式 自Java 几乎 开始以来,Java开发人员就通过java.util.Date类(自JDK 1.0起)和java.util.Calendar类(自JDK 1.1起 )来处理日期 ...

  8. 【数据库】Mysql函数DATE_FORMAT() 显示日期/时间

    上一篇文章介绍了Mysql函数DATE_ADD(),这篇文章主要是为了解决上次遗留的问题: 把问题变得更复杂一点:假设有两个借款,其中一个借款1的到期还款日为2日,借款2的到期还款日为10日.这个字段 ...

  9. MySQL 计算两个日期/时间之间相差的天数、分钟数、秒数...

    MySQL 中经常遇到计算两个日期或者时间之间相差的天数.周数.小时数.分钟.秒等等,下面分享一个MySQL内置的函数:TimeStampDiff() 这个函数是MySQL本身提供的可以计算两个时间间 ...

最新文章

  1. Java中 与,||与|的区别
  2. 通过案例学调优之--AWR baseline对比生成AWR报告
  3. JavaScript之Unspecified error或无法设置selected属性。未指明的错误。解决方案
  4. Exchange-OWA与域控集成-实现单点登录
  5. Firefox/Chrome/Safari的中可直接使用$/$$函数进行调试
  6. 【机器视觉学习笔记】伽马变换(C++)
  7. git log 你学废了吗?
  8. ASP面向对象编程探讨及比较
  9. 马斯克脑机接口_如何看待“马斯克:脑机接口或一年内植入人脑,可修复任何大脑问题”?...
  10. C++ STL 函数partial_sum的正确使用方法
  11. C#流处理文件 文件读写常用类
  12. <自由之路>LeetCode每日一题(DFS + 记忆化搜索)
  13. django+djangorestframework开发代码编辑器
  14. MFC 时钟 计算器 日期天数计算
  15. tinode客户端安卓版编译手账
  16. 呼叫中心静态座席的配置
  17. 复盘 20160629
  18. python list是不可变的数据类型吗_python中不可变数据类型和可变数据类型
  19. TensorFlow 高性能数据输入管道设计指南
  20. 【iOS开发】iOS系统更新到10.3以上无法下载企业应用

热门文章

  1. VirtualBox虚拟机与主机互通,并且虚拟机又能上网
  2. linux日志pdf下载,Linux系统下改进的日志采集模块.pdf
  3. SPN用三个队列C语言,SPN-DES-AES算法原理与应用
  4. 陷波超宽带天线设计学习
  5. NR, 8个问题全面了解5G关键技术Massive MIMO
  6. 二代测序之SNV检测总结笔记
  7. 读书笔记——《深入理解计算机系统》第三章_程序的机器级表示(一)
  8. C++初阶 —— 缺省参数
  9. 网上订票系统数据库c语言实现,基于c 的火车票订票管理系统的设计与实现.docx...
  10. laravel-debugbar 不显示解决办法