题
我有一个来自我的应用程序的时间戳值。用户可以位于任何给定的本地时区。
由于该日期用于假设给定时间始终为 GMT 的 WebService,因此我需要将用户的参数从 (EST) 转换为 (GMT)。这是关键点:用户忘记了他的 TZ。他输入了他想要发送到 WS 的创建日期,所以我需要的是:
用户输入: 2008 年 5 月 1 日下午 6:12(美国东部时间)
WS 的参数需要是:2008 年 5 月 1 日下午 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]
解决方案 2
谢谢大家的回复。经过进一步调查后,我得到了正确的答案。正如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)
当前用户的TimeZone:EST
格林尼治标准时间的当前偏差(小时): - 4(通常为-5,除了经过夏令时调整) -
来自ACP的TS:2008-05-01 14:12:00.0
使用GMT和US_EN语言转换自TS的日历日期:5/1/08 6:12 PM(GMT)
其他提示
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()
),则输出如下:
格林威治标准时间12:09:05是美国东部时间8:09:05。DEBUG - 输入日历有日期[Thu Oct 23 12:09:05 2008 EDT]
DEBUG - 抵消是-14400000
DEBUG - 创建GMT cal与日期[Thu Oct 23 08:09:05 EDT 2008]
这里令人困惑的部分是 Calendar.getTime()
返回当前时区的 Date
,并且没有方法可以修改a的时区日历也有基础日期。根据您的Web服务所使用的参数类型,您可能只希望以纪元为单位的毫秒来处理WS协议。
您说该日期与Web服务一起使用,因此我假设在某些时候将其序列化为字符串。
如果是这种情况,你应该看一下 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());
您可以使用 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"));
看起来你的TimeStamp被设置为原始系统的时区。
这已被弃用,但应该可以使用:
cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());
不推荐的方法是使用
Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
但这需要在客户端完成,因为该系统知道它所在的时区。
从一个时区转换到另一个时区的方法(可能是有效的:))。
/**
* 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;
}
日期和时间戳对象是时区遗忘的:它们代表了自纪元以来的特定秒数,而未承诺对该时刻的特定解释为小时和天。 时区仅在 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的输入以及真正需要显式时区指示的输入隔离开来。
过去对我有用的东西是确定用户时区和GMT之间的偏差(以毫秒为单位)。获得偏移后,您可以简单地添加/减去(取决于转换的方式)以在任一时区获得适当的时间。我通常会通过设置Calendar对象的毫秒字段来完成此操作,但我确信您可以轻松地将其应用于时间戳对象。这是我用来获取偏移量的代码
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneId是用户时区的ID(例如EST)。
java.time
现代方法使用 java.time 这些类取代了与最早版本的 Java 捆绑在一起的麻烦的遗留日期时间类。
这 java.sql.Timestamp
class 是那些遗留类之一。不再需要。而是使用 Instant
或使用 JDBC 4.2 及更高版本直接与数据库连接的其他 java.time 类。
这 Instant
类代表时间线上的一个时刻 世界标准时间 分辨率为 纳秒 (最多九 (9) 位小数)。
Instant instant = myResultSet.getObject( … , Instant.class ) ;
如果您必须与现有的 Timestamp
, ,通过添加到旧类中的新转换方法立即转换为 java.time。
Instant instant = myTimestamp.toInstant() ;
要调整到另一个时区,请将时区指定为 ZoneId
目的。指定一个 正确的时区名称 格式为 continent/region
, , 例如 America/Montreal
, Africa/Casablanca
, , 或者 Pacific/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.Date
, Calendar
, & SimpleDateFormat
.
这 乔达时间 项目,现在在 维护模式, ,建议迁移到 java.time 类。
要了解更多信息,请参阅 甲骨文教程. 。并在 Stack Overflow 上搜索许多示例和解释。规格为 JSR 310.
从哪里获取 java.time 类?
- Java SE 8, Java SE 9, , 然后
- 内置。
- 具有捆绑实现的标准 Java API 的一部分。
- Java 9 添加了一些小功能和修复。
- Java SE 6 和 Java SE 7
- 许多 java.time 功能都向后移植到 Java 6 和 7 三十后端口.
- 安卓
- 更高版本的 Android 捆绑了 java.time 类的实现。
- 对于早期的 Android, 三十ABP 项目适应 三十后端口 (上文提到的)。看 如何使用 ThreeTenABP....
这 三十额外 项目通过附加类扩展了 java.time。该项目是 java.time 未来可能添加的内容的试验场。您可能会在这里找到一些有用的类,例如 Interval
, YearWeek
, YearQuarter
, , 和 更多的.