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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/230126/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:47:46  来源:igfitidea点击:

How to handle calendar TimeZones using Java?

javacalendartimezone

提问by Jorge Valois

I have a Timestamp value that comes from my application. The user can be in any given local TimeZone.

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

Since this date is used for a WebService that assumes the time given is always in GMT, I have a need to convert the user's parameter from say (EST) to (GMT). Here's the kicker: The user is oblivious to his TZ. He enters the creation date that he wants to send to the WS, so what I need is:

由于此日期用于假定给定时间始终为 GMT 的 WebService,因此我需要将用户的参数从 say (EST) 转换为 (GMT)。关键是:用户没有注意到他的 TZ。他输入了他想发送给 WS 的创建日期,所以我需要的是:

User enters:5/1/2008 6:12 PM (EST)
The parameter to the WS needs to be: 5/1/2008 6:12 PM (GMT)

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

I know TimeStamps are always supposed to be in GMT by default, but when sending the parameter, even though I created my Calendar from the TS (which is supposed to be in GMT), the hours are always off unless the user is in GMT. What am I missing?

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

With the previous Code, this is what I get as a result (Short Format for easy reading):

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

[May 1, 2008 11:12 PM]

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

采纳答案by Jorge Valois

Thank you all for responding. After a further investigation I got to the right answer. As mentioned by Skip Head, the TimeStamped I was getting from my application was being adjusted to the user's TimeZone. So if the User entered 6:12 PM (EST) I would get 2:12 PM (GMT). What I needed was a way to undo the conversion so that the time entered by the user is the time I sent to the WebServer request. Here's how I accomplished this:

谢谢大家的回应。经过进一步调查,我得到了正确的答案。正如 Skip Head 所提到的,我从我的应用程序获得的 TimeStamped 正在调整为用户的时区。因此,如果用户输入 6:12 PM (EST),我将得到 2:12 PM (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()));

The code's output is: (User entered 5/1/2008 6:12PM (EST)

代码的输出是:(用户输入 5/1/2008 6:12PM (EST)

Current User's TimeZone: EST
Current Offset from GMT (in hrs):-4 (Normally -5, except is DST adjusted)
TS from ACP: 2008-05-01 14:12:00.0
Calendar Date converted from TS using GMT and US_EN Locale: 5/1/08 6:12 PM (GMT)

当前用户的时区:EST
与 GMT 的当前偏移量(以小时为单位):-4(通常为 -5,除了 DST 调整外)
来自 ACP 的 TS:2008-05-01 14:12:00.0
日历日期使用 GMT 和 US_EN Locale 从 TS 转换而来: 5/1/08 下午 6:12 (格林威治标准时间)

回答by Adam

Something that has worked for me in the past was to determine the offset (in milliseconds) between the user's timezone and GMT. Once you have the offset, you can simply add/subtract (depending on which way the conversion is going) to get the appropriate time in either timezone. I would usually accomplish this by setting the milliseconds field of a Calendar object, but I'm sure you could easily apply it to a timestamp object. Here's the code I use to get the offset

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

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

timezoneId is the id of the user's timezone (such as EST).

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

回答by Skip Head

It looks like your TimeStamp is being set to the timezone of the originating system.

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

This is deprecated, but it should work:

这已被弃用,但它应该可以工作:

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

The non-deprecated way is to use

不推荐使用的方法是使用

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

but that would need to be done on the client side, since that system knows what timezone it is in.

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

回答by matt b

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

Here's the output if I pass the current time ("12:09:05 EDT" from Calendar.getInstance()) in:

如果我将当前时间(“12:09:05 EDT” from Calendar.getInstance())传递给以下内容,则输出如下:

DEBUG - input calendar has date [Thu Oct 23 12:09:05 EDT 2008]
DEBUG - offset is -14400000
DEBUG - Created GMT cal with date [Thu Oct 23 08:09:05 EDT 2008]

DEBUG - 输入日历有日期 [Thu Oct 23 12:09:05 EDT 2008]
DEBUG - 偏移量为 -14400000
DEBUG - 创建 GMT cal with date [Thu Oct 23 08:09:05 EDT 2008]

12:09:05 GMT is 8:09:05 EDT.

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

The confusing part here is that Calendar.getTime()returns you a Datein your current timezone, and also that there is no method to modify the timezone of a calendar and have the underlying date rolled also. Depending on what type of parameter your web service takes, your may just want to have the WS deal in terms of milliseconds from epoch.

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

回答by Henrik Aasted S?rensen

You say that the date is used in connection with web services, so I assume that is serialized into a string at some point.

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

If this is the case, you should take a look at the setTimeZone methodof the DateFormat class. This dictates which time zone that will be used when printing the time stamp.

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

A simple example:

一个简单的例子:

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

回答by Helpa

Method for converting from one timeZone to other(probably it works :) ).

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

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

回答by Vitalii Fedorenko

