2016-08-23 3 views
15

有効期限が&であるレコードがデータストアにあります。この情報は、Instantという文字列表現を使用して格納されます。インスタントのtoStringの前にプラス

期限切れにならないレコードがあります。しかし、有効期限の値は必須であるため、Instant.MAXの文字列表現を格納することに決めました。

これまでのところとても良いです。入力時間範囲[s,e]内でアクティブなすべてのレコードを返す検索ユースケースがあります。データストアを照会し、そのようなすべてのレコードを返しますSi < s && e < Eiを満たす[Si, 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形式です。IMHO無限の境界を実現するため、または文字列の比較に頼るために、有限の値(Instant.MAX)を使用するのはちょっと疑問です。インスタントオブジェクトの比較に基づいて、アプリケーションサーバーレイヤーでインスタントを取得したり照会したりすることはできませんか?または、db/storeに経過秒数(数値として)を格納することもできます。 –

+0

それを指摘してくれてありがとう。比較はDB自体で行う必要があり、唯一許される比較は整数と文字列です。私はepoch秒の代わりに文字列表現を使用しているので、データを手動で照会すると読みやすくなります。 – nitish712

+0

ISO委員会が9999年を超えて+をプレフィックスとして選択したことは悲しいことです.ISO形式の優れた特性は、標準ASCII /ユニコードストリングの順序と時刻の順序が一致し、+記号が違反していることです。どんな手紙も9999を超えてそれを保存していたでしょう。さらにいくつか調整すれば、0000より前の日付にこれを拡張することも可能です。 – chi

答えて

10

あなたの質問への答え「?なぜ」:Javaでは、範囲は通常、上位独占的、低含まれていますので、これを使用する(例えばsubstring()subList()などを参照) (私は簡潔にするために無関係なコードを左)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); 
} 

あなたが見ることができるように、それは万年周期の境界をチェックし、値がそれらの少なくとも1つを超えた場合、それが追加されます+とそのような期間の量。

したがって、このような動作を防ぐには、最大期限をエポック境界内に保ちます。Instant.MAXは使用しないでください。

+0

正確なコードを指摘してくれてありがとう! – nitish712

2

文字列値のソートをサポートしたい場合は、0000から9999の年の範囲を決して超えないようにする必要があります。

これは、Instant.MAXInstant.parse("9999-12-31T23:59:59Z")に置き換えることを意味します。これは、ほとんどのRDBMSで処理できる最大の日付です。

解析ステップをスキップするには、Instant.ofEpochSecond(253402300799L)を使用します。


しかし、むしろオープンエンド期間の「最大」値を設定するよりも、ヌル値を使用し、ない「最大」値がある、すなわち。

にごSi < s && e < Ei条件を変更するときは、になります。また、

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

、あなたはおそらく、その状態で=を逃しています。

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

+0

問題のデータストアはDynamoDBであり、範囲クエリが機能するためにはnull以外の値が必要です。 – nitish712

2

ほとんどの日付フォーマッタのデフォルトの動作は、4桁を超える長さの場合は、プラス記号を1つ前に付加することです。これは、解析と書式設定の両方に適用されます。たとえば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)); 

ここで重要なのは、代わりに年は4桁のパディングの上に行けば+を付加しますデフォルトSignStyle.EXCEEDS_PADで、SignStyle.NORMALです。

+0

大きな落とし穴の1つで、このコードは 'Instant.MAX'では機能しません。それを試してみて、試してみてください。理由は、 'LocalDate'のようなサポートされているクラスの範囲を超えて、' Instant'のサポートされている値の範囲の奇妙な選択です。それ以外の場合は、適切なサインスタイルを選択することをお勧めします。 –