为什么GregorianCalendar设置方法会给出错误时间略有不一致的错误?

问题描述

在NetBeans中,我运行下面的代码

一次运行的输出

Intended time   = 1970-0-1 0:0:0.000
GC time in ms   = 102
Reformated GC time = 1970-01-01 00:00:00.102
Deprecated Date ms = 0
Deprecated time    = 1970-01-01 00:00:00.000

另一次运行的输出

Intended time   = 1970-0-1 0:0:0.000
GC time in ms   = 575
Reformated GC time = 1970-01-01 00:00:00.575
Deprecated Date ms = 0
Deprecated time    = 1970-01-01 00:00:00.000

Q1:为什么重新格式化的时间(通过GregorianCalendar set(...)方法)具有额外的毫秒数-已过时-使用新的Date(...)不会!

Q2:为什么每次运行的毫秒数不同?

    public class TestGregorianCalendar {
    public static void main(String[] args){
        String timeZonestring = "GMT+00";
        GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone(timeZonestring));
        int year = 1970;
        int month = 0;  // Jan
        int day = 1;
        int hour  = 0;
        int min  = 0;
        int sec  = 0;
        calendar.set(year,month,day,hour,min,sec);
        long ms;
        Date date;
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        df.setTimeZone(TimeZone.getTimeZone(timeZonestring));
        System.out.println("Intended time   = "+year+"-"+month+"-"+day
            +" "+hour+":"+min+":"+sec+".000");
        calendar.set(year,sec);
        ms = calendar.getTimeInMillis();
        System.out.println("GC time in ms   = "+Long.toString(ms));
        date = new Date(ms);
        System.out.println("Reformated GC time = "+df.format(date));
        Date deprecatedDate = new Date(year-1900,hour+1,sec);
        System.out.println("Deprecated Date ms = "+Long.toString(deprecatedDate.getTime()));
        System.out.println("Deprecated time    = "+df.format(deprecatedDate));  
    }
}

感谢您的见解!

解决方法

java.time

我建议您使用java.time(现代的Java日期和时间API)进行日期和时间工作。

    OffsetDateTime dateTime = OffsetDateTime.of(1970,1,ZoneOffset.UTC);
    System.out.println(dateTime);
    
    long ms = dateTime.toInstant().toEpochMilli();
    System.out.println(ms);

输出:

1970-01-01T00:00Z
0

没有多余的毫秒数,否则它们将被打印两次,每行输出一次。 java.time的数字与人类的月份相同,因此我将一月的数值设为1。OffsetDateTIme.of()的参数列表中的最后0为纳秒,并确保秒的分数为0。>

如果要格式化输出:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSS");
    System.out.println(dateTime.format(formatter));

1970-01-01 00:00:00.000

您的代码出了什么问题?

评论中的Mark Rotteveel已经回答了您的两个问题。 new GregorianCalendar(TimeZone.getTimeZone(timeZoneString))创建了一个GregorianCalendar,代表毫秒在指定时区中的当前时刻。 calendar.set(year,month,day,hour,min,sec)将上述字段设置为这些值。它不会更改其他字段,例如age和ms(秒)。因此,您获得的毫秒值反映了GregorianCalendar对象创建的秒数,显然每次都不相同。这解释了明显的随机变化。这只是GregorianCalendar众多令人困惑的方面之一。我建议您不要使用该课程。

new Date(year-1900,hour+1,sec) –使用了自1997年2月起就被弃用的构造函数,因为它在各个时区中均不能可靠地工作–单独使用这些值创建了一个新的Date对象,因此无需查看时钟,因此在这里毫秒是0。

链接

Oracle tutorial: Date Time解释了如何使用java.time。

,

Instant.EPOCH

Instant.EPOCH1970-01-01T00:00:00Z时期的常量。

import java.time.Instant;

public class Main {
    public static void main(String[] args) {
        Instant instant = Instant.EPOCH;
        System.out.println(instant);
    }
}

输出:

1970-01-01T00:00:00Z

如果您需要一个表示该瞬间的日期时间字符串为不同的模式,则可以使用Instant#atOffset将其转换为OffsetDateTime,然后使用DateTimeFormatter将其格式化为{所需的模式。

import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        Instant instant = Instant.EPOCH;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSS");
        String formatted = formatter.format(instant.atOffset(ZoneOffset.UTC));
        System.out.println(formatted);
    }
}

输出:

1970-01-01 00:00:00.000

java.util的日期时间API及其格式设置SimpleDateFormat已过时且容易出错。我建议您应该完全停止使用它们,并切换到modern date-time API。在 Trail: Date Time 上了解有关现代日期时间API的更多信息。