处理Web应用程序中的时区

在我们的网络应用程序中,我们需要显示和输入不同时区的不同国家/地区的日期时间信息。 现在,我们正在为每个国家/地区维护单独的Web服务器和单独的数据库(oracle 11g)。

我们计划将所有门户网站合并为一个数据库(oracle 11g)。 此门户应捕获/显示用户本地时区的日期和时间。

到目前为止,我已经搜索了这个,我得到了以下建议。

1)将Web服务器和数据库服务器的时区设置为UTC,同时将获取数据(数据和时间)转换为用户本地时区。

如果您建议采用这种方法,请澄清以下具体问题。

  • 大多数时候我们只捕捉日期,是否需要始终捕捉日期和时间以及时区?

  • 同时存储我们需要在javascript / java / oracle中将用户本地时区转换为UTC的日期和时间?

  • 在获取我们需要将UTC转换为用户的日期和时间时
    本地时区查询本身/ java / java脚本?

  • 很多地方我们有报告显示基于日期列,如
    今天/当前月/日期范围。我们可以处理这个(输入 – 用户本地时区 – UTC中的数据库)?

  • 我们必须将哪种数据类型用于日期字段(日期/时间戳/时间戳与时区/时间戳与本地时区)?

2)以用户本地时区和UTC捕获日期和时间。 存储为单独的列,用户本地时区将用于显示目的,UTC将用于业务逻辑。

如果您建议采用这种方法,请澄清以下具体问题。

  • 存储用户本地时区和UTC的常见做法是什么?

  • 我需要检查条件,同时根据日期列显示报告,例如今天/当前月/日期范围?

  • 我们必须使用哪种数据类型作为日期列
    (日期/时间戳/时间戳,带时区/时间戳和本地时间
    区)?

提前致谢

阅读问题夏令时和时区最佳做法 。 你的基本上是重复的。

UTC中的服务器

是的,通常服务器应将其操作系统设置为UTC作为时区,或者如果未提供则使用GMT或雷克雅未克冰岛时区 。 您的Java实现可能会将此设置作为其自己的当前默认时区。

指定时区

但是不要依赖于设置为UTC的时区。 系统管理员可以改变它。 JVM中任何应用程序的任何线程中的任何Java代码都可以通过调用TimeZone.setDefault 在运行时更改JVM的当前默认时区。 因此,养成一种习惯,即始终通过在Java代码中传递可选参数来指定所需/预期的时区。

我认为这是一个设计缺陷,任何日期时间框架都会使时区成为可选的。 作为可选项会产生无穷无尽的混乱,因为程序员和其他人一样,无意识地根据自己的个人时区进行思考,除非提示。 因此,在日期工作中经常不关注这个问题。 添加JVM默认值变化的问题。 顺便说一句,同样的问题,同样的问题,应该始终明确指定。

世界标准时间

您的业​​务逻辑,数据存储和数据交换几乎总是以UTC完成。 几乎每个数据库都有一个function,可以将任何输入调整为UTC并以UTC格式存储。

向用户显示日期时间时,请调整到预期时区。 序列化日期时间值时,请使用ISO 8601字符串格式。 请参阅VickyArora for Oracle 的答案 (我是Postgres的人)。 请务必仔细阅读文档,并通过试验来完全理解数据库的行为。 在这方面,SQL规范并未详细说明,并且行为差异很大。

java.sql中

请记住,在使用Java和JDBC时 ,您将使用java.sql.Timestamp和相关数据类型。 它们始终以UTC格式自动生成。 将来希望看到JDBC驱动程序更新,以直接使用Java 8及更高版本中内置的java.time框架中定义的新数据类型。

java.time

旧类已经过时了java.time 。 学习使用java.time,同时避免使用旧的java.util.Date/.Calendar,让您的编程生活更加愉快。

在更新JDBC驱动程序之前,您可以使用java.time中内置的转换便捷方法。 请参阅下面的示例,其中Instant是UTC时刻, ZonedDateTime是Instant调整为时区 。

 Instant instant = myJavaSqlTimestamp.toInstant(); ZoneId zoneId = ZoneId.of( "America/Montreal" ); ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId ); 

走向另一个方向。

 java.sql.Timestamp myJavaSqlTimestamp = java.sql.Timestamp.from( zdt.toInstant() ); 

如果您需要原始时区,请存储它

如果您的业务需求认为原始输入数据的时区很重要,需要记住,那么将其显式存储为数据库表中的单独列。 您可以使用UTC的偏移量 ,但不提供完整信息。 时区是偏移量加上一组规则,用于过去,现在和将来处理exception情况,例如夏令时 。 所以适当的时区名称是最合适的,例如America/Montreal

仅限日期是模棱两可的

您说您收集了许多仅限日期的值,没有时间和没有时区。 java.time中的类是LocalDate 。 与LocalTimeLocalDateTime ,“Local …”部分表示没有特定的位置,因此没有时区,因此没有时间轴上的点 – 没有实际意义。

请记住,根据定义,仅限日期的值不明确。 在任何特定时刻,日期都在世界各地不同。 例如,在巴黎午夜之后, 法国是新的一天,但在蒙特利尔魁北克 ,日期仍然是“昨天”。

通常在商业中,某些时区是隐含的,甚至是无意识的直觉。 对数据点的无意识直觉往往不能长期有效,特别是在软件方面。 最好明确说明时区是什么意思。 您可以将预期区域与日期一起存储在数据库表中,例如另一列,或者您可以在编程代码中进行注释。 我相信存储日期时间值会更好更安全。 那么我们如何将仅限日期转换为日期时间?

