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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 5f13a88877 Fix timezone issues: - Specify better how timezone is used in `WKTFormat(Locale, Timezone)` constructor. - Broken output in `StandardDateFormat.format(Date)` when the timezone is not UTC. - Command-line tool should not use local timezone unless explicitely requested. 5f13a88877 is described below commit 5f13a88877da3843cf04a598343a9da6a05422ef Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Jan 29 18:37:55 2024 +0100 Fix timezone issues: - Specify better how timezone is used in `WKTFormat(Locale, Timezone)` constructor. - Broken output in `StandardDateFormat.format(Date)` when the timezone is not UTC. - Command-line tool should not use local timezone unless explicitely requested. --- .../main/org/apache/sis/console/CommandRunner.java | 19 ++++- .../apache/sis/console/FormattedOutputCommand.java | 4 +- .../main/org/apache/sis/console/InfoCommand.java | 4 +- .../main/org/apache/sis/io/wkt/WKTDictionary.java | 2 +- .../main/org/apache/sis/io/wkt/WKTFormat.java | 20 +++++- .../referencing/factory/sql/EPSGDataAccess.java | 3 +- .../operation/AbstractCoordinateOperation.java | 2 +- .../org/apache/sis/io/wkt/ComparisonWithEPSG.java | 2 +- .../test/org/apache/sis/io/wkt/WKTFormatTest.java | 20 +++--- .../org/apache/sis/referencing/Assertions.java | 2 +- .../operation/CoordinateOperationFinderTest.java | 2 +- .../operation/CoordinateOperationRegistryTest.java | 2 +- .../DefaultCoordinateOperationFactoryTest.java | 2 +- .../sis/test/integration/ConsistencyTest.java | 10 +-- .../sis/storage/sql/feature/InfoStatements.java | 2 +- .../org/apache/sis/storage/base/URIDataStore.java | 3 +- .../main/org/apache/sis/storage/wkt/Store.java | 18 +---- .../main/org/apache/sis/io/CompoundFormat.java | 1 + .../sis/util/internal/StandardDateFormat.java | 81 +++++++--------------- .../sis/storage/shapefile/ShapefileStore.java | 5 +- .../sis/gui/metadata/StandardMetadataTree.java | 2 +- 21 files changed, 92 insertions(+), 114 deletions(-) diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java index 8b121d2371..355a5e1857 100644 --- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java +++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/CommandRunner.java @@ -89,8 +89,12 @@ abstract class CommandRunner { protected final Locale locale; /** - * The locale specified by the {@code "--timezone"} option. If no such option was provided, - * then this field is set to the {@linkplain TimeZone#getDefault() default timezone}. + * The locale specified by the {@code "--timezone"} option, or null if no timezone was specified. + * The null value may be interpreted as the {{@linkplain TimeZone#getDefault() default timezone} + * or as UTC, depending on the context. For example, WKT parsing and formatting use UTC unless + * specified otherwise. + * + * @see #getTimeZone() */ protected final TimeZone timezone; @@ -220,7 +224,7 @@ abstract class CommandRunner { locale = (s != null) ? Locales.parse(s) : Locale.getDefault(Locale.Category.DISPLAY); value = s = getOptionAsString(option = Option.TIMEZONE); - timezone = (s != null) ? TimeZone.getTimeZone(s) : TimeZone.getDefault(); + timezone = (s != null) ? TimeZone.getTimeZone(s) : null; value = s = getOptionAsString(option = Option.ENCODING); explicitEncoding = (s != null); @@ -255,6 +259,15 @@ abstract class CommandRunner { } } + /** + * {@return a non-null timezone, either the specified timezone or the default one}. + * This method is invoked when a null {@link #timezone} would be interpreted as UTC, + * but the {@linkplain TimeZone#getDefault() default timezone} is preferred instead. + */ + protected final TimeZone getTimeZone() { + return (timezone != null) ? timezone : TimeZone.getDefault(); + } + /** * Returns the value of the specified option as a character string. * diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java index 4bda2c2de6..ab86ed268f 100644 --- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java +++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/FormattedOutputCommand.java @@ -18,9 +18,9 @@ package org.apache.sis.console; import java.util.Locale; import java.util.EnumSet; +import java.util.function.Predicate; import java.io.Console; import java.io.IOException; -import java.util.function.Predicate; import jakarta.xml.bind.Marshaller; import jakarta.xml.bind.JAXBException; import org.opengis.metadata.Metadata; @@ -221,7 +221,7 @@ abstract class FormattedOutputCommand extends CommandRunner { final TreeTable tree = MetadataStandard.ISO_19115.asTreeTable(object, (object instanceof Metadata) ? Metadata.class : null, ValueExistencePolicy.COMPACT); - final var tf = new TreeTableFormat(locale, timezone); + final var tf = new TreeTableFormat(locale, getTimeZone()); tf.setColumns(TableColumn.NAME, TableColumn.VALUE); tf.setNodeFilter(getNodeFilter()); tf.format(tree, out); diff --git a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/InfoCommand.java b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/InfoCommand.java index ce0314b417..a888d498b5 100644 --- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/InfoCommand.java +++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/InfoCommand.java @@ -94,7 +94,7 @@ final class InfoCommand extends FormattedOutputCommand { } catch (BackingStoreException e) { throw e.unwrapOrRethrow(DataStoreException.class); } - final var tf = new TreeTableFormat(locale, timezone); + final var tf = new TreeTableFormat(locale, getTimeZone()); tf.format(tree, out); return 0; } @@ -133,7 +133,7 @@ final class InfoCommand extends FormattedOutputCommand { final TableColumn<? super String> column) { target.setValue(column, Vocabulary.forLocale(locale).getString(Vocabulary.Keys.SampleDimensions)); - final var rf = new RangeFormat(locale, timezone); + final var rf = new RangeFormat(locale, getTimeZone()); final var sb = new StringBuffer(); for (SampleDimension band : bands) { band = band.forConvertedValues(true); diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java index 1213f4451b..142be5aef6 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTDictionary.java @@ -414,7 +414,7 @@ public class WKTDictionary extends GeodeticAuthorityFactory { definitions = new HashMap<>(); codeCaches = new HashMap<>(); codespaces = new FrequencySortedSet<>(true); - parser = new WKTFormat(null, null); + parser = new WKTFormat(); lock = new ReentrantReadWriteLock(); authorities = (authority != null) ? null : new FrequencySortedSet<>(true); this.authority = authority; diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java index ac1e9489ed..3ac3fde4dd 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/WKTFormat.java @@ -299,12 +299,28 @@ public class WKTFormat extends CompoundFormat<Object> { */ private transient Warnings warnings; + /** + * Creates a format for the root locale and UTC timezone. + * This is the standard configuration for ISO 19162 Well-Known Text. + * + * @since 1.5 + */ + public WKTFormat() { + this(null, null); + } + /** * Creates a format for the given locale and timezone. The given locale will be used for - * {@link InternationalString} localization; this is <strong>not</strong> the locale for number format. + * {@link InternationalString} localization, <strong>not</strong> for formatting numbers. + * The given timezone will be used for parsing and formatting dates in temporal elements + * such as {@code TimeOrigin[…]}. Note that the specified timezone will not be formatted + * in WKT elements, as it will be assumed implicit. * * @param locale the locale for the new {@code Format}, or {@code null} for {@code Locale.ROOT}. - * @param timezone the timezone, or {@code null} for UTC. + * @param timezone the timezone for dates in the WKT temporal elements, or {@code null} for UTC. + * + * @see #getLocale() + * @see #getTimeZone() */ public WKTFormat(final Locale locale, final TimeZone timezone) { super(locale, timezone); diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java index 93763bd14f..d445a891a4 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java @@ -1733,8 +1733,7 @@ codes: for (int i=0; i<codes.length; i++) { throw new FactoryDataException(resources().getString(Resources.Keys.DatumOriginShallBeDate)); } if (dateFormat == null) { - dateFormat = new StandardDateFormat(); - dateFormat.setCalendar(getCalendar()); // Use UTC timezone. + dateFormat = new StandardDateFormat(); // Default to UTC timezone. } try { originDate = dateFormat.parse(anchor); diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java index 218d4ae696..b87ba7353b 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java @@ -913,7 +913,7 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 * To enabled this variant, {@link org.apache.sis.io.wkt.WKTFormat} can be configured as below: * * {@snippet lang="java" : - * format = new WKTFormat(null, null); + * format = new WKTFormat(); * format.setConvention(Convention.WKT1_IGNORE_AXES); * format.setNameAuthority(Citations.ESRI); * } diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/ComparisonWithEPSG.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/ComparisonWithEPSG.java index dbd2152e25..9a8cfb3949 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/ComparisonWithEPSG.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/ComparisonWithEPSG.java @@ -181,7 +181,7 @@ public final class ComparisonWithEPSG extends TestCase { assumeTrue(factory != null); CoordinateOperation opFromCode = factory.createCoordinateOperation("5630"); String wkt = opFromCode.toWKT(); - WKTFormat parser = new WKTFormat(null, null); + WKTFormat parser = new WKTFormat(); CoordinateOperation opFromWKT = (CoordinateOperation) parser.parseObject(wkt); assertEqualsIgnoreMetadata(opFromCode, opFromWKT); } diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTFormatTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTFormatTest.java index c998e75f7a..e368cbaf0f 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTFormatTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/io/wkt/WKTFormatTest.java @@ -77,7 +77,7 @@ public final class WKTFormatTest extends TestCase { */ @Test public void testParse() throws ParseException { - format = new WKTFormat(null, null); + format = new WKTFormat(); final VerticalCRS crs = (VerticalCRS) format.parseObject( "VERT_CS[“Gravity-related height”,\n" + " VERT_DATUM[“Mean Sea Level”, 2005],\n" + @@ -96,7 +96,7 @@ public final class WKTFormatTest extends TestCase { */ @Test public void testConsistencyOfWKT1() throws ParseException { - format = new WKTFormat(null, null); + format = new WKTFormat(); format.setConvention(Convention.WKT1); parser = format; testConsistency(); @@ -112,9 +112,9 @@ public final class WKTFormatTest extends TestCase { @Test @DependsOnMethod("testConsistencyOfWKT1") public void testConsistencyOfWKT1_WithCommonUnits() throws ParseException { - format = new WKTFormat(null, null); + format = new WKTFormat(); format.setConvention(Convention.WKT1_COMMON_UNITS); - parser = new WKTFormat(null, null); + parser = new WKTFormat(); parser.setConvention(Convention.WKT1); testConsistency(); testConsistencyWithDenormalizedBaseCRS(); @@ -129,7 +129,7 @@ public final class WKTFormatTest extends TestCase { @Test @DependsOnMethod("testConsistencyOfWKT1") public void testConsistencyOfWKT2() throws ParseException { - format = new WKTFormat(null, null); + format = new WKTFormat(); format.setConvention(Convention.WKT2); parser = format; testConsistency(); @@ -144,7 +144,7 @@ public final class WKTFormatTest extends TestCase { @Test @DependsOnMethod("testConsistencyOfWKT2") public void testConsistencyOfWKT2_Simplified() throws ParseException { - format = new WKTFormat(null, null); + format = new WKTFormat(); format.setConvention(Convention.WKT2_SIMPLIFIED); parser = format; testConsistency(); @@ -249,7 +249,7 @@ public final class WKTFormatTest extends TestCase { public void testConsistencyOfGeogTran() throws ParseException { final Symbols symbols = new Symbols(Symbols.SQUARE_BRACKETS); symbols.setPairedQuotes("“”", "\"\""); - format = new WKTFormat(null, null); + format = new WKTFormat(); format.setConvention(Convention.WKT1_IGNORE_AXES); format.setNameAuthority(Citations.ESRI); format.setSymbols(symbols); @@ -299,7 +299,7 @@ public final class WKTFormatTest extends TestCase { public void testVariousConventions() throws ParseException { final Symbols symbols = new Symbols(Symbols.SQUARE_BRACKETS); symbols.setPairedQuotes("“”"); - parser = format = new WKTFormat(null, null); + parser = format = new WKTFormat(); format.setSymbols(symbols); final DefaultProjectedCRS crs = (DefaultProjectedCRS) parser.parseObject( "PROJCS[“OSGB 1936 / British National Grid”,\n" + @@ -463,7 +463,7 @@ public final class WKTFormatTest extends TestCase { */ @Test public void testFragments() throws ParseException { - format = new WKTFormat(null, null); + format = new WKTFormat(); format.addFragment("deg", "UNIT[“degree”, 0.0174532925199433]"); format.addFragment("Bessel", "SPHEROID[“Bessel 1841”, 6377397.155, 299.1528128, AUTHORITY[“EPSG”,“7004”]]"); format.addFragment("Tokyo", "DATUM[“Tokyo”, $Bessel]"); @@ -493,7 +493,7 @@ public final class WKTFormatTest extends TestCase { public void testSourceFile() throws Exception { final var source = new URI("test/wkt.prj"); final var factory = new MathTransformFactoryMock("Mercator"); - format = new WKTFormat(null, null); + format = new WKTFormat(); format.setSourceFile(source); format.setFactory(MathTransformFactory.class, factory); final Parameterized mt = assertInstanceOf(Parameterized.class, format.parseObject( diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java index 052b56de5b..53e1b0f8ac 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/Assertions.java @@ -65,7 +65,7 @@ public final class Assertions extends Static { * This formatter uses the {@code “…”} quotation marks instead of {@code "…"} * for easier readability of {@link String} constants in Java code. */ - private static final WKTFormat WKT_FORMAT = new WKTFormat(null, null); + private static final WKTFormat WKT_FORMAT = new WKTFormat(); static { final Symbols s = new Symbols(Symbols.SQUARE_BRACKETS); s.setPairedQuotes("“”", "\"\""); diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java index 7a850ddf1d..65f0756661 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java @@ -130,7 +130,7 @@ public final class CoordinateOperationFinderTest extends MathTransformTestCase { @BeforeClass public static void createFactory() throws ParseException { factory = new DefaultCoordinateOperationFactory(); - parser = new WKTFormat(null, null); + parser = new WKTFormat(); /* * The first keyword in WKT below should be "GeodeticCRS" in WKT 2, but we use the WKT 1 keyword ("GEOGCS") * for allowing inclusion in ProjectedCRS. SIS is okay with mixed WKT versions, but this is of course not diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java index 5b246296f2..20e103127a 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java @@ -127,7 +127,7 @@ public final class CoordinateOperationRegistryTest extends MathTransformTestCase @BeforeClass public static void createFactory() throws ParseException { factory = new DefaultCoordinateOperationFactory(); - parser = new WKTFormat(null, null); + parser = new WKTFormat(); parser.addFragment("NTF", "Datum[“Nouvelle Triangulation Française (Paris)”,\n" + " Ellipsoid[“Clarke 1880 (IGN)”, 6378249.2, 293.4660212936269]]"); diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java index 185444a0f1..8152637613 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactoryTest.java @@ -99,7 +99,7 @@ public final class DefaultCoordinateOperationFactoryTest extends MathTransformTe @BeforeClass public static void createFactory() throws ParseException { factory = new DefaultCoordinateOperationFactory(); - parser = new WKTFormat(null, null); + parser = new WKTFormat(); parser.addFragment("NTF", "ProjectedCRS[“NTF (Paris) / Lambert zone II”,\n" + " BaseGeodCRS[“NTF (Paris)”,\n" + diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java index 2ae585cbd4..e766cba968 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/test/integration/ConsistencyTest.java @@ -102,7 +102,7 @@ public final class ConsistencyTest extends TestCase { public void debug() throws FactoryException { final String code = "EPSG::29871"; final CoordinateReferenceSystem crs = CRS.forCode(code); - final WKTFormat format = new WKTFormat(null, null); + final WKTFormat format = new WKTFormat(); format.setConvention(Convention.WKT2); lookup(parseAndFormat(format, code, crs), crs); } @@ -115,10 +115,10 @@ public final class ConsistencyTest extends TestCase { @Test public void testCoordinateReferenceSystems() throws FactoryException { assumeTrue("Extensive tests not enabled.", RUN_EXTENSIVE_TESTS); - final WKTFormat v1 = new WKTFormat(null, null); - final WKTFormat v1c = new WKTFormat(null, null); - final WKTFormat v2 = new WKTFormat(null, null); - final WKTFormat v2s = new WKTFormat(null, null); + final WKTFormat v1 = new WKTFormat(); + final WKTFormat v1c = new WKTFormat(); + final WKTFormat v2 = new WKTFormat(); + final WKTFormat v2s = new WKTFormat(); v1 .setConvention(Convention.WKT1); v1c.setConvention(Convention.WKT1_COMMON_UNITS); v2 .setConvention(Convention.WKT2); diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java index 93c1459240..882f5b8f1f 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java @@ -547,7 +547,7 @@ public class InfoStatements implements Localized, AutoCloseable { */ private WKTFormat wktReader() { if (wktReader == null) { - wktReader = new WKTFormat(null, null); + wktReader = new WKTFormat(); wktReader.setConvention(Convention.WKT1_COMMON_UNITS); } return wktReader; diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java index 5a43496c70..25d3f42255 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java @@ -96,7 +96,8 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R /** * User-specified locale for textual content, or {@code null} for {@link Locale#ROOT} (usually English). - * This locale is usually <strong>not</strong> used for parsing numbers or dates. + * This locale is usually for {@link org.opengis.util.InternationalString} localization rather than for + * parsing numbers or dates, but the exact interpretation is at subclasses choice. * Subclasses may replace this value by a value read from the data file. */ protected Locale dataLocale; diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java index c3ea161c3c..7ecb16f8b1 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/wkt/Store.java @@ -19,8 +19,6 @@ package org.apache.sis.storage.wkt; import java.util.List; import java.util.Arrays; import java.util.ArrayList; -import java.util.Locale; -import java.util.TimeZone; import java.io.Reader; import java.io.IOException; import java.text.ParsePosition; @@ -61,18 +59,6 @@ final class Store extends URIDataStore { */ private volatile Reader source; - /** - * The locale for {@link org.opengis.util.InternationalString} localization - * or {@code null} for {@link Locale#ROOT} (usually English). - * This locale is <strong>not</strong> used for parsing numbers or dates. - */ - private final Locale locale; - - /** - * Timezone for dates, or {@code null} for UTC. - */ - private final TimeZone timezone; - /** * The geometry library, or {@code null} for the default. */ @@ -99,8 +85,6 @@ final class Store extends URIDataStore { public Store(final StoreProvider provider, final StorageConnector connector) throws DataStoreException { super(provider, connector); objects = new ArrayList<>(); - locale = connector.getOption(OptionKey.LOCALE); // For `InternationalString`, not for numbers. - timezone = connector.getOption(OptionKey.TIMEZONE); library = connector.getOption(OptionKey.GEOMETRY_LIBRARY); source = connector.commit(Reader.class, StoreProvider.NAME); listeners.useReadOnlyEvents(); @@ -140,7 +124,7 @@ final class Store extends URIDataStore { * definitions. */ final ParsePosition pos = new ParsePosition(0); - final StoreFormat parser = new StoreFormat(locale, timezone, library, listeners); + final StoreFormat parser = new StoreFormat(dataLocale, timezone, library, listeners); do { final Object obj = parser.parse(wkt, pos); objects.add(obj); diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/CompoundFormat.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/CompoundFormat.java index 2f30c62fb5..b9231e2f89 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/CompoundFormat.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/CompoundFormat.java @@ -475,6 +475,7 @@ public abstract class CompoundFormat<T> extends Format implements Localized { * documented in this method javadoc. But actually it is not, since the call to * DefaultFormat.getInstance(…) will indirectly perform this kind of comparison. */ + @SuppressWarnings("LocalVariableHidesMemberVariable") final Locale locale = getLocale(Locale.Category.FORMAT); if (Number.class.isAssignableFrom(valueType)) { if (Locale.ROOT.equals(locale)) { diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/StandardDateFormat.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/StandardDateFormat.java index b101103cf2..5164fac26c 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/StandardDateFormat.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/StandardDateFormat.java @@ -51,10 +51,6 @@ import org.apache.sis.util.CharSequences; * the time is optional. For this class, "Standard" is interpreted as "close to ISO 19162 requirements", * which is not necessarily identical to other ISO standards. * - * <p>External users should use nothing else than the parsing and formatting methods. - * The methods for configuring the {@code DateFormat} instances may or may not work - * depending on the branch.</p> - * * <p>The main usage for this class is Well Known Text (WKT) parsing and formatting. * ISO 19162 uses ISO 8601:2004 for the dates. Any precision is allowed: the date could have only the year, * or only the year and month, <i>etc</i>. The clock part is optional and also have optional fields: can be @@ -99,7 +95,7 @@ public final class StandardDateFormat extends DateFormat { .optionalStart().appendLiteral(':').appendValue(ChronoField.SECOND_OF_MINUTE, 2) .appendFraction(ChronoField.MILLI_OF_SECOND, 3, 3, true) .optionalEnd().optionalEnd().optionalEnd() // Move back to the optional block of HOUR_OF_DAY. - .optionalStart().appendZoneOrOffsetId() + .optionalStart().optionalStart().appendLiteral(' ').optionalEnd().appendZoneOrOffsetId() .toFormatter(Locale.ROOT); /** @@ -245,40 +241,6 @@ replace: if (Character.isWhitespace(c)) { */ public static final int NANOS_PER_SECOND = 1000_000_000; - /** - * Converts the given legacy {@code Date} object into a {@code java.time} implementation in given timezone. - * The method performs the following choice: - * - * <ul> - * <li>If the given date has zero values in hours, minutes, seconds and milliseconds fields in UTC timezone, - * then the returned implementation will be a {@link LocalDate}, dropping the timezone information (i.e. - * the date is considered an approximation). Note that this is consistent with ISO 19162 requirement that - * dates are always in UTC, even if Apache SIS allows some flexibility.</li> - * <li>Otherwise if the timezone is not {@code null} and not UTC, then this method returns an {@link OffsetDateTime}.</li> - * <li>Otherwise this method returns a {@link LocalDateTime} in the given timezone.</li> - * </ul> - * - * @param date the date to convert, or {@code null}. - * @param zone the timezone of the temporal object to obtain, or {@code null} for UTC. - * @return the temporal object for the given date, or {@code null} if the given argument was null. - */ - public static Temporal toHeuristicTemporal(final Date date, ZoneId zone) { - if (date == null) { - return null; - } - final long time = date.getTime(); - if ((time % MILLISECONDS_PER_DAY) == 0) { - return LocalDate.ofEpochDay(time / MILLISECONDS_PER_DAY); - } - final Instant instant = Instant.ofEpochMilli(time); - if (zone == null) { - zone = ZoneOffset.UTC; - } else if (!zone.equals(ZoneOffset.UTC)) { - return OffsetDateTime.ofInstant(instant, zone); - } - return LocalDateTime.ofInstant(instant, zone); - } - /** * Converts the given temporal object into a date. * The given temporal object is typically the value parsed by {@link #FORMAT}. @@ -347,11 +309,18 @@ replace: if (Character.isWhitespace(c)) { */ private DateTimeFormatter format; + /** + * The formatter without timezone. This is usually the same object as {@link #format}, + * unless a timezone has been set in which case this field keep the original formatter. + * This is used for formatting local dates without the formatter timezone. + */ + private final DateTimeFormatter formatWithoutZone; + /** * Creates a new format for a default locale in the UTC timezone. */ public StandardDateFormat() { - format = FORMAT; + formatWithoutZone = format = FORMAT; } /** @@ -360,7 +329,8 @@ replace: if (Character.isWhitespace(c)) { * @param locale the locale of the format to create. */ public StandardDateFormat(final Locale locale) { - format = FORMAT.withLocale(locale); // Same instance as FORMAT if the locales are equal. + // Same instance as FORMAT if the locales are equal. + formatWithoutZone = format = FORMAT.withLocale(locale); } /** @@ -449,8 +419,9 @@ replace: if (Character.isWhitespace(c)) { } /** - * Formats the given date. If hours, minutes, seconds and milliseconds are zero and the timezone is UTC, - * then this method omits the clock part (unless the user has overridden the pattern). + * Formats the given date. If hours, minutes, seconds and milliseconds are zero in the timezone of this formatter, + * then this method omits the clock part. The timezone is always omitted (ISO 19162 does not include timezone in + * WKT elements because all dates are required to be in UTC). * * @param date the date to format. * @param toAppendTo where to format the date. @@ -459,7 +430,16 @@ replace: if (Character.isWhitespace(c)) { */ @Override public StringBuffer format(final Date date, final StringBuffer toAppendTo, final FieldPosition pos) { - format.formatTo(toHeuristicTemporal(date, null), toAppendTo); + ZoneId zone = format.getZone(); + if (zone == null) { + zone = ZoneOffset.UTC; + } + final LocalDateTime dt = LocalDateTime.ofInstant(date.toInstant(), zone); + TemporalAccessor value = dt; + if (dt.getHour() == 0 && dt.getMinute() == 0 && dt.getSecond() == 0 && dt.getNano() == 0) { + value = dt.toLocalDate(); + } + formatWithoutZone.formatTo(value, toAppendTo); return toAppendTo; } @@ -530,17 +510,4 @@ replace: if (Character.isWhitespace(c)) { public boolean equals(final Object obj) { return (obj instanceof StandardDateFormat) && format.equals(((StandardDateFormat) obj).format); } - - /** - * Returns a clone of this format. - * - * @return a clone of this format. - */ - @Override - @SuppressWarnings("CloneDoesntCallSuperClone") - public Object clone() { - final StandardDateFormat clone = new StandardDateFormat(); - clone.format = format; - return clone; - } } diff --git a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java index b3db8bf79b..8220ba595e 100644 --- a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java +++ b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java @@ -19,7 +19,6 @@ package org.apache.sis.storage.shapefile; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.nio.ByteBuffer; -import java.math.BigInteger; import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.Charset; @@ -32,14 +31,12 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map.Entry; import java.util.Optional; import java.util.OptionalLong; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; -import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; @@ -738,7 +735,7 @@ public final class ShapefileStore extends DataStore implements WritableFeatureSe //write prj try { - final WKTFormat format = new WKTFormat(Locale.ENGLISH, null); + final WKTFormat format = new WKTFormat(); format.setConvention(Convention.WKT1_COMMON_UNITS); format.setNameAuthority(Citations.ESRI); format.setIndentation(WKTFormat.SINGLE_LINE); diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/StandardMetadataTree.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/StandardMetadataTree.java index e057893d7c..b40e00a40e 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/StandardMetadataTree.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/StandardMetadataTree.java @@ -190,7 +190,7 @@ public class StandardMetadataTree extends MetadataTree { final String text; try { if (source == copyAsWKT) { // Well Known Text. - final WKTFormat f = new WKTFormat(null, null); + final WKTFormat f = new WKTFormat(); text = f.format(obj); } else if (source == copyAsXML) { // GML or ISO 19115-3:2016. text = XML.marshal(obj);