如何使用Java处理日历TimeZones?


92

我有一个来自我的应用程序的时间戳值。用户可以在任何给定的本地TimeZone中。

由于此日期用于假定给定时间始终为格林尼治标准时间的Web服务,因此我需要将用户的参数从(EST)转换为(GMT)。这是一个关键点:用户忽略了自己的TZ。他输入了要发送到WS的创建日期,所以我需要的是:

用户输入: 5/1/2008 6:12 PM(EST)
WS的参数需要为:5/1/2008 6:12 PM(GMT)

我知道默认情况下始终应该将TimeStamps设置为GMT,但是即使在我从TS(应该是GMT)创建日历的情况下,发送参数时,除非用户使用GMT,否则时间总是会关闭。我想念什么?

Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
...
private static java.util.Calendar convertTimestampToJavaCalendar(Timestamp ts_) {
  java.util.Calendar cal = java.util.Calendar.getInstance(
      GMT_TIMEZONE, EN_US_LOCALE);
  cal.setTimeInMillis(ts_.getTime());
  return cal;
}

使用之前的代码,这就是我得到的结果(简短格式,易于阅读):

[2008年5月1日晚上11:12]


2
为什么只更改时区而不转换日期/时间?
Spencer Kormos

1
选择一个时区中的Java日期,并在另一个时区中获取日期确实是一个痛苦。IE,以5PM EDT取得5PM PDT。
mtyson

Answers:


62
public static Calendar convertToGmt(Calendar cal) {

    Date date = cal.getTime();
    TimeZone tz = cal.getTimeZone();

    log.debug("input calendar has date [" + date + "]");

    //Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT 
    long msFromEpochGmt = date.getTime();

    //gives you the current offset in ms from GMT at the current date
    int offsetFromUTC = tz.getOffset(msFromEpochGmt);
    log.debug("offset is " + offsetFromUTC);

    //create a new calendar in GMT timezone, set to this date and add the offset
    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.setTime(date);
    gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);

    log.debug("Created GMT cal with date [" + gmtCal.getTime() + "]");

    return gmtCal;
}

如果通过以下时间传递当前时间(来自的“ 12:09:05 EDT” Calendar.getInstance()),则输出如下:

调试-输入日历具有日期[2008年10月23日星期四12:09:05]
调试-偏移量是-14400000
调试-创建的GMT校准日期为[星期四2008年10月23日08:09:05]

格林尼治标准时间12:09:05是美国东部时间8:09:05。

此处令人困惑的部分是,Calendar.getTime()您将Date在当前时区返回a ,并且没有方法可以修改日历的时区并使基础日期也滚动。根据您的Web服务采用什么类型的参数,您可能只想让WS处理时间间隔为毫秒。


11
您不应该减去offsetFromUTC而不是相加吗?以您的示例为例,如果12:09 GMT是8:09 EDT(这是正确的),并且用户输入“ 12:09 EDT”,我认为算法应输出“ 16:09 GMT”。
DzinX 2012年

29

谢谢大家的回应。经过进一步调查,我得到了正确的答案。正如Skip Head所述,我从应用程序中获取的时间戳已调整为用户的TimeZone。因此,如果用户输入的是美国东部标准时间下午6:12,我将获得格林尼治标准时间2:12。我需要的是一种撤消转换的方法,这样用户输入的时间就是我发送到WebServer请求的时间。这是我的完成方式:

// Get TimeZone of user
TimeZone currentTimeZone = sc_.getTimeZone();
Calendar currentDt = new GregorianCalendar(currentTimeZone, EN_US_LOCALE);
// Get the Offset from GMT taking DST into account
int gmtOffset = currentTimeZone.getOffset(
    currentDt.get(Calendar.ERA), 
    currentDt.get(Calendar.YEAR), 
    currentDt.get(Calendar.MONTH), 
    currentDt.get(Calendar.DAY_OF_MONTH), 
    currentDt.get(Calendar.DAY_OF_WEEK), 
    currentDt.get(Calendar.MILLISECOND));
