Add multi-unit DurationFormat.Style for duration parsing/printing
This adds the COMPOSITE style, which allows multiple segments each similar to the SIMPLE style. See gh-30396 Closes gh-33262
This commit is contained in:
@@ -27,7 +27,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.format.annotation.DurationFormat.Unit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.springframework.format.annotation.DurationFormat.Style.COMPOSITE;
|
||||
import static org.springframework.format.annotation.DurationFormat.Style.ISO8601;
|
||||
import static org.springframework.format.annotation.DurationFormat.Style.SIMPLE;
|
||||
|
||||
@@ -132,6 +134,60 @@ class DurationFormatterUtilsTests {
|
||||
.withCause(new DateTimeParseException("Text cannot be parsed to a Duration", "", 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseComposite() {
|
||||
assertThat(DurationFormatterUtils.parse("1d2h34m57s28ms3us2ns", COMPOSITE))
|
||||
.isEqualTo(Duration.ofDays(1).plusHours(2)
|
||||
.plusMinutes(34).plusSeconds(57)
|
||||
.plusMillis(28).plusNanos(3002));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCompositeWithExplicitPlusSign() {
|
||||
assertThat(DurationFormatterUtils.parse("+1d2h34m57s28ms3us2ns", COMPOSITE))
|
||||
.isEqualTo(Duration.ofDays(1).plusHours(2)
|
||||
.plusMinutes(34).plusSeconds(57)
|
||||
.plusMillis(28).plusNanos(3002));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCompositeWithExplicitMinusSign() {
|
||||
assertThat(DurationFormatterUtils.parse("-1d2h34m57s28ms3us2ns", COMPOSITE))
|
||||
.isEqualTo(Duration.ofDays(-1).plusHours(-2)
|
||||
.plusMinutes(-34).plusSeconds(-57)
|
||||
.plusMillis(-28).plusNanos(-3002));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCompositePartial() {
|
||||
assertThat(DurationFormatterUtils.parse("34m57s", COMPOSITE))
|
||||
.isEqualTo(Duration.ofMinutes(34).plusSeconds(57));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCompositePartialWithSpaces() {
|
||||
assertThat(DurationFormatterUtils.parse("34m 57s", COMPOSITE))
|
||||
.isEqualTo(Duration.ofMinutes(34).plusSeconds(57));
|
||||
}
|
||||
|
||||
@Test //Kotlin style compatibility
|
||||
void parseCompositeNegativeWithSpacesAndParenthesis() {
|
||||
assertThat(DurationFormatterUtils.parse("-(34m 57s)", COMPOSITE))
|
||||
.isEqualTo(Duration.ofMinutes(-34).plusSeconds(-57));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCompositeBadSign() {
|
||||
assertThatException().isThrownBy(() -> DurationFormatterUtils.parse("+-34m57s", COMPOSITE))
|
||||
.havingCause().withMessage("Does not match composite duration pattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCompositeBadUnit() {
|
||||
assertThatException().isThrownBy(() -> DurationFormatterUtils.parse("34mo57s", COMPOSITE))
|
||||
.havingCause().withMessage("Does not match composite duration pattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
void printSimple() {
|
||||
assertThat(DurationFormatterUtils.print(Duration.ofNanos(12345), SIMPLE, Unit.NANOS))
|
||||
@@ -164,6 +220,26 @@ class DurationFormatterUtilsTests {
|
||||
.isEqualTo("PT-3S");
|
||||
}
|
||||
|
||||
@Test
|
||||
void printCompositePositive() {
|
||||
Duration composite = DurationFormatterUtils.parse("+1d2h34m57s28ms3us2ns", COMPOSITE);
|
||||
assertThat(DurationFormatterUtils.print(composite, COMPOSITE))
|
||||
.isEqualTo("1d2h34m57s28ms3us2ns");
|
||||
}
|
||||
|
||||
@Test
|
||||
void printCompositeZero() {
|
||||
assertThat(DurationFormatterUtils.print(Duration.ZERO, COMPOSITE))
|
||||
.isEqualTo("0s");
|
||||
}
|
||||
|
||||
@Test
|
||||
void printCompositeNegative() {
|
||||
Duration composite = DurationFormatterUtils.parse("-1d2h34m57s28ms3us2ns", COMPOSITE);
|
||||
assertThat(DurationFormatterUtils.print(composite, COMPOSITE))
|
||||
.isEqualTo("-1d2h34m57s28ms3us2ns");
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectAndParse() {
|
||||
assertThat(DurationFormatterUtils.detectAndParse("PT1.234S", Unit.NANOS))
|
||||
@@ -177,6 +253,10 @@ class DurationFormatterUtilsTests {
|
||||
assertThat(DurationFormatterUtils.detectAndParse("1234", Unit.NANOS))
|
||||
.as("simple without suffix")
|
||||
.isEqualTo(Duration.ofNanos(1234));
|
||||
|
||||
assertThat(DurationFormatterUtils.detectAndParse("3s45ms", Unit.NANOS))
|
||||
.as("composite")
|
||||
.isEqualTo(Duration.ofMillis(3045));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -192,6 +272,10 @@ class DurationFormatterUtilsTests {
|
||||
assertThat(DurationFormatterUtils.detectAndParse("1234"))
|
||||
.as("simple without suffix")
|
||||
.isEqualTo(Duration.ofMillis(1234));
|
||||
|
||||
assertThat(DurationFormatterUtils.detectAndParse("3s45ms"))
|
||||
.as("composite")
|
||||
.isEqualTo(Duration.ofMillis(3045));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -210,6 +294,10 @@ class DurationFormatterUtilsTests {
|
||||
.as("invalid yet matching ISO8601 pattern")
|
||||
.isEqualTo(ISO8601);
|
||||
|
||||
assertThat(DurationFormatterUtils.detect("-(1d 2h 34m 2ns)"))
|
||||
.as("COMPOSITE")
|
||||
.isEqualTo(COMPOSITE);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> DurationFormatterUtils.detect("WPT2H-4M"))
|
||||
.withMessage("'WPT2H-4M' is not a valid duration, cannot detect any known style")
|
||||
.withNoCause();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.format.datetime.standard
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.format.annotation.DurationFormat
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.toKotlinDuration
|
||||
|
||||
/**
|
||||
* Tests for [DurationFormatterUtils] compatibility with Kotlin.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
class DurationFormatterUtilsKotlinTests {
|
||||
|
||||
@Test
|
||||
fun `Composite style parses Kotlin toString() with seconds resolution`() {
|
||||
val kotlinString = "-(1d 2h 34m 57s)"
|
||||
val duration = Duration.parse(kotlinString)
|
||||
|
||||
val parseResult = DurationFormatterUtils.parse(kotlinString, DurationFormat.Style.COMPOSITE)
|
||||
.toKotlinDuration()
|
||||
|
||||
Assertions.assertThat(parseResult).isEqualTo(duration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Composite style fails Kotlin toString() with sub-second resolution`() {
|
||||
val kotlinString = "-(1d 2h 34m 57.028003002s)"
|
||||
|
||||
Assertions.assertThatException().isThrownBy {
|
||||
DurationFormatterUtils.parse(kotlinString, DurationFormat.Style.COMPOSITE) }
|
||||
.withMessage("'$kotlinString' is not a valid composite duration")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Detect and parse fails Kotlin toString() with sub-second resolution`() {
|
||||
val kotlinString = "-(1d 2h 34m 57.028003002s)"
|
||||
|
||||
Assertions.assertThatException().isThrownBy { DurationFormatterUtils.detectAndParse(kotlinString) }
|
||||
.withMessage("'$kotlinString' is not a valid duration, cannot detect any known style")
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user