通过 calendsr 或 simpleDateFormatter 解析日期时间的不同结果

Different result in parsing datetime via calendsr or simpleDateFormatter

提问人:Wizzard 提问时间:3/16/2018 最后编辑:Wizzard 更新时间:3/16/2018 访问量:694

问:

我现在使用 java 1.6 并遇到奇怪的行为,可能是错误,这是代码:

import org.junit.Test;

import javax.xml.bind.DatatypeConverter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class TestDate {
@Test
public void testConvert() throws Exception {
    Calendar parsedCalendar = DatatypeConverter.parseDateTime("0001-01-01T00:00:00");
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    Date sdfDate = simpleDateFormat.parse("0001-01-01T00:00:00");

    Calendar parsedCalendar2 = DatatypeConverter.parseDateTime("1980-03-01T00:00:00");
    SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    Date sdfDate2 = simpleDateFormat2.parse("1980-03-01T00:00:00");


    System.out.println("parsedCalendar: " + parsedCalendar.getTimeInMillis());
    System.out.println("parsedCalendar TZ: " + parsedCalendar.getTimeZone());
    System.out.println("parsedCalendar Date: " + parsedCalendar.getTime());
    System.out.println("sdfDate: " + sdfDate);
    System.out.println("sdfDate millis: " + sdfDate.getTime());

    System.out.println("parsedCalendar2: " + parsedCalendar2.getTimeInMillis());
    System.out.println("parsedCalendar2 TZ: " + parsedCalendar2.getTimeZone());
    System.out.println("parsedCalendar2 Date: " + parsedCalendar2.getTime());
    System.out.println("sdfDate2: " + sdfDate2);
    System.out.println("sdfDate2 millis: " + sdfDate2.getTime());

}
}

这是问题:debug

输出:

parsedCalendar: -62135622000000
parsedCalendar TZ: sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]
parsedCalendar Date: Mon Jan 03 00:00:00 NOVT 1
sdfDate: Sat Jan 01 00:00:00 NOVT 1
sdfDate millis: -62135794800000

parsedCalendar2: 320691600000
parsedCalendar2 TZ: sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]
parsedCalendar2 Date: Sat Mar 01 00:00:00 NOVT 1980
sdfDate2: Sat Mar 01 00:00:00 NOVT 1980
sdfDate2 millis: 320691600000

调试:

 parsedCalendar.getTimeInMillis() = -62135622000000
    sdfDate.getTime() = -62135794800000
    parsedCalendar.getTime() = {Date@790} "Mon Jan 03 00:00:00 NOVT 1"
    sdfDate = {Date@759} "Sat Jan 01 00:00:00 NOVT 1"
    parsedCalendar2.getTimeInMillis() = 320691600000
    sdfDate2.getTime() = 320691600000
    parsedCalendar2.getTimeZone() = {ZoneInfo@755} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"
    parsedCalendar.getTimeZone() = {ZoneInfo@756} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"
    simpleDateFormat.getTimeZone() = {ZoneInfo@757} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"
    simpleDateFormat2.getTimeZone() = {ZoneInfo@758} "sun.util.calendar.ZoneInfo[id="Asia/Novosibirsk",offset=25200000,dstSavings=0,useDaylight=false,transitions=67,lastRule=null]"

正如您在解析 0001 dateTime 时所看到的,毫秒存在差异!在解析 1980 年时,情况并非如此。 谁能解释为什么?

Java 日期 解析 DateTime 日历

评论