// convert to hours
gmtOffset = gmtOffset / (60*60*1000);
System.out.println("Current User's TimeZone: " + currentTimeZone.getID());
System.out.println("Current Offset from GMT (in hrs):" + gmtOffset);
// Get TS from User Input
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
System.out.println("TS from ACP: " + issuedDate);
// Set TS into Calendar
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
// Adjust for GMT (note the offset negation)
issueDate.add(Calendar.HOUR_OF_DAY, -gmtOffset);
System.out.println("Calendar Date converted from TS using GMT and US_EN Locale: "
    + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
    .format(issueDate.getTime()));

代码的输出为:(用户输入时间:5/1/2008 6:12 PM(美国东部标准时间)

当前用户的时区:EST
与GMT的当前偏移量(以小时为单位):-4(通常为-5,DST调整除外)
ACP的TS:2008-05-01 14:12:
00.0日历日期使用GMT和US_EN语言环境从TS转换:08/5/1下午6:12(GMT)


20

您说该日期是与Web服务结合使用的,因此我假设该日期在某些时候被序列化为字符串。

在这种情况下,您应该查看DateFormat类的setTimeZone方法。这决定了在打印时间戳时将使用哪个时区。

一个简单的例子:

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));

Calendar cal = Calendar.getInstance();
String timestamp = formatter.format(cal.getTime());

4
没关系SDF有它自己的时区,想知道为什么更改Calendar TimeZone似乎没有效果!
克里斯·詹金斯(Chris.Jenkins)2012年

TimeZone.getTimeZone(“UTC”)是无效的,因为UTC不AvailableIDs()......天知道为什么
childno͡.de

TimeZone.getTimeZone(“ UTC”)在我的机器上可用。是否依赖JVM?
CodeClimber 2012年

2
使用setTimeZone()方法的好主意。您为我省去了很多麻烦,非常感谢!
Bogdan Zurac

1
正是我所需要的!您可以在格式器中设置服务器时区,然后将其转换为日历格式时,无需担心。迄今为止最好的方法!对于getTimeZone,您可能需要使用格式Ex:“ GMT-4:00”作为ETD
Michael Kern

12

您可以使用Joda Time解决它:

Date utcDate = new Date(timezoneFrom.convertLocalToUTC(date.getTime(), false));
Date localDate = new Date(timezoneTo.convertUTCToLocal(utcDate.getTime()));

Java 8:

LocalDateTime localDateTime = LocalDateTime.parse("2007-12-03T10:15:30");
ZonedDateTime fromDateTime = localDateTime.atZone(
    ZoneId.of("America/Toronto"));
ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant(
    ZoneId.of("Canada/Newfoundland"));

但是请注意,如果您使用的是休眠4,则在没有侧面依赖性和额外配置的情况下不能直接兼容。但是,对于第三版,这是最快,最容易使用的方法。
茄子

8

看起来您的时间戳记已设置为原始系统的时区。

不推荐使用此方法,但它应该可以工作:

cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());

不推荐使用的方法是使用

Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)

但这需要在客户端完成,因为该系统知道它所在的时区。


7

从一个timeZone转换为另一个的方法(可能有效:))。

/**
 * Adapt calendar to client time zone.
 * @param calendar - adapting calendar
 * @param timeZone - client time zone
 * @return adapt calendar to client time zone
 */
public static Calendar convertCalendar(final Calendar calendar, final TimeZone timeZone) {
    Calendar ret = new GregorianCalendar(timeZone);
    ret.setTimeInMillis(calendar.getTimeInMillis() +
            timeZone.getOffset(calendar.getTimeInMillis()) -
            TimeZone.getDefault().getOffset(calendar.getTimeInMillis()));
    ret.getTime();
    return ret;
}

6

