【问题标题】:Date gives one day less than actual date while converting from date to calendar从日期转换为日历时,日期比实际日期少一天
【发布时间】:2015-06-25 06:32:43
【问题描述】:

我从前端传递日期,即 IST(印度时区日期)。在 java 代码中,我使用以下代码将日期转换为日历(这发生在美国 PST 时区的服务器中)。

Calendar cal = Calendar.getInstance();
int offset = date.getTimezoneOffset();
logger.info("Calendar Instance - " + cal);
cal.setTime(date);
logger.info("Calendar Instance after setting date - " + cal);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
logger.info("Calendar Instance after setting zeros - " + cal);
return cal;

所以当我看到最后一个日志时,该月的日期将比我通过的日期少一天。例如。如果我通过 22/06/2015 IST,它将转移到 21/06/2015。所以最终处理后,它会在另一个 UI 页面中的数据列表中显示 21/06/2015。

【问题讨论】:

  • date 设置在哪里?
  • 一般不要使用日历。那个图书馆乱七八糟。改用 java.time
  • @gurghet:请您详细说明一下。 java 代码中的 api 只接受日历。如何使用 java.time。
  • @Ryan:日期是从 UI 传递过来的。
  • @bharathkumar 查看 UI 在何处/如何创建 Date 对象将是相关的,因为可能存在一些字段值数学错误。 DAY_OF_MONTH 字段的第一个值从 1 开始,而不是 0。您是否在任何地方进行任何数字操作?

标签: java date calendar


【解决方案1】:

这是因为服务器端的 JVM 和客户端的 JVM 默认使用不同的时区Java TimeZone:

通常,您会使用 getDefault 获得一个 TimeZone,它会创建一个 TimeZone 基于程序运行的时区。为了 例如,对于在日本运行的程序,getDefault 创建一个 TimeZone 基于日本标准时间的对象。

如我们所见,服务器上的Pacific Time Zone 具有UTC−8:00,客户端上的Indian Standard Time 具有UTC+05:30。它们相差 13.30 并且印度日期 X 转换为美国为 X-13.30,这可能会在某些 X 的服务器端产生前一天。

根据您如何影响/修改您的服务器和客户端应用程序,可能有几种解决方法。例如,您可以在服务器端和客户端使用 UTC+00:00 时区的日期。如果您需要向用户显示日期,您可以在需要时将其转换为印度时区。

// Set default GMT+0:00 time zone
TimeZone timeZone;
timeZone = TimeZone.getTimeZone("GMT+0:00");
TimeZone.setDefault(timeZone);

您可以创建“清晰”日历,而不是简单地使用Calendar cal = Calendar.getInstance();,您稍后将使用它来设置日、月和年

public static Calendar createClearedCalendar() {
    Calendar cal = Calendar.getInstance();

    cal.setTimeZone(timeZone);

    cal.set(1970, 0, 1, 0, 0, 0);
    cal.set(Calendar.HOUR_OF_DAY, 0);

    cal.clear(Calendar.MILLISECOND);

    return cal;
}

顺便说一句,如果您在 Java 中操作日期时间,您可以考虑Joda Time,它有更多的扩展选项和optimized performance

【讨论】:

  • 不要使用 Joda Time,它不可移植,改用 java.time。
  • 您可以参考this discussion as an example来决定是否在自己的项目中使用Joda Time
  • 老了,java.time 好多了
  • 请注意,java.time从JDK1.8开始可用
  • Joda-Time 启发了 java.time,由同一个人设计和构建两者。每个都具有其他缺乏的功能,例如 Joda-Time 中的Interval,但 java.time 中没有(还没有?)。 Joda-Time 继续作为一个活跃的项目,在多个版本的 Java 和 Android 中运行。所以,是的,应该尽可能从 java.time 开始,否则使用 Joda-Time。另外,请查看 ThreeTen-Extra 项目以向 java.time 添加功能。
【解决方案2】:

Antonio 的The Answer 是正确的,应该被接受(点击大的空复选标记)。

此答案添加了一些想法和示例代码。

避免使用 3 个字母的时区代码

避免使用或甚至考虑那些 3 或 4 个字母的代码,例如 ISTPST。它们不是标准化的,它们不是唯一的,它们进一步混淆了Daylight Saving Time (DST) 周围的问题。例如,IST 表示“印度标准时间”、“爱尔兰标准时间”等。

使用proper time zone names。其中大部分是“大陆”+“/”+“城市/地区”模式。城市/地区名称并非专门针对该城镇,而是作为一个易于识别的名称,用于尽可能广泛的区域,该名称与 time zone rules 和异常(包括 @987654325)共享相同的过去、现在和未来规则集@)。

使用 UTC

一般来说,您应该使用UTC time zone 进行所有业务逻辑、数据存储和数据交换。调整到特定时区,仅在用户期望时进行演示。

使用合适的日期时间框架

旧的 java.util.Date/.Calendar 类是处理日期时间工作的大胆尝试,但最终失败了。它们是出了名的麻烦,在设计和实现上都有缺陷。避开他们。

第 3 方 Joda-Time 库是一种解决方案。它适用于许多版本的 Java 和 Android。 Joda-Time 启发了另一个解决方案,即 Java 8 及更高版本中的java.time package (Tutorial)。

解决方案

这个问题的目标似乎是获取一个 java.util.Date 对象,分配所需的时区,并生成一个 java.util.Calendar 对象。

幸运的是 java.time 框架有转换方法。见this Tutorial page

示例代码如下,使用 Java 8 Update 45 中的 java.time。

您可能需要以下导入:

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

让我们模拟传入一个 java.util.Date。我们将基于“now”实例化一个 Date。

Date inputDate = new Date( );  // Simulate getting a java.util.Date object.

然后我们使用proper time zone names 定义所需的时区。让我们把蒙特利尔以及问题中提到的美国太平洋时区和印度时区抛在一边。

ZoneId zoneLosAngeles = ZoneId.of( "America/Los_Angeles" );
ZoneId zoneMontréal = ZoneId.of( "America/Montreal" );
ZoneId zoneKolkata = ZoneId.of( "Asia/Kolkata" );

然后我们将其转换为Instant,即时间线上的一个点,与时区无关。

Instant instant = inputDate.toInstant( );

然后我们分配不同的时区来创建ZonedDateTime 实例。看看我们如何以两种方式实例化 ZonedDateTime:[a] 从 Instant,或 [b] 通过 withZoneSameInstant 方法从另一个 ZonedDateTime。两种方式如下所示。

请注意,java.time(和 Joda-Time)使用immutable objects,这是一种设计模式,我们基于旧实例创建新实例,而不是更改(“变异”)旧实例。 Thread-safety 是主要好处之一。

ZonedDateTime zdtLosAngeles = ZonedDateTime.ofInstant( instant, zoneLosAngeles );
ZonedDateTime zdtMontréal = ZonedDateTime.ofInstant( instant, zoneMontréal );
ZonedDateTime zdtKolkata = ZonedDateTime.ofInstant( instant, zoneKolkata );
ZonedDateTime zdtUtc = zdtKolkata.withZoneSameInstant( ZoneOffset.UTC );

最后,我们将其中一个转换为GregorianCalendar 对象,它是java.util.Calendar 的子类。

GregorianCalendar calendarKolkata = GregorianCalendar.from( zdtKolkata );

转储到控制台。

System.out.println( "inputDate: " + inputDate );
System.out.println( "zdtLosAngeles: " + zdtLosAngeles );
System.out.println( "zdtMontréal: " + zdtMontréal );
System.out.println( "zdtKolkata: " + zdtKolkata );
System.out.println( "zdtUtc: " + zdtUtc );
System.out.println( "calendarKolkata: " + calendarKolkata );

运行时。

inputDate: Wed Jun 24 15:12:12 PDT 2015
zdtLosAngeles: 2015-06-24T15:12:12.153-07:00[America/Los_Angeles]
zdtMontréal: 2015-06-24T18:12:12.153-04:00[America/Montreal]
zdtKolkata: 2015-06-25T03:42:12.153+05:30[Asia/Kolkata]
zdtUtc: 2015-06-24T22:12:12.153Z
calendarKolkata: java.util.GregorianCalendar[time=1435183932153,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Kolkata",offset=19800000,dstSavings=0,useDaylight=false,transitions=6,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2015,MONTH=5,WEEK_OF_YEAR=26,WEEK_OF_MONTH=4,DAY_OF_MONTH=25,DAY_OF_YEAR=176,DAY_OF_WEEK=5,DAY_OF_WEEK_IN_MONTH=4,AM_PM=0,HOUR=3,HOUR_OF_DAY=3,MINUTE=42,SECOND=12,MILLISECOND=153,ZONE_OFFSET=19800000,DST_OFFSET=0]

【讨论】:

  • 时区动态变化我不能特别像“亚洲/加尔各答”。用户可以从任何时区访问。我尝试使用 dateobject.getTimeZoneOffset() 动态地从日期获取时区并将其添加到日历中,但这在编组时已被弃用,他们使用了一些仅将服务器时区用于将日历转换为 xmlgreagoriancalendar 的 jar,我无法为其设置时区。
  • 实际上,java.util.Date 没有时区。基本上,它只是 UTC 1970 纪元的毫秒数的包装器。实际上,源代码深处有一个时区,在内部用于某些事情但对您没有用处。 j.u.Date 对象为您提供所需的关键部分:UTC 日期时间值。只需按照我的说明指定您想要/预期的时区。
  • 没有我可以坚持的特定时区。我需要动态获取客户端区域并进行设置。但他们已经将 GWT 用于前端,我不确定如何动态获取客户端区域。
  • 在 StackOverflow 上搜索该问题,从 Web 客户端获取时区。最终,唯一确定的方法是询问用户,让用户选择proper time zone name
  • 用户没有选择时区的选项。他们只能从 GWT 日期选择器中选择日期。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-29
  • 2012-07-07
相关资源
最近更新 更多