Timestamp.getTime()将时间戳值视为系统时区中的时间



假设我有一个时间戳值。

编辑

Calendar curCal = new GregorianCalendar(TimeZone.getDefault());
    curCal.setTimeInMillis(System.currentTimeMillis());
    TimeZone fromTz = TimeZone.getDefault();            
    curCal.setTimeZone(fromTz);            
    TimeZone gmtTZ = TimeZone.getTimeZone("GMT");            
    Calendar toCal = new GregorianCalendar(gmtTZ);
    toCal.setTimeInMillis(curCal.getTimeInMillis());
    Date dd = toCal.getTime();
    SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss a",Locale.US);
    format.setTimeZone(gmtTZ);
    String ff = format.format(dd);
    java.sql.Timestamp curTimeInGMT = new java.sql.Timestamp(dateInLong(ff, "dd/MM/yyyy hh:mm:ss a"));

现在我使用getTime()得到上述时间的毫秒值;

Long l = t.getTime();

根据Java文档,getTime()方法的定义如下Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Timestamp object.

因此,根据我多次测试的理解,getTime()将给出给定时间与1970年1月1日00:00:00 GMT之间的毫秒差。

getTime()要获得1970年1月1日00:00:00 GMT的差值,需要另一个GMT时间。因此,它需要将给定的时间转换为GMT时间。对于这种转换,它需要给定时间的时区。它会将给定时间的时区视为系统时区,并获得相应的GMT时间,然后它会找到两个GMT时间之间的差异,并返回差异。

我的理解正确吗?

我真的无法评估你的理解,但这是我的:

Timestamp类继承自Date。根据您从Javadoc中引用的内容,Date只是一个长值的包装器。发生的情况是,字符串被转换为时间值(不知何故——如果你想共享你的机制,请更新代码片段)。这隐式或显式使用时区,但生成的长值与时区无关,与在给定时间在转换中使用的时区中调用System.currentTimeMillis()时相同。如果您想控制用于转换的时区,可以使用SimpleDateFormat并按如下方式设置时区:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
  simpleDateFormat.parse("2016-01-08 08:03:52.0");
} catch (ParseException e) {
  // handle the error here
}

根据您的编辑(缺少函数dateInLong的定义),我创建了以下测试类,在其中添加了一些控制台输出:

public class test {
  @Test
  public void test() throws ParseException {
    Calendar curCal = new GregorianCalendar(TimeZone.getDefault());
    curCal.setTimeInMillis(System.currentTimeMillis());
    System.out.println("curCal 1:   " + curCal.getTimeInMillis());
    TimeZone fromTz = TimeZone.getDefault();
    curCal.setTimeZone(fromTz);
    System.out.println("curCal 2:   " + curCal.getTimeInMillis());
    TimeZone gmtTZ = TimeZone.getTimeZone("GMT");
    Calendar toCal = new GregorianCalendar(gmtTZ);
    toCal.setTimeInMillis(curCal.getTimeInMillis());
    Date dd = toCal.getTime();
    System.out.println("dd:         " + dd.getTime());
    SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss a", Locale.US);
    format.setTimeZone(gmtTZ);
    String ff = format.format(dd);
    long time = dateInLong(ff, "dd/MM/yyyy hh:mm:ss a");
    System.out.println("time:       " + time);
    java.sql.Timestamp curTimeInGMT = new java.sql.Timestamp(time);
    System.out.println("curTimeGMT: " + curTimeInGMT.getTime());
  }
  private long dateInLong(String dateString, String formatStr) throws ParseException {
    SimpleDateFormat format = new SimpleDateFormat(formatStr);
    return format.parse(dateString).getTime();
  }
}

它产生以下输出:

curCal 1:   1452603245943
curCal 2:   1452603245943
dd:         1452603245943
time:       1452599645000
curTimeGMT: 1452599645000

