如何使用Java处理日历时区?

如何使用Java处理日历时区?,java,calendar,timezone,Java,Calendar,Timezone,我有一个来自我的应用程序的时间戳值。用户可以在任何给定的本地时区 由于此日期用于假定给定时间始终以GMT为单位的Web服务,因此我需要将用户参数从say(EST)转换为(GMT)。关键是:用户没有意识到自己的错误。他输入要发送给WS的创建日期,因此我需要: 用户输入:2008年1月5日下午6:12(美国东部时间) WS的参数必须为:2008年1月5日下午6:12(格林尼治标准时间) 我知道时间戳在默认情况下总是应该以GMT为单位,但在发送参数时,即使我从TS创建了日历(应该以GMT为单位),除非

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

由于此日期用于假定给定时间始终以GMT为单位的Web服务,因此我需要将用户参数从say(EST)转换为(GMT)。关键是:用户没有意识到自己的错误。他输入要发送给WS的创建日期,因此我需要:

用户输入:2008年1月5日下午6:12(美国东部时间)
WS的参数必须为:2008年1月5日下午6:12(格林尼治标准时间)

我知道时间戳在默认情况下总是应该以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 PM]

过去对我有效的方法是确定用户时区和GMT之间的偏移量(以毫秒为单位)。获得偏移量后,您只需添加/减去(取决于转换的方式)即可在任一时区中获得适当的时间。我通常会通过设置日历对象的毫秒字段来实现这一点,但我相信您可以轻松地将其应用于时间戳对象。这是我用来获取偏移量的代码

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

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

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

这是不推荐的,但它应该可以工作:

cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());
不推荐使用的方法是使用

Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
但这需要在客户端完成,因为该系统知道它所在的时区

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()
)传递到以下位置,则输出如下:

调试-输入日历有日期[Thu Oct 23 12:09:05 EDT 2008]
调试-偏移量为-14400000
调试-创建了日期为[Thu Oct 23 08:09:05 EDT 2008]的GMT校准

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


这里令人困惑的是,
Calendar.getTime()
返回当前时区中的
Date
,并且没有方法修改日历的时区并同时滚动基础日期。根据您的web服务所采用的参数类型,您可能只希望从epoch开始以毫秒为单位执行WS-deal。

谢谢大家的响应。经过进一步的调查,我得到了正确的答案。正如Skip Head所提到的,我从应用程序中获得的时间戳正在根据用户的时区进行调整。因此,如果用户输入6:12 PM(美国东部标准时间),我将得到2:12 PM(格林尼治标准时间)。我需要的是一种撤销转换的方法,这样用户输入的时间就是我发送到Web服务器请求的时间。我是如何做到这一点的:

// 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()));
代码的输出是:(用户输入2008年1月5日下午6:12(美国东部标准时间)

当前用户的时区:EST
当前相对于GMT的偏移量(小时):-4(通常为-5,DST调整除外)
来自机场核心计划的TS:2008-05-01 14:12:00.0

使用GMT和美国地区从TS转换的日历日期:5/1/08 6:12 PM(GMT)

您说该日期用于web服务,因此我假设该日期在某个点被序列化为字符串

如果是这种情况,您应该查看DateFormat类的名称。这指示在打印时间戳时将使用哪个时区

一个简单的例子:

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());

从一个时区转换到另一个时区的方法(可能有效:))

您可以通过以下方法解决此问题:

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"));

日期时间戳对象是不受时区影响的:它们表示从纪元开始的特定秒数,而不将该瞬间指定为小时和天。 时区仅在GregoriaCalendar(此任务不直接需要)和SimpleDateFormat中输入图片,这需要时区偏移量在单独的字段和日期(或长)值之间进行转换

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

无法将解析“06:12”为“HH:MM”(默认为本地时区)的简化格式改为UTCSimpleDateFormat本身就有点太聪明了

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

不需要显式设置GregoriaCalendar字段,也不需要检索和使用时区和夏令时偏移

真正的问题是将默认为本地时区的输入、默认为UTC的输入和真正需要显式时区指示的输入分离。

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

java.sql.Timestamp
类就是这些遗留类之一。不再需要了。相反,使用JDBC 4.2及更高版本直接在数据库中使用
Instant
或其他java.time类

该类表示时间线上的一个时刻,分辨率为(小数点的九(9)位)

如果必须与现有的
时间戳
进行互操作,请通过添加到旧类中的新转换方法立即转换为java.time

Instant instant = myTimestamp.toInstant() ;
适应
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"));
Instant instant = myResultSet.getObject( … , Instant.class ) ; 
Instant instant = myTimestamp.toInstant() ;
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;
OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
Instant instant = odt.toInstant() ;
Instant instant = zdt.toInstant() ; 
myPreparedStatement.setObject( … , instant ) ;