Skip to content

Instantly share code, notes, and snippets.

@jodastephen
Last active May 31, 2016 13:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jodastephen/68857dd344e33bd6c0b3b4d24279d2e4 to your computer and use it in GitHub Desktop.
Save jodastephen/68857dd344e33bd6c0b3b4d24279d2e4 to your computer and use it in GitHub Desktop.
Proposed OffsetIdPrinterParser changes
//-----------------------------------------------------------------------
/**
* 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