Skip to content

Commit ef13600

Browse files
rbrigbrail
authored andcommitted
Make NativeDate.toLocaleString() behave more like real browsers
Co-authored-by: Lai Quang Duong
1 parent 74d9c79 commit ef13600

2 files changed

Lines changed: 99 additions & 82 deletions

File tree

rhino/src/main/java/org/mozilla/javascript/NativeDate.java

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
import java.time.Instant;
1414
import java.time.ZoneId;
15+
import java.time.chrono.IsoChronology;
1516
import java.time.format.DateTimeFormatter;
17+
import java.time.format.DateTimeFormatterBuilder;
1618
import java.time.format.FormatStyle;
1719
import java.util.ArrayList;
1820
import java.util.Arrays;
@@ -1647,30 +1649,6 @@ private static NativeDate jsConstructor(Context cx, Object[] args) {
16471649
}
16481650

16491651
private static String toLocale_helper(Context cx, double t, int methodId, Object[] args) {
1650-
DateTimeFormatter formatter;
1651-
switch (methodId) {
1652-
case Id_toLocaleString:
1653-
formatter =
1654-
cx.getLanguageVersion() >= Context.VERSION_ES6
1655-
? localeDateTimeFormatterES6
1656-
: localeDateTimeFormatter;
1657-
break;
1658-
case Id_toLocaleTimeString:
1659-
formatter =
1660-
cx.getLanguageVersion() >= Context.VERSION_ES6
1661-
? localeTimeFormatterES6
1662-
: localeTimeFormatter;
1663-
break;
1664-
case Id_toLocaleDateString:
1665-
formatter =
1666-
cx.getLanguageVersion() >= Context.VERSION_ES6
1667-
? localeDateFormatterES6
1668-
: localeDateFormatter;
1669-
break;
1670-
default:
1671-
throw new AssertionError(); // unreachable
1672-
}
1673-
16741652
final List<String> languageTags = new ArrayList<>();
16751653
if (args.length != 0) {
16761654
// we use the 'locales' argument but ignore the second 'options' argument as per spec of
@@ -1686,14 +1664,63 @@ private static String toLocale_helper(Context cx, double t, int methodId, Object
16861664
}
16871665
}
16881666

1667+
Locale firstSupportedLocale = null;
16891668
final List<Locale> availableLocales = Arrays.asList(Locale.getAvailableLocales());
16901669
for (String languageTag : languageTags) {
16911670
Locale locale = Locale.forLanguageTag(languageTag);
16921671
if (availableLocales.contains(locale)) {
1693-
formatter = formatter.withLocale(locale);
1672+
firstSupportedLocale = locale;
16941673
break;
16951674
}
16961675
}
1676+
if (firstSupportedLocale == null) {
1677+
firstSupportedLocale = Locale.getDefault();
1678+
}
1679+
1680+
DateTimeFormatter formatter;
1681+
switch (methodId) {
1682+
case Id_toLocaleString:
1683+
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
1684+
final String pattern =
1685+
DateTimeFormatterBuilder.getLocalizedDateTimePattern(
1686+
FormatStyle.SHORT,
1687+
FormatStyle.MEDIUM,
1688+
IsoChronology.INSTANCE,
1689+
firstSupportedLocale);
1690+
formatter = DateTimeFormatter.ofPattern(pattern.replaceAll("y+", "yyyy"));
1691+
} else {
1692+
formatter = localeDateTimeFormatter;
1693+
}
1694+
break;
1695+
case Id_toLocaleTimeString:
1696+
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
1697+
final String pattern =
1698+
DateTimeFormatterBuilder.getLocalizedDateTimePattern(
1699+
null,
1700+
FormatStyle.MEDIUM,
1701+
IsoChronology.INSTANCE,
1702+
firstSupportedLocale);
1703+
formatter = DateTimeFormatter.ofPattern(pattern);
1704+
} else {
1705+
formatter = localeTimeFormatter;
1706+
}
1707+
break;
1708+
case Id_toLocaleDateString:
1709+
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
1710+
final String pattern =
1711+
DateTimeFormatterBuilder.getLocalizedDateTimePattern(
1712+
FormatStyle.SHORT,
1713+
null,
1714+
IsoChronology.INSTANCE,
1715+
firstSupportedLocale);
1716+
formatter = DateTimeFormatter.ofPattern(pattern.replaceAll("y+", "yyyy"));
1717+
} else {
1718+
formatter = localeDateFormatter;
1719+
}
1720+
break;
1721+
default:
1722+
throw new AssertionError(); // unreachable
1723+
}
16971724

