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/01/04 08:54:50 UTC
[isis] branch master updated: ISIS-2882: introduces TimePrecision for time parsing (applib)
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 d993037 ISIS-2882: introduces TimePrecision for time parsing (applib)
d993037 is described below
commit d993037806b2ab93ff71764abc3e3bf10583ca52
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Jan 4 09:54:39 2022 +0100
ISIS-2882: introduces TimePrecision for time parsing (applib)
- while Java's FormatStyle is for rendering customization, the new
TimePrecision is specific for parsing/editing and allows to specify from
HOUR up to NANO_SECOND precision
---
.../isis/applib/annotation/TimePrecision.java | 59 ++++++++++
.../isis/applib/annotation/ValueSemantics.java | 15 ++-
.../value/semantics/TemporalValueSemantics.java | 120 +++++++++++++++++++--
.../value/semantics/ValueSemanticsAbstract.java | 63 +++--------
.../temporal/TemporalValueSemanticsProvider.java | 42 ++++++--
.../JavaUtilDateValueSemanticsProviderTest.java | 19 ++--
.../TemporalValueSemanticsProviderTest.java | 113 +++++++++++++++++++
7 files changed, 354 insertions(+), 77 deletions(-)
diff --git a/api/applib/src/main/java/org/apache/isis/applib/annotation/TimePrecision.java b/api/applib/src/main/java/org/apache/isis/applib/annotation/TimePrecision.java
new file mode 100644
index 0000000..1c66f5d
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/annotation/TimePrecision.java
@@ -0,0 +1,59 @@
+/*
+ * 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.isis.applib.annotation;
+
+/**
+ * Precision for time of day.
+ * @since 2.x {@index}
+ */
+public enum TimePrecision {
+
+ UNSPECIFIED,
+
+ /**
+ * 9 fractional digits for <i>Second</i>
+ */
+ NANO_SECOND,
+
+ /**
+ * 6 fractional digits for <i>Second</i>
+ */
+ MICRO_SECOND,
+
+ /**
+ * 3 fractional digits for <i>Second</i>
+ */
+ MILLI_SECOND,
+
+ /**
+ * <i>Second</i>
+ */
+ SECOND,
+
+ /**
+ * <i>Minute</i>
+ */
+ MINUTE,
+
+ /**
+ * <i>Hour</i>
+ */
+ HOUR;
+
+}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java b/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java
index 17376e0..c30a8ea2 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java
@@ -86,20 +86,29 @@ public @interface ValueSemantics {
int minFractionalDigits()
default 0;
- // -- TEMPORAL FORMAT STYLE
+ // -- TEMPORAL FORMATTING
/**
- * If associated with a temporal date value, the style of a localized date.
+ * If associated with a temporal date value, the rendering style of a localized date.
* @see FormatStyle
*/
FormatStyle dateFormatStyle()
default FormatStyle.MEDIUM;
/**
- * If associated with a temporal time value, the style of a localized time.
+ * If associated with a temporal time value, the rendering style of a localized time.
* @see FormatStyle
*/
FormatStyle timeFormatStyle()
default FormatStyle.MEDIUM;
+ /**
+ * If associated with a temporal time value, the time of day precision,
+ * used for editing a time field in the UI.<br>
+ * default = {@link TimePrecision#SECOND}
+ * @see TimePrecision
+ */
+ TimePrecision timePrecision()
+ default TimePrecision.SECOND;
+
}
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 bf0a285..010cdf3 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
@@ -24,6 +24,7 @@ import java.time.temporal.Temporal;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
+import org.apache.isis.applib.annotation.TimePrecision;
import org.apache.isis.commons.internal.exceptions._Exceptions;
import lombok.Data;
@@ -80,6 +81,23 @@ extends
TemporalCharacteristic getTemporalCharacteristic();
OffsetCharacteristic getOffsetCharacteristic();
+ static enum EditingFormatDirection {
+
+ /**
+ * Input parsable text.
+ */
+ INPUT,
+
+ /**
+ * Output parsable text.
+ */
+ OUTPUT;
+
+ public boolean isInput() {return this == INPUT;}
+ public boolean isOutput() {return this == OUTPUT;}
+ }
+
+
@Data
public static class TemporalEditingPattern {
@@ -89,13 +107,63 @@ extends
@NotNull @NotEmpty
private String datePattern = "yyyy-MM-dd";
+ // -- TIME PATTERNS - SECOND
+
/**
- * The locale-independent (canonical) pattern used for editing time in the UI.
+ * The locale-independent (canonical) input pattern used for editing time in the UI.
+ * Yielding {@link TimeFormatPrecision#NANO_SECOND}.
* <p>
- * When editing, omitting nano-seconds, seconds or minutes will use zeros instead.
+ * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
*/
@NotNull @NotEmpty
- private String timePattern = "HH:mm:ss"; //FIXME[ISIS-2882] support omitted parts on input "HH[:mm[:ss[.SSSSSSSSS]]]"
+ private String timePatternNanoSecond = "HH[:mm[:ss][.SSSSSSSSS]]";
+
+ /**
+ * The locale-independent (canonical) input pattern used for editing time in the UI.
+ * Yielding {@link TimeFormatPrecision#MICRO_SECOND}.
+ * <p>
+ * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
+ */
+ @NotNull @NotEmpty
+ private String timePatternMicroSecond = "HH[:mm[:ss][.SSSSSS]]";
+
+ /**
+ * The locale-independent (canonical) input pattern used for editing time in the UI.
+ * Yielding {@link TimeFormatPrecision#MILLI_SECOND}.
+ * <p>
+ * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
+ */
+ @NotNull @NotEmpty
+ private String timePatternMilliSecond = "HH[:mm[:ss][.SSS]]";
+
+ /**
+ * The locale-independent (canonical) input pattern used for editing time in the UI.
+ * Yielding {@link TimeFormatPrecision#SECOND}.
+ * <p>
+ * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
+ */
+ @NotNull @NotEmpty
+ private String timePatternSecond = "HH[:mm[:ss]]";
+
+ /**
+ * The locale-independent (canonical) input pattern used for editing time in the UI.
+ * Yielding {@link TimeFormatPrecision#MINUTE}.
+ * <p>
+ * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
+ */
+ @NotNull @NotEmpty
+ private String timePatternMinute = "HH[:mm]";
+
+ /**
+ * The locale-independent (canonical) input pattern used for editing time in the UI.
+ * Yielding {@link TimeFormatPrecision#HOUR}.
+ * <p>
+ * Any missing temporal parts are filled up with zeros to meet the {@link TimeFormatPrecision}.
+ */
+ @NotNull @NotEmpty
+ private String timePatternHour = "HH";
+
+ // -- ZONE PATTERN
/**
* The locale-independent (canonical) pattern used for editing time-zone in the UI.
@@ -103,6 +171,8 @@ extends
@NotNull @NotEmpty
private String zonePattern = "x";
+ // -- JOINING PATTERNS
+
/**
* The locale-independent (canonical) pattern used for editing date and time in the UI.
* <p>
@@ -126,12 +196,14 @@ extends
public String getEditingFormatAsPattern(
final @NonNull TemporalCharacteristic temporalCharacteristic,
- final @NonNull OffsetCharacteristic offsetCharacteristic) {
+ final @NonNull OffsetCharacteristic offsetCharacteristic,
+ final @NonNull TimePrecision timePrecision,
+ final @NonNull EditingFormatDirection direction) {
switch (temporalCharacteristic) {
case DATE_TIME:
val dateTimePattern =
- String.format(getDateTimeJoiningPattern(), getDatePattern(), getTimePattern());
+ String.format(getDateTimeJoiningPattern(), getDatePattern(), timePattern(timePrecision, direction));
return offsetCharacteristic.isLocal()
? dateTimePattern
: String.format(getZoneJoiningPattern(), dateTimePattern, getZonePattern());
@@ -141,13 +213,47 @@ extends
: String.format(getZoneJoiningPattern(), getDatePattern(), getZonePattern());
case TIME_ONLY:
return offsetCharacteristic.isLocal()
- ? getTimePattern()
- : String.format(getZoneJoiningPattern(), getTimePattern(), getZonePattern());
+ ? timePattern(timePrecision, direction)
+ : String.format(getZoneJoiningPattern(), timePattern(timePrecision, direction), getZonePattern());
default:
throw _Exceptions.unmatchedCase(temporalCharacteristic);
}
}
+ // -- HELPER
+
+ private String timePattern(
+ final @NonNull TimePrecision timePrecision,
+ final @NonNull EditingFormatDirection direction) {
+ switch (direction) {
+ case INPUT:
+ return timePattern(timePrecision);
+ case OUTPUT:
+ return timePattern(timePrecision)
+ .replace("[", "").replace("]", ""); // remove brackets for optional temporal parts
+ }
+ throw _Exceptions.unmatchedCase(direction);
+ }
+
+ private String timePattern(final @NonNull TimePrecision timePrecision) {
+ switch (timePrecision) {
+ case NANO_SECOND:
+ return getTimePatternNanoSecond();
+ case MICRO_SECOND:
+ return getTimePatternMicroSecond();
+ case MILLI_SECOND:
+ return getTimePatternMilliSecond();
+ case UNSPECIFIED:
+ case SECOND:
+ return getTimePatternSecond();
+ case MINUTE:
+ return getTimePatternMinute();
+ case HOUR:
+ return getTimePatternHour();
+ }
+ throw _Exceptions.unmatchedCase(timePrecision);
+ }
+
}
}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java
index 3d1fc16..53b7151 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java
@@ -33,9 +33,11 @@ import java.util.function.Function;
import org.springframework.lang.Nullable;
+import org.apache.isis.applib.annotation.TimePrecision;
import org.apache.isis.applib.exceptions.recoverable.TextEntryParseException;
import org.apache.isis.applib.locale.UserLocale;
import org.apache.isis.applib.services.iactnlayer.InteractionContext;
+import org.apache.isis.applib.value.semantics.TemporalValueSemantics.EditingFormatDirection;
import org.apache.isis.applib.value.semantics.TemporalValueSemantics.TemporalEditingPattern;
import org.apache.isis.commons.collections.Can;
import org.apache.isis.commons.internal.base._Strings;
@@ -107,6 +109,14 @@ implements
.orElseGet(UserLocale::getDefault);
}
+ protected String render(final T value, final Function<T, String> toString) {
+ return Optional.ofNullable(value)
+ .map(toString)
+ .orElse(NULL_REPRESENTATION);
+ }
+
+ // -- NUMBER FORMATTING/PARSING
+
/**
* @param context - nullable in support of JUnit testing
* @return {@link NumberFormat} the default from from given context's locale
@@ -124,14 +134,6 @@ implements
return format;
}
- protected String render(final T value, final Function<T, String> toString) {
- return Optional.ofNullable(value)
- .map(toString)
- .orElse(NULL_REPRESENTATION);
- }
-
- // -- NUMBER FORMATTING/PARSING
-
protected @Nullable BigInteger parseInteger(
final @Nullable ValueSemanticsProvider.Context context,
final @Nullable String text) {
@@ -211,55 +213,22 @@ implements
}
}
- protected DateTimeFormatter getEditingFormat(
+ protected DateTimeFormatter getTemporalEditingFormat(
final @Nullable ValueSemanticsProvider.Context context,
final @NonNull TemporalValueSemantics.TemporalCharacteristic temporalCharacteristic,
final @NonNull TemporalValueSemantics.OffsetCharacteristic offsetCharacteristic,
+ final @NonNull TimePrecision timePrecision,
+ final @NonNull EditingFormatDirection direction,
final @NonNull TemporalEditingPattern editingPattern) {
- return getEditingFormatAsBuilder(
- temporalCharacteristic, offsetCharacteristic, editingPattern)
+ return new DateTimeFormatterBuilder()
+ .appendPattern(editingPattern.getEditingFormatAsPattern(
+ temporalCharacteristic, offsetCharacteristic, timePrecision, direction))
.parseLenient()
.parseCaseInsensitive()
.toFormatter(getUserLocale(context).getTimeFormatLocale());
}
- protected DateTimeFormatterBuilder getEditingFormatAsBuilder(
- final @NonNull TemporalValueSemantics.TemporalCharacteristic temporalCharacteristic,
- final @NonNull TemporalValueSemantics.OffsetCharacteristic offsetCharacteristic,
- final @NonNull TemporalEditingPattern editingPattern) {
-
- return new DateTimeFormatterBuilder()
- .appendPattern(getEditingFormatAsPattern(
- temporalCharacteristic, offsetCharacteristic, editingPattern));
- }
-
- protected String getEditingFormatAsPattern(
- final @NonNull TemporalValueSemantics.TemporalCharacteristic temporalCharacteristic,
- final @NonNull TemporalValueSemantics.OffsetCharacteristic offsetCharacteristic,
- final @NonNull TemporalEditingPattern editingPattern) {
-
- switch (temporalCharacteristic) {
- case DATE_TIME:
- val dateTimePattern =
- String.format(editingPattern.getDateTimeJoiningPattern(), editingPattern.getDatePattern(), editingPattern.getTimePattern());
- return offsetCharacteristic.isLocal()
- ? dateTimePattern
- : String.format(editingPattern.getZoneJoiningPattern(), dateTimePattern, editingPattern.getZonePattern());
- case DATE_ONLY:
- return offsetCharacteristic.isLocal()
- ? editingPattern.getDatePattern()
- : String.format(editingPattern.getZoneJoiningPattern(), editingPattern.getDatePattern(), editingPattern.getZonePattern());
- case TIME_ONLY:
- return offsetCharacteristic.isLocal()
- ? editingPattern.getTimePattern()
- : String.format(editingPattern.getZoneJoiningPattern(), editingPattern.getTimePattern(), editingPattern.getZonePattern());
- default:
- throw _Exceptions.unmatchedCase(temporalCharacteristic);
- }
- }
-
-
protected DateTimeFormatter getTemporalIsoFormat(
final @NonNull TemporalValueSemantics.TemporalCharacteristic temporalCharacteristic,
final @NonNull TemporalValueSemantics.OffsetCharacteristic offsetCharacteristic) {
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 67db9da..9993ff6 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
@@ -31,6 +31,7 @@ import javax.inject.Inject;
import org.springframework.lang.Nullable;
+import org.apache.isis.applib.annotation.TimePrecision;
import org.apache.isis.applib.exceptions.recoverable.TextEntryParseException;
import org.apache.isis.applib.value.semantics.EncodingException;
import org.apache.isis.applib.value.semantics.TemporalValueSemantics;
@@ -154,7 +155,7 @@ implements TemporalValueSemantics<T> {
@Override
public final String parseableTextRepresentation(final ValueSemanticsProvider.Context context, final T value) {
- return value==null ? "" : getEditingFormat(context).format(value);
+ return value==null ? "" : getEditingOutputFormat(context).format(value);
}
@Override
@@ -173,7 +174,7 @@ implements TemporalValueSemantics<T> {
}
}
- val format = getEditingFormat(context);
+ val format = getEditingInputFormat(context);
try {
return format.parse(temporalString, query);
@@ -199,17 +200,38 @@ implements TemporalValueSemantics<T> {
}
/**
- * Format used for editing.
+ * Format used for rendering editable text representation.
*/
- protected DateTimeFormatter getEditingFormat(final ValueSemanticsProvider.Context context) {
- return getEditingFormat(context, temporalCharacteristic, offsetCharacteristic,
+ protected DateTimeFormatter getEditingOutputFormat(final ValueSemanticsProvider.Context context) {
+
+ val dateAndTimeFormatStyle = DateAndTimeFormatStyle.forContext(mmc, context);
+
+ return getTemporalEditingFormat(context, temporalCharacteristic, offsetCharacteristic,
+ dateAndTimeFormatStyle.getTimePrecision(),
+ EditingFormatDirection.OUTPUT,
+ temporalEditingPattern());
+ }
+
+ /**
+ * Format used for parsing editable text representation.
+ */
+ protected DateTimeFormatter getEditingInputFormat(final ValueSemanticsProvider.Context context) {
+
+ val dateAndTimeFormatStyle = DateAndTimeFormatStyle.forContext(mmc, context);
+
+ return getTemporalEditingFormat(context, temporalCharacteristic, offsetCharacteristic,
+ dateAndTimeFormatStyle.getTimePrecision(),
+ EditingFormatDirection.INPUT,
temporalEditingPattern());
}
@Override
public String getPattern(final ValueSemanticsProvider.Context context) {
- return getEditingFormatAsPattern(temporalCharacteristic, offsetCharacteristic,
- temporalEditingPattern());
+
+ val dateAndTimeFormatStyle = DateAndTimeFormatStyle.forContext(mmc, context);
+
+ return temporalEditingPattern().getEditingFormatAsPattern(temporalCharacteristic, offsetCharacteristic,
+ dateAndTimeFormatStyle.getTimePrecision(), EditingFormatDirection.INPUT);
}
/**
@@ -225,6 +247,7 @@ implements TemporalValueSemantics<T> {
static class DateAndTimeFormatStyle {
@Nullable FormatStyle dateFormatStyle;
@Nullable FormatStyle timeFormatStyle;
+ @Nullable TimePrecision timePrecision;
static DateAndTimeFormatStyle forContext(
final @Nullable MetaModelContext mmc, // nullable .. JUnit support
@@ -247,7 +270,10 @@ implements TemporalValueSemantics<T> {
.map(TimeFormatStyleFacet::getTimeFormatStyle)
.orElse(FormatStyle.MEDIUM);
- return of(dateFormatStyle, timeFormatStyle);
+ //FIXME[ISIS-2882] honor facets
+ val timePrecision = TimePrecision.SECOND;
+
+ return of(dateFormatStyle, timeFormatStyle, timePrecision);
}
}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaUtilDateValueSemanticsProviderTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaUtilDateValueSemanticsProviderTest.java
index 07bb8ca..2511a16 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaUtilDateValueSemanticsProviderTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaUtilDateValueSemanticsProviderTest.java
@@ -60,7 +60,7 @@ extends ValueSemanticsProviderAbstractTestCase {
}
@Test
- public void testInvalidParse() throws Exception {
+ void testInvalidParse() {
try {
valueSemantics.parseTextRepresentation(null, "invalid entry");
fail();
@@ -68,20 +68,15 @@ extends ValueSemanticsProviderAbstractTestCase {
}
}
- /**
- * Something rather bizarre here, that the epoch formats as 01:00 rather
- * than 00:00. It's obviously because of some sort of timezone issue, but I
- * don't know where that dependency is coming from.
- */
@Test
- public void testRendering() {
+ void testRendering() {
val _context = Context.of(null, InteractionContext.builder().locale(UserLocale.valueOf(Locale.ENGLISH)).build());
assertEquals("Mar 13, 2013, 5:59:03 PM", valueSemantics.simpleTextPresentation(_context , date));
}
//FIXME[ISIS-2882] support omitted parts on input
@Test @Disabled
- public void testParseNoMinutes() throws Exception {
+ void testParseNoMinutes() {
val _context = Context.of(null, InteractionContext.builder().locale(UserLocale.valueOf(Locale.ENGLISH)).build());
val parsedDate = valueSemantics.parseTextRepresentation(_context, "2013-03-13 17");
assertEquals(date.getTime() - 3540_000L - 3000L, parsedDate.getTime());
@@ -89,14 +84,14 @@ extends ValueSemanticsProviderAbstractTestCase {
//FIXME[ISIS-2882] support omitted parts on input
@Test @Disabled
- public void testParseNoSeconds() throws Exception {
+ void testParseNoSeconds() {
val _context = Context.of(null, InteractionContext.builder().locale(UserLocale.valueOf(Locale.ENGLISH)).build());
val parsedDate = valueSemantics.parseTextRepresentation(_context, "2013-03-13 17:59");
assertEquals(date.getTime() - 3000L, parsedDate.getTime());
}
@Test
- public void testParseSeconds() throws Exception {
+ void testParseSeconds() {
val _context = Context.of(null, InteractionContext.builder().locale(UserLocale.valueOf(Locale.ENGLISH)).build());
val parsedDate = valueSemantics.parseTextRepresentation(_context, "2013-03-13 17:59:03");
assertEquals(date.getTime(), parsedDate.getTime());
@@ -107,7 +102,7 @@ extends ValueSemanticsProviderAbstractTestCase {
* @see https://stackoverflow.com/questions/30103167/jsr-310-parsing-seconds-fraction-with-variable-length
*/
@Test @Disabled("cannot find a format pattern that can handle both millis and nanos")
- public void testParseMillis() throws Exception {
+ void testParseMillis() {
val _context = Context.of(null, InteractionContext.builder().locale(UserLocale.valueOf(Locale.ENGLISH)).build());
val parsedDate = valueSemantics.parseTextRepresentation(_context, "2013-03-13 17:59:03.123");
assertEquals(date.getTime() + 123L, parsedDate.getTime());
@@ -115,7 +110,7 @@ extends ValueSemanticsProviderAbstractTestCase {
//FIXME[ISIS-2882] support omitted parts on input
@Test @Disabled
- public void testParseNanos() throws Exception {
+ void testParseNanos() {
val _context = Context.of(null, InteractionContext.builder().locale(UserLocale.valueOf(Locale.ENGLISH)).build());
val parsedDate = valueSemantics.parseTextRepresentation(_context, "2013-03-13 17:59:03.123456789");
assertEquals(date.getTime() + 123L, parsedDate.getTime());
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/valuesemantics/temporal/TemporalValueSemanticsProviderTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/valuesemantics/temporal/TemporalValueSemanticsProviderTest.java
new file mode 100644
index 0000000..1041718
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/valuesemantics/temporal/TemporalValueSemanticsProviderTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.isis.core.metamodel.valuesemantics.temporal;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.isis.applib.annotation.TimePrecision;
+import org.apache.isis.applib.locale.UserLocale;
+import org.apache.isis.applib.value.semantics.TemporalValueSemantics;
+import org.apache.isis.applib.value.semantics.TemporalValueSemantics.EditingFormatDirection;
+import org.apache.isis.applib.value.semantics.TemporalValueSemantics.OffsetCharacteristic;
+import org.apache.isis.applib.value.semantics.TemporalValueSemantics.TemporalCharacteristic;
+import org.apache.isis.applib.value.semantics.TemporalValueSemantics.TemporalEditingPattern;
+import org.apache.isis.applib.value.semantics.ValueSemanticsProvider.Context;
+import org.apache.isis.core.config.IsisConfiguration;
+import org.apache.isis.schema.common.v2.ValueType;
+
+import lombok.NonNull;
+import lombok.val;
+
+class TemporalValueSemanticsProviderTest {
+
+ private TemporalValueSemanticsProvider_forTesting target;
+ private TemporalEditingPattern editingPattern;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ editingPattern = (new IsisConfiguration.ValueTypes.Temporal()).getEditing();
+ }
+
+ @ParameterizedTest
+ @EnumSource(TimePrecision.class)
+ void testTimeFormats(final TimePrecision timePrecision) {
+
+ target = new TemporalValueSemanticsProvider_forTesting(
+ TemporalCharacteristic.TIME_ONLY, OffsetCharacteristic.LOCAL);
+
+ Context context = null;
+ LocalTime localTime = LocalTime.of(13, 12, 45);
+
+ val formatter = target.getTemporalEditingFormat(context ,
+ target.getTemporalCharacteristic(),
+ target.getOffsetCharacteristic(),
+ timePrecision,
+ EditingFormatDirection.OUTPUT,
+ editingPattern);
+
+ val formattedTemporal = formatter.format(localTime);
+ assertNotNull(formattedTemporal);
+
+ System.out.println(formattedTemporal);
+ }
+
+ // -- HELPER
+
+ private static class TemporalValueSemanticsProvider_forTesting
+ extends TemporalValueSemanticsProvider<Temporal> {
+
+ public TemporalValueSemanticsProvider_forTesting(
+ final TemporalCharacteristic temporalCharacteristic,
+ final OffsetCharacteristic offsetCharacteristic) {
+ super(temporalCharacteristic, offsetCharacteristic, 80, 80, null, null);
+ }
+
+ @Override public Class<Temporal> getCorrespondingClass() {
+ return Temporal.class;}
+
+ @Override public ValueType getSchemaValueType() {
+ return ValueType.VOID;}
+
+ @Override protected UserLocale getUserLocale(final Context context) {
+ return super.getUserLocale(context);}
+
+ @Override public Duration epsilon() {
+ return null;}
+
+ @Override public DateTimeFormatter getTemporalEditingFormat(final Context context,
+ @NonNull final TemporalValueSemantics.TemporalCharacteristic temporalCharacteristic,
+ @NonNull final TemporalValueSemantics.OffsetCharacteristic offsetCharacteristic,
+ @NonNull final TimePrecision timePrecision,
+ @NonNull final EditingFormatDirection direction,
+ @NonNull final TemporalEditingPattern editingPattern) {
+ return super.getTemporalEditingFormat(context, temporalCharacteristic, offsetCharacteristic, timePrecision, direction,
+ editingPattern); }
+
+ }
+
+}