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)); + } + } + } + } +}