16981725
final ZoneId zoneid = cx.getTimeZone().toZoneId();
16991726
final String formatted = formatter.format(Instant.ofEpochMilli((long) t).atZone(zoneid));
@@ -2052,13 +2079,5 @@ private static double makeDate(Context cx, double date, Object[] args, int metho
20522079
private static final DateTimeFormatter localeTimeFormatter =
20532080
DateTimeFormatter.ofPattern("h:mm:ss a z");
20542081

2055-
// use FormatStyle.SHORT for these as per spec of an implementation that has no
2056-
// Intl.DateTimeFormat support
2057-
private static final DateTimeFormatter localeDateTimeFormatterES6 =
2058-
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
2059-
private static final DateTimeFormatter localeDateFormatterES6 =
2060-
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
2061-
private static final DateTimeFormatter localeTimeFormatterES6 =
2062-
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
20632082
private double date;
20642083
}

tests/src/test/java/org/mozilla/javascript/tests/es6/NativeDateTest.java

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -581,75 +581,50 @@ public void ctorDouble() {
581581

582582
@Test
583583
public void toLocaleEnUs() {
584-
// real browser toLocale("12/18/2021, 10:23:00 PM", "new
585-
// Date('2021-12-18T22:23').toLocaleString('en-US')");
586-
// toLocale("12/18/21 10:23 PM", "new Date('2021-12-18T22:23').toLocaleString('en-US')");
587-
toLocale("12/18/21, 10:23 PM", "new Date('2021-12-18T22:23').toLocaleString('en-US')");
588-
589-
// real browser toLocale("12/18/2021", "new
590-
// Date('2021-12-18T22:23').toLocaleDateString('en-US')");
591-
toLocale("12/18/21", "new Date('2021-12-18T22:23').toLocaleDateString('en-US')");
592-
593-
// real browser toLocale("10:23:00 PM", "new
594-
// Date('2021-12-18T22:23').toLocaleTimeString('en-US')");
595-
toLocale("10:23 PM", "new Date('2021-12-18T22:23').toLocaleTimeString('en-US')");
584+
toLocale("12/18/2021, 10:23:00 PM", "new Date('2021-12-18T22:23').toLocaleString('en-US')");
585+
toLocale("12/18/2021", "new Date('2021-12-18T22:23').toLocaleDateString('en-US')");
586+
toLocale("10:23:00 PM", "new Date('2021-12-18T22:23').toLocaleTimeString('en-US')");
596587
}
597588

598589
@Test
599590
public void toLocaleDeDe() {
600-
// real browser toLocale("18.12.2021, 22:23:00", "new
601-
// Date('2021-12-18T22:23').toLocaleString('de-DE')");
602-
// toLocale("18.12.21 22:23", "new Date('2021-12-18T22:23').toLocaleString('de-DE')");
603-
toLocale("18.12.21, 22:23", "new Date('2021-12-18T22:23').toLocaleString('de-DE')");
604-
605-
// real browser toLocale("18.12.2021", "new
606-
// Date('2021-12-18T22:23').toLocaleDateString('de-DE')");
607-
toLocale("18.12.21", "new Date('2021-12-18T22:23').toLocaleDateString('de-DE')");
608-
609-
// real browser toLocale("22:23:00", "new
610-
// Date('2021-12-18T22:23').toLocaleTimeString('de-DE')");
611-
toLocale("22:23", "new Date('2021-12-18T22:23').toLocaleTimeString('de-DE')");
591+
toLocale("18.12.2021, 22:23:00", "new Date('2021-12-18T22:23').toLocaleString('de-DE')");
592+
toLocale("18.12.2021", "new Date('2021-12-18T22:23').toLocaleDateString('de-DE')");
593+
toLocale("22:23:00", "new Date('2021-12-18T22:23').toLocaleTimeString('de-DE')");
612594
}
613595

614596
@Test
615597
public void toLocaleJaJp() {
616-
// real browser toLocale("2021/12/18 22:23:00", "new
617-
// Date('2021-12-18T22:23').toLocaleString('ja-JP')");
618-
// toLocale("21/12/18 22:23", "new Date('2021-12-18T22:23').toLocaleString('ja-JP')");
619-
toLocale("2021/12/18 22:23", "new Date('2021-12-18T22:23').toLocaleString('ja-JP')");
620-
621-
// real browser toLocale("2021/12/18", "new
622-
// Date('2021-12-18T22:23').toLocaleDateString('ja-JP')");
623-
// toLocale("21/12/18", "new Date('2021-12-18T22:23').toLocaleDateString('ja-JP')");
598+
toLocale("2021/12/18 22:23:00", "new Date('2021-12-18T22:23').toLocaleString('ja-JP')");
624599
toLocale("2021/12/18", "new Date('2021-12-18T22:23').toLocaleDateString('ja-JP')");
600+
toLocale("22:23:00", "new Date('2021-12-18T22:23').toLocaleTimeString('ja-JP')");
601+
}
625602

626-
// real browser toLocale("22:23:00", "new
627-
// Date('2021-12-18T22:23').toLocaleTimeString('ja-JP')");
628-
toLocale("22:23", "new Date('2021-12-18T22:23').toLocaleTimeString('ja-JP')");
603+
@Test
604+
public void toLocaleFrFr() {
605+
toLocale("18/12/2021 22:23:00", "new Date('2021-12-18T22:23').toLocaleString('fr-FR')");
606+
toLocale("18/12/2021", "new Date('2021-12-18T22:23').toLocaleDateString('fr-FR')");
607+
toLocale("22:23:00", "new Date('2021-12-18T22:23').toLocaleTimeString('fr-FR')");
608+
}
609+
610+
@Test
611+
public void toLocaleFiFi() {
612+
// real browser: "18.12.2021 klo 22.23.00" (includes "klo" between date and time)
613+
toLocale("18.12.2021 22.23.00", "new Date('2021-12-18T22:23').toLocaleString('fi-FI')");
614+
toLocale("18.12.2021", "new Date('2021-12-18T22:23').toLocaleDateString('fi-FI')");
615+
toLocale("22.23.00", "new Date('2021-12-18T22:23').toLocaleTimeString('fi-FI')");
629616
}
630617

631618
@Test
632619
public void toLocaleArray() {
633-
// real browser toLocale("2021/12/18 22:23:00", "new
634-
// Date('2021-12-18T22:23').toLocaleString(['foo', 'ja-JP', 'en-US'])");
635-
// toLocale("21/12/18 22:23", "new Date('2021-12-18T22:23').toLocaleString(['foo', 'ja-JP',
636-
// 'en-US'])");
637620
toLocale(
638-
"2021/12/18 22:23",
621+
"2021/12/18 22:23:00",
639622
"new Date('2021-12-18T22:23').toLocaleString(['foo', 'ja-JP', 'en-US'])");
640-
641-
// real browser toLocale("2021/12/18", "new
642-
// Date('2021-12-18T22:23').toLocaleDateString(['foo', 'ja-JP', 'en-US'])");
643-
// toLocale("21/12/18", "new Date('2021-12-18T22:23').toLocaleDateString(['foo', 'ja-JP',
644-
// 'en-US'])");
645623
toLocale(
646624
"2021/12/18",
647625
"new Date('2021-12-18T22:23').toLocaleDateString(['foo', 'ja-JP', 'en-US'])");
648-
649-
// real browser toLocale("22:23:00", "new
650-
// Date('2021-12-18T22:23').toLocaleTimeString(['foo', 'ja-JP', 'en-US'])");
651626
toLocale(
652-
"22:23",
627+
"22:23:00",
653628
"new Date('2021-12-18T22:23').toLocaleTimeString(['foo', 'ja-JP', 'en-US'])");
654629
}
655630

@@ -666,6 +641,29 @@ private static void toLocale(final String expected, final String js) {
666641
});
667642
}
668643

