diff --git a/src/java.base/share/classes/java/time/Instant.java b/src/java.base/share/classes/java/time/Instant.java
index 37dd9f8d2ace0..3019e8bdd5f17 100644
--- a/src/java.base/share/classes/java/time/Instant.java
+++ b/src/java.base/share/classes/java/time/Instant.java
@@ -80,6 +80,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
@@ -96,6 +98,9 @@
import java.time.temporal.ValueRange;
import java.util.Objects;
+import jdk.internal.access.JavaLangAccess;
+import jdk.internal.access.SharedSecrets;
+
/**
* An instantaneous point on the time-line.
*
@@ -210,6 +215,8 @@
public final class Instant
implements Temporal, TemporalAdjuster, Comparable, Serializable {
+ private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
+
/**
* Constant for the 1970-01-01T00:00:00Z epoch instant.
*/
@@ -1352,7 +1359,28 @@ public int hashCode() {
*/
@Override
public String toString() {
- return DateTimeFormatter.ISO_INSTANT.format(this);
+ LocalDate date = LocalDate.ofEpochDay(
+ Math.floorDiv(seconds, SECONDS_PER_DAY));
+ LocalTime time = LocalTime.ofSecondOfDay(
+ Math.floorMod(seconds, SECONDS_PER_DAY));
+
+ int yearSize = LocalDate.yearSize(date.getYear());
+ int nanoSize = LocalTime.nanoSize(nanos);
+
+ byte[] buf = new byte[yearSize + 16 + nanoSize];
+
+ int off = date.getChars(buf, 0);
+ buf[off] = 'T';
+
+ off = time.getChars(buf, off + 1);
+ LocalTime.getNanoChars(buf, off, nanos);
+ buf[off + nanoSize] = 'Z';
+
+ try {
+ return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
+ }
}
// -----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java
index 2649c098a5b43..d1799ac3f6374 100644
--- a/src/java.base/share/classes/java/time/LocalDate.java
+++ b/src/java.base/share/classes/java/time/LocalDate.java
@@ -80,6 +80,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.IsoEra;
import java.time.chrono.IsoChronology;
@@ -103,6 +105,11 @@
import java.util.stream.LongStream;
import java.util.stream.Stream;
+import jdk.internal.access.JavaLangAccess;
+import jdk.internal.access.SharedSecrets;
+import jdk.internal.util.ByteArrayLittleEndian;
+import jdk.internal.util.DecimalDigits;
+
/**
* A date without a time-zone in the ISO-8601 calendar system,
* such as {@code 2007-12-03}.
@@ -140,6 +147,8 @@
public final class LocalDate
implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable {
+ private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
+
/**
* The minimum supported {@code LocalDate}, '-999999999-01-01'.
* This could be used by an application as a "far past" date.
@@ -2147,28 +2156,60 @@ public int hashCode() {
*/
@Override
public String toString() {
- int yearValue = year;
- int monthValue = month;
- int dayValue = day;
- int absYear = Math.abs(yearValue);
- StringBuilder buf = new StringBuilder(10);
- if (absYear < 1000) {
- if (yearValue < 0) {
- buf.append(yearValue - 10000).deleteCharAt(1);
- } else {
- buf.append(yearValue + 10000).deleteCharAt(0);
+ byte[] buf = new byte[yearSize(year) + 6];
+ getChars(buf, 0);
+
+ try {
+ return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
+ }
+ }
+
+ static int yearSize(int year) {
+ if (Math.abs(year) < 1000) {
+ return year < 0 ? 5 : 4;
+ }
+ return DecimalDigits.stringSize(year) + (year > 9999 ? 1 : 0);
+ }
+
+ int getChars(byte[] buf, int off) {
+ int year = this.year;
+ int yearSize = yearSize(year);
+ int yearAbs = Math.abs(year);
+
+ int yearEnd = off + yearSize;
+ if (yearAbs < 1000) {
+ if (year < 0) {
+ buf[off++] = '-';
}
+ int y01 = yearAbs / 100;
+ int y23 = yearAbs - y01 * 100;
+
+ ByteArrayLittleEndian.setInt(
+ buf,
+ off,
+ (DecimalDigits.digitPair(y23) << 16) | DecimalDigits.digitPair(y01));
} else {
- if (yearValue > 9999) {
- buf.append('+');
+ if (year > 9999) {
+ buf[off] = '+';
}
- buf.append(yearValue);
+ DecimalDigits.getCharsLatin1(year, yearEnd, buf);
}
- return buf.append(monthValue < 10 ? "-0" : "-")
- .append(monthValue)
- .append(dayValue < 10 ? "-0" : "-")
- .append(dayValue)
- .toString();
+
+ off = yearEnd;
+ buf[off] = '-';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 1,
+ DecimalDigits.digitPair(month)); // mm
+ buf[off + 3] = '-';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 4,
+ DecimalDigits.digitPair(day)); // dd
+
+ return off + 6;
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/LocalDateTime.java b/src/java.base/share/classes/java/time/LocalDateTime.java
index 024aa1dacbeda..9dcef4ac99923 100644
--- a/src/java.base/share/classes/java/time/LocalDateTime.java
+++ b/src/java.base/share/classes/java/time/LocalDateTime.java
@@ -78,6 +78,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.chrono.ChronoLocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@@ -96,6 +98,8 @@
import java.time.zone.ZoneRules;
import java.util.Objects;
+import jdk.internal.access.SharedSecrets;
+
/**
* A date-time without a time-zone in the ISO-8601 calendar system,
* such as {@code 2007-12-03T10:15:30}.
@@ -1965,7 +1969,23 @@ public int hashCode() {
*/
@Override
public String toString() {
- return date.toString() + 'T' + time.toString();
+ int yearSize = LocalDate.yearSize(date.getYear());
+ int nano = time.getNano();
+ int nanoSize = LocalTime.nanoSize(nano);
+
+ byte[] buf = new byte[yearSize + 15 + nanoSize];
+
+ int off = date.getChars(buf, 0);
+ buf[off] = 'T';
+ off = time.getChars(buf, off + 1);
+ LocalTime.getNanoChars(buf, off, nano);
+
+ try {
+ return SharedSecrets.getJavaLangAccess()
+ .newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
+ }
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/LocalTime.java b/src/java.base/share/classes/java/time/LocalTime.java
index 499dca627e286..6484968755e84 100644
--- a/src/java.base/share/classes/java/time/LocalTime.java
+++ b/src/java.base/share/classes/java/time/LocalTime.java
@@ -76,6 +76,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
@@ -92,6 +94,11 @@
import java.time.temporal.ValueRange;
import java.util.Objects;
+import jdk.internal.access.JavaLangAccess;
+import jdk.internal.access.SharedSecrets;
+import jdk.internal.util.ByteArrayLittleEndian;
+import jdk.internal.util.DecimalDigits;
+
/**
* A time without a time-zone in the ISO-8601 calendar system,
* such as {@code 10:15:30}.
@@ -126,6 +133,8 @@
public final class LocalTime
implements Temporal, TemporalAdjuster, Comparable, Serializable {
+ private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
+
/**
* The minimum supported {@code LocalTime}, '00:00'.
* This is the time of midnight at the start of the day.
@@ -1629,27 +1638,100 @@ public int hashCode() {
*/
@Override
public String toString() {
- StringBuilder buf = new StringBuilder(18);
- int hourValue = hour;
- int minuteValue = minute;
- int secondValue = second;
- int nanoValue = nano;
- buf.append(hourValue < 10 ? "0" : "").append(hourValue)
- .append(minuteValue < 10 ? ":0" : ":").append(minuteValue);
- if (secondValue > 0 || nanoValue > 0) {
- buf.append(secondValue < 10 ? ":0" : ":").append(secondValue);
- if (nanoValue > 0) {
- buf.append('.');
- if (nanoValue % 1000_000 == 0) {
- buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1));
- } else if (nanoValue % 1000 == 0) {
- buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1));
- } else {
- buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1));
- }
+ int nano = this.nano;
+ int nanoSize = LocalTime.nanoSize(nano);
+
+ byte[] buf = new byte[8 + nanoSize];
+ getChars(buf, 0);
+ LocalTime.getNanoChars(buf, 8, nano);
+
+ try {
+ return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
+ }
+ }
+
+ static int nanoSize(int nano) {
+ if (nano == 0) {
+ return 0;
+ }
+
+ int div = nano / 1000;
+ int div2 = div / 1000;
+
+ if (nano - div * 1000 != 0) {
+ return 10;
+ }
+
+ return (div - div2 * 1000 == 0) ? 4 : 7;
+ }
+
+ int getChars(byte[] buf, int off) {
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off,
+ DecimalDigits.digitPair(hour)); // hh
+ buf[off + 2] = ':';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 3,
+ DecimalDigits.digitPair(minute)); // minute
+ buf[off + 5] = ':';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 6,
+ DecimalDigits.digitPair(second)); // second
+ return off + 8;
+ }
+
+ static void getNanoChars(byte[] buf, int off, int nano) {
+ if (nano == 0) {
+ return;
+ }
+
+ int div = nano / 1000;
+ int div2 = div / 1000;
+
+ int div2_k = div2 / 100;
+ buf[off] = '.';
+ buf[off + 1] = (byte) ('0' + div2_k);
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 2,
+ DecimalDigits.digitPair(div2 - div2_k * 100)
+ );
+ off += 4;
+
+ int rem1 = nano - div * 1000;
+ int rem2;
+ if (rem1 == 0) {
+ rem2 = div - div2 * 1000;
+ if (rem2 == 0) {
+ return;
}
+ } else {
+ rem2 = div - div2 * 1000;
+ }
+
+ int rem2_k = rem2 / 100;
+ buf[off] = (byte) ('0' + rem2_k);
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 1,
+ DecimalDigits.digitPair(rem2 - rem2_k * 100)
+ );
+ off += 3;
+
+ if (rem1 != 0) {
+ int rem1_k = rem1 / 100;
+ buf[off] = (byte) ('0' + rem1_k);
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 1,
+ DecimalDigits.digitPair(rem1 - rem1_k * 100)
+ );
}
- return buf.toString();
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/OffsetDateTime.java b/src/java.base/share/classes/java/time/OffsetDateTime.java
index bd75fffb24e55..7e700257aee8e 100644
--- a/src/java.base/share/classes/java/time/OffsetDateTime.java
+++ b/src/java.base/share/classes/java/time/OffsetDateTime.java
@@ -74,6 +74,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@@ -93,6 +95,8 @@
import java.util.Comparator;
import java.util.Objects;
+import jdk.internal.access.SharedSecrets;
+
/**
* A date-time with an offset from UTC/Greenwich in the ISO-8601 calendar system,
* such as {@code 2007-12-03T10:15:30+01:00}.
@@ -1922,8 +1926,28 @@ public int hashCode() {
* @return a string representation of this date-time, not null
*/
@Override
+ @SuppressWarnings("deprecation")
public String toString() {
- return dateTime.toString() + offset.toString();
+ int yearSize = LocalDate.yearSize(dateTime.getYear());
+ int nano = dateTime.getNano();
+ int nanoSize = LocalTime.nanoSize(nano);
+
+ String offSetId = offset.getId();
+
+ byte[] buf = new byte[yearSize + 15 + nanoSize + offSetId.length()];
+
+ int off = toLocalDate().getChars(buf, 0);
+ buf[off] = 'T';
+ off = toLocalTime().getChars(buf, off + 1);
+ LocalTime.getNanoChars(buf, off, nano);
+ offSetId.getBytes(0, offSetId.length(), buf, off + nanoSize);
+
+ try {
+ return SharedSecrets.getJavaLangAccess()
+ .newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
+ }
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/OffsetTime.java b/src/java.base/share/classes/java/time/OffsetTime.java
index b9fe5e533d80e..5cd3e95b748dc 100644
--- a/src/java.base/share/classes/java/time/OffsetTime.java
+++ b/src/java.base/share/classes/java/time/OffsetTime.java
@@ -75,6 +75,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
@@ -92,6 +94,8 @@
import java.time.zone.ZoneRules;
import java.util.Objects;
+import jdk.internal.access.SharedSecrets;
+
/**
* A time with an offset from UTC/Greenwich in the ISO-8601 calendar system,
* such as {@code 10:15:30+01:00}.
@@ -118,7 +122,6 @@
@jdk.internal.ValueBased
public final class OffsetTime
implements Temporal, TemporalAdjuster, Comparable, Serializable {
-
/**
* The minimum supported {@code OffsetTime}, '00:00:00+18:00'.
* This is the time of midnight at the start of the day in the maximum offset
@@ -1397,8 +1400,24 @@ public int hashCode() {
* @return a string representation of this time, not null
*/
@Override
+ @SuppressWarnings("deprecation")
public String toString() {
- return time.toString() + offset.toString();
+ int nano = time.getNano();
+ int nanoSize = LocalTime.nanoSize(nano);
+
+ String offSetId = offset.getId();
+ byte[] buf = new byte[8 + nanoSize + offSetId.length()];
+ time.getChars(buf, 0);
+
+ LocalTime.getNanoChars(buf, 8, nano);
+ offSetId.getBytes(0, offSetId.length(), buf, 8 + nanoSize);
+
+ try {
+ return SharedSecrets.getJavaLangAccess()
+ .newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
+ }
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/time/ZonedDateTime.java b/src/java.base/share/classes/java/time/ZonedDateTime.java
index 555392cf513ce..6ad5607e638ea 100644
--- a/src/java.base/share/classes/java/time/ZonedDateTime.java
+++ b/src/java.base/share/classes/java/time/ZonedDateTime.java
@@ -71,6 +71,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.chrono.ChronoZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@@ -91,6 +93,8 @@
import java.util.List;
import java.util.Objects;
+import jdk.internal.access.SharedSecrets;
+
/**
* A date-time with a time-zone in the ISO-8601 calendar system,
* such as {@code 2007-12-03T10:15:30+01:00 Europe/Paris}.
@@ -2213,12 +2217,47 @@ public int hashCode() {
* @return a string representation of this date-time, not null
*/
@Override // override for Javadoc
+ @SuppressWarnings("deprecation")
public String toString() {
- String str = dateTime.toString() + offset.toString();
+ int yearSize = LocalDate.yearSize(dateTime.getYear());
+ int nano = dateTime.getNano();
+ int nanoSize = LocalTime.nanoSize(nano);
+
+ String offSetId = offset.getId();
+ int offsetIdLenth = offSetId.length();
+
+ String zoneStr = null;
+ int zoneLength = 0;
+
+ int offsetLength = offsetIdLenth;
+ if (offset != zone) {
+ zoneStr = zone.toString();
+ zoneLength = zoneStr.length();
+ offsetLength += zoneLength + 2;
+ }
+
+ byte[] buf = new byte[yearSize + 15 + nanoSize + offsetLength];
+
+ int off = toLocalDate().getChars(buf, 0);
+ buf[off] = 'T';
+ off = toLocalTime().getChars(buf, off + 1);
+ LocalTime.getNanoChars(buf, off, nano);
+ off += nanoSize;
+ offSetId.getBytes(0, offsetIdLenth, buf, off);
+
if (offset != zone) {
- str += '[' + zone.toString() + ']';
+ off += offsetIdLenth;
+ buf[off] = '[';
+ zoneStr.getBytes(0, zoneLength, buf, off + 1);
+ buf[off + zoneLength + 1] = ']';
+ }
+
+ try {
+ return SharedSecrets.getJavaLangAccess()
+ .newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
}
- return str;
}
//-----------------------------------------------------------------------
diff --git a/src/java.base/share/classes/java/util/Date.java b/src/java.base/share/classes/java/util/Date.java
index 1850564d8b290..f05e753d9957d 100644
--- a/src/java.base/share/classes/java/util/Date.java
+++ b/src/java.base/share/classes/java/util/Date.java
@@ -29,7 +29,13 @@
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
import java.time.Instant;
+import jdk.internal.access.JavaLangAccess;
+import jdk.internal.access.SharedSecrets;
+import jdk.internal.util.ByteArrayLittleEndian;
+import jdk.internal.util.DecimalDigits;
import sun.util.calendar.BaseCalendar;
import sun.util.calendar.CalendarSystem;
import sun.util.calendar.CalendarUtils;
@@ -128,6 +134,8 @@
public class Date
implements java.io.Serializable, Cloneable, Comparable
{
+ private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
+
private static final BaseCalendar gcal =
CalendarSystem.getGregorianCalendar();
private static BaseCalendar jcal;
@@ -1025,40 +1033,68 @@ public int hashCode() {
* @see java.util.Date#toLocaleString()
* @see java.util.Date#toGMTString()
*/
+ @SuppressWarnings("deprecation")
public String toString() {
// "EEE MMM dd HH:mm:ss zzz yyyy";
BaseCalendar.Date date = normalize();
- StringBuilder sb = new StringBuilder(28);
+
+ int year = date.getYear();
+ int yearSize = year >= 1000 && year <= 9999 ? 4 : DecimalDigits.stringSize(year);
+
+ TimeZone zi = date.getZone();
+ String shortName = zi != null ? zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US) : "GMT";
+ int shortNameLength = shortName.length();
+
+ byte[] buf = new byte[21 + yearSize + shortNameLength];
+
int index = date.getDayOfWeek();
if (index == BaseCalendar.SUNDAY) {
index = 8;
}
- convertToAbbr(sb, wtb[index]).append(' '); // EEE
- convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM
- CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd
+ convertToAbbr(buf, 0, wtb[index]); // EEE
+ buf[3] = ' ';
+ convertToAbbr(buf, 4, wtb[date.getMonth() - 1 + 2 + 7]); // MMM
+ buf[7] = ' ';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ 8,
+ DecimalDigits.digitPair(date.getDayOfMonth())); // dd
+ buf[10] = ' ';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ 11,
+ DecimalDigits.digitPair(date.getHours())); // HH
+ buf[13] = ':';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ 14,
+ DecimalDigits.digitPair(date.getMinutes())); // mm
+ buf[16] = ':';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ 17,
+ DecimalDigits.digitPair(date.getSeconds())); // ss
+ buf[19] = ' ';
- CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH
- CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm
- CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss
- TimeZone zi = date.getZone();
- if (zi != null) {
- sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz
- } else {
- sb.append("GMT");
+ shortName.getBytes(0, shortNameLength, buf, 20);
+ buf[20 + shortNameLength] = ' ';
+
+ DecimalDigits.getCharsLatin1(year, buf.length, buf);
+ try {
+ return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
}
- sb.append(' ').append(date.getYear()); // yyyy
- return sb.toString();
}
/**
* Converts the given name to its 3-letter abbreviation (e.g.,
- * "monday" -> "Mon") and stored the abbreviation in the given
- * {@code StringBuilder}.
+ * "monday" -> "Mon") and stored the abbreviation in the given buf
*/
- private static final StringBuilder convertToAbbr(StringBuilder sb, String name) {
- sb.append(Character.toUpperCase(name.charAt(0)));
- sb.append(name.charAt(1)).append(name.charAt(2));
- return sb;
+ private static final void convertToAbbr(byte[] buf, int off, String name) {
+ buf[off] = (byte) (name.charAt(0) - 32);
+ buf[off + 1] = (byte) name.charAt(1);
+ buf[off + 2] = (byte) name.charAt(2);
}
/**
@@ -1118,18 +1154,56 @@ public String toLocaleString() {
public String toGMTString() {
// d MMM yyyy HH:mm:ss 'GMT'
long t = getTime();
- BaseCalendar cal = getCalendarSystem(t);
BaseCalendar.Date date =
- (BaseCalendar.Date) cal.getCalendarDate(getTime(), (TimeZone)null);
- StringBuilder sb = new StringBuilder(32);
- CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 1).append(' '); // d
- convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM
- sb.append(date.getYear()).append(' '); // yyyy
- CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH
- CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm
- CalendarUtils.sprintf0d(sb, date.getSeconds(), 2); // ss
- sb.append(" GMT"); // ' GMT'
- return sb.toString();
+ (BaseCalendar.Date) getCalendarSystem(t)
+ .getCalendarDate(getTime(), (TimeZone)null);
+
+ int year = date.getYear();
+ int yearSize = year >= 1000 && year <= 9999 ? 4 : DecimalDigits.stringSize(year);
+ int dayOfMonth = date.getDayOfMonth();
+
+ byte[] buf = new byte[(dayOfMonth < 10 ? 19 : 20) + yearSize];
+ int off;
+ if (dayOfMonth < 10) {
+ buf[0] = (byte) ('0' + dayOfMonth);
+ off = 1;
+ } else {
+ ByteArrayLittleEndian.setShort(
+ buf,
+ 0,
+ DecimalDigits.digitPair(dayOfMonth)); // dd
+ off = 2;
+ }
+ buf[off++] = ' ';
+ convertToAbbr(buf, off, wtb[date.getMonth() + 8]); // EEE
+ buf[off + 3] = ' ';
+ DecimalDigits.getCharsLatin1(year, off + yearSize + 4, buf);
+ off += yearSize + 4;
+ buf[off++] = ' ';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off,
+ DecimalDigits.digitPair(date.getHours())); // HH
+ buf[off + 2] = ':';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 3,
+ DecimalDigits.digitPair(date.getMinutes())); // mm
+ buf[off + 5] = ':';
+ ByteArrayLittleEndian.setShort(
+ buf,
+ off + 6,
+ DecimalDigits.digitPair(date.getSeconds())); // mm
+ buf[off + 8] = ' ';
+ buf[off + 9] = 'G';
+ buf[off + 10] = 'M';
+ buf[off + 11] = 'T';
+
+ try {
+ return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
+ } catch (CharacterCodingException cce) {
+ throw new AssertionError(cce);
+ }
}
/**
diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java
index f55452868a610..96a60ef5819ed 100644
--- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java
+++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java
@@ -157,4 +157,80 @@ public int size(long value) {
public static short digitPair(int i) {
return DIGITS[i];
}
+
+ /**
+ * Returns the string representation size for a given int value.
+ *
+ * @param x int value
+ * @return string size
+ *
+ * @implNote There are other ways to compute this: e.g. binary search,
+ * but values are biased heavily towards zero, and therefore linear search
+ * wins. The iteration results are also routinely inlined in the generated
+ * code after loop unrolling.
+ */
+ public static int stringSize(int x) {
+ int d = 1;
+ if (x >= 0) {
+ d = 0;
+ x = -x;
+ }
+ int p = -10;
+ for (int i = 1; i < 10; i++) {
+ if (x > p)
+ return i + d;
+ p = 10 * p;
+ }
+ return 10 + d;
+ }
+
+ /**
+ * Places characters representing the integer i into the
+ * character array buf. The characters are placed into
+ * the buffer backwards starting with the least significant
+ * digit at the specified index (exclusive), and working
+ * backwards from there.
+ *
+ * @implNote This method converts positive inputs into negative
+ * values, to cover the Integer.MIN_VALUE case. Converting otherwise
+ * (negative to positive) will expose -Integer.MIN_VALUE that overflows
+ * integer.
+ *
+ * @param i value to convert
+ * @param index next index, after the least significant digit
+ * @param buf target buffer, Latin1-encoded
+ * @return index of the most significant digit or minus sign, if present
+ */
+ public static int getCharsLatin1(int i, int index, byte[] buf) {
+ // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller.
+ int q, r;
+ int charPos = index;
+
+ boolean negative = i < 0;
+ if (!negative) {
+ i = -i;
+ }
+
+ // Generate two digits per iteration
+ while (i <= -100) {
+ q = i / 100;
+ r = (q * 100) - i;
+ i = q;
+ charPos -= 2;
+ ByteArrayLittleEndian.setShort(buf, charPos, DIGITS[r]);
+ }
+
+ // We know there are at most two digits left at this point.
+ if (i < -9) {
+ charPos -= 2;
+ ByteArrayLittleEndian.setShort(buf, charPos, DIGITS[-i]);
+ } else {
+ buf[--charPos] = (byte)('0' - i);
+ }
+
+ if (negative) {
+ buf[--charPos] = (byte)'-';
+ }
+ return charPos;
+ }
}
diff --git a/test/micro/org/openjdk/bench/java/time/ToStringBench.java b/test/micro/org/openjdk/bench/java/time/ToStringBench.java
new file mode 100644
index 0000000000000..4b8b8e26a5ba0
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/time/ToStringBench.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2021, 2022, 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
+ * 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;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZonedDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+import java.util.Locale;
+import java.util.Random;
+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 ToStringBench {
+ private static final Instant[] INSTANTS;
+ private static final ZonedDateTime[] ZONED_DATE_TIMES;
+ private static final LocalDateTime[] LOCAL_DATE_TIMES;
+ private static final LocalDate[] LOCAL_DATES;
+ private static final LocalTime[] LOCAL_TIMES;
+
+ static {
+ Instant loInstant = Instant.EPOCH.plus(Duration.ofDays(365*50)); // 2020-01-01
+ Instant hiInstant = loInstant.plus(Duration.ofDays(1));
+ long maxOffsetNanos = Duration.between(loInstant, hiInstant).toNanos();
+ Random random = new Random(0);
+ INSTANTS = IntStream
+ .range(0, 1_000)
+ .mapToObj(ignored -> {
+ final long offsetNanos = (long) Math.floor(random.nextDouble() * maxOffsetNanos);
+ return loInstant.plus(offsetNanos, ChronoUnit.NANOS);
+ })
+ .toArray(Instant[]::new);
+
+ ZONED_DATE_TIMES = Stream.of(INSTANTS)
+ .map(instant -> ZonedDateTime.ofInstant(instant, ZoneOffset.UTC))
+ .toArray(ZonedDateTime[]::new);
+
+ LOCAL_DATE_TIMES = Stream.of(ZONED_DATE_TIMES)
+ .map(zdt -> zdt.toLocalDateTime())
+ .toArray(LocalDateTime[]::new);
+
+ LOCAL_DATES = Stream.of(LOCAL_DATE_TIMES)
+ .map(ldt -> ldt.toLocalDate())
+ .toArray(LocalDate[]::new);
+
+ LOCAL_TIMES = Stream.of(LOCAL_DATE_TIMES)
+ .map(ldt -> ldt.toLocalTime())
+ .toArray(LocalTime[]::new);
+ }
+
+ @Benchmark
+ public void testZonedDateTimeToString(Blackhole bh) {
+ for (final ZonedDateTime zonedDateTime : ZONED_DATE_TIMES) {
+ bh.consume(zonedDateTime.toString());
+ }
+ }
+
+ @Benchmark
+ public void testLocalDateTimeToString(Blackhole bh) {
+ for (LocalDateTime localDateTime : LOCAL_DATE_TIMES) {
+ bh.consume(localDateTime.toString());
+ }
+ }
+
+ @Benchmark
+ public void testLocalDateToString(Blackhole bh) {
+ for (LocalDate localDate : LOCAL_DATES) {
+ bh.consume(localDate.toString());
+ }
+ }
+
+ @Benchmark
+ public void testLocalTimeToString(Blackhole bh) {
+ for (LocalTime localTime : LOCAL_TIMES) {
+ bh.consume(localTime.toString());
+ }
+ }
+
+ @Benchmark
+ public void testInstantToString(Blackhole bh) {
+ for (Instant instant : INSTANTS) {
+ bh.consume(instant.toString());
+ }
+ }
+
+ @Benchmark
+ public void testZoneOffsetOffHours(Blackhole bh) {
+ for (int hour = 0; hour < 12; hour++) {
+ for (int minute = 0; minute < 60; minute += 15) {
+ for (int second = 0; second < 60; second += 15) {
+ bh.consume(ZoneOffset.ofHoursMinutesSeconds(hour, minute, second));
+ }
+ }
+ }
+ }
+}