Skip to content

Commit

Permalink
- use DateTimeFormatter instead of SimpleDateFormat
Browse files Browse the repository at this point in the history
- Date.prototype.toLocaleString now handles locales argument
- make the formatter use Context.timezone instead of system default
- ES6: use FormatStyle.SHORT for the formatter to make it behave more like real browsers when Intl.DateTimeFormat is not supported
  • Loading branch information
rbri committed Jun 15, 2024
1 parent 01e0a7f commit 3a4f05b
Show file tree
Hide file tree
Showing 2 changed files with 495 additions and 62 deletions.
87 changes: 64 additions & 23 deletions rhino-runtime/src/main/java/org/mozilla/javascript/NativeDate.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

package org.mozilla.javascript;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;

/**
* This class implements the Date native object. See ECMA 15.9.
Expand All @@ -21,7 +23,6 @@ final class NativeDate extends IdScriptableObject {
private static final long serialVersionUID = -8307438915861678966L;

private static final Object DATE_TAG = "Date";

private static final String js_NaN_date_str = "Invalid Date";

static void init(Scriptable scope, boolean sealed) {
Expand Down Expand Up @@ -336,7 +337,7 @@ public Object execIdCall(
case Id_toLocaleTimeString:
case Id_toLocaleDateString:
if (!Double.isNaN(t)) {
return toLocale_helper(t, id);
return toLocale_helper(cx, t, id, args);
}
return js_NaN_date_str;

Expand Down Expand Up @@ -1348,10 +1349,8 @@ private static String date_format(Context cx, double t, int methodId) {
t = MakeDate(day, TimeWithinDay(t));
}
result.append(" (");
Date date = new Date((long) t);
synchronized (timeZoneFormatter) {
result.append(timeZoneFormatter.format(date));
}
final ZoneId zoneid = cx.getTimeZone().toZoneId();
result.append(timeZoneFormatter.format(Instant.ofEpochMilli((long) t).atZone(zoneid)));
result.append(')');
}
return result.toString();
Expand Down Expand Up @@ -1399,25 +1398,57 @@ private static Object jsConstructor(Context cx, Object[] args) {
return obj;
}

private static String toLocale_helper(double t, int methodId) {
DateFormat formatter;
private static String toLocale_helper(Context cx, double t, int methodId, Object[] args) {
DateTimeFormatter formatter;
switch (methodId) {
case Id_toLocaleString:
formatter = localeDateTimeFormatter;
formatter =
cx.getLanguageVersion() >= Context.VERSION_ES6
? localeDateTimeFormatterES6
: localeDateTimeFormatter;
break;
case Id_toLocaleTimeString:
formatter = localeTimeFormatter;
formatter =
cx.getLanguageVersion() >= Context.VERSION_ES6
? localeTimeFormatterES6
: localeTimeFormatter;
break;
case Id_toLocaleDateString:
formatter = localeDateFormatter;
formatter =
cx.getLanguageVersion() >= Context.VERSION_ES6
? localeDateFormatterES6
: localeDateFormatter;
break;
default:
throw new AssertionError(); // unreachable
}

synchronized (formatter) {
return formatter.format(new Date((long) t));
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
// an
// implementation that has no Intl.DateTimeFormat support
if (args[0] instanceof NativeArray) {
final NativeArray array = (NativeArray) args[0];
for (Object languageTag : array) {
languageTags.add(Context.toString(languageTag));
}
} else {
languageTags.add(Context.toString(args[0]));
}
}

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

final ZoneId zoneid = cx.getTimeZone().toZoneId();
return formatter.format(Instant.ofEpochMilli((long) t).atZone(zoneid));
}

private static String js_toUTCString(double date) {
Expand Down Expand Up @@ -1916,12 +1947,22 @@ protected int findPrototypeId(String s) {

private static final int Id_toGMTString = Id_toUTCString; // Alias, see Ecma B.2.6

// not thread safe
private static final DateFormat timeZoneFormatter = new SimpleDateFormat("zzz");
private static final DateFormat localeDateTimeFormatter =
new SimpleDateFormat("MMMM d, yyyy h:mm:ss a z");
private static final DateFormat localeDateFormatter = new SimpleDateFormat("MMMM d, yyyy");
private static final DateFormat localeTimeFormatter = new SimpleDateFormat("h:mm:ss a z");

private static final DateTimeFormatter timeZoneFormatter = DateTimeFormatter.ofPattern("zzz");

private static final DateTimeFormatter localeDateTimeFormatter =
DateTimeFormatter.ofPattern("MMMM d, yyyy h:mm:ss a z");
private static final DateTimeFormatter localeDateFormatter =
DateTimeFormatter.ofPattern("MMMM d, yyyy");
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;
}
Loading

0 comments on commit 3a4f05b

Please sign in to comment.