日期时间戳记对象是时区可忽略的:它们表示自该纪元以来的一定秒数,而没有承诺对该瞬间的特定解释为小时和天。时区仅在GregorianCalendar(此任务不需要直接)和SimpleDateFormat中输入图片,后者需要时区偏移量才能在单独的字段和Date(或long)值之间转换。

OP的问题就在处理开始时就出现了:用户输入的小时数不明确,并且以本地非GMT时区进行解释;此时,该值为“ 6:12 EST”,可以轻松将其打印为“ 11.12 GMT”或任何其他时区,但是永远不会更改“ 6.12 GMT”

无法将将“ 06:12”解析为“ HH:MM”(默认为本地时区)的SimpleDateFormat缺省设置为UTC。SimpleDateFormat本身就太聪明了。

但是,如果将SimpleDateFormat实例明确地输入到输入中,则可以说服任何SimpleDateFormat实例使用正确的时区:只需在接收到的(并经过充分验证的)“ 06:12”后面添加固定的字符串,即可将“ 06:12 GMT”解析为“ HH:MM z”

无需显式设置GregorianCalendar字段,也无需检索和使用时区和夏时制时间偏移量。

真正的问题是将默认为本地时区的输入,默认为UTC的输入以及确实需要显式时区指示的输入进行隔离。


4

过去对我有用的方法是确定用户的时区与GMT之间的偏移量(以毫秒为单位)。有了偏移量后,您可以简单地加/减(取决于转换的方式)来获取任一时区的适当时间。通常,我可以通过设置Calendar对象的毫秒字段来完成此操作,但是我确信您可以轻松地将其应用于timestamp对象。这是我用来获取偏移量的代码

int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();

timezoneId是用户时区的ID(例如EST)。


9
使用原始偏移量,您将忽略DST。
Werner Lehmann

1
确实,这种方法会在半年内给出错误的结果。错误的最坏形式。
Danubian Sailor

1

java.time

现代方法使用了java.time类,该类取代了与最早的Java版本捆绑在一起的麻烦的旧式日期时间类。

java.sql.Timestamp班是这些传统类之一。不再需要。而是使用InstantJDBC 4.2及更高版本直接在数据库中使用或其他java.time类。

Instant级表示时间轴上的时刻UTC,分辨率为纳秒(最多小数的9个位数)。

Instant instant = myResultSet.getObject(  , Instant.class ) ; 

如果您必须与现有互操作Timestamp,则可以通过添加到旧类中的新转换方法立即将其转换为java.time。

Instant instant = myTimestamp.toInstant() ;

要调整到另一个时区,请将时区指定为ZoneId对象。指定适当的时区名称,格式continent/region,如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用3-4个字母的伪时区,例如EST或,IST因为它们不是真实的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;

应用于Instant生成ZonedDateTime对象。

ZonedDateTime zdt = instant.atZone( z ) ;

要生成要显示给用户的字符串,请搜索Stack Overflow DateTimeFormatter以查找许多讨论和示例。

您的问题实际上是关于从用户数据输入到日期时间对象的另一个方向。通常最好将数据输入分为两部分,日期和一天中的时间。

LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;

您的问题不清楚。您是否要解释用户以UTC输入的日期和时间?还是在另一个时区?

如果您要使用UTC,请OffsetDateTime使用UTC的常数创建一个带有偏移量的ZoneOffset.UTC

OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;

如果您要使用其他时区,请与时区对象组合在一起ZoneId。但是哪个时区?您可能会检测到默认时区。或者,如果很关键,则必须与用户确认他们的意图。

ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

要获得一个定义始终为UTC的简单对象,请提取一个Instant

Instant instant = odt.toInstant() ;

…要么…

Instant instant = zdt.toInstant() ; 

发送到您的数据库。

myPreparedStatement.setObject(  , instant ) ;

关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

现在处于维护模式Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

在哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time。该项目是将来可能向java.time添加内容的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.