【问题标题】:Timestamp.getTime() considers the timestamp value as time in system time zoneTimestamp.getTime() 将时间戳值视为系统时区中的时间
【发布时间】:2016-04-12 20:34:51
【问题描述】:

假设我有一个时间戳值。

编辑

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 时间。对于该转换,它需要给定时间的时区。 它将给定时间的时区视为SYSTEM TIME ZONE,它会得到对应的GMT时间,然后它会找到两个GMT时间之间的差异,并返回差异。

我的理解正确吗?

【问题讨论】:

  • 您显示的不是时间戳,而是时间戳的字符串表示形式……答案完全取决于您如何创建实际的时间戳。
  • 为了理解,我只是将存储在表中的时间戳值复制到这里。
  • 什么表?在数据库表中?数据库列的类型是什么?等等。您需要提供更多信息...
  • 阅读此代码片段让我对您想要实现的目标有些困惑。您将Timestamp 变量命名为curTimeInGMT - 这是一种误导,因为它独立于任何时区。只有当您将时间值与字符串相互转换时,时区才会发挥作用,因为同一时刻根据相关时区的名称不同。当向/从数据库保存/检索值并在前端和后端(例如服务)之间传递它们时会出现类似的问题,因为源可能与目的地使用的时区不同(待详细说明)。

标签: java timezone timestamp


【解决方案1】:

两点会让这项工作变得更容易:

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

首先我们必须指定一个格式化程序来解析你的输入字符串,或者改变它以满足java.time默认使用的ISO 8601标准。 ISO 8601 格式接近 SQL 格式,将中间的空格替换为T

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

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

LocalDateTime localDateTime = LocalDateTime.parse ( inputIso8601 );

让我们应用假定的时区。我随意选择蒙特利尔,但显然您需要知道并使用该字符串输入的任何时区。如果您确定该字符串表示 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 对象相互转换。转换需要一个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.0 是 localDateTime: 2016-01-08T08:03:52 zoneId: America/Montreal 是 zdt: 2016 -01-08T08:03:52-05:00[America/Montreal] 给出即时:2016-01-08T13: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 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

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

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

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

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    【解决方案2】:

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

    Timestamp 类继承自 Date。日期 - 与您从 Javadoc 中引用的内容一致 - 只是长值的包装器。发生的情况是将字符串转换为时间值(不知何故 - 如果您想共享您的机制,请更新代码片段)。这隐式或显式使用时区,但生成的 long 值与时区无关,这与在给定时间在转换中使用的时区中调用 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 值更改的唯一情况是当日期转换为字符串或从字符串转换时(即在调用 dateInLong 之后),即由于使用了两个不同的时区(到字符串:GMT,从字符串:默认值 - 此处为 CET)。只要您传递内部的 long 值,时间点就会保持不变 - 无论它是由 Calendar 还是 Date 的后代包裹。

    【讨论】:

    • 没问题,我只希望我的回答有助于阐明这个问题。
    • 对不起那个时间戳值。我复制了存储在表中的时间并将其粘贴到这里以便理解。我们没有像那样声明时间戳值。我的疑问很简单。当我们调用 getTime() 时,它是在内部将给定时间转换为 GMT 时间并获得差异,还是仅将给定时间视为 GMT 时间并获得差异。因为要找到与一个 GMT 的差异,它需要另一个 GMT 时间
    • getTime() 不执行任何转换,它只是返回长值。此外,没有与 DateTimeZone 关联的 TZ。同样,如果您能分享您如何获得 TimeZone 实例,这可能会有所帮助。
    • 我添加了获取我们正在讨论的时间戳对象的代码。使用它,我将获得当前的 GMT 时间。 PS:代码不是我写的。它是我们项目中的代码
    猜你喜欢
    • 1970-01-01
    • 2018-06-06
    • 1970-01-01
    • 2021-09-22
    • 2018-12-18
    • 1970-01-01
    • 1970-01-01
    • 2011-02-26
    • 2017-04-25
    相关资源
    最近更新 更多