package com.oracle.truffle.js.runtime.builtins.intl;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.UnmodifiableEconomicMap;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DateTimePatternGenerator;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage.ContextReference;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.js.builtins.intl.DateTimeFormatFunctionBuiltins;
import com.oracle.truffle.js.builtins.intl.DateTimeFormatPrototypeBuiltins;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.PropertySetNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSContext.BuiltinFunctionKey;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.JavaScriptRootNode;
import com.oracle.truffle.js.runtime.builtins.JSArray;
import com.oracle.truffle.js.runtime.builtins.JSConstructor;
import com.oracle.truffle.js.runtime.builtins.JSConstructorFactory;
import com.oracle.truffle.js.runtime.builtins.JSDate;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSNonProxy;
import com.oracle.truffle.js.runtime.builtins.JSObjectFactory;
import com.oracle.truffle.js.runtime.builtins.JSOrdinary;
import com.oracle.truffle.js.runtime.builtins.PrototypeSupplier;
import com.oracle.truffle.js.runtime.objects.JSAttributes;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.JSObjectUtil;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.IntlUtil;
import com.oracle.truffle.js.runtime.util.LazyValue;
public final class JSDateTimeFormat extends JSNonProxy implements JSConstructorFactory.Default.WithFunctions, PrototypeSupplier {
public static final String CLASS_NAME = "DateTimeFormat";
public static final String PROTOTYPE_NAME = "DateTimeFormat.prototype";
static final HiddenKey BOUND_OBJECT_KEY = new HiddenKey(CLASS_NAME);
public static final JSDateTimeFormat INSTANCE = new JSDateTimeFormat();
private static final LazyValue<UnmodifiableEconomicMap<String, String>> canonicalTimeZoneIDMap = new LazyValue<>(JSDateTimeFormat::initCanonicalTimeZoneIDMap);
private JSDateTimeFormat() {
}
public static boolean isJSDateTimeFormat(Object obj) {
return obj instanceof JSDateTimeFormatObject;
}
@Override
public String getClassName() {
return CLASS_NAME;
}
@Override
public String getClassName(DynamicObject object) {
return getClassName();
}
@Override
public DynamicObject createPrototype(JSRealm realm, DynamicObject ctor) {
JSContext ctx = realm.getContext();
DynamicObject numberFormatPrototype = JSObjectUtil.createOrdinaryPrototypeObject(realm);
JSObjectUtil.putConstructorProperty(ctx, numberFormatPrototype, ctor);
JSObjectUtil.putFunctionsFromContainer(realm, numberFormatPrototype, DateTimeFormatPrototypeBuiltins.BUILTINS);
JSObjectUtil.putBuiltinAccessorProperty(numberFormatPrototype, "format", createFormatFunctionGetter(realm, ctx), Undefined.instance);
JSObjectUtil.putToStringTag(numberFormatPrototype, "Intl.DateTimeFormat");
return numberFormatPrototype;
}
@Override
public Shape makeInitialShape(JSContext ctx, DynamicObject prototype) {
Shape initialShape = JSObjectUtil.getProtoChildShape(prototype, INSTANCE, ctx);
return initialShape;
}
public static JSConstructor createConstructor(JSRealm realm) {
return INSTANCE.createConstructorAndPrototype(realm, DateTimeFormatFunctionBuiltins.BUILTINS);
}
public static DynamicObject create(JSContext context) {
InternalState state = new InternalState();
JSRealm realm = context.getRealm();
JSObjectFactory factory = context.getDateTimeFormatFactory();
JSDateTimeFormatObject obj = new JSDateTimeFormatObject(factory.getShape(realm), state);
factory.initProto(obj, realm);
assert isJSDateTimeFormat(obj);
return context.trackAllocation(obj);
}
@TruffleBoundary
public static void setupInternalDateTimeFormat(
JSContext ctx,
InternalState state, String[] locales,
String weekdayOpt,
String eraOpt,
String yearOpt,
String monthOpt,
String dayOpt,
String hourOpt,
String hcOpt,
Boolean hour12Opt,
String minuteOpt,
String secondOpt,
String tzNameOpt,
TimeZone timeZone,
String calendarOpt,
String numberingSystemOpt,
String dateStyleOpt,
String timeStyleOpt) {
Locale selectedLocale = IntlUtil.selectedLocale(ctx, locales);
Locale strippedLocale = selectedLocale.stripExtensions();
Locale.Builder builder = new Locale.Builder();
builder.setLocale(strippedLocale);
String hc;
if (hour12Opt == null) {
String hcType = selectedLocale.getUnicodeLocaleType("hc");
if (hcType != null && (hcOpt == null || hcOpt.equals(hcType)) && isValidHCType(hcType)) {
hc = hcType;
builder.setUnicodeLocaleKeyword("hc", hcType);
} else {
hc = hcOpt;
}
} else {
hc = null;
}
String caType = IntlUtil.normalizeCAType(selectedLocale.getUnicodeLocaleType("ca"));
String normCalendarOpt = IntlUtil.normalizeCAType(calendarOpt);
if (caType != null && (normCalendarOpt == null || normCalendarOpt.equals(caType)) && isValidCAType(strippedLocale, caType)) {
state.calendar = caType;
builder.setUnicodeLocaleKeyword("ca", caType);
}
String nuType = selectedLocale.getUnicodeLocaleType("nu");
if ((nuType != null) && IntlUtil.isValidNumberingSystem(nuType) && (numberingSystemOpt == null || numberingSystemOpt.equals(nuType))) {
state.numberingSystem = nuType;
builder.setUnicodeLocaleKeyword("nu", nuType);
}
state.locale = builder.build().toLanguageTag();
if (normCalendarOpt != null && isValidCAType(strippedLocale, normCalendarOpt)) {
state.calendar = normCalendarOpt;
builder.setUnicodeLocaleKeyword("ca", normCalendarOpt);
}
if (numberingSystemOpt != null && IntlUtil.isValidNumberingSystem(numberingSystemOpt)) {
state.numberingSystem = numberingSystemOpt;
builder.setUnicodeLocaleKeyword("nu", numberingSystemOpt);
}
Locale javaLocale = builder.build();
if (state.numberingSystem == null) {
state.numberingSystem = IntlUtil.defaultNumberingSystemName(ctx, javaLocale);
}
state.dateStyle = dateStyleOpt;
state.timeStyle = timeStyleOpt;
DateFormat dateFormat;
if (timeStyleOpt == null) {
if (dateStyleOpt == null) {
String skeleton = makeSkeleton(weekdayOpt, eraOpt, yearOpt, monthOpt, dayOpt, hourOpt, hc, hour12Opt, minuteOpt, secondOpt, tzNameOpt);
DateTimePatternGenerator patternGenerator = DateTimePatternGenerator.getInstance(javaLocale);
String bestPattern = patternGenerator.getBestPattern(skeleton);
String baseSkeleton = patternGenerator.getBaseSkeleton(bestPattern);
if (containsOneOf(baseSkeleton, "eEc")) {
state.weekday = weekdayOpt;
}
if (baseSkeleton.contains("G")) {
state.era = eraOpt;
}
if (containsOneOf(baseSkeleton, "YyUu")) {
state.year = yearOpt;
}
if (containsOneOf(baseSkeleton, "ML")) {
state.month = monthOpt;
}
if (containsOneOf(baseSkeleton, "dDFg")) {
state.day = dayOpt;
}
if (containsOneOf(baseSkeleton, "hHKk")) {
state.hour = hourOpt;
if (hour12Opt != null) {
if (containsOneOf(baseSkeleton, "HK")) {
hc = hour12Opt ? IntlUtil.H11 : IntlUtil.H23;
} else {
hc = hour12Opt ? IntlUtil.H12 : IntlUtil.H24;
}
}
state.hourCycle = (hc == null) ? hourCycleFromPattern(baseSkeleton) : hc;
}
if (baseSkeleton.contains("m")) {
state.minute = minuteOpt;
}
if (containsOneOf(baseSkeleton, "sSA")) {
state.second = secondOpt;
}
dateFormat = new SimpleDateFormat(bestPattern, javaLocale);
} else {
dateFormat = DateFormat.getDateInstance(dateFormatStyle(dateStyleOpt), javaLocale);
}
} else {
if (dateStyleOpt == null) {
dateFormat = DateFormat.getTimeInstance(dateFormatStyle(timeStyleOpt), javaLocale);
} else {
dateFormat = DateFormat.getDateTimeInstance(dateFormatStyle(dateStyleOpt), dateFormatStyle(timeStyleOpt), javaLocale);
}
String pattern = ((SimpleDateFormat) dateFormat).toPattern();
if (hour12Opt != null) {
if (containsOneOf(pattern, "HK")) {
hc = hour12Opt ? IntlUtil.H11 : IntlUtil.H23;
} else {
hc = hour12Opt ? IntlUtil.H12 : IntlUtil.H24;
}
}
String patternHourCycle = hourCycleFromPattern(pattern);
if (hc == null) {
hc = patternHourCycle;
} else if (!hc.equals(patternHourCycle)) {
DateTimePatternGenerator patternGenerator = DateTimePatternGenerator.getInstance(strippedLocale);
String baseSkeleton = patternGenerator.getSkeleton(pattern);
String skeleton = replaceHourCycle(baseSkeleton, hc);
String bestPattern = patternGenerator.getBestPattern(skeleton);
dateFormat = new SimpleDateFormat(bestPattern, javaLocale);
}
state.hourCycle = hc;
}
state.dateFormat = dateFormat;
if (state.calendar == null) {
state.calendar = IntlUtil.normalizeCAType(Calendar.getInstance(javaLocale).getType());
}
if ("gregory".equals(state.calendar)) {
Calendar calendar = dateFormat.getCalendar();
if (!(calendar instanceof GregorianCalendar)) {
calendar = new GregorianCalendar(javaLocale);
dateFormat.setCalendar(calendar);
}
((GregorianCalendar) calendar).setGregorianChange(new Date(Long.MIN_VALUE));
}
if (tzNameOpt != null && !tzNameOpt.isEmpty()) {
state.timeZoneName = tzNameOpt;
}
state.dateFormat.setTimeZone(timeZone);
state.timeZone = timeZone.getID();
state.initialized = true;
}
private static int dateFormatStyle(String style) {
if (IntlUtil.FULL.equals(style)) {
return DateFormat.FULL;
} else if (IntlUtil.LONG.equals(style)) {
return DateFormat.LONG;
} else if (IntlUtil.MEDIUM.equals(style)) {
return DateFormat.MEDIUM;
} else {
assert IntlUtil.SHORT.equals(style);
return DateFormat.SHORT;
}
}
private static String hourCycleFromPattern(String pattern) {
if (pattern.indexOf('K') != -1) {
return IntlUtil.H11;
} else if (pattern.indexOf('h') != -1) {
return IntlUtil.H12;
} else if (pattern.indexOf('H') != -1) {
return IntlUtil.H23;
} else if (pattern.indexOf('k') != -1) {
return IntlUtil.H24;
} else {
return null;
}
}
private static String replaceHourCycle(String pattern, String hourCycle) {
StringBuilder sb = new StringBuilder();
char replacement;
if (IntlUtil.H11.equals(hourCycle)) {
replacement = 'K';
} else if (IntlUtil.H12.equals(hourCycle)) {
replacement = 'h';
} else if (IntlUtil.H23.equals(hourCycle)) {
replacement = 'H';
} else {
assert IntlUtil.H24.equals(hourCycle);
replacement = 'k';
}
for (char c : pattern.toCharArray()) {
switch (c) {
case 'K':
case 'h':
case 'H':
case 'k':
sb.append(replacement);
break;
default:
sb.append(c);
}
}
return sb.toString();
}
private static boolean isValidHCType(String hcType) {
return IntlUtil.H11.equals(hcType) || IntlUtil.H12.equals(hcType) || IntlUtil.H23.equals(hcType) || IntlUtil.H24.equals(hcType);
}
private static boolean isValidCAType(Locale locale, String calendar) {
assert Objects.equals(calendar, IntlUtil.normalizeCAType(calendar));
String[] validValues = Calendar.getKeywordValuesForLocale("ca", ULocale.forLocale(locale), false);
for (String validValue : validValues) {
if (IntlUtil.normalizeCAType(validValue).equals(calendar)) {
return true;
}
}
return false;
}
private static String weekdayOptToSkeleton(String weekdayOpt) {
if (weekdayOpt == null) {
return "";
}
switch (weekdayOpt) {
case IntlUtil.NARROW:
return "eeeee";
case IntlUtil.SHORT:
return "eee";
case IntlUtil.LONG:
return "eeee";
}
return "";
}
private static String eraOptToSkeleton(String eraOpt) {
if (eraOpt == null) {
return "";
}
switch (eraOpt) {
case IntlUtil.NARROW:
return "GGGGG";
case IntlUtil.SHORT:
return "GGG";
case IntlUtil.LONG:
return "GGGG";
}
return "";
}
private static String yearOptToSkeleton(String yearOpt) {
if (yearOpt == null) {
return "";
}
switch (yearOpt) {
case IntlUtil._2_DIGIT:
return "yy";
case IntlUtil.NUMERIC:
return "y";
}
return "";
}
private static String monthOptToSkeleton(String monthOpt) {
if (monthOpt == null) {
return "";
}
switch (monthOpt) {
case IntlUtil._2_DIGIT:
return "MM";
case IntlUtil.NUMERIC:
return "M";
case IntlUtil.NARROW:
return "MMMMM";
case IntlUtil.SHORT:
return "MMM";
case IntlUtil.LONG:
return "MMMM";
}
return "";
}
private static String dayOptToSkeleton(String dayOpt) {
if (dayOpt == null) {
return "";
}
switch (dayOpt) {
case IntlUtil._2_DIGIT:
return "dd";
case IntlUtil.NUMERIC:
return "d";
}
return "";
}
private static String hourOptToSkeleton(String hourOpt, String hcOpt, Boolean hour12Opt) {
if (hourOpt == null) {
return "";
}
switch (hourOpt) {
case IntlUtil._2_DIGIT:
if (hcOpt == null) {
if (hour12Opt != null) {
if (hour12Opt) {
return "KK";
} else {
return "HH";
}
} else {
return "jj";
}
} else {
switch (hcOpt) {
case IntlUtil.H11:
return "KK";
case IntlUtil.H12:
return "hh";
case IntlUtil.H23:
return "HH";
case IntlUtil.H24:
return "kk";
}
}
break;
case IntlUtil.NUMERIC:
if (hcOpt == null) {
if (hour12Opt != null) {
if (hour12Opt) {
return "K";
} else {
return "H";
}
} else {
return "j";
}
} else {
switch (hcOpt) {
case IntlUtil.H11:
return "K";
case IntlUtil.H12:
return "h";
case IntlUtil.H23:
return "H";
case IntlUtil.H24:
return "k";
}
}
}
return "";
}
private static String minuteOptToSkeleton(String minuteOpt) {
if (minuteOpt == null) {
return "";
}
switch (minuteOpt) {
case IntlUtil._2_DIGIT:
return "mm";
case IntlUtil.NUMERIC:
return "m";
}
return "";
}
private static String secondOptToSkeleton(String secondOpt) {
if (secondOpt == null) {
return "";
}
switch (secondOpt) {
case IntlUtil._2_DIGIT:
return "ss";
case IntlUtil.NUMERIC:
return "s";
}
return "";
}
private static String timeZoneNameOptToSkeleton(String timeZoneNameOpt) {
if (timeZoneNameOpt == null) {
return "";
}
switch (timeZoneNameOpt) {
case IntlUtil.SHORT:
return "v";
case IntlUtil.LONG:
return "vvvv";
}
return "";
}
private static String makeSkeleton(String weekdayOpt, String eraOpt, String yearOpt, String monthOpt, String dayOpt, String hourOpt, String hcOpt, Boolean hour12Opt, String minuteOpt,
String secondOpt, String timeZoneNameOpt) {
return weekdayOptToSkeleton(weekdayOpt) + eraOptToSkeleton(eraOpt) + yearOptToSkeleton(yearOpt) + monthOptToSkeleton(monthOpt) + dayOptToSkeleton(dayOpt) +
hourOptToSkeleton(hourOpt, hcOpt, hour12Opt) +
minuteOptToSkeleton(minuteOpt) + secondOptToSkeleton(secondOpt) + timeZoneNameOptToSkeleton(timeZoneNameOpt);
}
private static UnmodifiableEconomicMap<String, String> initCanonicalTimeZoneIDMap() {
CompilerAsserts.neverPartOfCompilation();
EconomicMap<String, String> map = EconomicMap.create();
for (String available : TimeZone.getAvailableIDs()) {
map.put(IntlUtil.toUpperCase(available), TimeZone.getCanonicalID(available));
}
return map;
}
@TruffleBoundary
public static String canonicalizeTimeZoneName(String tzId) {
String ucTzId = IntlUtil.toUpperCase(tzId);
String canTzId = canonicalTimeZoneIDMap.get().get(ucTzId);
if (canTzId == null) {
return null;
}
if (canTzId.equals("Etc/UTC") || canTzId.equals("Etc/GMT")) {
return "UTC";
} else {
return canTzId;
}
}
private static boolean containsOneOf(String suspect, String containees) {
for (int c : containees.getBytes()) {
if (suspect.indexOf(c) > -1) {
return true;
}
}
return false;
}
public static DateFormat getDateFormatProperty(DynamicObject obj) {
return getInternalState(obj).dateFormat;
}
@TruffleBoundary
public static String format(JSContext context, DynamicObject numberFormatObj, Object n) {
DateFormat dateFormat = getDateFormatProperty(numberFormatObj);
return dateFormat.format(timeClip(context, n));
}
private static double timeClip(JSContext context, Object n) {
double x;
if (n == Undefined.instance) {
x = context.getRealm().currentTimeMillis();
} else {
x = JSDate.timeClip(JSRuntime.toDouble(n));
if (Double.isNaN(x)) {
throwDateOutOfRange();
}
}
return x;
}
private static void throwDateOutOfRange() throws JSException {
throw Errors.createRangeError("Provided date is not in valid range.");
}
private static final LazyValue<UnmodifiableEconomicMap<DateFormat.Field, String>> fieldToTypeMap = new LazyValue<>(JSDateTimeFormat::initializeFieldToTypeMap);
@SuppressWarnings("deprecation")
private static UnmodifiableEconomicMap<DateFormat.Field, String> initializeFieldToTypeMap() {
CompilerAsserts.neverPartOfCompilation();
EconomicMap<DateFormat.Field, String> map = EconomicMap.create(14);
map.put(DateFormat.Field.AM_PM, "dayPeriod");
map.put(DateFormat.Field.ERA, "era");
map.put(DateFormat.Field.YEAR, "year");
map.put(DateFormat.Field.RELATED_YEAR, "relatedYear");
map.put(DateFormat.Field.MONTH, "month");
map.put(DateFormat.Field.DOW_LOCAL, "weekday");
map.put(DateFormat.Field.DAY_OF_WEEK, "weekday");
map.put(DateFormat.Field.DAY_OF_MONTH, "day");
map.put(DateFormat.Field.HOUR0, "hour");
map.put(DateFormat.Field.HOUR1, "hour");
map.put(DateFormat.Field.HOUR_OF_DAY0, "hour");
map.put(DateFormat.Field.HOUR_OF_DAY1, "hour");
map.put(DateFormat.Field.MINUTE, "minute");
map.put(DateFormat.Field.SECOND, "second");
map.put(DateFormat.Field.TIME_ZONE, "timeZoneName");
return map;
}
private static String fieldToType(DateFormat.Field field) {
return fieldToTypeMap.get().get(field);
}
@TruffleBoundary
public static DynamicObject formatToParts(JSContext context, DynamicObject numberFormatObj, Object n) {
DateFormat dateFormat = getDateFormatProperty(numberFormatObj);
String yearPattern = yearRelatedSubpattern(dateFormat);
int yearPatternIndex = 0;
double x = timeClip(context, n);
List<Object> resultParts = new ArrayList<>();
AttributedCharacterIterator fit = dateFormat.formatToCharacterIterator(x);
String formatted = dateFormat.format(x);
int i = fit.getBeginIndex();
while (i < fit.getEndIndex()) {
fit.setIndex(i);
Map<AttributedCharacterIterator.Attribute, Object> attributes = fit.getAttributes();
Set<AttributedCharacterIterator.Attribute> attKeySet = attributes.keySet();
if (!attKeySet.isEmpty()) {
for (AttributedCharacterIterator.Attribute a : attKeySet) {
if (a instanceof DateFormat.Field) {
String value = formatted.substring(fit.getRunStart(), fit.getRunLimit());
String type;
if (a == DateFormat.Field.YEAR) {
if (yearPatternIndex < yearPattern.length() && yearPattern.charAt(yearPatternIndex) == 'U') {
type = IntlUtil.YEAR_NAME;
} else {
type = IntlUtil.YEAR;
}
yearPatternIndex++;
} else {
type = fieldToType((DateFormat.Field) a);
assert type != null : a;
}
resultParts.add(makePart(context, type, value));
i = fit.getRunLimit();
break;
} else {
throw Errors.shouldNotReachHere();
}
}
} else {
String value = formatted.substring(fit.getRunStart(), fit.getRunLimit());
resultParts.add(makePart(context, IntlUtil.LITERAL, value));
i = fit.getRunLimit();
}
}
return JSArray.createConstant(context, resultParts.toArray());
}
private static String yearRelatedSubpattern(DateFormat dateFormat) {
if (dateFormat instanceof SimpleDateFormat) {
String pattern = ((SimpleDateFormat) dateFormat).toPattern();
StringBuilder sb = new StringBuilder();
boolean quoted = false;
for (char c : pattern.toCharArray()) {
if (c == '\'') {
quoted = !quoted;
} else if (!quoted && (c == 'y' || c == 'Y' || c == 'u' || c == 'U')) {
sb.append(c);
}
}
return sb.toString();
}
return "";
}
private static Object makePart(JSContext context, String type, String value) {
DynamicObject p = JSOrdinary.create(context);
JSObject.set(p, IntlUtil.TYPE, type);
JSObject.set(p, IntlUtil.VALUE, value);
return p;
}
public static class InternalState {
private boolean initialized = false;
private DateFormat dateFormat;
private DynamicObject boundFormatFunction = null;
private String locale;
private String calendar;
private String numberingSystem;
private String weekday;
private String era;
private String year;
private String month;
private String day;
private String hour;
private String minute;
private String second;
private String hourCycle;
private String timeZoneName;
private String timeZone;
private String dateStyle;
private String timeStyle;
DynamicObject toResolvedOptionsObject(JSContext context) {
DynamicObject result = JSOrdinary.create(context);
JSObjectUtil.defineDataProperty(result, IntlUtil.LOCALE, locale, JSAttributes.getDefault());
if (calendar != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.CALENDAR, calendar, JSAttributes.getDefault());
}
JSObjectUtil.defineDataProperty(result, IntlUtil.NUMBERING_SYSTEM, numberingSystem, JSAttributes.getDefault());
if (timeZone != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.TIME_ZONE, timeZone, JSAttributes.getDefault());
}
if (hourCycle != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.HOUR_CYCLE, hourCycle, JSAttributes.getDefault());
boolean hour12 = IntlUtil.H11.equals(hourCycle) || IntlUtil.H12.equals(hourCycle);
JSObjectUtil.defineDataProperty(result, IntlUtil.HOUR12, hour12, JSAttributes.getDefault());
}
if (weekday != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.WEEKDAY, weekday, JSAttributes.getDefault());
}
if (era != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.ERA, era, JSAttributes.getDefault());
}
if (year != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.YEAR, year, JSAttributes.getDefault());
}
if (month != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.MONTH, month, JSAttributes.getDefault());
}
if (day != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.DAY, day, JSAttributes.getDefault());
}
if (hour != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.HOUR, hour, JSAttributes.getDefault());
}
if (minute != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.MINUTE, minute, JSAttributes.getDefault());
}
if (second != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.SECOND, second, JSAttributes.getDefault());
}
if (timeZoneName != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.TIME_ZONE_NAME, timeZoneName, JSAttributes.getDefault());
}
if (dateStyle != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.DATE_STYLE, dateStyle, JSAttributes.getDefault());
}
if (timeStyle != null) {
JSObjectUtil.defineDataProperty(result, IntlUtil.TIME_STYLE, timeStyle, JSAttributes.getDefault());
}
return result;
}
}
@TruffleBoundary
public static DynamicObject resolvedOptions(JSContext context, DynamicObject numberFormatObj) {
InternalState state = getInternalState(numberFormatObj);
return state.toResolvedOptionsObject(context);
}
public static InternalState getInternalState(DynamicObject obj) {
assert isJSDateTimeFormat(obj);
return ((JSDateTimeFormatObject) obj).getInternalState();
}
private static CallTarget createGetFormatCallTarget(JSContext context) {
return Truffle.getRuntime().createCallTarget(new JavaScriptRootNode(context.getLanguage(), null, null) {
private final BranchProfile errorBranch = BranchProfile.create();
@CompilationFinal private ContextReference<JSRealm> realmRef;
@Child private PropertySetNode setBoundObjectNode = PropertySetNode.createSetHidden(BOUND_OBJECT_KEY, context);
@Override
public Object execute(VirtualFrame frame) {
Object[] frameArgs = frame.getArguments();
Object dateTimeFormatObj = JSArguments.getThisObject(frameArgs);
if (isJSDateTimeFormat(dateTimeFormatObj)) {
InternalState state = getInternalState((DynamicObject) dateTimeFormatObj);
if (state == null || !state.initialized) {
errorBranch.enter();
throw Errors.createTypeErrorMethodCalledOnNonObjectOrWrongType("format");
}
if (state.boundFormatFunction == null) {
if (realmRef == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
realmRef = lookupContextReference(JavaScriptLanguage.class);
}
JSFunctionData formatFunctionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.DateTimeFormatFormat, c -> createFormatFunctionData(c));
DynamicObject formatFn = JSFunction.create(realmRef.get(), formatFunctionData);
setBoundObjectNode.setValue(formatFn, dateTimeFormatObj);
state.boundFormatFunction = formatFn;
}
return state.boundFormatFunction;
}
errorBranch.enter();
throw Errors.createTypeErrorTypeXExpected(CLASS_NAME);
}
});
}
private static JSFunctionData createFormatFunctionData(JSContext context) {
return JSFunctionData.createCallOnly(context, Truffle.getRuntime().createCallTarget(new JavaScriptRootNode(context.getLanguage(), null, null) {
@Child private PropertyGetNode getBoundObjectNode = PropertyGetNode.createGetHidden(BOUND_OBJECT_KEY, context);
@Override
public Object execute(VirtualFrame frame) {
Object[] arguments = frame.getArguments();
DynamicObject thisObj = (DynamicObject) getBoundObjectNode.getValue(JSArguments.getFunctionObject(arguments));
assert isJSDateTimeFormat(thisObj);
Object n = JSArguments.getUserArgumentCount(arguments) > 0 ? JSArguments.getUserArgument(arguments, 0) : Undefined.instance;
return format(context, thisObj, n);
}
}), 1, "");
}
private static DynamicObject createFormatFunctionGetter(JSRealm realm, JSContext context) {
JSFunctionData fd = realm.getContext().getOrCreateBuiltinFunctionData(BuiltinFunctionKey.DateTimeFormatGetFormat, (c) -> {
CallTarget ct = createGetFormatCallTarget(context);
return JSFunctionData.create(context, ct, ct, 0, "get format", false, false, false, true);
});
return JSFunction.create(realm, fd);
}
@Override
public DynamicObject getIntrinsicDefaultProto(JSRealm realm) {
return realm.getDateTimeFormatPrototype();
}
}