2016-08-23 38 views
15

我們在我們的數據存儲中有有效的&到期時間的記錄。該信息使用Instant的字符串表示形式存儲。Instant toString prepends plus

有些記錄永遠不會過期。但由於過期日期的值是強制性的,我們決定存儲Instant.MAX的字符串表示形式。

到目前爲止這麼好。我們有搜索用例來返回在輸入時間範圍[s,e]內有效的所有記錄。我們查詢數據存儲並返回滿足條件的所有這些記錄[Si, Ei]Si < s && e < Ei請注意,字符串表示正在這裏進行比較。

現在問題在於+被預置爲Instant.MAX的字符串表示形式。自ASCII('+') < ASCII(digit)以來,這是失敗條件e < Ei

我寫了一段代碼就知道之後second,則+開始變得前綴:

Long e = Instant.now().getEpochSecond()*1000; 
for (int i = 0; i < 5; i++) { 
    System.out.println(e + "->" + Instant.ofEpochMilli(e)); 
    e *= 10; 
} 

它打印:

1471925168000->2016-08-23T04:06:08Z 
14719251680000->2436-06-07T17:01:20Z 
147192516800000->6634-05-07T02:13:20Z 
1471925168000000->+48613-06-14T22:13:20Z 
14719251680000000->+468404-07-08T06:13:20Z 

我已經截斷+的選項在堅持數據存儲之前。我更感興趣的是爲什麼會發生這種情況,我們如何明確地避免它?

+6

領先的加號是超出了9999年的所有有效的ISO-8601格式恕我直言,這是一個有點可疑的使用有限值(Instant.MAX)實現無限邊界還是要靠字符串比較。基於即時對象比較,您能否僅從數據庫中檢索實例並在您的應用服務器層中查詢?或者你可以在數據庫/商店中存儲經過的秒數(如數字)。 –

+0

謝謝你指出。比較應該在數據庫本身進行,唯一允許的比較是整數和字符串。我已經使用了字符串表示而不是紀元秒,以便在手動查詢數據時它看起來更具可讀性。 – nitish712

+0

這是可悲的是,ISO委員會選擇+作爲前綴年超越9999的ISO格式的一個很好的特性是標準的ASCII/Unicode字符串排序,時間排序相吻合,這是由符號+侵犯。任何一封信都會保留在9999之後。再加上一些調整,將它延長到0000之前的日期看起來也是可能的。 – chi

回答

10

回答你的問題「?爲什麼」隱藏在DateTimeFormatterBuilder.InstantPrinterParser.format()實施(我離開了爲簡潔無關的代碼):

// use INSTANT_SECONDS, thus this code is not bound by Instant.MAX 
Long inSec = context.getValue(INSTANT_SECONDS); 
if (inSec >= -SECONDS_0000_TO_1970) { 
    // current era 
    long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970; 
    long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1; 
    long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS); 
    LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC); 
    if (hi > 0) { 
     buf.append('+').append(hi); 
    } 
    buf.append(ldt); 
} 

正如你所看到的,它會檢查的10000年的時間界限,如果值超過了它們中的至少一個,它增加+和這些時期的數量。

因此,爲防止這種行爲,劃時代範圍內保持你的最大日期,不要使用Instant.MAX

+0

感謝您指出確切的代碼! – nitish712

2

如果您希望能夠支持對字符串值進行排序,則需要確保您永遠不會超過00009999的年份範圍。

這意味着用Instant.parse("9999-12-31T23:59:59Z")替換Instant.MAX,這也是大多數RDBMS可以處理的最大日期。

要跳過解析步驟,請使用Instant.ofEpochSecond(253402300799L)


然而,而不是設置一個開放式的日期範圍內的「最大」值,使用空值,即有沒有「最大」值。

你會當更改Si < s && e < Ei條件:

Si < s && (Ei == null || e < Ei)

此外,你可能缺少這一條件的=。在Java中,範圍通常較低,包容,上獨佔(例如見substring()subList()等),所以用這個:

Si <= s && (Ei == null || e < Ei)

+0

有問題的數據存儲區是DynamoDB,需要有一個非空值才能使範圍查詢正常工作。 – nitish712

2

大多數日期格式化的默認行爲是前面加上一個加號到一年,如果是超過4個位數更長。這適用於解析和格式。例如見Year.parse()部分here。該格式是相當多烤成DateTimeFormatter.ISO_INSTANT(見@ AndrewLygin的答案),所以如果你想改變它,你將不得不您的即時轉換爲ZonedDateTime(因爲瞬間沒有自然領域),並使用自定義格式:

DateTimeFormatter formatter = new DateTimeFormatterBuilder() 
     .parseCaseInsensitive() 
     .appendValue(ChronoField.YEAR, 4, 10, SignStyle.NORMAL) 
     .appendLiteral('-') 
     .appendValue(ChronoField.MONTH_OF_YEAR, 2) 
     .appendLiteral('-') 
     .appendValue(ChronoField.DAY_OF_MONTH, 2) 
     .appendLiteral('T') 
     .append(DateTimeFormatter.ISO_OFFSET_TIME) 
     .toFormatter(); 

String instant = formatter 
     .format(Instant.ofEpochMilli(e).atOffset(ZoneOffset.UTC)); 

的這裏關鍵是SignStyle.NORMAL,而不是默認的SignStyle.EXCEEDS_PAD如果一年越過4位填充這會在前面加上+

+0

一個很大的錯誤,這段代碼不適用於'Instant.MAX'。我自己測試過,也嘗試過。 Reason對於支持'Instant'的值範圍來說是奇怪的選擇,超過了''LocalDate''類支持的範圍。否則一個好主意,選擇一個適當的標誌風格。 –