< prev index next >
src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java
Print this page
*** 926,936 ****
--- 926,953 ----
* <li>{@code +HH:MM:SS} - hour, minute and second, with colon
* <li>{@code +HHmmss} - hour, with minute if non-zero or with minute and
* second if non-zero, no colon
* <li>{@code +HH:mm:ss} - hour, with minute if non-zero or with minute and
* second if non-zero, with colon
+ * <li>{@code +H} - hour only, ignoring minute and second
+ * <li>{@code +Hmm} - hour, with minute if non-zero, ignoring second, no colon
+ * <li>{@code +H:mm} - hour, with minute if non-zero, ignoring second, with colon
+ * <li>{@code +HMM} - hour and minute, ignoring second, no colon
+ * <li>{@code +H:MM} - hour and minute, ignoring second, with colon
+ * <li>{@code +HMMss} - hour and minute, with second if non-zero, no colon
+ * <li>{@code +H:MM:ss} - hour and minute, with second if non-zero, with colon
+ * <li>{@code +HMMSS} - hour, minute and second, no colon
+ * <li>{@code +H:MM:SS} - hour, minute and second, with colon
+ * <li>{@code +Hmmss} - hour, with minute if non-zero or with minute and
+ * second if non-zero, no colon
+ * <li>{@code +H:mm:ss} - hour, with minute if non-zero or with minute and
+ * second if non-zero, with colon
* </ul>
+ * Patterns containing "HH" will format and parse a two digit hour,
+ * zero-padded if necessary. Patterns containing "H" will format with no
+ * zero-padding, and parse either one or two digits.
+ * In the lenient mode, parser will be greedy and parse maximum digits possible.
* The "no offset" text controls what text is printed when the total amount of
* the offset fields to be output is zero.
* Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
* Three formats are accepted for parsing UTC - the "no offset" text, and the
* plus and minus versions of zero defined by the pattern.
*** 3474,3489 ****
--- 3491,3508 ----
* 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
*** 3491,3500 ****
--- 3510,3520 ----
*/
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++) {
*** 3503,3512 ****
--- 3523,3540 ----
}
}
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;
*** 3518,3536 ****
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 ? "-" : "+")
! .append((char) (absHours / 10 + '0')).append((char) (absHours % 10 + '0'));
! if ((type >= 3 && type < 9) || (type >= 9 && absSeconds > 0) || (type >= 1 && absMinutes > 0)) {
! buf.append((type % 2) == 0 ? ":" : "")
! .append((char) (absMinutes / 10 + '0')).append((char) (absMinutes % 10 + '0'));
output += absMinutes;
! if (type == 7 || type == 8 || (type >= 5 && absSeconds > 0)) {
! buf.append((type % 2) == 0 ? ":" : "")
! .append((char) (absSeconds / 10 + '0')).append((char) (absSeconds % 10 + '0'));
output += absSeconds;
}
}
if (output == 0) {
buf.setLength(bufPos);
--- 3546,3566 ----
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) {
! 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);
*** 3538,3560 ****
}
}
return true;
}
@Override
public int parse(DateTimeParseContext context, CharSequence text, int position) {
int length = text.length();
int noOffsetLen = noOffsetText.length();
- int parseType = type;
- if (context.isStrict() == false) {
- if ((parseType > 0 && (parseType % 2) == 0) ||
- (parseType == 0 && length > position + 3 && text.charAt(position + 3) == ':')) {
- parseType = 10;
- } else {
- parseType = 9;
- }
- }
if (noOffsetLen == 0) {
if (position == length) {
return context.setParsedField(OFFSET_SECONDS, 0, position, position);
}
} else {
--- 3568,3587 ----
}
}
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 {
*** 3569,3584 ****
// parse normal plus/minus offset
char sign = text.charAt(position); // IOOBE if invalid position
if (sign == '+' || sign == '-') {
// starts
int negative = (sign == '-' ? -1 : 1);
int[] array = new int[4];
array[0] = position + 1;
! if ((parseNumber(array, 1, text, true, parseType) ||
! parseNumber(array, 2, text, parseType >= 3 && parseType < 9, parseType) ||
! parseNumber(array, 3, text, parseType == 7 || parseType == 8, parseType)) == false) {
! // success
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
--- 3596,3686 ----
// 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 (paddedHour) {
! if (isColon || (parseType == 0 && length > position + 3 && text.charAt(position + 3) == ':')) {
! isColon = true; // needed in cases like ("+HH", "+01:01")
! parseType = 10;
! } else {
! parseType = 9;
! }
! } else {
! if (isColon || (parseType == 11 && length > position + 3 && (text.charAt(position + 2) == ':' || text.charAt(position + 3) == ':'))) {
! isColon = true;
! parseType = 21; // needed in cases like ("+H", "+1:01")
! } 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
! parseVariableWidthDigits(text, 1, 4, array);
! break;
! case 14: // +HMM
! parseVariableWidthDigits(text, 3, 4, array);
! break;
! case 16: // +HMMss
! parseVariableWidthDigits(text, 3, 6, array);
! break;
! case 18: // +HMMSS
! parseVariableWidthDigits(text, 5, 6, array);
! break;
! case 20: // +Hmmss
! parseVariableWidthDigits(text, 1, 6, array);
! break;
! }
! if (array[0] > 0) {
! if (array[1] > 23 || array[2] > 59 || array[3] > 59) {
! throw new DateTimeException("Value out of range: Hour[0-23], Minute[0-59], Second[0-59]");
! }
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
*** 3586,3631 ****
return context.setParsedField(OFFSET_SECONDS, 0, position, position);
}
return ~position;
}
! /**
! * Parse a two digit zero-prefixed number.
! *
! * @param array the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null
! * @param arrayIndex the index to parse the value into
! * @param parseText the offset ID, not null
! * @param required whether this number is required
! * @param parseType the offset pattern type
! * @return true if an error occurred
! */
! private boolean parseNumber(int[] array, int arrayIndex, CharSequence parseText, boolean required, int parseType) {
! if ((parseType + 3) / 2 < arrayIndex) {
! return false; // ignore seconds/minutes
}
int pos = array[0];
! if ((parseType % 2) == 0 && arrayIndex > 1) {
if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') {
! return required;
}
pos++;
}
if (pos + 2 > parseText.length()) {
! return required;
}
char ch1 = parseText.charAt(pos++);
char ch2 = parseText.charAt(pos++);
if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
! return required;
}
int value = (ch1 - 48) * 10 + (ch2 - 48);
if (value < 0 || value > 59) {
! return required;
}
array[arrayIndex] = value;
array[0] = pos;
! return false;
}
@Override
public String toString() {
String converted = noOffsetText.replace("'", "''");
--- 3688,3809 ----
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
! parseVariableWidthDigits(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 arrayIndex, int[] array) {
int pos = array[0];
! if (pos < 0) {
! return true;
! }
! if (isColon && arrayIndex != 1) { // ':' will precede only in case of minute/second
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[arrayIndex] = value;
array[0] = pos;
! return true;
! }
!
! private void parseVariableWidthDigits(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[maxDigits];
! for (int i = 0; i < maxDigits; i++) {
! if (pos + 1 > parseText.length()) {
! break;
! }
! char ch = parseText.charAt(pos++);
! if (ch < '0' || ch > '9') {
! pos--;
! 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;
}
@Override
public String toString() {
String converted = noOffsetText.replace("'", "''");
< prev index next >