簡體   English   中英

為什么將“0000:00:00 00:00:00”解析為日期返回-0001-11-28T00:00:00Z?

[英]Why does parsing “0000:00:00 00:00:00” into a Date return -0001-11-28T00:00:00Z?

為什么下面的代碼 output -0001-11-28T00:00:00Z而不是0000-00-00T00:00:00Z

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.TimeZone;

class Main
{
    public static void main (String[] args) throws ParseException
    {
        DateFormat parser = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
        parser.setTimeZone(TimeZone.getTimeZone("GMT"));
        Date date = parser.parse("0000:00:00 00:00:00");
        System.out.println(date.toInstant());
    }
}

我的第一個想法是這是一個時區問題,但 output 比預期日期早了 34 天。

這是一個第 3 方庫,因此我實際上無法修改代碼,但如果我能理解它為什么返回此值,那么也許我可以調整輸入以獲得所需的 output。

如果您想知道, 0000:00:00 00:00:00來自圖像或視頻的EXIF 元數據

請注意,舊版 API 中的年份年份之間沒有區別。 年份0實際上是1 BC 0和日0是無效值,但SimpleDateFormat不會拋出異常,而是錯誤地解析它們。

月份轉換為11的原因:

SimpleDateFormat將文本中的月份數字減1 ,因為java.util.Date0開始。 換句話說,月1SimpleDateFormat解析為0 ,即java.util.DateJan 同樣,月, 0SimpleDateFormat解析為-1 現在,負月份由java.util.Date處理如下:

month = CalendarUtils.mod(month, 12);

並且CalendarUtils#mod定義如下:

public static final int mod(int x, int y) {
    return (x - y * floorDivide(x, y));
}
public static final int floorDivide(int n, int d) {
    return ((n >= 0) ?
            (n / d) : (((n + 1) / d) - 1));
}

因此, CalendarUtils.mod(-1, 12)返回11

java.util.DateSimpleDateFormat充滿了這樣的驚喜。 建議完全停止使用它們並切換到現代日期時間 API

現代日期時間 API:

現代日期時間 API 分別使用yu區分時代年份

y指定時代的年份(時代指定為ADBC )並且始終為正數,而u指定年份,它是帶符號的 (+/-) 數字。

通常,我們不使用+號來寫入正數,但我們總是用-號指定負數。 同樣的規則適用於一年 只要您要使用時代的年份, ADyu都會給您相同的數字。 但是,當您使用時代的年份時,您會得到不同的數字,例如BC年份1 BC指定為year , 0 year-of-era , 2 BC被指定為year , -1等等。

您可以通過以下演示更好地理解它:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class Testing {
    public static void main(String[] args) {
        System.out.println(LocalDate.of(-1, 1, 1).format(DateTimeFormatter.ofPattern("u M d")));
        System.out.println(LocalDate.of(-1, 1, 1).format(DateTimeFormatter.ofPattern("y M d")));
        System.out.println(LocalDate.of(-1, 1, 1).format(DateTimeFormatter.ofPattern("yG M d")));

        System.out.println();

        System.out.println(LocalDate.of(0, 1, 1).format(DateTimeFormatter.ofPattern("u M d")));
        System.out.println(LocalDate.of(0, 1, 1).format(DateTimeFormatter.ofPattern("y M d")));
        System.out.println(LocalDate.of(0, 1, 1).format(DateTimeFormatter.ofPattern("yG M d")));

        System.out.println();

        System.out.println(LocalDate.of(1, 1, 1).format(DateTimeFormatter.ofPattern("u M d")));
        System.out.println(LocalDate.of(1, 1, 1).format(DateTimeFormatter.ofPattern("y M d")));
        System.out.println(LocalDate.of(1, 1, 1).format(DateTimeFormatter.ofPattern("yG M d")));
    }
}

Output:

-1 1 1
2 1 1
2BC 1 1

0 1 1
1 1 1
1BC 1 1

1 1 1
1 1 1
1AD 1 1

現代日期時間 API 如何對待0000:00:00 00:00:00

import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

class Main {
    public static void main(String[] args) {
        DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu:MM:dd HH:mm:ss")
                                                    .withZone(ZoneOffset.UTC)
                                                    .withLocale(Locale.ENGLISH);
        
        ZonedDateTime zdt = ZonedDateTime.parse("0000:00:00 00:00:00", parser);
    }
}

Output:

Exception in thread "main" java.time.format.DateTimeParseException: Text '0000:00:00 00:00:00' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 0
....

使用DateTimeFormatter#withResolverStyle(ResolverStyle.LENIENT)

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss", Locale.ENGLISH)
                .withResolverStyle(ResolverStyle.LENIENT);
        String str = "0000-00-00 00:00:00";

        LocalDateTime ldt = LocalDateTime.parse(str, dtf);
        System.out.println(ldt);
    }
}

Output:

-0001-11-30T00:00

正如其他答案所解釋的,這是使用未進行正確驗證的遺留 class ( SimpleDateFormat )處理無效時間戳(無效的年、月和日值)的結果。

簡而言之...垃圾進,垃圾出1

解決方案:

  1. 重寫使用SimpleDateFormat的代碼,以使用 Java 8 中引入的新日期/時間類。(如果必須使用 Java 7 及更早版本,則使用反向端口。)

  2. 在嘗試將字符串作為日期處理之前,通過測試此特定情況來解決此問題。

    從上下文看來,“0000:00:00 00:00:00”是 EXIF 表示“沒有這樣的日期時間”的方式。 如果是這種情況,那么試圖將其視為日期時間似乎適得其反。 而是將其視為特殊情況。

  3. 如果您無法重寫代碼或解決問題,請針對(第 3 方)庫提交錯誤報告和/或補丁,並希望獲得最好的結果......


1 - 為什么差異恰好是 1 年 34 天有點神秘,但我相信您可以通過深入研究源代碼來找出解釋。 IMO,這不值得努力。 但是,我無法想象為什么格里高利轉變會牽涉其中……

這是因為第 0 年是無效的,它不存在。 https://en.m.wikipedia.org/wiki/Year_zero

月、日也為 0 無效。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM