You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/07/13 11:22:19 UTC

[isis] branch master updated: ISIS-3049: value-semantics: fixes time-zone parsing

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new eb825ca277 ISIS-3049: value-semantics: fixes time-zone parsing
eb825ca277 is described below

commit eb825ca2778a792c46eca90bb4993c2e1b7ba913
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Jul 13 13:22:10 2022 +0200

    ISIS-3049: value-semantics: fixes time-zone parsing
---
 .../value/semantics/TemporalValueSemantics.java    | 34 ++++++++++--
 .../isis/commons/internal/base/_Strings.java       | 26 +++++++++
 .../isis/commons/internal/base/StringsTest.java    | 63 ++++++++++++----------
 .../temporal/TemporalValueSemanticsProvider.java   |  2 +-
 .../isis/testdomain/value/ValueSemanticsTest.java  | 27 +++++++++-
 .../scalars/datepicker/DateTimeConfig.java         |  5 +-
 6 files changed, 123 insertions(+), 34 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/TemporalValueSemantics.java b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/TemporalValueSemantics.java
index 07e817a778..dec2c29529 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/TemporalValueSemantics.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/TemporalValueSemantics.java
@@ -166,9 +166,29 @@ extends
 
         /**
          * The locale-independent (canonical) pattern used for editing time-zone in the UI.
+         * <p>
+         * Java time-zone formats<pre>
+         * V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
+         * z       time-zone name              zone-name         Pacific Standard Time; PST
+         * O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
+         * X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
+         * x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
+         * Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
+         *</pre>
+         *
+         * @apiNote Yet only tested with {@literal XXX}, as there needs to be a format correspondence with
+         * <i>momentJs</i> supported formats for the <i>tempus-dominus</i> date/time-picker to work
+         * (as used by the <i>Wicket Viewer</i>).
          */
         @NotNull @NotEmpty
-        private String zonePattern = "x";
+        private String zonePatternForOutput = "XXX";
+
+        /**
+         * Support both forms for parsing, with or without colon.
+         * @see "https://stackoverflow.com/questions/34637626/java-datetimeformatter-for-time-zone-with-an-optional-colon-separator"
+         */
+        @NotNull @NotEmpty
+        private String zonePatternForInput = "[XXX][X]";
 
         // -- JOINING PATTERNS
 
@@ -205,20 +225,26 @@ extends
                     String.format(getDateTimeJoiningPattern(), getDatePattern(), timePattern(timePrecision, direction));
                 return offsetCharacteristic.isLocal()
                         ? dateTimePattern
-                        : String.format(getZoneJoiningPattern(), dateTimePattern, getZonePattern());
+                        : String.format(getZoneJoiningPattern(), dateTimePattern, zonePattern(direction));
             case DATE_ONLY:
                 return offsetCharacteristic.isLocal()
                         ? getDatePattern()
-                        : String.format(getZoneJoiningPattern(), getDatePattern(), getZonePattern());
+                        : String.format(getZoneJoiningPattern(), getDatePattern(), zonePattern(direction));
             case TIME_ONLY:
                 return offsetCharacteristic.isLocal()
                         ? timePattern(timePrecision, direction)
-                        : String.format(getZoneJoiningPattern(), timePattern(timePrecision, direction), getZonePattern());
+                        : String.format(getZoneJoiningPattern(), timePattern(timePrecision, direction), zonePattern(direction));
             default:
                 throw _Exceptions.unmatchedCase(temporalCharacteristic);
             }
         }
 
+        private String zonePattern(@NonNull final EditingFormatDirection direction) {
+            return direction.isInput()
+                    ? getZonePatternForInput()
+                    : getZonePatternForOutput();
+        }
+
         // -- HELPER
 
         private String timePattern(
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java b/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
index f549e13d69..e23022ce3f 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
@@ -425,6 +425,32 @@ public final class _Strings {
         return nullToEmpty(str) + of(fillCount, c);
     }
 
+    /**
+     * Returns a string that is a substring of this string.
+     * The substring begins at the specified beginIndex and extends to the character at index endIndex - 1.
+     * Thus the length of the substring is endIndex-beginIndex.
+     * <p>
+     * Supports negative endIndex, as well as index overflow.
+     */
+    public static String substring(final @Nullable String str, final int beginIndex, final int endIndex) {
+        if(isEmpty(str)) {
+            return str;
+        }
+        final int maxIndex = str.length()-1; // >= 0
+
+        final int i1 = endIndex<0
+                ? str.length() + endIndex
+                : endIndex;
+
+        final int i0 = beginIndex>0
+                ? Math.min(beginIndex, maxIndex)
+                : 0;
+
+        return i0<i1
+                ? str.substring(i0, i1)
+                : "";
+    }
+
     // -- SPLITTING
 
     /**
diff --git a/commons/src/test/java/org/apache/isis/commons/internal/base/StringsTest.java b/commons/src/test/java/org/apache/isis/commons/internal/base/StringsTest.java
index 490d8246c5..2e984524e1 100644
--- a/commons/src/test/java/org/apache/isis/commons/internal/base/StringsTest.java
+++ b/commons/src/test/java/org/apache/isis/commons/internal/base/StringsTest.java
@@ -46,18 +46,27 @@ class StringsTest {
         assertThat(_Strings.isNotEmpty(null), is(false));
     }
 
+    @Test
+    public void substring() throws Exception {
+        assertThat(_Strings.substring("12abc", 0, 5), is("12abc"));
+        assertThat(_Strings.substring("12abc", 1, 5), is("2abc"));
+        assertThat(_Strings.substring("12abc", 0, 4), is("12ab"));
+        assertThat(_Strings.substring("12abc", 0, -1), is("12ab"));
+        assertThat(_Strings.substring("12abc", 10, -10), is("")); // index overflow
+        assertThat(_Strings.substring(null, 1, 1), nullValue());
+    }
 
     @Test
     public void lowerWithNull() throws Exception {
         assertThat(
-                _Strings.lower(null), 
+                _Strings.lower(null),
                 nullValue());
     }
 
     @Test
     public void lowerMixed() throws Exception {
         assertThat(
-                _Strings.lower("12aBc"), 
+                _Strings.lower("12aBc"),
                 is("12abc"));
     }
 
@@ -65,56 +74,56 @@ class StringsTest {
     @Test
     public void upperWithNull() throws Exception {
         assertThat(
-                _Strings.upper(null), 
+                _Strings.upper(null),
                 nullValue());
     }
 
     @Test
     public void upperMixed() throws Exception {
         assertThat(
-                _Strings.upper("12aBc"), 
+                _Strings.upper("12aBc"),
                 is("12ABC"));
     }
 
     @Test
     public void capitalizeWithNull() throws Exception {
         assertThat(
-                _Strings.capitalize(null), 
+                _Strings.capitalize(null),
                 nullValue());
     }
 
     @Test
     public void capitalizeSize0() throws Exception {
         assertThat(
-                _Strings.capitalize(""), 
+                _Strings.capitalize(""),
                 is(""));
     }
 
     @Test
     public void capitalizeSize1() throws Exception {
         assertThat(
-                _Strings.capitalize("a"), 
+                _Strings.capitalize("a"),
                 is("A"));
     }
 
     @Test
     public void capitalizeSize2() throws Exception {
         assertThat(
-                _Strings.capitalize("ab"), 
+                _Strings.capitalize("ab"),
                 is("Ab"));
     }
 
     @Test
     public void trimWithNull() throws Exception {
         assertThat(
-                _Strings.trim(null), 
+                _Strings.trim(null),
                 nullValue());
     }
 
     @Test
     public void trimMixed() throws Exception {
         assertThat(
-                _Strings.trim(" 12 aBc"), 
+                _Strings.trim(" 12 aBc"),
                 is("12 aBc"));
     }
 
@@ -157,7 +166,7 @@ class StringsTest {
                 .collect(Collectors.joining("|")),
                 is(" 1|2 a||Bc "));
     }
-    
+
     @Test
     public void splitThenStreamWithnewLine() throws Exception {
         assertThat(
@@ -169,14 +178,14 @@ class StringsTest {
     @Test
     public void condenseWhitespacesWithNull() throws Exception {
         assertThat(
-                _Strings.condenseWhitespaces(null,"|"), 
+                _Strings.condenseWhitespaces(null,"|"),
                 nullValue());
     }
 
     @Test
     public void condenseWhitespaces() throws Exception {
         assertThat(
-                _Strings.condenseWhitespaces("  12 aBc","|"), 
+                _Strings.condenseWhitespaces("  12 aBc","|"),
                 is("|12|aBc"));
     }
 
@@ -185,7 +194,7 @@ class StringsTest {
     @Test
     public void toByteConvertWithNull() throws Exception {
         assertThat(
-                _Strings.toBytes(null, StandardCharsets.UTF_8), 
+                _Strings.toBytes(null, StandardCharsets.UTF_8),
                 nullValue());
     }
 
@@ -208,21 +217,21 @@ class StringsTest {
     @Test
     public void fromByteConvertWithNull() throws Exception {
         assertThat(
-                _Strings.ofBytes(null, StandardCharsets.UTF_8), 
+                _Strings.ofBytes(null, StandardCharsets.UTF_8),
                 nullValue());
     }
 
     @Test
     public void fromByteConvertWithEmpty() throws Exception {
         assertThat(
-                _Strings.ofBytes(_Constants.emptyBytes, StandardCharsets.UTF_8), 
+                _Strings.ofBytes(_Constants.emptyBytes, StandardCharsets.UTF_8),
                 is(""));
     }
 
     @Test
     public void fromByteConvert() throws Exception {
         assertThat(
-                _Strings.ofBytes(new byte[] {48,49,50,51}, StandardCharsets.UTF_8), 
+                _Strings.ofBytes(new byte[] {48,49,50,51}, StandardCharsets.UTF_8),
                 is("0123"));
     }
 
@@ -232,11 +241,11 @@ class StringsTest {
     public void convertIdentity() throws Exception {
 
         assertThat(
-                _Strings.convert(null, _Bytes.operator(), StandardCharsets.UTF_8), 
+                _Strings.convert(null, _Bytes.operator(), StandardCharsets.UTF_8),
                 nullValue());
 
         assertThat(
-                _Strings.convert("0123", _Bytes.operator(), StandardCharsets.UTF_8), 
+                _Strings.convert("0123", _Bytes.operator(), StandardCharsets.UTF_8),
                 is("0123"));
     }
 
@@ -245,14 +254,14 @@ class StringsTest {
     @Test
     public void composeIdentityWithNull() throws Exception {
         assertThat(
-                _Strings.operator().apply(null), 
+                _Strings.operator().apply(null),
                 nullValue());
     }
 
     @Test
     public void composeIdentity() throws Exception {
         assertThat(
-                _Strings.operator().apply(" 12 aBc"), 
+                _Strings.operator().apply(" 12 aBc"),
                 is(" 12 aBc"));
     }
 
@@ -261,7 +270,7 @@ class StringsTest {
         assertThat(
                 _Strings.operator()
                 .andThen(_Strings::lower)
-                .apply(null), 
+                .apply(null),
                 nullValue());
     }
 
@@ -270,7 +279,7 @@ class StringsTest {
         assertThat(
                 _Strings.operator()
                 .andThen(_Strings::lower)
-                .apply(" 12 aBc"), 
+                .apply(" 12 aBc"),
                 is(" 12 abc"));
     }
 
@@ -280,7 +289,7 @@ class StringsTest {
                 _Strings.operator()
                 .andThen(_Strings::lower)
                 .andThen(_Strings::upper)
-                .apply(" 12 aBc"), 
+                .apply(" 12 aBc"),
                 is(" 12 ABC"));
     }
 
@@ -290,7 +299,7 @@ class StringsTest {
     public void asLowerDashed() throws Exception {
         assertThat(
                 _Strings.asLowerDashed
-                .apply(" 12    aBc"), 
+                .apply(" 12    aBc"),
                 is("-12-abc"));
     }
 
@@ -298,7 +307,7 @@ class StringsTest {
     public void asNormalized() throws Exception {
         assertThat(
                 _Strings.asNormalized
-                .apply(" 12 a B         c"), 
+                .apply(" 12 a B         c"),
                 is(" 12 a B c"));
     }
 
@@ -306,7 +315,7 @@ class StringsTest {
     public void asNaturalName2() throws Exception {
         assertThat(
                 _Strings.asNaturalName2
-                .apply("NextAvailableDate"), 
+                .apply("NextAvailableDate"),
                 is("Next Available Date"));
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/temporal/TemporalValueSemanticsProvider.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/temporal/TemporalValueSemanticsProvider.java
index 818bb30ab5..a1be6a5295 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/temporal/TemporalValueSemanticsProvider.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/temporal/TemporalValueSemanticsProvider.java
@@ -291,7 +291,7 @@ implements TemporalValueSemantics<T> {
                 .orElseGet(IsisConfiguration.ValueTypes.Temporal::new);
     }
 
-    private TemporalEditingPattern temporalEditingPattern() {
+    protected TemporalEditingPattern temporalEditingPattern() {
         return temporalConfig().getEditing();
     }
 
diff --git a/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTest.java b/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTest.java
index 742064d232..33d0989ef5 100644
--- a/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTest.java
+++ b/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTest.java
@@ -18,6 +18,9 @@
  */
 package org.apache.isis.testdomain.value;
 
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.ZonedDateTime;
 import java.util.Locale;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -53,6 +56,7 @@ import org.apache.isis.applib.value.semantics.ValueDecomposition;
 import org.apache.isis.applib.value.semantics.ValueSemanticsAbstract.PlaceholderLiteral;
 import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
 import org.apache.isis.applib.value.semantics.ValueSemanticsResolver;
+import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
@@ -202,12 +206,33 @@ class ValueSemanticsTest {
 
                         } else {
 
-                            System.err.printf("using %s trying to parse '%s'%n", valueType.getName(), stringified);
+                            //debug
+                            //System.err.printf("using %s trying to parse '%s'%n", valueType.getName(), stringified);
 
                             tester.assertValueEquals(
                                     example.getValue(),
                                     parser.parseTextRepresentation(context, stringified),
                                     "parser roundtrip failed");
+
+                            if(valueType.equals(OffsetDateTime.class)
+                                    || valueType.equals(OffsetTime.class)
+                                    || valueType.equals(ZonedDateTime.class)) {
+
+                                // test alternative time-zone format with 4 digits +-HHmm
+                                tester.assertValueEquals(
+                                        example.getValue(),
+                                        parser.parseTextRepresentation(context,
+                                                _Strings.substring(stringified, 0, -6) + "+0200"),
+                                        "parser roundtrip failed (alternative time-zone format with 4 digits +-HHmm)");
+
+                                // test alternative time-zone format with 2 digits +-HH
+                                tester.assertValueEquals(
+                                        example.getValue(),
+                                        parser.parseTextRepresentation(context,
+                                                _Strings.substring(stringified, 0, -6) + "+02"),
+                                        "parser roundtrip failed (alternative time-zone format with 2 digits +-HH)");
+                            }
+
                         }
                     }
                     @Override
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/datepicker/DateTimeConfig.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/datepicker/DateTimeConfig.java
index 4507544f65..2cf027dba9 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/datepicker/DateTimeConfig.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/datepicker/DateTimeConfig.java
@@ -379,6 +379,9 @@ public class DateTimeConfig extends AbstractConfig {
         return this;
     }
 
+    /**
+     * @param value Whether on show, will set the picker to the current date/time.
+     */
     public DateTimeConfig useCurrent(final boolean value) {
         put(UseCurrent, value);
         return this;
@@ -425,7 +428,7 @@ public class DateTimeConfig extends AbstractConfig {
     private static class TodayButtonSerializer extends JsonSerializer<TodayButton> {
 
         @Override
-        public void serialize(TodayButton value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        public void serialize(final TodayButton value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
             switch (value) {
             case TRUE:
                 jgen.writeBoolean(true);