Last active
May 31, 2016 13:45
-
-
Save jodastephen/68857dd344e33bd6c0b3b4d24279d2e4 to your computer and use it in GitHub Desktop.
Proposed OffsetIdPrinterParser changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------- | |
/** | |
* Prints or parses an offset ID. | |
*/ | |
static final class OffsetIdPrinterParser implements DateTimePrinterParser { | |
static final String[] PATTERNS = new String[] { | |
"+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS", "+HHmmss", "+HH:mm:ss", | |
"+H", "+Hmm", "+H:mm", "+HMM", "+H:MM", "+HMMss", "+H:MM:ss", "+HMMSS", "+H:MM:SS", "+Hmmss", "+H:mm:ss", | |
}; // order used in pattern builder | |
static final OffsetIdPrinterParser INSTANCE_ID_Z = new OffsetIdPrinterParser("+HH:MM:ss", "Z"); | |
static final OffsetIdPrinterParser INSTANCE_ID_ZERO = new OffsetIdPrinterParser("+HH:MM:ss", "0"); | |
private final String noOffsetText; | |
private final int type; | |
private final int style; | |
/** | |
* Constructor. | |
* | |
* @param pattern the pattern | |
* @param noOffsetText the text to use for UTC, not null | |
*/ | |
OffsetIdPrinterParser(String pattern, String noOffsetText) { | |
Objects.requireNonNull(pattern, "pattern"); | |
Objects.requireNonNull(noOffsetText, "noOffsetText"); | |
this.type = checkPattern(pattern); | |
this.style = type % 11; | |
this.noOffsetText = noOffsetText; | |
} | |
private int checkPattern(String pattern) { | |
for (int i = 0; i < PATTERNS.length; i++) { | |
if (PATTERNS[i].equals(pattern)) { | |
return i; | |
} | |
} | |
throw new IllegalArgumentException("Invalid zone offset pattern: " + pattern); | |
} | |
private boolean isPaddedHour() { | |
return type < 11; | |
} | |
private boolean isColon() { | |
return style > 0 && (style % 2) == 0; | |
} | |
@Override | |
public boolean format(DateTimePrintContext context, StringBuilder buf) { | |
Long offsetSecs = context.getValue(OFFSET_SECONDS); | |
if (offsetSecs == null) { | |
return false; | |
} | |
int totalSecs = Math.toIntExact(offsetSecs); | |
if (totalSecs == 0) { | |
buf.append(noOffsetText); | |
} else { | |
int absHours = Math.abs((totalSecs / 3600) % 100); // anything larger than 99 silently dropped | |
int absMinutes = Math.abs((totalSecs / 60) % 60); | |
int absSeconds = Math.abs(totalSecs % 60); | |
int bufPos = buf.length(); | |
int output = absHours; | |
buf.append(totalSecs < 0 ? "-" : "+"); | |
if (isPaddedHour() || absHours / 10 > 0) { | |
formatZeroPad(false, absHours, buf); | |
} else { | |
buf.append((char) (absHours + '0')); | |
} | |
if ((style >= 3 && style <= 8) || (style >= 9 && absSeconds > 0) || (style >= 1 && absMinutes > 0)) { | |
formatZeroPad(isColon(), absMinutes, buf); | |
output += absMinutes; | |
if (style == 7 || style == 8 || (style >= 5 && absSeconds > 0)) { | |
formatZeroPad(isColon(), absSeconds, buf); | |
output += absSeconds; | |
} | |
} | |
if (output == 0) { | |
buf.setLength(bufPos); | |
buf.append(noOffsetText); | |
} | |
} | |
return true; | |
} | |
private void formatZeroPad(boolean colon, int value, StringBuilder buf) { | |
buf.append(colon ? ":" : "") | |
.append((char) (value / 10 + '0')) | |
.append((char) (value % 10 + '0')); | |
} | |
@Override | |
public int parse(DateTimeParseContext context, CharSequence text, int position) { | |
int length = text.length(); | |
int noOffsetLen = noOffsetText.length(); | |
if (noOffsetLen == 0) { | |
if (position == length) { | |
return context.setParsedField(OFFSET_SECONDS, 0, position, position); | |
} | |
} else { | |
if (position == length) { | |
return ~position; | |
} | |
if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) { | |
return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen); | |
} | |
} | |
// parse normal plus/minus offset | |
char sign = text.charAt(position); // IOOBE if invalid position | |
if (sign == '+' || sign == '-') { | |
// starts | |
int negative = (sign == '-' ? -1 : 1); | |
boolean isColon = isColon(); | |
boolean paddedHour = isPaddedHour(); | |
int[] array = new int[4]; | |
array[0] = position + 1; | |
int parseType = type; | |
// select parse type when lenient | |
if (!context.isStrict()) { | |
if (isPaddedHour()) { | |
if (isColon || (parseType == 0 && length > position + 3 && text.charAt(position + 3) == ':')) { | |
parseType = 10; | |
} else { | |
parseType = 9; | |
} | |
} else { | |
if (isColon || (parseType == 0 && length > position + 3 && (text.charAt(position + 2) == ':' || text.charAt(position + 3) == ':'))) { | |
parseType = 21; | |
} else { | |
parseType = 20; | |
} | |
} | |
} | |
// parse according to the selected pattern | |
switch (parseType) { | |
case 0: // +HH | |
case 11: // +H | |
parseHour(text, paddedHour, array); | |
break; | |
case 1: // +HHmm | |
case 2: // +HH:mm | |
case 13: // +H:mm | |
parseHour(text, paddedHour, array); | |
parseMinute(text, isColon, false, array); | |
break; | |
case 3: // +HHMM | |
case 4: // +HH:MM | |
case 15: // +H:MM | |
parseHour(text, paddedHour, array); | |
parseMinute(text, isColon, true, array); | |
break; | |
case 5: // +HHMMss | |
case 6: // +HH:MM:ss | |
case 17: // +H:MM:ss | |
parseHour(text, paddedHour, array); | |
parseMinute(text, isColon, true, array); | |
parseSecond(text, isColon, false, array); | |
break; | |
case 7: // +HHMMSS | |
case 8: // +HH:MM:SS | |
case 19: // +H:MM:SS | |
parseHour(text, paddedHour, array); | |
parseMinute(text, isColon, true, array); | |
parseSecond(text, isColon, true, array); | |
break; | |
case 9: // +HHmmss | |
case 10: // +HH:mm:ss | |
case 21: // +H:mm:ss | |
parseHour(text, paddedHour, array); | |
parseOptionalMinuteSecond(text, isColon, array); | |
break; | |
case 12: // +Hmm | |
parsePeek(text, 1, 4, array); | |
break; | |
case 14: // +HMM | |
parsePeek(text, 3, 4, array); | |
break; | |
case 16: // +HMMss | |
parsePeek(text, 3, 6, array); | |
break; | |
case 18: // +HMMSS | |
parsePeek(text, 5, 6, array); | |
break; | |
case 20: // +Hmmss | |
parsePeek(text, 1, 6, array); | |
break; | |
} | |
if (array[0] > 0) { | |
long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]); | |
return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, array[0]); | |
} | |
} | |
// handle special case of empty no offset text | |
if (noOffsetLen == 0) { | |
return context.setParsedField(OFFSET_SECONDS, 0, position, position); | |
} | |
return ~position; | |
} | |
private void parseHour(CharSequence parseText, boolean paddedHour, int[] array) { | |
if (paddedHour) { | |
// parse two digits | |
if (!parseDigits(parseText, false, 1, array)) { | |
array[0] = ~array[0]; | |
} | |
} else { | |
// parse one or two digits | |
parsePeek(parseText, 1, 2, array); | |
} | |
} | |
private void parseMinute(CharSequence parseText, boolean isColon, boolean mandatory, int[] array) { | |
if (!parseDigits(parseText, isColon, 2, array)) { | |
if (mandatory) { | |
array[0] = ~array[0]; | |
} | |
} | |
} | |
private void parseSecond(CharSequence parseText, boolean isColon, boolean mandatory, int[] array) { | |
if (!parseDigits(parseText, isColon, 3, array)) { | |
if (mandatory) { | |
array[0] = ~array[0]; | |
} | |
} | |
} | |
private void parseOptionalMinuteSecond(CharSequence parseText, boolean isColon, int[] array) { | |
if (parseDigits(parseText, isColon, 2, array)) { | |
parseDigits(parseText, isColon, 3, array); | |
} | |
} | |
private boolean parseDigits(CharSequence parseText, boolean isColon, int minOrSec, int[] array) { | |
int pos = array[0]; | |
if (pos < 0) { | |
return true; | |
} | |
if (isColon) { | |
if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') { | |
return false; | |
} | |
pos++; | |
} | |
if (pos + 2 > parseText.length()) { | |
return false; | |
} | |
char ch1 = parseText.charAt(pos++); | |
char ch2 = parseText.charAt(pos++); | |
if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') { | |
return false; | |
} | |
int value = (ch1 - 48) * 10 + (ch2 - 48); | |
if (value < 0 || value > 59) { | |
return false; | |
} | |
array[minOrSec] = value; | |
array[0] = pos; | |
return true; | |
} | |
private void parsePeek(CharSequence parseText, int minDigits, int maxDigits, int[] array) { | |
// scan the text to find the available number of digits up to maxDigits | |
// so long as the number available is minDigits or more, the input is valid | |
// then parse the number of available digits | |
int pos = array[0]; | |
int available = 0; | |
char[] chars = new char[6]; | |
for (int i = 0; i < maxDigits; i++) { | |
if (i > parseText.length()) { | |
break; | |
} | |
char ch = parseText.charAt(pos++); | |
if (ch < '0' || ch > '9') { | |
break; | |
} | |
chars[i] = ch; | |
available++; | |
} | |
if (available< minDigits) { | |
array[0] = ~array[0]; | |
return; | |
} | |
switch (available) { | |
case 1: | |
array[1] = (chars[0] - 48); | |
break; | |
case 2: | |
array[1] = ((chars[0] - 48) * 10 + (chars[1] - 48)); | |
break; | |
case 3: | |
array[1] = (chars[0] - 48); | |
array[2] = ((chars[1] - 48) * 10 + (chars[2] - 48)); | |
break; | |
case 4: | |
array[1] = ((chars[0] - 48) * 10 + (chars[1] - 48)); | |
array[2] = ((chars[2] - 48) * 10 + (chars[3] - 48)); | |
break; | |
case 5: | |
array[1] = (chars[0] - 48); | |
array[2] = ((chars[1] - 48) * 10 + (chars[2] - 48)); | |
array[3] = ((chars[3] - 48) * 10 + (chars[4] - 48)); | |
break; | |
case 6: | |
array[1] = ((chars[0] - 48) * 10 + (chars[1] - 48)); | |
array[2] = ((chars[2] - 48) * 10 + (chars[3] - 48)); | |
array[3] = ((chars[4] - 48) * 10 + (chars[5] - 48)); | |
break; | |
} | |
array[0] = pos; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment