从毫秒返回1970年1月的日期时失去一小时

时间:2011-04-28 09:31:16

标签: java date milliseconds date-formatting

我有以下代码,它采用一个毫秒的字符串(将来自RSS提要,因此将是一个字符串,下面的示例是一个快速测试程序)并将这些毫秒转换为Date对象。

public static void main(String[] args) {
    String ms = "1302805253";
    SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(Long.parseLong(ms));

    try {
        String dateFormat = dateFormatter.format(calendar.getTime());
        System.out.println("Date Format = " + dateFormat);

        Date dateParse = dateFormatter.parse(dateFormatter.format(calendar.getTime()));
        System.out.println("Date Parse  = " + dateParse);
    } catch (ParseException e) {
        // TODO: handle exception
    }
}


Output:
    Date Format = Fri, 16 Jan 1970 02:53:25 GMT
    Date Parse  = Fri Jan 16 03:53:25 GMT 1970

正如您所看到的,在日历对象的格式化和解析生成的String之间,一小时正在丢失。此外,输出的格式已更改。任何人都可以帮我解释为什么会发生这种情况,以及如何解决这个问题?我希望Date对象的格式与“Date Format”输出格式相同。

2 个答案:

答案 0 :(得分:8)

我相信这种情况正在发生,因为英国在1970年实际上没有使用GMT,而且Java有一个错误...它将在1970年格式化一个日期好像英国正在使用GMT,但没有实际改变偏移量。简单的例子:

Date date = new Date(0);
SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy HH:mm:ss zzz");
sdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));
System.out.println(sdf.format(date));

结果:

01 Jan 1970 01:00:00 GMT

请注意,它声称格林尼治标准时间凌晨1点......这是不正确的。在欧洲/伦敦时间凌晨1点 ,但是欧洲/伦敦没有观察到GMT。

Joda Time得到了正确的结果,它打印出BST - 但Joda Time不喜欢用时区缩写解析值。但是,您可以使用时区 offets 来代替:

import org.joda.time.*;
import org.joda.time.format.*;

public class Test {
    public static void main(String[] args) throws Exception {
        DateTime date = new DateTime(0, DateTimeZone.forID("Europe/London"));

        DateTimeFormatter formatter = DateTimeFormat.forPattern(
            "dd MMM yyyy HH:mm:ss Z");

        String text = formatter.print(date); // 01 Jan 1970 01:00:00 +0100
        System.out.println(text);

        DateTime parsed = formatter.parseDateTime(text);
        System.out.println(parsed.equals(date)); // true
    }
}

答案 1 :(得分:0)

Answer by Jon Skeet是正确的。

java.time

让我们通过java.time运行相同的输入来查看结果。

指定proper time zone name。切勿使用诸如BSTESTIST之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。所以我们使用Europe/London

Instant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds

String input = "1302805253";
long millis = Long.parseLong ( input );
Instant instant = Instant.ofEpochMilli ( millis );

应用时区以生成ZonedDateTime对象。

ZoneId zoneId = ZoneId.of ( "Europe/London" );
ZonedDateTime zdt = instant.atZone ( zoneId );

转储到控制台。我们确实看到Europe/London时间比那时提前一小时。因此,时间为02小时而不是01小时。两者都代表了时间轴上的同一时刻,只是通过两个不同wall-clock times的镜头来观察。

System.out.println ( "input: " + input + " | instant: " + instant + " | zdt: " + zdt );
  

输入:1302805253 |瞬间:1970-01-16T01:53:25.253Z | zdt:1970-01-16T02:53:25.253 + 01:00 [欧洲/伦敦]

整秒

顺便说一下,我怀疑你的输入字符串代表自1970年UTC世纪以来的整个秒,而不是毫秒。解释说,在这个问题发布的月份,我们在2011年获得了一个日期。

String output = Instant.ofEpochSecond ( Long.parseLong ( "1302805253" ) ).atZone ( ZoneId.of ( "Europe/London" ) ).toString ();
  

2011-04-14T19:20:53 + 01:00 [欧洲/伦敦]

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了旧的麻烦日期时间类,例如java.util.Date.Calendar和& java.text.SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。

大部分java.time功能都被反向移植到Java 6& ThreeTen-Backport中的7,并进一步适应Android中的ThreeTenABP

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。