2017-06-05 224 views
1

我需要創建格式化程序來解析時間戳,其中可選的毫秒,微秒或納秒分數。Java 8:如何使用毫秒,微秒或納秒創建DateTimeFormatter?

例如,對於我的要求,我看到以下機會:

DateTimeFormatter formatter = new DateTimeFormatterBuilder() 
            .append(DateTimeFormatter.BASIC_ISO_DATE) 
            .appendLiteral('-') 
            .append(DateTimeFormatter.ISO_LOCAL_TIME) 
            .appendOffset("+HH:mm", "Z") 
            .toFormatter(); 

,或者也可以使用appendFraction(field, minWidth, maxWidth, decimalPoint)

但是,在這些情況下,可以使用任意數量的小數(高達9或maxWidth)解析時間戳。如何實現我們可以在逗號後面(可選)僅分析3,6或9個數字?

應當能夠解析下列時間部分:

  • HH:mm:ss.SSS
  • HH:mm:ss.SSSSSS
  • HH:mm:ss.SSSSSSSSS

但無法解析:HH:mm:ss.SSSS

+3

不要以爲你可以,但你爲什麼在意?接受例如什麼是錯誤的4個小數位?根據[ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)完全有效。 – Andreas

回答

2

DateTimeFormatter只支持寬度範圍,所以這對單個實例是不可能的。你可以使用.appendFraction(NANO_OF_SECOND, #, #, true)其中#是3,6或9那就試試他們按順序做3個獨立的格式化,忽略任何DateTimeParseException直到最後一個人:

private static TemporalAccessor parse(String text) { 
    try { 
     return formatter3.parse(text); 
    } catch (DateTimeParseException e) { 
     // ignore 
    } 
    try { 
     return formatter6.parse(text); 
    } catch (DateTimeParseException e) { 
     // ignore 
    } 
    return formatter9.parse(text); // let this one throw 
} 

另一種選擇是檢查與正則表達式輸入首先,像text.matches("[^.]+(.+\\.(\\d{3}|\\d{6}|\\d{9})\\b.*)?")

3

您可以使用可選部分模式(由[]定界),並創建3個可選部分:1個用於9位數字,另一個用於6位數字,另一個用於3位數字。

DateTimeFormatterBuilder docs,您可以使用S模式(這相當於NANO_OF_SECOND field):

Pattern Count Equivalent builder methods 
------- ----- -------------------------- 
S..S  1..n appendFraction(ChronoField.NANO_OF_SECOND, n, n, false) 

在舊的API(SimpleDateFormat),S is the pattern used for milliseconds,但新的API中又改爲納秒。

所以格式化會這樣創建:

DateTimeFormatter formatter = new DateTimeFormatterBuilder() 
    // here is the same as your code 
    .append(DateTimeFormatter.BASIC_ISO_DATE).appendLiteral('-') 
    // time (hour/minute/seconds) 
    .appendPattern("HH:mm:ss") 
    // optional nanos, with 9, 6 or 3 digits 
    .appendPattern("[.SSSSSSSSS][.SSSSSS][.SSS]") 
    // offset 
    .appendOffset("+HH:mm", "Z") 
    // create formatter 
    .toFormatter(); 

一些測試:

// 3 digits 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.123Z", formatter)); // 2016-12-01T10:30:45.123Z 

// 6 digits 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.123456Z", formatter)); // 2016-12-01T10:30:45.123456Z 

// 9 digits 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.123456789Z", formatter)); // 2016-12-01T10:30:45.123456789Z 

// 4 digits (throws DateTimeParseException: Text '20161201-10:30:45.1234Z' could not be parsed at index 21) 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.1234Z", formatter)); 

輸出是:

2016-12-01T10:30:45.123 Z
2016-12-01T10:30:45.123456Z
2016-12-01T10:30:45.123456789Z
線程「main」中的異常java.time.format.DateTimeParseException:Text'20161201-10:30:45。

  • DateTimeFormatter此實例格式化,因爲它打印所有可選節(:1234Z」無法在指數21


注意事項解析所以納秒將被打印3次):

// don't use it to format, it prints all optional sections 
// (so nanos are printed 3 times: with 9, 6 and 3 digits) 
OffsetDateTime odt = OffsetDateTime.parse("20161201-10:30:45.123Z", formatter); 
System.out.println(formatter.format(odt)); 
// output is 20161201Z-10:30:45.123000000.123000.123Z 

因此,如果您想以其他格式顯示日期,請考慮創建另一個DateTimeFormatter

  • 在您的代碼中,您使用了DateTimeFormatter.ISO_LOCAL_TIME。根據javadoc,在此格式化程序中,秒是可選的。如果你想有相同的行爲,只是改變了時間模式:

    // time (hour/minute) with optional seconds 
    .appendPattern("HH:mm[:ss]") 
    
  • []模式是一個很好的快捷方式,使其可選部分,但你也可以將它們使用創建optionalStart()appendFraction()

    DateTimeFormatter formatter = new DateTimeFormatterBuilder() 
        // here is the same as your code 
        .append(DateTimeFormatter.BASIC_ISO_DATE).appendLiteral('-') 
        // time (hour/minute/seconds) 
        .appendPattern("HH:mm:ss") 
        // optional nanos with 9 digits (including decimal point) 
        .optionalStart() 
        .appendFraction(ChronoField.NANO_OF_SECOND, 9, 9, true) 
        .optionalEnd() 
        // optional nanos with 6 digits (including decimal point) 
        .optionalStart() 
        .appendFraction(ChronoField.NANO_OF_SECOND, 6, 6, true) 
        .optionalEnd() 
        // optional nanos with 3 digits (including decimal point) 
        .optionalStart() 
        .appendFraction(ChronoField.NANO_OF_SECOND, 3, 3, true) 
        .optionalEnd() 
        // offset 
        .appendOffset("+HH:mm", "Z") 
        // create formatter 
        .toFormatter(); 
    

此格式化程序的工作原理與第一個使用[]模式創建的第一個程序完全相同。


副作用

由於@SeanVanGorder注意到in his comment,這種格式具有接收多種模式爲納秒場的副作用,但它只能如果值是相同的:

// side effect 
// multiple nanos values (accepts multiple values if they're all the same) 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.123000.123Z", formatter)); // 2016-12-01T10:30:45.123Z 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.123000000.123Z", formatter)); // 2016-12-01T10:30:45.123Z 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.123000000.123000.123Z", formatter)); // 2016-12-01T10:30:45.123Z 

以上所有行都輸出2016-12-01T10:30:45.123Z,但請注意它們接受所有可選值(如.123000000.123)。由於這些值是相同的,所以解析過程沒有錯誤。

如果值不同,但是,它會引發異常:

// multiple nanos values (throws exception if values are different) 
System.out.println(OffsetDateTime.parse("20161201-10:30:45.123000.124Z", formatter)); // exception 

如果此行爲是不希望的,唯一的選擇是創建許多不同格式器(一個用於每種情況下),並做了for循環,直到獲得有效的解析值(非常類似於this answer)。

首先,我創建了一個方法接收的DateTimeFormatter的列表和一個TemporalQuery來解析字符串轉換成任何你想要的對象:

// method to parse, it receives a list of DateTimeFormatter and a TemporalQuery to convert the parsed string 
public <T> T parse(String input, TemporalQuery<T> query, DateTimeFormatter... formatters) { 
    for (DateTimeFormatter fmt : formatters) { 
     try { 
      // try to parse 
      return fmt.parse(input, query); 
     } catch (Exception e) {} 
    } 

    // none worked, throw exception 
    throw new DateTimeParseException("Text '" + input + "' could not be parsed", input, 0); 
} 

現在你只需要創建格式化,並在使用它們parse方法:

// alternative: have 3 different formatters 
DateTimeFormatter f1 = new DateTimeFormatterBuilder() 
    // here is the same as your code 
    .append(DateTimeFormatter.BASIC_ISO_DATE).appendLiteral('-') 
    // time (hour/minute/seconds/3 digit nanos) 
    .appendPattern("HH:mm:ss.SSS") 
    // offset 
    .appendOffset("+HH:mm", "Z") 
    // create formatter 
    .toFormatter(); 
DateTimeFormatter f2 = new DateTimeFormatterBuilder() 
    // here is the same as your code 
    .append(DateTimeFormatter.BASIC_ISO_DATE).appendLiteral('-') 
    // time (hour/minute/seconds/6 digit nanos) 
    .appendPattern("HH:mm:ss.SSSSSS") 
    // offset 
    .appendOffset("+HH:mm", "Z") 
    // create formatter 
    .toFormatter(); 
DateTimeFormatter f3 = new DateTimeFormatterBuilder() 
    // here is the same as your code 
    .append(DateTimeFormatter.BASIC_ISO_DATE).appendLiteral('-') 
    // time (hour/minute/seconds/9 digit nanos) 
    .appendPattern("HH:mm:ss.SSSSSSSSS") 
    // offset 
    .appendOffset("+HH:mm", "Z") 
    // create formatter 
    .toFormatter(); 

// all formatters 
DateTimeFormatter[] formatters = new DateTimeFormatter[] { f1, f2, f3 }; 

// 3 digits 
System.out.println(parse("20161201-10:30:45.123Z", OffsetDateTime::from, formatters)); // 2016-12-01T10:30:45.123Z 
// 6 digits 
System.out.println(parse("20161201-10:30:45.123456Z", OffsetDateTime::from, formatters)); // 2016-12-01T10:30:45.123456Z 
// 9 digits 
System.out.println(parse("20161201-10:30:45.123456789Z", OffsetDateTime::from, formatters)); // 2016-12-01T10:30:45.123456789Z 

// 4 digits (throws exception) 
System.out.println(parse("20161201-10:30:45.1234Z", OffsetDateTime::from, formatters)); 
// java.time.format.DateTimeParseException: Text '20161201-10:30:45.1234Z' could not be parsed 

// multiple values (throws exception) 
System.out.println(parse("20161201-10:30:45.123000.123Z", OffsetDateTime::from, formatters)); 
// java.time.format.DateTimeParseException: Text '20161201-10:30:45.123000.123Z' could not be parsed 

請注意,我用的方法參考OffsetDateTime::fromTemporalQuery,但你可以把它改成你需要的任何查詢。

+0

我在想這會允許多個小數(如'.123456.123'),但經過測試,它看起來像DateTimeFormatter不接受同一字段的多個不同的值。但它確實接受'.123000.123',但我懷疑這是一個問題。 –

+0

@SeanVanGorder事實上,這是對同一領域有很多可選部分的副作用。但正如你所說的,它只有在值相同時(如在你的例子中,或者.123000000.123000.123或者.123000000.123)才接受。如果輸入與'.123000.456'類似,則會引發異常。我認爲這可以通過驗證輸入來避免 - 或者您不需要,如果確保輸入字符串正確生成(OP沒有詳細說明輸入是如何生成的,但我假設它們來自日期/時間對象)。無論如何,我同意這是一個小問題。謝謝! – 2017-06-06 16:41:41