通常新的一天是午夜之后,即一天的第一时刻。 您可能认为这意味着时间00:00:00.0但并非总是如此。 夏令时(DST)和可能的其他exception可能会将第一时刻推到不同的挂钟时间 。 让java.time确定第一时间通过LocalDate类及其atStartOfDay方法的正确时间。

 ZoneId zoneId = ZoneId.of( "America/Montreal" ); LocalDate today = LocalDate.now( zoneId ); ZonedDateTime todayStart = today.atStartOfDay( zoneId ); 

在某些业务环境中,可以将新的一天定义(或假设)为营业时间。 例如,假设纽约的出版商在当地时间上午9点表示“书籍草稿将于1月2日到期”。 让我们在该时区获得该日期的时间。

 ZoneId zoneId = ZoneId.of( "America/New_York" ); ZonedDateTime zdt = ZonedDateTime.of( 2016 , 1 , 2 , 9 , 0 , 0 , 0 , zoneId ); 

这对在新西兰工作的作者意味着什么? 通过调用withZoneSameInstant调整到她的特定时区以呈现给她。

 ZoneId zoneId_Pacific_Auckland = ZoneId.of( "Pacific/Auckland" ); ZonedDateTime zdt_Pacific_Auckland = zdt.withZoneSameInstant( zoneId_Pacific_Auckland ); 

数据库

对于数据库存储,我们转换为Instant (UTC时间轴上的一个时刻)并传递为java.sql.Timestamp ,如上所述。

 java.sql.Timestamp ts = java.sql.Timestamp.from( zdt.toInstant() ); 

从数据库中检索时,转换回纽约日期时间。 从java.sql.Timestamp转换为Instant ,然后应用时区ZoneId以获取ZonedDateTime

 Instant instant = ts.toInstant(); ZoneId zoneId = ZoneId.of( "America/New_York" ); ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId ); 

如果数据库驱动程序符合JDBC 4.2或更高版本,则可以直接传递/获取java.time类型,而不是转换为java.sql类型。 尝试PreparedStatement::setObjectResultSet::getObject方法。

使用TIMESTAMP和LOCAL TIME ZONE
如果您希望数据库自动转换数据库和会话时区之间的时间。

存储最多9位小数精度的日期和时间。 此数据类型对时区差异很敏感。 此类型的值将在数据库时区和本地(会话)时区之间自动转换。 当值存储在数据库中时,它们将转换为数据库时区,但不会存储本地(会话)时区。 从数据库中检索值时,该值将从数据库时区转换为本地(会话)时区。

在这里,我澄清了以下具体问题。

问:大多数时候我们只捕捉日期,是否需要始终捕捉日期和时间以及时区?

答:是的

问:在javascript / java / oracle中存储需要将用户本地时区转换为UTC的日期和时间?

A.在数据保存期间不转换,保存为源日期+时间+区域

问:在获取我们需要将UTC转换为用户本地时区查询本身/ java / java脚本的日期和时间时?

A.始终转换为以本地时区显示或以应用程序打开的UTC格式显示。

问:很多地方我们有报告根据日期列显示,例如今天/当前月/日期范围。我们可以处理这个(输入 – 用户本地时区 – 数据库中的UTC)?

:系统应为用户提供设置选项,以日期时间显示,以何种格式显示应用程序打开的本地格式或UTC。 一切都只在前端完成。

问:我们必须将哪种数据类型用于日期字段(日期/时间戳/时间戳与时区/时间戳与本地时区)?

A.时间戳

因此,简而言之,在源时区中保存日期时间,并根据用户首选项在本地页面打开或UTC格式转换。 手段,转换将通过脚本完成,仅供显示。 也可以找到产品受欢迎的地区。

我只是将存储在DB中的现有日期转换为Long ,并保持(ETL过程)此Long值以及已知(或扣除) patternLocaleTimeZone (默认元)。 只要使用默认元Date ,就可以保留任何新Date

ETL示例

比方说2015-11-29 10:07:49.500 UTC存储在DB中:

 // Known or deducted format of the persisted date String pattern = "yyyy-MM-dd HH:mm:ss.SSS"; Locale locale = Locale.ENGLISH; TimeZone zone = "UTC"; // Date to ms SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale); sdf.setTimeZone(TimeZone.getTimeZone(zone)); Date date = sdf.parse(pattern); // ETL: Can now be persisted in Long, along with default META (pattern, Locale, TZ) Long dateL = date.getTime(); // for eg 1448827660720 ... 

如果需要,持久化的Long值也可以以任何其他格式进行转换

 pattern | locale | tz | result ============================================ yyyy/MM/dd | null | null | 2015/11/29 dd-M-yyyy hh:mm:ss | null | null | 29-11-2015 10:07:40 dd MMMM yyyy zzzz | ENGLISH | null | 29 November 2015 Central European Time yyyy-MM-dd HH:mm:ss.SSS | null | UTC | 2015-11-29 10:07:49 UTC 

可行和合乎逻辑的方法是; 将用户输入的时间转换为GMT / UTC +00并将其存储在db中,无论是否有时区标识符都无关紧要。 当您需要显示用户将java中的GMT / UTC时间转换为用户当地时间的时间。

您应该考虑JodaTime并且应该遵循第一个建议.JodaTime有许多类,如LocalDate,LocalDateTime,您可以将它们用于不同的用例。