You can solve it with Joda Time:

您可以使用Joda Time解决它:

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

Java 8:

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

回答by user412090

Dateand Timestampobjects are timezone-oblivious: they represent a certain number of seconds since the epoch, without committing to a particular interpretation of that instant as hours and days. Timezones enter the picture only in GregorianCalendar(not directly needed for this task) and SimpleDateFormat, which need a timezone offset to convert between separate fields and Date (or long) values.

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

The OP's problem is right at the beginning of his processing: the user inputs hours, which are ambiguous, and they are interpreted in the local, non-GMT timezone; at this point the value is "6:12 EST", which can be easily printed as "11.12 GMT"or any other timezone but is never going to changeto "6.12 GMT".

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

There is no way to make the SimpleDateFormatthat parses "06:12"as "HH:MM"(defaulting to the local time zone) default to UTC instead; SimpleDateFormatis a bit too smart for its own good.

没有办法让解析"06:12""HH:MM"(默认为本地时区)的SimpleDateFormat默认为 UTC;SimpleDateFormat有点太聪明了。

However, you can convince any SimpleDateFormatinstance to use the right time zone if you put it explicitly in the input: just append a fixed string to the received (and adequately validated) "06:12"to parse "06:12 GMT"as "HH:MM z".

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

There is no need of explicit setting of GregorianCalendarfields or of retrieving and using timezone and daylight saving time offsets.

不需要显式设置GregorianCalendar字段或检索和使用时区和夏令时偏移。

The real problem is segregating inputs that default to the local timezone, inputs that default to UTC, and inputs that really require an explicit timezone indication.

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

回答by Basil Bourque

java.time

时间

The modern approach uses the java.timeclasses that supplanted the troublesome legacy date-time classes bundled with the earliest versions of Java.

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

The java.sql.Timestampclass is one of those legacy classes. No longer needed. Instead use Instantor other java.time classes directly with your database using JDBC 4.2 and later.

java.sql.Timestamp课程是那些遗留课程之一。不再需要。而是使用InstantJDBC 4.2 及更高版本直接将或 其他 java.time 类与您的数据库一起使用。

The Instantclass represents a moment on the timeline in UTCwith a resolution of nanoseconds(up to nine (9) digits of a decimal fraction).

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

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

If you must interoperate with an existing Timestamp, convert immediately into java.time via the new conversion methods added to the old classes.

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

Instant instant = myTimestamp.toInstant() ;

To adjust into another time zone, specify the time zone as a ZoneIdobject. Specify a proper time zone namein the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter pseudo-zones such as ESTor ISTas they are nottrue time zones, not standardized, and not even unique(!).

要调整到另一个时区,请将时区指定为ZoneId对象。以、、 或等格式指定正确的时区名称。永远不要使用 3-4 个字母的伪区域,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。continent/regionAmerica/MontrealAfrica/CasablancaPacific/AucklandESTIST

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

Apply to the Instantto produce a ZonedDateTimeobject.

应用于Instant产生一个ZonedDateTime对象。

ZonedDateTime zdt = instant.atZone( z ) ;

To generate a string for display to the user, search Stack Overflow for DateTimeFormatterto find many discussions and examples.

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

Your Question is really about going the other direction, from user data-entry to the date-time objects. Generally best to break your data-entry into two parts, a date and a time-of-day.

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

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

Your Question is not clear. Do you want to interpret the date and the time entered by the user to be in UTC? Or in another time zone?

你的问题不清楚。您想将用户输入的日期和时间解释为 UTC 吗?还是在另一个时区?

If you meant UTC, create a OffsetDateTimewith an offset using the constant for UTC, ZoneOffset.UTC.

如果您的意思是 UTC,请OffsetDateTime使用 UTC 常量创建带有偏移量的ZoneOffset.UTC.

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

If you meant another time zone, combine along with a time zone object, a ZoneId. But which time zone? You might detect a default time zone. Or, if critical, you must confirm with the user to be certain of their intention.

如果您的意思是另一个时区,请与时区对象结合使用ZoneId. 但哪个时区?您可能会检测到默认时区。或者,如果关键,您必须与用户确认以确定他们的意图。

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

To get a simpler object that is always in UTC by definition, extract an Instant.

要获得根据定义始终采用 UTC 的更简单对象,请提取Instant.

Instant instant = odt.toInstant() ;

…or…

…或者…

Instant instant = zdt.toInstant() ; 

Send to your database.

发送到您的数据库。

myPreparedStatement.setObject( … , instant ) ;


About java.time

关于 java.time

The java.timeframework is built into Java 8 and later. These classes supplant the troublesome old legacydate-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

The Joda-Timeproject, now in maintenance mode, advises migration to the java.timeclasses.

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

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310

Where to obtain the java.time classes?

从哪里获得 java.time 类?

The ThreeTen-Extraproject extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

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