Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 56 additions & 33 deletions rhino/src/main/java/org/mozilla/javascript/NativeDate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,6 +26,7 @@
* This class implements the Date native object. See ECMA 15.9.
*
* @author Mike McCabe
* @author Lai Quang Duong
* <p>Significant parts of this code are adapted from the venerable jsdate.cpp (also Mozilla):
* <a href="https://dxr.mozilla.org/mozilla-central/source/js/src/jsdate.cpp">jsdate.cpp</a>
*/
Expand Down Expand Up @@ -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<String> languageTags = new ArrayList<>();
if (args.length != 0) {
// we use the 'locales' argument but ignore the second 'options' argument as per spec of
Expand All @@ -1686,15 +1665,67 @@ private static String toLocale_helper(Context cx, double t, int methodId, Object
}
}

Locale firstSupportedLocale = null;
final List<Locale> 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'
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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");
Expand Down