正如您所看到的,内部long值唯一发生变化的情况是,由于使用了两个不同的时区(从String:GMT,从String:default-CET),日期被转换为String和从String转换(即在调用dateInLong之后)。只要传递内部的long值,时间时刻就会保持不变——无论它是由Calendar还是Date的后代包裹)。

两点将使这项工作变得更容易:

  • 使用日期-时间对象而不是字符串
    应该使用JDBC从数据库中提取java.sql.Timestamp对象,而不是那些日期-时间值的String表示
  • 使用java 8及更高版本中内置的java.time框架
    避免使用旧的java.util.Date/.Calendar类

首先,我们必须指定一个用于解析输入字符串的格式化程序,或者将其更改为符合java.time中默认使用的ISO8601标准。ISO8601格式接近SQL格式,将中间的空格替换为T

String input = "2016-01-08 08:03:52.0";
String inputIso8601 = input.replace ( " ", "T" );

将该字符串解析为本地日期时间,这意味着任何位置。输入字符串缺少任何时区或UTC信息的偏移量,因此我们从本地开始,然后应用假定的时区。

LocalDateTime localDateTime = LocalDateTime.parse ( inputIso8601 );

让我们应用假定的时区。我随意选择Montréal,但显然你需要知道并使用字符串输入的时区。如果您确定该字符串表示UTC,请使用ZoneOffset.UTC

ZoneId zoneId = ZoneId.of ( "America/Montreal" ); // Or perhaps ZoneOffset.UTC constant.
ZonedDateTime zdt = ZonedDateTime.of ( localDateTime, zoneId );

现在,我们已经准备好转换为java.sql.Timestamp对象,并将其发送到数据库。那个旧类有一个新方法,用于转换到java.time对象或从java.time对象转换。转换需要一个Instant对象,该对象是UTC时间线上的一个时刻。我们可以从我们的ZonedDateTime中提取一个Instant

Instant instant = zdt.toInstant ( );
java.sql.Timestamp ts = java.sql.Timestamp.from ( instant );

转储到控制台。

System.out.println ( "input: " + input + " in ISO 8601: " + inputIso8601 + " is localDateTime: " + localDateTime + " in zoneId: " + zoneId + " is zdt: " + zdt + " gives instant: " + instant + " which converts to java.sql.Timestamp ts: " + ts );

输入:ISO 8601中的2016-01-08 08:03:52.0:2016-01-08T08:03:52.0is localDateTime:2016-01-08T08-03:52 in zoneId:America/Montreal is zdt:2016-01-08C08:03:52-05:00[America/Montreal]给出即时消息:2016-01-08C13:03:52Z,转换为java.sql.Timestamp ts:2016-01-08 05:03:52.0

仔细阅读控制台的输出。记下ts上的时间。这显示了java.sql.Timestamp方法toString在生成日期-时间值的文本表示时静默地应用JVM的当前默认时区的不幸行为。我的JVM有一个默认时区America/Los_Angeles。因此,一天中的时间被调整了(令人困惑)。


关于java.time

java.time框架构建在Java8及更高版本中。这些类取代了诸如java.util.DateCalendar、&SimpleDateFormat

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

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

使用符合JDBC 4.2或更高版本的JDBC驱动程序,您可以直接与数据库交换java.time对象。不需要字符串或java.sql.*类。

从哪里获得java.time类?

  • Java SE 8Java SE 9及更高版本
    • 内置
    • 标准Java API的一部分,带有捆绑实现
    • Java 9添加了一些小功能和修复程序
  • Java SE 6Java SE 7
    • 大部分java.time功能都是向后移植到Java6&7英寸ThreeTen背包
  • 安卓
    • java.time类的Android捆绑包实现的后续版本
    • 对于早期的Android,ThreeTenABP项目适用于ThreeTen Backport(如上所述)。请参阅如何使用ThreeTenABP…

ThreeTen Extra项目通过附加类扩展java.time。这个项目是将来可能添加到java.time的试验场。您可以在这里找到一些有用的类,如IntervalYearWeekYearQuarter等等。

相关内容

  • 没有找到相关文章

最新更新