1赞 Jon Skeet 3/16/2018
我强烈怀疑,如果字符串中没有偏移量,则假设时区为 UTC,而将默认为系统默认时区。我建议你看看.DatatypeConverterSimpleDateFormatparsedCalendar.getTimeZone()
1赞 Wizzard 3/16/2018
如果 TZ 差异存在问题,则在这两种情况下都会有问题。我添加了具有相同参数的第二种情况来证明这一点。
0赞 Jon Skeet 3/16/2018
“如果 TZ 差异存在问题,则这两种情况都会存在。”否 - 如果系统默认时区在 1980-03-01T00:00:00 处的 UTC 偏移量为 0,但在 0001-01-01T00:00:00 处的 UTC 偏移量为非零,您将看到您描述的行为。与其争辩说我一定是错的,为什么不按照我的建议去看呢?parsedCalendar.getTimeZone()
0赞 Jon Skeet 3/16/2018
(如果您告诉我们您正在观察此问题的系统默认时区,这也会有所帮助,以便我们可以重现该问题。
1赞 Jon Skeet 3/16/2018
我的意思是它们没有在您的代码中打印出来。如果我们复制/粘贴/运行您的代码,它打印出来的只是“一些东西”。相反,您应该将示例打印出所有相关信息,然后将该程序的确切输出包含在您的问题中。

答:

3赞 Jon Skeet 3/16/2018 #1

这是由于儒略历和公历系统之间的差异。

SimpleDateFormat使用默认的日历系统,我相信它在您的系统和我的系统上都有。 (尽管它的名字)根据属性在公历系统和儒略历系统之间切换。它假定在该转换之后提供的任何日期都是公历,而在此之前提供的任何日期都是儒略历。默认直接转换为 1582 年。GregorianCalendarGregorianCalendargregorianChange

DatatypeConverter改用纯公历,因为这是 W3C XML 架构文档所要求的。

这意味着,如果你在日历切换之前解析一个值,你会看到一个很大的差异 - 而且随着时间的进一步发展,这种差异会越来越小,每400年有3天的差异。(不能被 400 整除的三个世纪年,因此在儒略历中是闰年,但在公历中则不然。

如果您将日历设置为您首先调用的日历,则两者将达成一致。SimpleDateFormatGregorianCalendarsetGregorianChange(Long.MIN_VALUE)

下面代码可以更轻松地探索差异:

import javax.xml.bind.DatatypeConverter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class Test {    
    public static void main(String[] args) throws ParseException {
        convert("0001-01-01T00:00:00");
        convert("1000-01-01T00:00:00");
        convert("1580-01-01T00:00:00");
        convert("1590-01-01T00:00:00");
        convert("1980-03-01T00:00:00");
    }

    private static void convert(String input) throws ParseException {
        Calendar datatypeConverterResult = DatatypeConverter.parseDateTime(input);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        Date sdfResult = simpleDateFormat.parse(input);

        System.out.println("Input: " + input);
        long datatypeConverterMillis = datatypeConverterResult.getTimeInMillis();
        long sdfResultMillis = sdfResult.getTime();
        long days = TimeUnit.MILLISECONDS.toDays(datatypeConverterMillis - sdfResultMillis);
        System.out.println("DatatypeConverter epoch millis: " + datatypeConverterMillis);
        System.out.println("SimpleDateTime epoch millis: " + sdfResultMillis);
        System.out.println("Difference in days: " + days);
        System.out.println("Parsed calendar time zone: " + datatypeConverterResult.getTimeZone().getID());
        System.out.println();
    }
}

请注意,在 Java 9 上,您需要显式指定模块。这是最简单的 java.se.ee:

$ javac Test.java --add-modules java.se.ee
$ java --add-modules java.se.ee Test

我的盒子上的输出:

Input: 0001-01-01T00:00:00
DatatypeConverter epoch millis: -62135596800000
SimpleDateTime epoch millis: -62135769600000
Difference in days: 2
Parsed calendar time zone: Europe/London

Input: 1000-01-01T00:00:00
DatatypeConverter epoch millis: -30610224000000
SimpleDateTime epoch millis: -30609792000000
Difference in days: -5
Parsed calendar time zone: Europe/London

Input: 1580-01-01T00:00:00
DatatypeConverter epoch millis: -12307248000000
SimpleDateTime epoch millis: -12306384000000
Difference in days: -10
Parsed calendar time zone: Europe/London

Input: 1590-01-01T00:00:00
DatatypeConverter epoch millis: -11991628800000
SimpleDateTime epoch millis: -11991628800000
Difference in days: 0
Parsed calendar time zone: Europe/London

Input: 1980-03-01T00:00:00
DatatypeConverter epoch millis: 320716800000
SimpleDateTime epoch millis: 320716800000
Difference in days: 0
Parsed calendar time zone: Europe/London

评论

0赞 Wizzard 3/16/2018
呵呵,实际上,当我发布我的第一个问题/示例时,我只期望得到您的第一段或第一段和第二段之类的答案......