From b6495573e9dc5470df268b63f8e7a93f38406cd2 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Fri, 21 Nov 2025 20:42:48 +0800 Subject: [PATCH 1/4] add benchmark --- .../time/format/DateTimeFormatterParse.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 test/micro/org/openjdk/bench/java/time/format/DateTimeFormatterParse.java diff --git a/test/micro/org/openjdk/bench/java/time/format/DateTimeFormatterParse.java b/test/micro/org/openjdk/bench/java/time/format/DateTimeFormatterParse.java new file mode 100644 index 0000000000000..cdf6f47083576 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/time/format/DateTimeFormatterParse.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.time.format; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +@State(Scope.Thread) +public class DateTimeFormatterParse { + static final DateTimeFormatter formatterLocalTime = DateTimeFormatter.ofPattern("HH:mm:ss"); + static final DateTimeFormatter formatterLocalTimeWithNano = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + static final DateTimeFormatter formatterLocalDate = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + static final DateTimeFormatter formatterLocalDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + static final DateTimeFormatter formatterLocalDateTimeWithNano = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); + static final DateTimeFormatter formatterOffsetDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZZZ"); + static final DateTimeFormatter formatterZonedDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZZZ'['VV']'"); + + static final String STR_LOCALTIME = "21:11:48"; + static final String STR_LOCALTIME_WITH_NANO = "21:11:48.456"; + static final String STR_LOCALDATE = "2024-08-01"; + static final String STR_LOCALDATETIME = "2024-08-01T21:11:48"; + static final String STR_LOCALDATETIME_WITH_NANO = "2024-08-01T21:11:48.456"; + static final String STR_INSTANT = "2024-08-12T03:25:54.980339Z"; + static final String STR_OFFSETDATETIME = "2024-08-12T11:50:46.731509+08:00"; + static final String STR_ZONEDDATETIME = "2024-08-12T11:50:46.731509+08:00[Asia/Shanghai]"; + + @Benchmark + public LocalTime parseLocalTime() { + return LocalTime.parse(STR_LOCALTIME, formatterLocalTime); + } + + @Benchmark + public LocalTime parseLocalTimeWithNano() { + return LocalTime.parse(STR_LOCALTIME_WITH_NANO, formatterLocalTimeWithNano); + } + + @Benchmark + public LocalDate parseLocalDate() { + return LocalDate.parse(STR_LOCALDATE, formatterLocalDate); + } + + @Benchmark + public LocalDateTime parseLocalDateTime() { + return LocalDateTime.parse(STR_LOCALDATETIME, formatterLocalDateTime); + } + + @Benchmark + public LocalDateTime parseLocalDateTimeWithNano() { + return LocalDateTime.parse(STR_LOCALDATETIME_WITH_NANO, formatterLocalDateTimeWithNano); + } + + @Benchmark + public OffsetDateTime parseOffsetDateTime() { + return OffsetDateTime.parse(STR_OFFSETDATETIME, formatterOffsetDateTime); + } + + @Benchmark + public ZonedDateTime parseZonedDateTime() { + return ZonedDateTime.parse(STR_ZONEDDATETIME, formatterZonedDateTime); + } + + @Benchmark + public Instant parseInstant() { + return Instant.parse(STR_INSTANT); + } +} \ No newline at end of file From d8742d7514abfe0e36f105fa7310fdb1755ae546 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 24 Nov 2025 11:43:14 +0800 Subject: [PATCH 2/4] use EnumMap --- .../java/time/format/DateTimeFormatter.java | 7 +++++++ .../time/format/DateTimeFormatterBuilder.java | 17 +++++++++++++++++ .../time/format/DateTimeParseContext.java | 2 +- .../classes/java/time/format/Parsed.java | 19 ++++++++++++++++--- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java index 16d7193c5561f..3b9b84b56832e 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java @@ -544,6 +544,12 @@ public final class DateTimeFormatter { */ private final ZoneId zone; + /** + * Flag indicating whether this formatter only uses ChronoField instances. + * This is used to optimize the storage of parsed field values in the Parsed class. + */ + final boolean onlyChronoField; + //----------------------------------------------------------------------- /** * Creates a formatter using the specified pattern. @@ -1486,6 +1492,7 @@ public static final TemporalQuery parsedLeapSecond() { this.resolverStyle = Objects.requireNonNull(resolverStyle, "resolverStyle"); this.chrono = chrono; this.zone = zone; + this.onlyChronoField = printerParser.onlyChronoField(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index 4708094effbde..3aa98b01a8a47 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -2580,6 +2580,23 @@ public int parse(DateTimeParseContext context, CharSequence text, int position) } } + /** + * Checks whether this composite printer parser only uses ChronoField instances. + * This is used to optimize the storage of parsed field values in the Parsed class. + * + * @return true if all printer parsers in this composite only use ChronoField instances, + * false otherwise + */ + boolean onlyChronoField() { + for (DateTimePrinterParser pp : printerParsers) { + if ((pp instanceof NumberPrinterParser npp && !(npp.field instanceof ChronoField)) + || (pp instanceof CompositePrinterParser cpp && !cpp.onlyChronoField())) { + return false; + } + } + return true; + } + @Override public String toString() { StringBuilder buf = new StringBuilder(); diff --git a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java index 0a4c7e825a3b6..13ed730cb7a73 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java +++ b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java @@ -120,7 +120,7 @@ final class DateTimeParseContext { DateTimeParseContext(DateTimeFormatter formatter) { super(); this.formatter = formatter; - parsed.add(new Parsed()); + parsed.add(new Parsed(formatter.onlyChronoField)); } /** diff --git a/src/java.base/share/classes/java/time/format/Parsed.java b/src/java.base/share/classes/java/time/format/Parsed.java index 1ec956dfa2cd3..9305605307977 100644 --- a/src/java.base/share/classes/java/time/format/Parsed.java +++ b/src/java.base/share/classes/java/time/format/Parsed.java @@ -98,6 +98,7 @@ import java.time.temporal.TemporalQueries; import java.time.temporal.TemporalQuery; import java.time.temporal.UnsupportedTemporalTypeException; +import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -125,10 +126,21 @@ final class Parsed implements TemporalAccessor { // some fields are accessed using package scope from DateTimeParseContext + final boolean onlyChronoField; + @SuppressWarnings("unchecked") + private Map createFieldValuesMap() { + if (onlyChronoField) { + return new HashMap<>(); + } else { + // Create the EnumMap with raw types and cast it appropriately + // This is safe because ChronoField implements TemporalField + return (Map) (Map) new EnumMap<>(ChronoField.class); + } + } /** * The parsed fields. */ - final Map fieldValues = new HashMap<>(); + final Map fieldValues = createFieldValuesMap(); /** * The parsed zone. */ @@ -169,7 +181,8 @@ final class Parsed implements TemporalAccessor { /** * Creates an instance. */ - Parsed() { + Parsed(boolean onlyChronoField) { + this.onlyChronoField = onlyChronoField; } /** @@ -177,7 +190,7 @@ final class Parsed implements TemporalAccessor { */ Parsed copy() { // only copy fields used in parsing stage - Parsed cloned = new Parsed(); + Parsed cloned = new Parsed(onlyChronoField); cloned.fieldValues.putAll(this.fieldValues); cloned.zone = this.zone; cloned.zoneNameType = this.zoneNameType; From 5a050fe9750a067d20a6b72efa1ec2c74cebf93a Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Tue, 25 Nov 2025 09:18:30 +0800 Subject: [PATCH 3/4] bug fix, form @liach --- .../time/format/DateTimeFormatterBuilder.java | 1 + .../classes/java/time/format/Parsed.java | 21 +++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index 3aa98b01a8a47..e330b8cbee01d 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -2590,6 +2590,7 @@ public int parse(DateTimeParseContext context, CharSequence text, int position) boolean onlyChronoField() { for (DateTimePrinterParser pp : printerParsers) { if ((pp instanceof NumberPrinterParser npp && !(npp.field instanceof ChronoField)) + || (pp instanceof TextPrinterParser tpp && !(tpp.field instanceof ChronoField)) || (pp instanceof CompositePrinterParser cpp && !cpp.onlyChronoField())) { return false; } diff --git a/src/java.base/share/classes/java/time/format/Parsed.java b/src/java.base/share/classes/java/time/format/Parsed.java index 9305605307977..46623357b9e50 100644 --- a/src/java.base/share/classes/java/time/format/Parsed.java +++ b/src/java.base/share/classes/java/time/format/Parsed.java @@ -126,21 +126,10 @@ final class Parsed implements TemporalAccessor { // some fields are accessed using package scope from DateTimeParseContext - final boolean onlyChronoField; - @SuppressWarnings("unchecked") - private Map createFieldValuesMap() { - if (onlyChronoField) { - return new HashMap<>(); - } else { - // Create the EnumMap with raw types and cast it appropriately - // This is safe because ChronoField implements TemporalField - return (Map) (Map) new EnumMap<>(ChronoField.class); - } - } /** * The parsed fields. */ - final Map fieldValues = createFieldValuesMap(); + final Map fieldValues; /** * The parsed zone. */ @@ -181,8 +170,12 @@ private Map createFieldValuesMap() { /** * Creates an instance. */ + @SuppressWarnings("unchecked") Parsed(boolean onlyChronoField) { - this.onlyChronoField = onlyChronoField; + // Create the EnumMap with raw types and cast it appropriately + // This is safe because ChronoField implements TemporalField + fieldValues = onlyChronoField ? (Map) (Map) new EnumMap<>(ChronoField.class) + : new HashMap<>(); } /** @@ -190,7 +183,7 @@ private Map createFieldValuesMap() { */ Parsed copy() { // only copy fields used in parsing stage - Parsed cloned = new Parsed(onlyChronoField); + Parsed cloned = new Parsed(fieldValues instanceof EnumMap); cloned.fieldValues.putAll(this.fieldValues); cloned.zone = this.zone; cloned.zoneNameType = this.zoneNameType; From 7137d9ec285170ea9aa7428f4d2c1e5031c5e37f Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Tue, 25 Nov 2025 14:37:41 +0800 Subject: [PATCH 4/4] copyright --- .../share/classes/java/time/format/DateTimeParseContext.java | 2 +- src/java.base/share/classes/java/time/format/Parsed.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java index 13ed730cb7a73..fff139dc9f38f 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java +++ b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/java.base/share/classes/java/time/format/Parsed.java b/src/java.base/share/classes/java/time/format/Parsed.java index 46623357b9e50..12623367ca08d 100644 --- a/src/java.base/share/classes/java/time/format/Parsed.java +++ b/src/java.base/share/classes/java/time/format/Parsed.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it