diff --git a/src/java.base/share/classes/java/time/MonthDay.java b/src/java.base/share/classes/java/time/MonthDay.java index a25c9beec95..26ca69bbe0d 100644 --- a/src/java.base/share/classes/java/time/MonthDay.java +++ b/src/java.base/share/classes/java/time/MonthDay.java @@ -88,6 +88,8 @@ import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.util.DecimalDigits; + /** * A month-day in the ISO-8601 calendar system, such as {@code --12-03}. *
@@ -764,10 +766,12 @@ public final class MonthDay */ @Override public String toString() { - return new StringBuilder(10).append("--") - .append(month < 10 ? "0" : "").append(month) - .append(day < 10 ? "-0" : "-").append(day) - .toString(); + StringBuilder buf = new StringBuilder(10); + buf.append("--"); + DecimalDigits.appendPair(buf, month); + buf.append('-'); + DecimalDigits.appendPair(buf, day); + return buf.toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/YearMonth.java b/src/java.base/share/classes/java/time/YearMonth.java index b3d1aff7bc2..665c8c85544 100644 --- a/src/java.base/share/classes/java/time/YearMonth.java +++ b/src/java.base/share/classes/java/time/YearMonth.java @@ -101,6 +101,8 @@ import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.util.DecimalDigits; + /** * A year-month in the ISO-8601 calendar system, such as {@code 2007-12}. *
@@ -1213,18 +1215,17 @@ public final class YearMonth public String toString() { int absYear = Math.abs(year); StringBuilder buf = new StringBuilder(9); - if (absYear < 1000) { + if (absYear < 10000) { if (year < 0) { - buf.append(year - 10000).deleteCharAt(1); - } else { - buf.append(year + 10000).deleteCharAt(0); + buf.append('-'); } + DecimalDigits.appendQuad(buf, absYear); } else { buf.append(year); } - return buf.append(month < 10 ? "-0" : "-") - .append(month) - .toString(); + buf.append('-'); + DecimalDigits.appendPair(buf, month); + return buf.toString(); } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/ZoneOffset.java b/src/java.base/share/classes/java/time/ZoneOffset.java index 4199d17735c..2a45e7cbf82 100644 --- a/src/java.base/share/classes/java/time/ZoneOffset.java +++ b/src/java.base/share/classes/java/time/ZoneOffset.java @@ -88,6 +88,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReferenceArray; +import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.Stable; /** @@ -465,12 +466,14 @@ public final class ZoneOffset StringBuilder buf = new StringBuilder(); int absHours = absTotalSeconds / SECONDS_PER_HOUR; int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; - buf.append(totalSeconds < 0 ? "-" : "+") - .append(absHours < 10 ? "0" : "").append(absHours) - .append(absMinutes < 10 ? ":0" : ":").append(absMinutes); + buf.append(totalSeconds < 0 ? '-' : '+'); + DecimalDigits.appendPair(buf, absHours); + buf.append(':'); + DecimalDigits.appendPair(buf, absMinutes); int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE; if (absSeconds != 0) { - buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds); + buf.append(':'); + DecimalDigits.appendPair(buf, absSeconds); } return buf.toString(); } diff --git a/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java b/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java index ca226b70d24..67f08c5cb4f 100644 --- a/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java +++ b/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java @@ -74,6 +74,8 @@ import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; +import jdk.internal.util.DecimalDigits; + /** * A date expressed in terms of a standard year-month-day calendar system. *
@@ -426,18 +428,22 @@ abstract class ChronoLocalDateImpl
+ * The integer {@code v} is formatted as two decimal digits.
+ * Values from 0 to 9 are formatted with a leading zero (e.g., 5 becomes "05"),
+ * and values from 10 to 99 are formatted as regular two-digit numbers.
+ * If the value is outside the range 0-99, the behavior is unspecified.
+ *
+ * @param buf the {@code StringBuilder} to append to.
+ * @param v the {@code int} value (should be between 0 and 99 inclusive).
+ */
+ public static void appendPair(StringBuilder buf, int v) {
+ // The & 0x7f operation keeps the index within the safe range [0, 127] for the DIGITS array,
+ // which allows the JIT compiler to eliminate array bounds checks for performance.
+ int packed = DIGITS[v & 0x7f];
+ // The temporary String and byte[] objects created here are typically eliminated
+ // by the JVM's escape analysis and scalar replacement optimizations during
+ // runtime compilation, avoiding actual heap allocations in optimized code.
+ buf.append(
+ JLA.uncheckedNewStringWithLatin1Bytes(
+ new byte[] {(byte) packed, (byte) (packed >> 8)}));
+ }
+
+ /**
+ * Appends the four-digit string representation of the {@code int}
+ * argument to the given {@code StringBuilder}.
+ *
+ * The integer {@code v} is formatted as four decimal digits.
+ * Values from 0 to 9 are formatted with leading zeros (e.g., 5 becomes "0005"),
+ * values from 10 to 99 add two leading zeros (e.g., 25 becomes "0025"),
+ * values from 100 to 999 add one leading zero (e.g., 123 becomes "0123"),
+ * and values from 1000 to 9999 have no leading zeros.
+ * If the value is outside the range 0-9999, the behavior is unspecified.
+ *
+ * @param buf the {@code StringBuilder} to append to.
+ * @param v the {@code int} value (should be between 0 and 9999 inclusive).
+ */
+ public static void appendQuad(StringBuilder buf, int v) {
+ // The & 0x7f operation keeps the index within the safe range [0, 127] for the DIGITS array,
+ // which allows the JIT compiler to eliminate array bounds checks for performance.
+ int packedHigh = DIGITS[(v / 100) & 0x7f];
+ int packedLow = DIGITS[(v % 100) & 0x7f];
+ // The temporary String and byte[] objects created here are typically eliminated
+ // by the JVM's escape analysis and scalar replacement optimizations during
+ // runtime compilation, avoiding actual heap allocations in optimized code.
+ buf.append(
+ JLA.uncheckedNewStringWithLatin1Bytes(
+ new byte[] {(byte) packedHigh, (byte) (packedHigh >> 8),
+ (byte) packedLow, (byte) (packedLow >> 8)}));
+ }
}