10000년까지 살아 있을지는 모르겠지만 테스트 케이스를 만들었는데
LocalDate.parse("10000-01-01")
위와 같은 코드는 아래와 같은 파싱 예외가 발생한다.
java.time.format.DateTimeParseException: Text '10000-01-01' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.LocalDate.parse(LocalDate.java:400)
at java.time.LocalDate.parse(LocalDate.java:385)
사실 원인은 포매터가 아래와 같이 구현되어 있기 때문이다.
public final class LocalDate
implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable {
// ..
public static LocalDate parse(CharSequence text) {
return parse(text, DateTimeFormatter.ISO_LOCAL_DATE);
}
public static final DateTimeFormatter ISO_LOCAL_DATE;
static {
ISO_LOCAL_DATE = new DateTimeFormatterBuilder()
.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(DAY_OF_MONTH, 2)
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
}
분명 java.time.temporal.ChronoField#YEAR 를 추가할 때 최소 4자리 최대 10자리라고 되어 있다.
근데 왜 고작 5자리가 파싱이 안되는 걸까?
DateTimeFormatterBuilder에 있는 NumberPinterParser는 아래와 같이 필드 및 생성자가 구현되어 있다.
static class NumberPrinterParser implements DateTimePrinterParser {
final TemporalField field;
final int minWidth;
final int maxWidth;
private final SignStyle signStyle;
final int subsequentWidth;
/**
* Constructor.
*
* @param field the field to format, not null
* @param minWidth the minimum field width, from 1 to 19
* @param maxWidth the maximum field width, from minWidth to 19
* @param signStyle the positive/negative sign style, not null
*/
NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
// validated by caller
this.field = field;
this.minWidth = minWidth;
this.maxWidth = maxWidth;
this.signStyle = signStyle;
this.subsequentWidth = 0;
}
DateTimeFormatterBuilder의 파싱하는 부분을 살펴보면 아래부분이 초과시에 기호를 붙이는지 여부를 검사를 하는 것을 알 수 있다.
if (signStyle == SignStyle.EXCEEDS_PAD && context.isStrict()) 안쪽이다.
주석도 해당 내용을 설명하고 있다.
// static class NumberPrinterParser implements DateTimePrinterParser {
@Override
public int parse(DateTimeParseContext context, CharSequence text, int position) {
// ..
for (int pass = 0; pass < 2; pass++) {
int maxEndPos = Math.min(pos + effMaxWidth, length);
while (pos < maxEndPos) {
// .. 길이마다 처리하는 부분
}
if (negative) {
if (totalBig != null) {
if (totalBig.equals(BigInteger.ZERO) && context.isStrict()) {
return ~(position - 1); // minus zero not allowed
}
totalBig = totalBig.negate();
} else {
if (total == 0 && context.isStrict()) {
return ~(position - 1); // minus zero not allowed
}
total = -total;
}
} else if (signStyle == SignStyle.EXCEEDS_PAD && context.isStrict()) {
int parseLen = pos - position;
if (positive) {
if (parseLen <= minWidth) {
return ~(position - 1); // '+' only parsed if minWidth exceeded
}
} else {
if (parseLen > minWidth) {
return ~position; // '+' must be parsed if minWidth exceeded
}
}
}
// ..
결국 + 가 없는 채로 파싱이 되면 parseLen이 최소 길이보다 커지게 되어 응답(position)이 -1로 반환이 되어 에러로 빠지게 된다.
정상이라면 postion이 0보다 크거나 같아야 하기 때문이다.
static final class CompositePrinterParser implements DateTimePrinterParser {
// ..
@Override
public int parse(DateTimeParseContext context, CharSequence text, int position) {
if (optional) {
context.startOptional();
int pos = position;
for (DateTimePrinterParser pp : printerParsers) {
pos = pp.parse(context, text, pos);
if (pos < 0) {
context.endOptional(false);
return position; // return original position
}
}
context.endOptional(true);
return pos;
} else {
for (DateTimePrinterParser pp : printerParsers) {
position = pp.parse(context, text, position);
if (position < 0) {
break;
}
}
return position;
}
}
레퍼런스 체크!
그런데 Java8의 Year 클래스의 parse 메서드의 JavaDoc 문서를 보면 0000 ~ 9999 밖의 년도에는 + 나 - 기호를 붙여야 한다고 되어 있다.
올바른 방법
따라서 해결책은 간단하다.
LocalDate.parse("+10000-01-01")
근데 살아 생전에 3000년도 겪지 않을 텐데 테스트 코드를 만들다 좀 머~언 미래에 갔다왔다.
Y10K 문제
참고로 2000년이 될 때 많은 사람들이 걱정했던 밀레니엄 버그의 다른 버전이 Y10K 문제라고 부른다는 것을 알게되었다.
자세한 것은 아래 위키 백과 참고.
https://en.wikipedia.org/wiki/Year_10,000_problem
'Programing > JVM(Java, Kotlin)' 카테고리의 다른 글
[Java] Object.hashCode JavaDoc - History (0) | 2020.01.29 |
---|---|
[Java] "서로 다른 두 객체는 결코 같은 해시코드를 가질 수 없다."? (0) | 2020.01.28 |
[Java] toString에서 나타나는 [, L 등의 문자의 정체는? (0) | 2020.01.09 |
[Java] String: literal vs new (0) | 2020.01.09 |
[Java] hashCode() internal : String, Object (0) | 2020.01.09 |