644+
@Test
645+
public void toLocaleEpochDate() {
646+
toLocale("1/1/1970, 12:00:00 AM", "new Date(0).toLocaleString('en-US')");
647+
// real browser: "1.1.1970, 00:00:00" (without zero-padding)
648+
toLocale("01.01.1970, 00:00:00", "new Date(0).toLocaleString('de-DE')");
649+
// real browser: "1970/1/1 0:00:00" (without zero-padding)
650+
toLocale("1970/01/01 0:00:00", "new Date(0).toLocaleString('ja-JP')");
651+
toLocale("1/1/1970", "new Date(0).toLocaleDateString('en-US')");
652+
// real browser: "1.1.1970" (without zero-padding)
653+
toLocale("01.01.1970", "new Date(0).toLocaleDateString('de-DE')");
654+
toLocale("12:00:00 AM", "new Date(0).toLocaleTimeString('en-US')");
655+
toLocale("00:00:00", "new Date(0).toLocaleTimeString('de-DE')");
656+
}
657+
658+
@Test
659+
public void toLocaleWithSeconds() {
660+
toLocale(
661+
"12/18/2021, 10:23:45 PM",
662+
"new Date('2021-12-18T22:23:45').toLocaleString('en-US')");
663+
toLocale("10:23:45 PM", "new Date('2021-12-18T22:23:45').toLocaleTimeString('en-US')");
664+
toLocale("22:23:45", "new Date('2021-12-18T22:23:45').toLocaleTimeString('ja-JP')");
665+
}
666+
669667
@Test
670668
public void toDateStringGMT() {
671669
toDateString("Sat Dec 18 2021", "GMT");

0 commit comments

Comments
 (0)