From 0f2d7f89783da298e315e496e3a385ac4981970b Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 5 Jan 2026 08:58:51 -0500 Subject: [PATCH 1/4] Squashed commit of the following: commit cdab7ade8433e9ef8661b935fbde0ec8125c7354 Merge: d025c9f ddcaf60 Author: kevin Date: Sun Jan 4 19:59:49 2026 -0500 Merge branch 'InitializeByZoneNames' of https://github.com/PhotoKevin/timeshape into InitializeByZoneNames commit d025c9fdc79875bb3213c38c9f41cf8ece081bde Author: kevin Date: Sun Jan 4 19:59:43 2026 -0500 Added an example of the new initializer method. commit ddcaf60d30e750654625a0cc881c0a25e6126b4b Merge: c54ff64 ff837e7 Author: Kevin Date: Sun Jan 4 19:48:28 2026 -0500 Merge branch 'RomanIakovlev:master' into InitializeByZoneNames commit c54ff644a365e8fbf21e8272346a33a66a9370e2 Author: kevin Date: Sat Jan 3 13:34:06 2026 -0500 Parameter order on assertEquals was backwards assertEquals parameters were reversed. Changed to expected, actual. commit 180966dab51259cec61173fa79067696e80dc211 Author: kevin Date: Sat Jan 3 13:31:18 2026 -0500 New initialization functions that use a list of ZoneIds. Added initialization functions to use a lists of ZoneIds. Users may find it easier to determine which time zones they're interested in instead what the bounding box is for, say, all of Europe. Added documentation comments for parameters. --- README.MD | 11 ++- .../java/net/iakovlev/timeshape/Index.java | 44 +++++++++++ .../iakovlev/timeshape/TimeZoneEngine.java | 77 +++++++++++++++++++ .../timeshape/TimeZoneEngineBoundedTest.java | 2 +- .../TimeZoneEngineBoundedZoneTest.java | 32 ++++++++ 5 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java diff --git a/README.MD b/README.MD index a70a703..07bda53 100644 --- a/README.MD +++ b/README.MD @@ -77,6 +77,15 @@ TimeZoneEngine engine = TimeZoneEngine.initialize(47.0599, 4.8237, 55.3300, 15.2 It is important to mention that for the time zone to be loaded by the second method, it must be covered by the bounding box completely, not just intersect with it. +if you have difficulty determining the bounding box, you may prefer to use a list of ZoneIds instead. +```Java +ArrayList timeZones = new ArrayList<> (); +timeZones.add(ZoneId.of("Europe/Berlin")); +timeZones.add(ZoneId.of("America/Detroit")); +TimeZoneEngine engine = TimeZoneEngine.initialize (timeZones, true); + +``` + During initialization, the data is read from resource and the index is built. Initialization takes a significant amount of time (approximately 1 second), so do it only once in program lifetime. @@ -141,7 +150,7 @@ for information about areas of the world where multiple time zones are to be exp Timeshape supports querying of multiple sequential geo points (a polyline, e.g. a GPS trace) in an optimized way using method `List queryPolyline(double[] points)`. Performance tests (see `net.iakovlev.timeshape.PolylineQueryBenchmark`) -show significant speedup of using this method for querying a polyline, comparing to separately querying each point from polyline +show significant speedup of using this method for querying a polyline, comparing to separately querying each point from polyline using `Optional query(double latitude, double longitude)` method: ``` diff --git a/core/src/main/java/net/iakovlev/timeshape/Index.java b/core/src/main/java/net/iakovlev/timeshape/Index.java index c40e728..bed0673 100644 --- a/core/src/main/java/net/iakovlev/timeshape/Index.java +++ b/core/src/main/java/net/iakovlev/timeshape/Index.java @@ -151,6 +151,7 @@ private static Stream getPolygons(Geojson.Feature f) { } } + @SuppressWarnings("SizeReplaceableByIsEmpty") static Index build(Stream features, int size, Envelope boundaries, boolean accelerateGeometry) { Envelope2D boundariesEnvelope = new Envelope2D(); boundaries.queryEnvelope2D(boundariesEnvelope); @@ -192,4 +193,47 @@ static Index build(Stream features, int size, Envelope boundari return new Index(quadTree, zoneIds); } + + @SuppressWarnings("SizeReplaceableByIsEmpty") + static Index build(Stream features, int size, List timeZones, boolean accelerateGeometry) { + Envelope2D boundariesEnvelope = new Envelope2D(); + Envelope boundaries = new Envelope(-180, -90, 180, +90);// (minLon, minLat, maxLon, maxLat); + boundaries.queryEnvelope2D(boundariesEnvelope); + QuadTree quadTree = new QuadTree(boundariesEnvelope, 8); + Envelope2D env = new Envelope2D(); + ArrayList zoneIds = new ArrayList<>(size); + PrimitiveIterator.OfInt indices = IntStream.iterate(0, i -> i + 1).iterator(); + List unknownZones = new ArrayList<>(); + OperatorIntersects operatorIntersects = OperatorIntersects.local(); + features.forEach(f -> { + String zoneIdName = f.getProperties(0).getValueString(); + try { + ZoneId zoneId = ZoneId.of(zoneIdName); + getPolygons(f).forEach(polygon -> { + if (timeZones.contains (zoneId)) { + log.debug("Adding zone {} to index", zoneIdName); + if (accelerateGeometry) { + operatorIntersects.accelerateGeometry(polygon, spatialReference, Geometry.GeometryAccelerationDegree.enumMild); + } + polygon.queryEnvelope2D(env); + int index = indices.next(); + quadTree.insert(index, env); + zoneIds.add(index, new Entry(zoneId, polygon)); + } else { + log.debug("Not adding zone {} to index because it's out of provided boundaries", zoneIdName); + } + }); + } catch (Exception ex) { + unknownZones.add(zoneIdName); + } + }); + if (unknownZones.size() != 0) { + String allUnknownZones = String.join(", ", unknownZones); + log.error( + "Some of the zone ids were not recognized by the Java runtime and will be ignored. " + + "The most probable reason for this is outdated Java runtime version. " + + "The following zones were not recognized: " + allUnknownZones); + } + return new Index(quadTree, zoneIds); + } } diff --git a/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java b/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java index adbcf61..7c7eca3 100644 --- a/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java +++ b/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java @@ -103,6 +103,7 @@ public List queryAll(double latitude, double longitude) { * @return {@code Optional#of(ZoneId)} if input corresponds * to some zone, or {@link Optional#empty()} otherwise. */ + @SuppressWarnings("SizeReplaceableByIsEmpty") public Optional query(double latitude, double longitude) { final List result = index.query(latitude, longitude); return result.size() > 0 ? Optional.of(result.get(0)) : Optional.empty(); @@ -138,6 +139,7 @@ public List getKnownZoneIds() { * Creates a new instance of {@link TimeZoneEngine} and initializes it. * This is a blocking long running operation. * + * @param accelerateGeometry Increase query speed at the expense of memory utilization * @return an initialized instance of {@link TimeZoneEngine} */ public static TimeZoneEngine initialize(boolean accelerateGeometry) { @@ -169,6 +171,7 @@ public static TimeZoneEngine initialize() { * } * }}} * + * @param f Input stream of timezone data tar archive * @return an initialized instance of {@link TimeZoneEngine} */ public static TimeZoneEngine initialize(TarArchiveInputStream f) { @@ -188,6 +191,13 @@ public static TimeZoneEngine initialize(TarArchiveInputStream f) { * throw new RuntimeException(e); * } * }}} + * + * @param minLat Minimum latitude of bounding box + * @param minLon Minimum longitude of bounding box + * @param maxLat Maximum latitude of bounding box + * @param maxLon Maximum longitude of bounding box + * @param accelerateGeometry Increase query speed at the expense of memory utilization + * @param f Input stream of timezone data tar archive * * @return an initialized instance of {@link TimeZoneEngine} */ @@ -224,11 +234,54 @@ public static TimeZoneEngine initialize(double minLat, accelerateGeometry)); } + /** + * Creates a new instance of {@link TimeZoneEngine} and initializes it. + * This is a blocking long running operation. + * + * @param timeZones List of ZoneIds to load. + * @param f Input stream of timezone data tar archive + * @param accelerateGeometry Increase query speed at the expense of memory utilization + * @return an initialized instance of {@link TimeZoneEngine} + */ + + public static TimeZoneEngine initialize(List timeZones, + boolean accelerateGeometry, + TarArchiveInputStream f) { + log.info("Initializing with list of time zones"); + + Spliterator tarArchiveEntrySpliterator = makeSpliterator(f); + Stream featureStream = StreamSupport.stream(tarArchiveEntrySpliterator, false).map(n -> { + try { + if (n != null) { + log.debug("Processing archive entry {}", n.getName()); + byte[] e = new byte[(int) n.getSize()]; + f.read(e); + return Geojson.Feature.parseFrom(e); + } else { + throw new RuntimeException("Data entry is not found in file"); + } + } catch (NullPointerException | IOException ex) { + throw new RuntimeException(ex); + } + }); + int numberOfTimezones = 449; // can't get number of entries from tar, need to set manually + return new TimeZoneEngine( + Index.build( + featureStream, + numberOfTimezones, + timeZones, + accelerateGeometry)); + } /** * Creates a new instance of {@link TimeZoneEngine} and initializes it. * This is a blocking long running operation. * + * @param minLat Minimum latitude of bounding box + * @param minLon Minimum longitude of bounding box + * @param maxLat Maximum latitude of bounding box + * @param maxLon Maximum longitude of bounding box + * @param accelerateGeometry Increase query speed at the expense of memory utilization * @return an initialized instance of {@link TimeZoneEngine} */ public static TimeZoneEngine initialize(double minLat, double minLon, double maxLat, double maxLon, boolean accelerateGeometry) { @@ -245,4 +298,28 @@ public static TimeZoneEngine initialize(double minLat, double minLon, double max throw new RuntimeException(e); } } + + + /** + * Creates a new instance of {@link TimeZoneEngine} and initializes it. + * This is a blocking long running operation. + * + * @param timeZones List of ZoneIds to load. + * @param accelerateGeometry Increase query speed at the expense of memory utilization + * @return an initialized instance of {@link TimeZoneEngine} + */ + public static TimeZoneEngine initialize(List timeZones, boolean accelerateGeometry) { + try (InputStream resourceAsStream = TimeZoneEngine.class.getResourceAsStream("/data.tar.zstd")) { + try (ZstdInputStream unzipStream = new ZstdInputStream(resourceAsStream)) { + try (BufferedInputStream bufferedStream = new BufferedInputStream(unzipStream)) { + try (TarArchiveInputStream shapeInputStream = new TarArchiveInputStream(bufferedStream)) { + return initialize(timeZones, accelerateGeometry, shapeInputStream); + } + } + } + } catch (NullPointerException | IOException e) { + log.error("Unable to read resource file", e); + throw new RuntimeException(e); + } + } } diff --git a/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java index 0f5dc9a..de368cc 100644 --- a/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java +++ b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java @@ -22,6 +22,6 @@ public void testSomeZones() { @Test public void testWorld() { List knownZoneIds = engine.getKnownZoneIds(); - assertEquals(knownZoneIds.size(), 39); + assertEquals(39, knownZoneIds.size()); } } diff --git a/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java new file mode 100644 index 0000000..b19d700 --- /dev/null +++ b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java @@ -0,0 +1,32 @@ +package net.iakovlev.timeshape; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Optional; + +import static junit.framework.TestCase.assertEquals; + +@RunWith(JUnit4.class) +public class TimeZoneEngineBoundedZoneTest { + + @Test + public void testSomeZones() { + ArrayList timeZones = new ArrayList<> (); + timeZones.add (ZoneId.of("Europe/Berlin")); + TimeZoneEngine engine = TimeZoneEngine.initialize (timeZones, true); + assertEquals(Optional.of(ZoneId.of("Europe/Berlin")), engine.query(52.52, 13.40)); + + timeZones.clear (); + timeZones.add(ZoneId.of("America/Detroit")); + engine = TimeZoneEngine.initialize (timeZones, true); + assertEquals(Optional.empty (), engine.query(52.52, 13.40)); + + timeZones.add(ZoneId.of("Europe/Berlin")); + engine = TimeZoneEngine.initialize (timeZones, true); + assertEquals(Optional.of(ZoneId.of("Europe/Berlin")), engine.query(52.52, 13.40)); + } +} From 01abc95b567123a6bec77850c9ef5b0edaad00dd Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 27 Jan 2026 13:56:02 -0500 Subject: [PATCH 2/4] Hoist a comparison out of the loop. --- core/src/main/java/net/iakovlev/timeshape/Index.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/net/iakovlev/timeshape/Index.java b/core/src/main/java/net/iakovlev/timeshape/Index.java index bed0673..50e09b7 100644 --- a/core/src/main/java/net/iakovlev/timeshape/Index.java +++ b/core/src/main/java/net/iakovlev/timeshape/Index.java @@ -209,8 +209,8 @@ static Index build(Stream features, int size, List time String zoneIdName = f.getProperties(0).getValueString(); try { ZoneId zoneId = ZoneId.of(zoneIdName); - getPolygons(f).forEach(polygon -> { - if (timeZones.contains (zoneId)) { + if (timeZones.contains (zoneId)) { + getPolygons(f).forEach(polygon -> { log.debug("Adding zone {} to index", zoneIdName); if (accelerateGeometry) { operatorIntersects.accelerateGeometry(polygon, spatialReference, Geometry.GeometryAccelerationDegree.enumMild); @@ -219,10 +219,10 @@ static Index build(Stream features, int size, List time int index = indices.next(); quadTree.insert(index, env); zoneIds.add(index, new Entry(zoneId, polygon)); - } else { - log.debug("Not adding zone {} to index because it's out of provided boundaries", zoneIdName); - } - }); + }); + } else { + log.debug("Not adding zone {} to index because it's out of provided boundaries", zoneIdName); + } } catch (Exception ex) { unknownZones.add(zoneIdName); } From 54c3dea895c1313d7c00115e9f0901d728f6413d Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 4 Feb 2026 09:22:41 -0500 Subject: [PATCH 3/4] Use sets instead of lists. Pull out duplicate code. --- .../java/net/iakovlev/timeshape/Index.java | 4 +- .../iakovlev/timeshape/TimeZoneEngine.java | 76 +++++++++---------- .../timeshape/TimeZoneEngineBoundedTest.java | 2 +- .../TimeZoneEngineBoundedZoneTest.java | 7 +- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/net/iakovlev/timeshape/Index.java b/core/src/main/java/net/iakovlev/timeshape/Index.java index 50e09b7..a640edd 100644 --- a/core/src/main/java/net/iakovlev/timeshape/Index.java +++ b/core/src/main/java/net/iakovlev/timeshape/Index.java @@ -10,7 +10,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.PrimitiveIterator; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -195,7 +197,7 @@ static Index build(Stream features, int size, Envelope boundari @SuppressWarnings("SizeReplaceableByIsEmpty") - static Index build(Stream features, int size, List timeZones, boolean accelerateGeometry) { + static Index build(Stream features, int size, Set timeZones, boolean accelerateGeometry) { Envelope2D boundariesEnvelope = new Envelope2D(); Envelope boundaries = new Envelope(-180, -90, 180, +90);// (minLon, minLat, maxLon, maxLat); boundaries.queryEnvelope2D(boundariesEnvelope); diff --git a/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java b/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java index 7c7eca3..a1668be 100644 --- a/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java +++ b/core/src/main/java/net/iakovlev/timeshape/TimeZoneEngine.java @@ -25,6 +25,9 @@ public final class TimeZoneEngine implements Serializable { private final Index index; + + private final static int NUMBER_OF_TIMEZONES = 449; // can't get number of entries from tar, need to set manually + private final static String DATA_FILE_NAME = "/data.tar.zstd"; private final static double MIN_LAT = -90; private final static double MIN_LON = -180; @@ -80,6 +83,25 @@ public boolean tryAdvance(Consumer action) { } }; } + + + private static Stream spliterateInputStream(TarArchiveInputStream f) { + Spliterator tarArchiveEntrySpliterator = makeSpliterator(f); + return StreamSupport.stream(tarArchiveEntrySpliterator, false).map(n -> { + try { + if (n != null) { + log.debug("Processing archive entry {}", n.getName()); + byte[] e = new byte[(int) n.getSize()]; + f.read(e); + return Geojson.Feature.parseFrom(e); + } else { + throw new RuntimeException("Data entry is not found in file"); + } + } catch (NullPointerException | IOException ex) { + throw new RuntimeException(ex); + } + }); + } /** * Queries the {@link TimeZoneEngine} for a {@link java.time.ZoneId} @@ -209,27 +231,13 @@ public static TimeZoneEngine initialize(double minLat, TarArchiveInputStream f) { log.info("Initializing with bounding box: {}, {}, {}, {}", minLat, minLon, maxLat, maxLon); validateCoordinates(minLat, minLon, maxLat, maxLon); - Spliterator tarArchiveEntrySpliterator = makeSpliterator(f); - Stream featureStream = StreamSupport.stream(tarArchiveEntrySpliterator, false).map(n -> { - try { - if (n != null) { - log.debug("Processing archive entry {}", n.getName()); - byte[] e = new byte[(int) n.getSize()]; - f.read(e); - return Geojson.Feature.parseFrom(e); - } else { - throw new RuntimeException("Data entry is not found in file"); - } - } catch (NullPointerException | IOException ex) { - throw new RuntimeException(ex); - } - }); - int numberOfTimezones = 449; // can't get number of entries from tar, need to set manually + Stream featureStream = spliterateInputStream (f); + Envelope boundaries = new Envelope(minLon, minLat, maxLon, maxLat); return new TimeZoneEngine( Index.build( featureStream, - numberOfTimezones, + NUMBER_OF_TIMEZONES, boundaries, accelerateGeometry)); } @@ -239,36 +247,23 @@ public static TimeZoneEngine initialize(double minLat, * This is a blocking long running operation. * * @param timeZones List of ZoneIds to load. - * @param f Input stream of timezone data tar archive * @param accelerateGeometry Increase query speed at the expense of memory utilization + * @param numberOfTimeZones How many timezones are in the tar archive. + * @param f Input stream of timezone data tar archive * @return an initialized instance of {@link TimeZoneEngine} */ - public static TimeZoneEngine initialize(List timeZones, + public static TimeZoneEngine initialize(Set timeZones, boolean accelerateGeometry, + int numberOfTimeZones, TarArchiveInputStream f) { log.info("Initializing with list of time zones"); - - Spliterator tarArchiveEntrySpliterator = makeSpliterator(f); - Stream featureStream = StreamSupport.stream(tarArchiveEntrySpliterator, false).map(n -> { - try { - if (n != null) { - log.debug("Processing archive entry {}", n.getName()); - byte[] e = new byte[(int) n.getSize()]; - f.read(e); - return Geojson.Feature.parseFrom(e); - } else { - throw new RuntimeException("Data entry is not found in file"); - } - } catch (NullPointerException | IOException ex) { - throw new RuntimeException(ex); - } - }); - int numberOfTimezones = 449; // can't get number of entries from tar, need to set manually + Stream featureStream = spliterateInputStream (f); + return new TimeZoneEngine( Index.build( featureStream, - numberOfTimezones, + numberOfTimeZones, timeZones, accelerateGeometry)); } @@ -285,7 +280,7 @@ public static TimeZoneEngine initialize(List timeZones, * @return an initialized instance of {@link TimeZoneEngine} */ public static TimeZoneEngine initialize(double minLat, double minLon, double maxLat, double maxLon, boolean accelerateGeometry) { - try (InputStream resourceAsStream = TimeZoneEngine.class.getResourceAsStream("/data.tar.zstd")) { + try (InputStream resourceAsStream = TimeZoneEngine.class.getResourceAsStream(DATA_FILE_NAME)) { try (ZstdInputStream unzipStream = new ZstdInputStream(resourceAsStream)) { try (BufferedInputStream bufferedStream = new BufferedInputStream(unzipStream)) { try (TarArchiveInputStream shapeInputStream = new TarArchiveInputStream(bufferedStream)) { @@ -308,12 +303,12 @@ public static TimeZoneEngine initialize(double minLat, double minLon, double max * @param accelerateGeometry Increase query speed at the expense of memory utilization * @return an initialized instance of {@link TimeZoneEngine} */ - public static TimeZoneEngine initialize(List timeZones, boolean accelerateGeometry) { + public static TimeZoneEngine initialize(Set timeZones, boolean accelerateGeometry) { try (InputStream resourceAsStream = TimeZoneEngine.class.getResourceAsStream("/data.tar.zstd")) { try (ZstdInputStream unzipStream = new ZstdInputStream(resourceAsStream)) { try (BufferedInputStream bufferedStream = new BufferedInputStream(unzipStream)) { try (TarArchiveInputStream shapeInputStream = new TarArchiveInputStream(bufferedStream)) { - return initialize(timeZones, accelerateGeometry, shapeInputStream); + return initialize(timeZones, accelerateGeometry, NUMBER_OF_TIMEZONES, shapeInputStream); } } } @@ -322,4 +317,5 @@ public static TimeZoneEngine initialize(List timeZones, boolean accelera throw new RuntimeException(e); } } + } diff --git a/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java index de368cc..14f541d 100644 --- a/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java +++ b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedTest.java @@ -12,7 +12,7 @@ @RunWith(JUnit4.class) public class TimeZoneEngineBoundedTest { - private static TimeZoneEngine engine = TimeZoneEngine.initialize(47.0599, 4.8237, 55.3300, 15.2486, true); + private static final TimeZoneEngine engine = TimeZoneEngine.initialize(47.0599, 4.8237, 55.3300, 15.2486, true); @Test public void testSomeZones() { diff --git a/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java index b19d700..84ea4d1 100644 --- a/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java +++ b/core/src/test/java/net/iakovlev/timeshape/TimeZoneEngineBoundedZoneTest.java @@ -5,8 +5,9 @@ import org.junit.runners.JUnit4; import java.time.ZoneId; -import java.util.ArrayList; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import static junit.framework.TestCase.assertEquals; @@ -15,9 +16,9 @@ public class TimeZoneEngineBoundedZoneTest { @Test public void testSomeZones() { - ArrayList timeZones = new ArrayList<> (); + Set timeZones = new HashSet<> (); timeZones.add (ZoneId.of("Europe/Berlin")); - TimeZoneEngine engine = TimeZoneEngine.initialize (timeZones, true); + TimeZoneEngine engine = TimeZoneEngine.initialize (timeZones, true); assertEquals(Optional.of(ZoneId.of("Europe/Berlin")), engine.query(52.52, 13.40)); timeZones.clear (); From 544cf35c4d2a31d182c3b07d280e7b067e344249 Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 11 Feb 2026 11:31:11 -0500 Subject: [PATCH 4/4] Update example to use a Set instead of a List. --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 07bda53..efafc4f 100644 --- a/README.MD +++ b/README.MD @@ -79,7 +79,7 @@ it must be covered by the bounding box completely, not just intersect with it. if you have difficulty determining the bounding box, you may prefer to use a list of ZoneIds instead. ```Java -ArrayList timeZones = new ArrayList<> (); +Set timeZones = new HashSet<> (); timeZones.add(ZoneId.of("Europe/Berlin")); timeZones.add(ZoneId.of("America/Detroit")); TimeZoneEngine engine = TimeZoneEngine.initialize (timeZones, true);