diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeDate.java b/rhino/src/main/java/org/mozilla/javascript/NativeDate.java
index 426258fc25..bb6d405548 100644
--- a/rhino/src/main/java/org/mozilla/javascript/NativeDate.java
+++ b/rhino/src/main/java/org/mozilla/javascript/NativeDate.java
@@ -12,7 +12,9 @@
import java.time.Instant;
import java.time.ZoneId;
+import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Arrays;
@@ -24,6 +26,7 @@
* This class implements the Date native object. See ECMA 15.9.
*
* @author Mike McCabe
+ * @author Lai Quang Duong
*
Significant parts of this code are adapted from the venerable jsdate.cpp (also Mozilla):
* jsdate.cpp
*/
@@ -1647,30 +1650,6 @@ private static NativeDate jsConstructor(Context cx, Object[] args) {
}
private static String toLocale_helper(Context cx, double t, int methodId, Object[] args) {
- DateTimeFormatter formatter;
- switch (methodId) {
- case Id_toLocaleString:
- formatter =
- cx.getLanguageVersion() >= Context.VERSION_ES6
- ? localeDateTimeFormatterES6
- : localeDateTimeFormatter;
- break;
- case Id_toLocaleTimeString:
- formatter =
- cx.getLanguageVersion() >= Context.VERSION_ES6
- ? localeTimeFormatterES6
- : localeTimeFormatter;
- break;
- case Id_toLocaleDateString:
- formatter =
- cx.getLanguageVersion() >= Context.VERSION_ES6
- ? localeDateFormatterES6
- : localeDateFormatter;
- break;
- default:
- throw new AssertionError(); // unreachable
- }
-
final List languageTags = new ArrayList<>();
if (args.length != 0) {
// we use the 'locales' argument but ignore the second 'options' argument as per spec of
@@ -1686,15 +1665,67 @@ private static String toLocale_helper(Context cx, double t, int methodId, Object
}
}
+ Locale firstSupportedLocale = null;
final List availableLocales = Arrays.asList(Locale.getAvailableLocales());
for (String languageTag : languageTags) {
Locale locale = Locale.forLanguageTag(languageTag);
if (availableLocales.contains(locale)) {
- formatter = formatter.withLocale(locale);
+ firstSupportedLocale = locale;
break;
}
}
+ DateTimeFormatter formatter;
+ switch (methodId) {
+ case Id_toLocaleString:
+ if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
+ final String pattern =
+ DateTimeFormatterBuilder.getLocalizedDateTimePattern(
+ FormatStyle.SHORT,
+ FormatStyle.MEDIUM,
+ IsoChronology.INSTANCE,
+ firstSupportedLocale != null
+ ? firstSupportedLocale
+ : Locale.getDefault());
+ formatter = DateTimeFormatter.ofPattern(pattern.replaceAll("y+", "yyyy"));
+ } else {
+ formatter = localeDateTimeFormatter;
+ }
+ break;
+ case Id_toLocaleTimeString:
+ if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
+ final String pattern =
+ DateTimeFormatterBuilder.getLocalizedDateTimePattern(
+ null,
+ FormatStyle.MEDIUM,
+ IsoChronology.INSTANCE,
+ firstSupportedLocale != null
+ ? firstSupportedLocale
+ : Locale.getDefault());
+ formatter = DateTimeFormatter.ofPattern(pattern);
+ } else {
+ formatter = localeTimeFormatter;
+ }
+ break;
+ case Id_toLocaleDateString:
+ if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
+ final String pattern =
+ DateTimeFormatterBuilder.getLocalizedDateTimePattern(
+ FormatStyle.SHORT,
+ null,
+ IsoChronology.INSTANCE,
+ firstSupportedLocale != null
+ ? firstSupportedLocale
+ : Locale.getDefault());
+ formatter = DateTimeFormatter.ofPattern(pattern.replaceAll("y+", "yyyy"));
+ } else {
+ formatter = localeDateFormatter;
+ }
+ break;
+ default:
+ throw new AssertionError(); // unreachable
+ }
+
final ZoneId zoneid = cx.getTimeZone().toZoneId();
final String formatted = formatter.format(Instant.ofEpochMilli((long) t).atZone(zoneid));
// jdk 21 uses a nnbsp in front of 'PM'
@@ -2052,13 +2083,5 @@ private static double makeDate(Context cx, double date, Object[] args, int metho
private static final DateTimeFormatter localeTimeFormatter =
DateTimeFormatter.ofPattern("h:mm:ss a z");
- // use FormatStyle.SHORT for these as per spec of an implementation that has no
- // Intl.DateTimeFormat support
- private static final DateTimeFormatter localeDateTimeFormatterES6 =
- DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
- private static final DateTimeFormatter localeDateFormatterES6 =
- DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
- private static final DateTimeFormatter localeTimeFormatterES6 =
- DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
private double date;
}
diff --git a/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeDateTest.java b/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeDateTest.java
index 0c250e6f96..f9682a2d18 100644
--- a/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeDateTest.java
+++ b/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeDateTest.java
@@ -581,76 +581,45 @@ public void ctorDouble() {
@Test
public void toLocaleEnUs() {
- // real browser toLocale("12/18/2021, 10:23:00 PM", "new
- // Date('2021-12-18T22:23').toLocaleString('en-US')");
- // toLocale("12/18/21 10:23 PM", "new Date('2021-12-18T22:23').toLocaleString('en-US')");
- toLocale("12/18/21, 10:23 PM", "new Date('2021-12-18T22:23').toLocaleString('en-US')");
-
- // real browser toLocale("12/18/2021", "new
- // Date('2021-12-18T22:23').toLocaleDateString('en-US')");
- toLocale("12/18/21", "new Date('2021-12-18T22:23').toLocaleDateString('en-US')");
-
- // real browser toLocale("10:23:00 PM", "new
- // Date('2021-12-18T22:23').toLocaleTimeString('en-US')");
- toLocale("10:23 PM", "new Date('2021-12-18T22:23').toLocaleTimeString('en-US')");
+ toLocale("12/18/2021, 10:23:00 PM", "new Date('2021-12-18T22:23').toLocaleString('en-US')");
+ toLocale("12/18/2021", "new Date('2021-12-18T22:23').toLocaleDateString('en-US')");
+ toLocale("10:23:00 PM", "new Date('2021-12-18T22:23').toLocaleTimeString('en-US')");
}
@Test
public void toLocaleDeDe() {
- // real browser toLocale("18.12.2021, 22:23:00", "new
- // Date('2021-12-18T22:23').toLocaleString('de-DE')");
- // toLocale("18.12.21 22:23", "new Date('2021-12-18T22:23').toLocaleString('de-DE')");
- toLocale("18.12.21, 22:23", "new Date('2021-12-18T22:23').toLocaleString('de-DE')");
-
- // real browser toLocale("18.12.2021", "new
- // Date('2021-12-18T22:23').toLocaleDateString('de-DE')");
- toLocale("18.12.21", "new Date('2021-12-18T22:23').toLocaleDateString('de-DE')");
-
- // real browser toLocale("22:23:00", "new
- // Date('2021-12-18T22:23').toLocaleTimeString('de-DE')");
- toLocale("22:23", "new Date('2021-12-18T22:23').toLocaleTimeString('de-DE')");
+ toLocale("18.12.2021, 22:23:00", "new Date('2021-12-18T22:23').toLocaleString('de-DE')");
+ toLocale("18.12.2021", "new Date('2021-12-18T22:23').toLocaleDateString('de-DE')");
+ toLocale("22:23:00", "new Date('2021-12-18T22:23').toLocaleTimeString('de-DE')");
}
@Test
public void toLocaleJaJp() {
- // real browser toLocale("2021/12/18 22:23:00", "new
- // Date('2021-12-18T22:23').toLocaleString('ja-JP')");
- // toLocale("21/12/18 22:23", "new Date('2021-12-18T22:23').toLocaleString('ja-JP')");
- toLocale("2021/12/18 22:23", "new Date('2021-12-18T22:23').toLocaleString('ja-JP')");
-
- // real browser toLocale("2021/12/18", "new
- // Date('2021-12-18T22:23').toLocaleDateString('ja-JP')");
- // toLocale("21/12/18", "new Date('2021-12-18T22:23').toLocaleDateString('ja-JP')");
+ toLocale("2021/12/18 22:23:00", "new Date('2021-12-18T22:23').toLocaleString('ja-JP')");
toLocale("2021/12/18", "new Date('2021-12-18T22:23').toLocaleDateString('ja-JP')");
+ toLocale("22:23:00", "new Date('2021-12-18T22:23').toLocaleTimeString('ja-JP')");
+ }
- // real browser toLocale("22:23:00", "new
- // Date('2021-12-18T22:23').toLocaleTimeString('ja-JP')");
- toLocale("22:23", "new Date('2021-12-18T22:23').toLocaleTimeString('ja-JP')");
+ @Test
+ public void toLocaleFrFr() {
+ toLocale("18/12/2021 22:23:00", "new Date('2021-12-18T22:23').toLocaleString('fr-FR')");
+ toLocale("18/12/2021", "new Date('2021-12-18T22:23').toLocaleDateString('fr-FR')");
+ toLocale("22:23:00", "new Date('2021-12-18T22:23').toLocaleTimeString('fr-FR')");
+ }
+
+ @Test
+ public void toLocaleFiFi() {
+ // real browser: "18.12.2021 klo 22.23.00" (includes "klo" between date and time)
+ toLocale("18.12.2021 22.23.00", "new Date('2021-12-18T22:23').toLocaleString('fi-FI')");
+ toLocale("18.12.2021", "new Date('2021-12-18T22:23').toLocaleDateString('fi-FI')");
+ toLocale("22.23.00", "new Date('2021-12-18T22:23').toLocaleTimeString('fi-FI')");
}
@Test
public void toLocaleArray() {
- // real browser toLocale("2021/12/18 22:23:00", "new
- // Date('2021-12-18T22:23').toLocaleString(['foo', 'ja-JP', 'en-US'])");
- // toLocale("21/12/18 22:23", "new Date('2021-12-18T22:23').toLocaleString(['foo', 'ja-JP',
- // 'en-US'])");
- toLocale(
- "2021/12/18 22:23",
- "new Date('2021-12-18T22:23').toLocaleString(['foo', 'ja-JP', 'en-US'])");
-
- // real browser toLocale("2021/12/18", "new
- // Date('2021-12-18T22:23').toLocaleDateString(['foo', 'ja-JP', 'en-US'])");
- // toLocale("21/12/18", "new Date('2021-12-18T22:23').toLocaleDateString(['foo', 'ja-JP',
- // 'en-US'])");
- toLocale(
- "2021/12/18",
- "new Date('2021-12-18T22:23').toLocaleDateString(['foo', 'ja-JP', 'en-US'])");
-
- // real browser toLocale("22:23:00", "new
- // Date('2021-12-18T22:23').toLocaleTimeString(['foo', 'ja-JP', 'en-US'])");
- toLocale(
- "22:23",
- "new Date('2021-12-18T22:23').toLocaleTimeString(['foo', 'ja-JP', 'en-US'])");
+ toLocale("2021/12/18 22:23:00", "new Date('2021-12-18T22:23').toLocaleString(['foo', 'ja-JP', 'en-US'])");
+ toLocale("2021/12/18", "new Date('2021-12-18T22:23').toLocaleDateString(['foo', 'ja-JP', 'en-US'])");
+ toLocale("22:23:00", "new Date('2021-12-18T22:23').toLocaleTimeString(['foo', 'ja-JP', 'en-US'])");
}
private static void toLocale(final String expected, final String js) {
@@ -666,6 +635,27 @@ private static void toLocale(final String expected, final String js) {
});
}
+ @Test
+ public void toLocaleEpochDate() {
+ toLocale("1/1/1970, 12:00:00 AM", "new Date(0).toLocaleString('en-US')");
+ // real browser: "1.1.1970, 00:00:00" (without zero-padding)
+ toLocale("01.01.1970, 00:00:00", "new Date(0).toLocaleString('de-DE')");
+ // real browser: "1970/1/1 0:00:00" (without zero-padding)
+ toLocale("1970/01/01 0:00:00", "new Date(0).toLocaleString('ja-JP')");
+ toLocale("1/1/1970", "new Date(0).toLocaleDateString('en-US')");
+ // real browser: "1.1.1970" (without zero-padding)
+ toLocale("01.01.1970", "new Date(0).toLocaleDateString('de-DE')");
+ toLocale("12:00:00 AM", "new Date(0).toLocaleTimeString('en-US')");
+ toLocale("00:00:00", "new Date(0).toLocaleTimeString('de-DE')");
+ }
+
+ @Test
+ public void toLocaleWithSeconds() {
+ toLocale("12/18/2021, 10:23:45 PM", "new Date('2021-12-18T22:23:45').toLocaleString('en-US')");
+ toLocale("10:23:45 PM", "new Date('2021-12-18T22:23:45').toLocaleTimeString('en-US')");
+ toLocale("22:23:45", "new Date('2021-12-18T22:23:45').toLocaleTimeString('ja-JP')");
+ }
+
@Test
public void toDateStringGMT() {
toDateString("Sat Dec 18 2021", "GMT");