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

时间:2008-10-23 15:15:49

标签: java calendar timezone

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

由于此日期用于假定给定时间始终为GMT的WebService,因此我需要将用户的参数从say(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分]

9 个答案:

答案 0 :(得分:60)

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;
}

如果我将当前时间(“{1}}的12:09:05 EDT”)传递给:

,这是输出
  

DEBUG - 输入日历有日期[Thu Oct 23 12:09:05 2008 EDT]
  调试 - 抵消是-14400000
  DEBUG - 创建GMT cal与日期[Thu Oct 23 08:09:05 EDT 2008]

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

这里令人困惑的部分是Calendar.getInstance()在您当前的时区中返回Calendar.getTime(),并且没有方法可以修改日历的时区并同时滚动基础日期。根据您的Web服务所使用的参数类型,您可能只希望以纪元为单位的毫秒来处理WS协议。

答案 1 :(得分:29)

谢谢大家的回复。经过进一步调查后,我得到了正确的答案。正如Skip Head所提到的,我从我的应用程序中获取的TimeStamped正在调整为用户的TimeZone。因此,如果用户在下午6:12(美国东部时间)进入,我将在下午2:12(GMT)。我需要的是撤消转换的方法,以便用户输入的时间是我发送到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)

当前用户的时区:EST
格林尼治标准时间的当前偏差(小时): - 4(通常为-5,除了调整夏令时)
来自ACP的TS:2008-05-01 14:12:00.0
使用GMT和US_EN语言转换自TS的日历日期:5/1/08 6:12 PM(GMT)

答案 2 :(得分:20)

您说该日期与Web服务一起使用,因此我假设在某些时候将其序列化为字符串。

如果是这种情况,您应该查看DateFormat类的setTimeZone method。这决定了打印时间戳时将使用的时区。

一个简单的例子:

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

答案 3 :(得分: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)

看起来你的TimeStamp被设置为原始系统的时区。

这已被弃用,但应该可以使用:

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

不推荐的方法是使用

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

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

答案 5 :(得分:7)

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

/**
 * 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 :(得分:6)

日期时间戳对象是时区遗忘的:它们代表了自纪元以来的特定秒数,而未承诺对该时刻的特定解释为小时和天。 时区仅在 GregorianCalendar (此任务不直接需要)和 SimpleDateFormat 中输入图片,需要时区偏移量才能在单独的字段和日期(或 long)之间进行转换)值。

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

没有办法让 SimpleDateFormat “06:12”解析为“HH:MM”(默认为本地时区)默认为UTC; SimpleDateFormat 对于自己的好处来说太聪明了。

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

无需明确设置 GregorianCalendar 字段,也无需检索和使用时区和夏令时偏移。

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

答案 7 :(得分:4)

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

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

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

答案 8 :(得分:1)

java.time

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

java.sql.Timestamp类是其中一个遗留类。不再需要。而是使用JDBC 4.2及更高版本直接将Instant或其他java.time类与数据库一起使用。

Instant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

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

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

Instant instant = myTimestamp.toInstant() ;

要调整到其他时区,请将时区指定为ZoneId个对象。以continent/region格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用诸如ESTIST之类的3-4个字母伪区域,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

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

申请Instant以生成ZonedDateTime个对象。

ZonedDateTime zdt = instant.atZone( z ) ;

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

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

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,请使用UTC的常量OffsetDateTime创建一个带有偏移量的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及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore