可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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:
User enters: 5/1/2008 6:12 PM (EST)
The parameter to the WS needs to be: 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?
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]
回答1:
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:
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]
12:09:05 GMT is 8:09:05 EDT.
The confusing part here is that Calendar.getTime()
returns you a Date
in 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.
回答2:
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:
// 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)
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)
回答3:
You say that the date is used in connection with web services, so I assume that is serialized into a string at some point.
If this is the case, you should take a look at the setTimeZone method of the DateFormat class. This dictates which time zone that will be used when printing the time stamp.
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());
回答4:
You can solve it with 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\"));
回答5:
It looks like your TimeStamp is being set to the timezone of the originating system.
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.
回答6:
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;
}
回答7:
Date and Timestamp objects 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.
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 change to \"6.12 GMT\".
There is no way to make the SimpleDateFormat that parses \"06:12\" as \"HH:MM\" (defaulting to the local time zone) default to UTC instead; SimpleDateFormat is a bit too smart for its own good.
However, you can convince any SimpleDateFormat instance 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\".
There is no need of explicit setting of GregorianCalendar fields or of retrieving and using timezone and daylight saving time offsets.
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.
回答8:
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
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneId is the id of the user\'s timezone (such as EST).
回答9:
java.time
The modern approach uses the java.time classes that supplanted the troublesome legacy date-time classes bundled with the earliest versions of Java.
The java.sql.Timestamp
class is one of those legacy classes. No longer needed. Instead use Instant
or other java.time classes directly with your database using JDBC 4.2 and later.
The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
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.
Instant instant = myTimestamp.toInstant() ;
To adjust into another time zone, specify the time zone as a ZoneId
object. Specify a proper time zone name in the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter pseudo-zones such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( \"America/Montreal\" ) ;
Apply to the Instant
to produce a ZonedDateTime
object.
ZonedDateTime zdt = instant.atZone( z ) ;
To generate a string for display to the user, search Stack Overflow for DateTimeFormatter
to find many discussions and examples.
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?
If you meant UTC, create a OffsetDateTime
with an offset using the constant for 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.
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
To get a simpler object that is always in UTC by definition, extract an Instant
.
Instant instant = odt.toInstant() ;
…or…
Instant instant = zdt.toInstant() ;
Send to your database.
myPreparedStatement.setObject( … , instant ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
- Java SE 8, Java SE 9, and later
- Built-in.
- Part of the standard Java API with a bundled implementation.
- Java 9 adds some minor features and fixes.
- Java SE 6 and Java SE 7
- Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
- Android
- Later versions of Android bundle implementations of the java.time classes.
- For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project 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.