This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 2aea6e15e370793cbfd5324e4c2f68b6e18a9a42 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Jun 13 16:58:14 2024 +0200 Add a `GeneralDuration.parse(CharSequence)` method. Add more factory methods. --- .../org/apache/sis/temporal/DefaultInstant.java | 5 ++ .../org/apache/sis/temporal/GeneralDuration.java | 48 +++++++++++++++---- .../org/apache/sis/temporal/TemporalUtilities.java | 55 +++++++++++++++++++++- .../apache/sis/temporal/GeneralDurationTest.java | 52 ++++++++++++++++++++ 4 files changed, 151 insertions(+), 9 deletions(-) diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java index ea299d910d..52064eeb7a 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java @@ -50,6 +50,11 @@ final class DefaultInstant implements Instant, Serializable { */ private static final long serialVersionUID = 3898772638524283287L; + /** + * The constant for the "unknown" instant. + */ + static final DefaultInstant UNKNOWN = new DefaultInstant(null, IndeterminateValue.UNKNOWN); + /** * The temporal position as a date, time or date/time. * May be {@code null} if {@link #indeterminate} is non-null and not "before" or "after". diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java index f7b08ab404..882721f7e5 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java @@ -45,15 +45,12 @@ import org.opengis.temporal.IndeterminatePositionException; /** * A data type to be used for describing length or distance in the temporal dimension. - * This implementation combine {@link java.time.Period} with {@link java.time.Duration} - * for situation where both of them are needed together (which is not recommended). - * - * This class also contains a {@code distance(…)} method for computing the distance between two ISO 19108 instants. - * This is defined here for reducing class loading in the common case where {@code distance(…)} is not invoked. + * This implementation combines {@link java.time.Period} with {@link java.time.Duration} + * for situations where both of them are needed together (which is not recommended). * * @author Martin Desruisseaux (Geomatys) */ -final class GeneralDuration implements TemporalAmount, Serializable { +public final class GeneralDuration implements TemporalAmount, Serializable { /** * For cross-version compatibility. */ @@ -63,13 +60,13 @@ final class GeneralDuration implements TemporalAmount, Serializable { * The period in numbers of years, months and days. * Shall be non-null and non-zero. */ - private final Period period; + public final Period period; /** * The time part of the period in numbers of hours, minutes and seconds. * Shall be non-null, non-zero and less than one day. */ - private final Duration time; + public final Duration time; /** * Creates a new instance with the given parts. @@ -83,6 +80,41 @@ final class GeneralDuration implements TemporalAmount, Serializable { this.time = time; } + /** + * Parses a temporal amount which may contain a period and a duration part. + * This method returns a {@link Period} or {@link Duration} if those objects + * are sufficient, or an instance of {@code GeneralDuration} is last resort. + * + * @param text the text to parse. + * @return the parsed period and/or duration. + * @throws DateTimeParseException if the given text cannot be parsed. + * + * @see Period#parse(CharSequence) + * @see Duration#parse(CharSequence) + */ + public static TemporalAmount parse(final CharSequence text) { + char previousLetter = 0; + final int length = text.length(); + for (int i=0; i<length; i++) { + char c = text.charAt(i); + if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; // Quick upper case, ASCII characters only. + if (c >= 'A' && c <= 'Z') { + if (c == 'T') { + if (previousLetter == 'P') { + return Duration.parse(text); + } + var period = Period.parse(text.subSequence(0, i)); + var duration = Duration.parse(new StringBuilder(length - i + 1).append('P').append(text, i, length)); + if (duration.isZero()) return period; + if (period.isZero()) return duration; + return new GeneralDuration(period, duration); + } + previousLetter = c; + } + } + return Period.parse(text); + } + /** * Returns the temporal position of the given instant if that position is determinate or is "now". * Otherwise, throws an exception. If the position is "now", then this method returns {@code null} diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalUtilities.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalUtilities.java index 250fa7ef56..e96f6c2220 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalUtilities.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalUtilities.java @@ -19,6 +19,8 @@ package org.apache.sis.temporal; import java.util.Date; import java.time.temporal.Temporal; import org.opengis.temporal.TemporalPrimitive; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.resources.Errors; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.temporal.IndeterminateValue; @@ -46,7 +48,42 @@ public final class TemporalUtilities { * @return the instant, or an unknown instant if the given time was null. */ public static Instant createInstant(final Temporal time) { - return new DefaultInstant(time, (time != null) ? null : IndeterminateValue.UNKNOWN); + return (time == null) ? DefaultInstant.UNKNOWN : new DefaultInstant(time, null); + } + + /** + * Creates an instant for the given Java temporal instant associated to the indeterminate value. + * This is used for creating "before" or "after" instant. + * + * @param time the date for which to create instant. + * @param value the indeterminate value. + * @return the instant. + */ + public static Instant createInstant(final Temporal time, final IndeterminateValue value) { + ArgumentChecks.ensureNonNull("value", value); + if (value == IndeterminateValue.UNKNOWN) { + return DefaultInstant.UNKNOWN; + } + if (value == IndeterminateValue.BEFORE || value == IndeterminateValue.AFTER) { + ArgumentChecks.ensureNonNull("time", time); + } + return new DefaultInstant(time, value); + } + + /** + * Creates an instant for the given indeterminate value. + * The given value cannot be "before" or "after". + * + * @param value the indeterminate value. + * @return the instant for the given indeterminate value. + * @throws IllegalArgumentException if the given value is "before" or "after". + */ + public static Instant createInstant(final IndeterminateValue value) { + ArgumentChecks.ensureNonNull("value", value); + if (value == IndeterminateValue.BEFORE || value == IndeterminateValue.AFTER) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "value", value)); + } + return (value == IndeterminateValue.UNKNOWN) ? DefaultInstant.UNKNOWN : new DefaultInstant(null, value); } /** @@ -63,6 +100,22 @@ public final class TemporalUtilities { return new DefaultPeriod(createInstant(beginning), createInstant(ending)); } + /** + * Creates a period for the given begin and end instant. + * + * @param beginning the begin instant (inclusive), or {@code null}. + * @param ending the end instant (exclusive), or {@code null}. + * @return the period, or {@code null} if both arguments are null. + */ + public static Period createPeriod(Instant beginning, Instant ending) { + if (beginning == null && ending == null) { + return null; + } + if (beginning == null) beginning = DefaultInstant.UNKNOWN; + if (ending == null) ending = DefaultInstant.UNKNOWN; + return new DefaultPeriod(beginning, ending); + } + /** * Returns the given value as a temporal position, or {@code null} if not available. * diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/GeneralDurationTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/GeneralDurationTest.java new file mode 100644 index 0000000000..a9a18354a4 --- /dev/null +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/GeneralDurationTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.sis.temporal; + +import java.time.Period; +import java.time.Duration; + +// Test dependencies +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.apache.sis.test.TestCase; + + +/** + * Tests the {@link GeneralDuration} class. + * + * @author Martin Desruisseaux (Geomatys) + */ +public final class GeneralDurationTest extends TestCase { + /** + * Creates a new test case. + */ + public GeneralDurationTest() { + } + + /** + * Tests {@link GeneralDuration#parse(CharSequence)}. + */ + @Test + public void testParse() { + assertEquals(Period.of(2, 3, 4), GeneralDuration.parse("P2Y3M4D")); + assertEquals(Duration.ofHours(100), GeneralDuration.parse("PT100H")); + assertEquals(Duration.ofHours(200), GeneralDuration.parse("pt200H")); + var r = assertInstanceOf(GeneralDuration.class, GeneralDuration.parse("P2Y3M4DT10H")); + assertEquals(Period.of(2, 3, 4), r.period); + assertEquals(Duration.ofHours(10), r.time); + } +}