diff --git a/src/main/java/com/ethlo/time/Field.java b/src/main/java/com/ethlo/time/Field.java index d131c5b..3ef84c1 100644 --- a/src/main/java/com/ethlo/time/Field.java +++ b/src/main/java/com/ethlo/time/Field.java @@ -40,7 +40,8 @@ public enum Field HOUR(13), MINUTE(16), SECOND(19), - NANO(20); + NANO(20), + ZONE_OFFSET(17); private final int requiredLength; diff --git a/src/main/java/com/ethlo/time/TimezoneOffset.java b/src/main/java/com/ethlo/time/TimezoneOffset.java index cdbe781..3a2a5c0 100644 --- a/src/main/java/com/ethlo/time/TimezoneOffset.java +++ b/src/main/java/com/ethlo/time/TimezoneOffset.java @@ -29,6 +29,9 @@ public class TimezoneOffset { public static final TimezoneOffset UTC = new TimezoneOffset(0, 0); + private static final int SECONDS_PER_HOUR = 3600; + private static final int SECONDS_PER_MINUTE = 60; + private static final int MINUTES_PER_HOUR = 60; private final int hours; private final int minutes; @@ -43,6 +46,13 @@ public static TimezoneOffset ofHoursMinutes(int hours, int minutes) return new TimezoneOffset(hours, minutes); } + public static TimezoneOffset ofTotalSeconds(int seconds) + { + final int absHours = seconds / SECONDS_PER_HOUR; + int absMinutes = (seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; + return ofHoursMinutes(absHours, absMinutes); + } + public static TimezoneOffset of(ZoneOffset offset) { final int seconds = offset.getTotalSeconds(); diff --git a/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java b/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java index 18cd9d2..81f39bd 100644 --- a/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java +++ b/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java @@ -52,7 +52,6 @@ public static int parsePositiveInt(final String strNum, int startInclusive, int int result = 0; try { - for (int i = startInclusive; i < endExclusive; i++) { final char c = strNum.charAt(i); @@ -60,7 +59,7 @@ public static int parsePositiveInt(final String strNum, int startInclusive, int { ErrorUtil.raiseUnexpectedCharacter(strNum, i); } - result = result * 10 + (c - ZERO); + result = (result * 10) + (c - ZERO); } } catch (StringIndexOutOfBoundsException exc) diff --git a/src/main/java/com/ethlo/time/token/ConfigurableDateTimeParser.java b/src/main/java/com/ethlo/time/token/ConfigurableDateTimeParser.java new file mode 100644 index 0000000..c8b4f4e --- /dev/null +++ b/src/main/java/com/ethlo/time/token/ConfigurableDateTimeParser.java @@ -0,0 +1,102 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import static com.ethlo.time.Field.NANO; +import static com.ethlo.time.Field.YEAR; + +import java.text.ParsePosition; + +import com.ethlo.time.DateTime; +import com.ethlo.time.Field; +import com.ethlo.time.TimezoneOffset; + +public class ConfigurableDateTimeParser implements DateTimeParser +{ + private final DateTimeToken[] tokens; + + public ConfigurableDateTimeParser(DateTimeToken... tokens) + { + this.tokens = tokens; + } + + public ConfigurableDateTimeParser combine(DateTimeToken... tokens) + { + return new ConfigurableDateTimeParser(combine(this.tokens, tokens)); + } + + private DateTimeToken[] combine(DateTimeToken[] a, DateTimeToken[] b) + { + final DateTimeToken[] result = new DateTimeToken[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + + @Override + public DateTime parse(String text, ParsePosition parsePosition) + { + int fractionsLength = 0; + int highestOrdinal = YEAR.ordinal(); + final int[] values = new int[]{0, 1, 1, 0, 0, 0, 0, -1}; + for (DateTimeToken token : tokens) + { + final int index = parsePosition.getIndex(); + final int value = token.read(text, parsePosition); + final Field field = token.getField(); + if (field != null) + { + final int ordinal = field.ordinal(); + values[ordinal] = value; + highestOrdinal = Math.max(ordinal, highestOrdinal); + if (token instanceof FractionsToken) + { + fractionsLength = parsePosition.getIndex() - index; + values[ordinal] = scale(value, fractionsLength); + } + } + } + + return new DateTime( + Field.values()[Math.min(highestOrdinal, NANO.ordinal())], + values[Field.YEAR.ordinal()], + values[Field.MONTH.ordinal()], + values[Field.DAY.ordinal()], + values[Field.HOUR.ordinal()], + values[Field.MINUTE.ordinal()], + values[Field.SECOND.ordinal()], + values[Field.NANO.ordinal()], + values[Field.ZONE_OFFSET.ordinal()] != -1 ? TimezoneOffset.ofTotalSeconds(values[Field.ZONE_OFFSET.ordinal()]) : null, + fractionsLength + ); + } + + private int scale(int value, int length) + { + int pos = length; + while (pos < 9) + { + value *= 10; + pos++; + } + return value; + } +} diff --git a/src/main/java/com/ethlo/time/token/DateTimeParser.java b/src/main/java/com/ethlo/time/token/DateTimeParser.java new file mode 100644 index 0000000..0d5f876 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/DateTimeParser.java @@ -0,0 +1,30 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.text.ParsePosition; + +import com.ethlo.time.DateTime; + +public interface DateTimeParser +{ + DateTime parse(String text, ParsePosition parsePosition); +} diff --git a/src/main/java/com/ethlo/time/token/DateTimeToken.java b/src/main/java/com/ethlo/time/token/DateTimeToken.java new file mode 100644 index 0000000..0e85fb1 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/DateTimeToken.java @@ -0,0 +1,32 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.text.ParsePosition; + +import com.ethlo.time.Field; + +public interface DateTimeToken +{ + int read(String text, ParsePosition parsePosition); + + Field getField(); +} diff --git a/src/main/java/com/ethlo/time/token/DigitsToken.java b/src/main/java/com/ethlo/time/token/DigitsToken.java new file mode 100644 index 0000000..5db9c74 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/DigitsToken.java @@ -0,0 +1,53 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.ethlo.time.Field; +import com.ethlo.time.internal.LimitedCharArrayIntegerUtil; + +import java.text.ParsePosition; + +public class DigitsToken implements DateTimeToken +{ + private final Field field; + private final int length; + + public DigitsToken(Field field, int length) + { + this.field = field; + this.length = length; + } + + @Override + public int read(String text, ParsePosition parsePosition) + { + final int offset = parsePosition.getIndex(); + final int end = offset + length; + final int value = LimitedCharArrayIntegerUtil.parsePositiveInt(text, offset, end); + parsePosition.setIndex(end); + return value; + } + + public Field getField() + { + return field; + } +} diff --git a/src/main/java/com/ethlo/time/token/FourDigitToken.java b/src/main/java/com/ethlo/time/token/FourDigitToken.java new file mode 100644 index 0000000..2a49edb --- /dev/null +++ b/src/main/java/com/ethlo/time/token/FourDigitToken.java @@ -0,0 +1,31 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.ethlo.time.Field; + +public class FourDigitToken extends DigitsToken +{ + public FourDigitToken(Field field) + { + super(field, 4); + } +} diff --git a/src/main/java/com/ethlo/time/token/FractionsToken.java b/src/main/java/com/ethlo/time/token/FractionsToken.java new file mode 100644 index 0000000..82e5e80 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/FractionsToken.java @@ -0,0 +1,60 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import static com.ethlo.time.internal.LimitedCharArrayIntegerUtil.DIGIT_9; +import static com.ethlo.time.internal.LimitedCharArrayIntegerUtil.ZERO; + +import java.text.ParsePosition; + +import com.ethlo.time.Field; + +public class FractionsToken implements DateTimeToken +{ + @Override + public int read(final String text, final ParsePosition parsePosition) + { + int idx = parsePosition.getIndex(); + final int length = text.length(); + int value = 0; + while (idx < length) + { + final char c = text.charAt(idx); + if (c < ZERO || c > DIGIT_9) + { + break; + } + else + { + value = value * 10 + (c - ZERO); + idx++; + } + } + parsePosition.setIndex(idx); + return value; + } + + @Override + public Field getField() + { + return Field.NANO; + } +} diff --git a/src/main/java/com/ethlo/time/token/SeparatorToken.java b/src/main/java/com/ethlo/time/token/SeparatorToken.java new file mode 100644 index 0000000..747e485 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/SeparatorToken.java @@ -0,0 +1,62 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.text.ParsePosition; + +import com.ethlo.time.Field; +import com.ethlo.time.internal.ErrorUtil; + +public class SeparatorToken implements DateTimeToken +{ + private final char separator; + + public SeparatorToken(char separator) + { + this.separator = separator; + } + + @Override + public int read(final String text, final ParsePosition parsePosition) + { + final int index = parsePosition.getIndex(); + if (text.length() >= index && text.charAt(index) == separator) + { + parsePosition.setIndex(index + 1); + } + else if (text.length() <= index) + { + ErrorUtil.raiseUnexpectedEndOfText(text, text.length()); + } + else if (text.charAt(index) != separator) + { + ErrorUtil.raiseUnexpectedCharacter(text, index); + } + parsePosition.setIndex(index + 1); + return 1; + } + + @Override + public Field getField() + { + return null; + } +} diff --git a/src/main/java/com/ethlo/time/token/SeparatorsToken.java b/src/main/java/com/ethlo/time/token/SeparatorsToken.java new file mode 100644 index 0000000..f9c8644 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/SeparatorsToken.java @@ -0,0 +1,65 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.text.ParsePosition; +import java.time.format.DateTimeParseException; +import java.util.Arrays; + +import com.ethlo.time.Field; +import com.ethlo.time.internal.ErrorUtil; + +public class SeparatorsToken implements DateTimeToken +{ + private final char[] separators; + + public SeparatorsToken(char... separators) + { + this.separators = separators; + } + + @Override + public int read(final String text, final ParsePosition parsePosition) + { + final int index = parsePosition.getIndex(); + if (text.length() <= index) + { + ErrorUtil.raiseUnexpectedEndOfText(text, text.length()); + } + + final char c = text.charAt(index); + for (char sep : separators) + { + if (c == sep) + { + parsePosition.setIndex(index + 1); + return 1; + } + } + throw new DateTimeParseException(String.format("Expected character %s at position %d, found %s: %s", Arrays.toString(separators), index + 1, text.charAt(index), text), text, index); + } + + @Override + public Field getField() + { + return null; + } +} diff --git a/src/main/java/com/ethlo/time/token/TimeZoneOffsetToken.java b/src/main/java/com/ethlo/time/token/TimeZoneOffsetToken.java new file mode 100644 index 0000000..7cf9f19 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/TimeZoneOffsetToken.java @@ -0,0 +1,83 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import static com.ethlo.time.internal.ErrorUtil.raiseUnexpectedCharacter; +import static com.ethlo.time.internal.LimitedCharArrayIntegerUtil.parsePositiveInt; + +import java.text.ParsePosition; +import java.time.format.DateTimeParseException; + +import com.ethlo.time.Field; + +public class TimeZoneOffsetToken implements DateTimeToken +{ + @Override + public int read(final String text, final ParsePosition parsePosition) + { + final int idx = parsePosition.getIndex(); + final int len = text.length(); + final int left = len - idx; + + if (left < 1) + { + return -1; + } + + final char c = text.charAt(idx); + if (c == 'Z' || c == 'z') + { + return 0; + } + + final char sign = text.charAt(idx); + if (sign != '+' && sign != '-') + { + raiseUnexpectedCharacter(text, idx); + } + + if (left < 6) + { + throw new DateTimeParseException(String.format("Invalid timezone offset: %s", text), text, idx); + } + + int hours = parsePositiveInt(text, idx + 1, idx + 3); + int minutes = parsePositiveInt(text, idx + 4, idx + 4 + 2); + if (sign == '-') + { + hours = -hours; + minutes = -minutes; + + if (hours == 0 && minutes == 0) + { + throw new DateTimeParseException("Unknown 'Local Offset Convention' date-time not allowed", text, idx); + } + } + + return hours * 3600 + minutes * 60; + } + + @Override + public Field getField() + { + return Field.ZONE_OFFSET; + } +} diff --git a/src/main/java/com/ethlo/time/token/TwoDigitToken.java b/src/main/java/com/ethlo/time/token/TwoDigitToken.java new file mode 100644 index 0000000..5e5b0c4 --- /dev/null +++ b/src/main/java/com/ethlo/time/token/TwoDigitToken.java @@ -0,0 +1,31 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import com.ethlo.time.Field; + +public class TwoDigitToken extends DigitsToken +{ + public TwoDigitToken(Field field) + { + super(field, 2); + } +} diff --git a/src/test/java/com/ethlo/time/token/ConfigurableDateTimeParserTest.java b/src/test/java/com/ethlo/time/token/ConfigurableDateTimeParserTest.java new file mode 100644 index 0000000..5868204 --- /dev/null +++ b/src/test/java/com/ethlo/time/token/ConfigurableDateTimeParserTest.java @@ -0,0 +1,71 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import static com.ethlo.time.Field.DAY; +import static com.ethlo.time.Field.HOUR; +import static com.ethlo.time.Field.MINUTE; +import static com.ethlo.time.Field.MONTH; +import static com.ethlo.time.Field.SECOND; +import static com.ethlo.time.Field.YEAR; +import static org.assertj.core.api.Assertions.assertThat; + +import java.text.ParsePosition; + +import org.junit.jupiter.api.Test; + +import com.ethlo.time.DateTime; +import com.ethlo.time.ITU; + +public class ConfigurableDateTimeParserTest +{ + @Test + void parseCustomFormat() + { + final ParsePosition pos = new ParsePosition(0); + final String input = "31-12-2000 235937"; + final DateTimeParser parser = new ConfigurableDateTimeParser( + new TwoDigitToken(DAY), + new SeparatorToken('-'), + new TwoDigitToken(MONTH), + new SeparatorToken('-'), + new FourDigitToken(YEAR), + new SeparatorToken(' '), + new TwoDigitToken(HOUR), + new TwoDigitToken(MINUTE), + new TwoDigitToken(SECOND) + ); + final DateTime result = parser.parse(input, pos); + assertThat(result).isEqualTo(DateTime.of(2000, 12, 31, 23, 59, 37, null)); + } + + @Test + void parseRfc3339Format() + { + final String input = "2023-01-01T23:38:34.987654321+06:00"; + final DateTime fixed = ITU.parseLenient(input); + final ParsePosition pos = new ParsePosition(0); + final DateTime custom = DateTimeParsers.rfc3339().parse(input, pos); + assertThat(custom).isEqualTo(fixed); + assertThat(fixed.toString()).isEqualTo(input); + assertThat(custom.toString()).isEqualTo(input); + } +} diff --git a/src/test/java/com/ethlo/time/token/DateTimeParsers.java b/src/test/java/com/ethlo/time/token/DateTimeParsers.java new file mode 100644 index 0000000..5a468f0 --- /dev/null +++ b/src/test/java/com/ethlo/time/token/DateTimeParsers.java @@ -0,0 +1,80 @@ +package com.ethlo.time.token; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import static com.ethlo.time.Field.DAY; +import static com.ethlo.time.Field.HOUR; +import static com.ethlo.time.Field.MINUTE; +import static com.ethlo.time.Field.MONTH; +import static com.ethlo.time.Field.SECOND; +import static com.ethlo.time.Field.YEAR; + +public class DateTimeParsers +{ + private static final ConfigurableDateTimeParser DATE = new ConfigurableDateTimeParser( + new FourDigitToken(YEAR), + new SeparatorToken('-'), + new TwoDigitToken(MONTH), + new SeparatorToken('-'), + new TwoDigitToken(DAY) + ); + + private static final ConfigurableDateTimeParser MINUTES = DATE.combine( + new SeparatorToken('T'), + new TwoDigitToken(HOUR), + new SeparatorToken(':'), + new TwoDigitToken(MINUTE) + ); + + private static final ConfigurableDateTimeParser LOCAL_TIME = MINUTES.combine( + new SeparatorToken(':'), + new TwoDigitToken(SECOND) + ); + + private static final ConfigurableDateTimeParser FRACTIONAL_SECONDS_LOCAL = LOCAL_TIME.combine( + new SeparatorToken('.'), + new FractionsToken() + ); + + private static final ConfigurableDateTimeParser FRACTIONAL_SECONDS_OFFSET = FRACTIONAL_SECONDS_LOCAL.combine( + new TimeZoneOffsetToken() + ); + + public static DateTimeParser rfc3339() + { + return FRACTIONAL_SECONDS_OFFSET; + } + + public static DateTimeParser minutes() + { + return MINUTES; + } + + public static DateTimeParser seconds() + { + return LOCAL_TIME; + } + + public static DateTimeParser fractionalSeconds() + { + return FRACTIONAL_SECONDS_LOCAL; + } +}