Skip to content
This repository was archived by the owner on Jun 27, 2020. It is now read-only.

Commit c872704

Browse files
authored
Merge pull request #13 from juandopazo/constructor
Fix Intl.DateTimeFormat throwing when called as a function
2 parents 9f936e1 + 5190d39 commit c872704

File tree

2 files changed

+137
-57
lines changed

2 files changed

+137
-57
lines changed

src/code/polyfill.js

Lines changed: 130 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,50 @@ import {
1010
getZoneNameForLocale
1111
} from './lookup-utill.js';
1212

13+
// Used to get the value of a super function without class syntax
14+
// This is taken (and slightly modified) from Babel's output for
15+
// ES6 class syntax
16+
function _get(object, property, receiver) {
17+
if (object === null)
18+
object = Function.prototype;
19+
let desc = Object.getOwnPropertyDescriptor(object, property);
20+
if (desc === undefined) {
21+
let parent = Object.getPrototypeOf(object);
22+
if (parent === null) {
23+
return undefined;
24+
}
25+
return _get(parent, property, receiver);
26+
}
27+
if ("value" in desc) {
28+
return desc.value;
29+
}
30+
let getter = desc.get;
31+
if (getter === undefined) {
32+
return undefined;
33+
}
34+
return getter.call(receiver);
35+
}
36+
37+
// Also from Babel, minimal subclassing utility function
38+
function _inherits(subClass, superClass) {
39+
if (typeof superClass !== "function" && superClass !== null) {
40+
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
41+
}
42+
subClass.prototype = Object.create(superClass && superClass.prototype, {
43+
constructor: {
44+
value: subClass,
45+
enumerable: false,
46+
writable: true,
47+
configurable: true
48+
}
49+
});
50+
if (superClass) {
51+
Object.setPrototypeOf
52+
? Object.setPrototypeOf(subClass, superClass)
53+
: subClass.__proto__ = superClass;
54+
}
55+
}
56+
1357
/**
1458
* Pollyfill ECMA402 DateTimeFormat
1559
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/format
@@ -26,74 +70,93 @@ export default function polyfill(globalSpace) {
2670
const jsonClone = function(o) {
2771
return JSON.parse(JSON.stringify(o));
2872
};
73+
const _DateTimeFormat = gIntl.DateTimeFormat;
2974

30-
gIntl._DateTimeFormat = gIntl.DateTimeFormat;
75+
gIntl._DateTimeFormat = _DateTimeFormat;
3176

3277
gIntl._DateTimeFormatTimeZone = {
3378
checkTimeZoneSupport: checkTimeZoneSupport
3479
};
3580

36-
class DateTimeFormatPolyfill extends gIntl._DateTimeFormat {
37-
38-
constructor(locale, options) {
39-
const timeZone = (options && options.timeZone) || 'UTC';
40-
41-
if (options === undefined) {
42-
// options is not provided. this means
43-
// we don't need to format arbitrary timezone
44-
super(locale, options);
81+
function DateTimeFormatPolyfill(locale, options) {
82+
if (!(this instanceof DateTimeFormatPolyfill)) {
83+
return new DateTimeFormatPolyfill(locale, options);
84+
}
4585

46-
return;
47-
}
86+
const timeZone = (options && options.timeZone) || 'UTC';
4887

49-
if (checkTimeZoneSupport(timeZone)) {
50-
// native method has support for timezone. no polyfill logic needed.
51-
super(locale, options);
88+
if (options === undefined) {
89+
// options is not provided. this means
90+
// we don't need to format arbitrary timezone
91+
_DateTimeFormat.call(this, locale, options);
5292

53-
return;
54-
}
93+
return;
94+
}
5595

56-
const timeZoneData = gIntl._timeZoneData.get(timeZone);
96+
if (checkTimeZoneSupport(timeZone)) {
97+
// native method has support for timezone. no polyfill logic needed.
98+
_DateTimeFormat.call(this, locale, options);
5799

58-
// check if we have timezone data for this timezone
59-
if (!timeZoneData) {
60-
throw new RangeError(`invalid time zone in DateTimeFormat(): ${timeZone}`);
61-
}
100+
return;
101+
}
62102

63-
// Do a timeshift to UTC to avoid explosion due to unsupprted timezone.
64-
const tsOption = jsonClone(options);
65-
tsOption.timeZone = 'UTC';
66-
super(locale, tsOption);
103+
const timeZoneData = gIntl._timeZoneData.get(timeZone);
67104

68-
const resolvedLocale = super.resolvedOptions().locale;
105+
// check if we have timezone data for this timezone
106+
if (!timeZoneData) {
107+
throw new RangeError(`invalid time zone in DateTimeFormat(): ${timeZone}`);
108+
}
69109

70-
if (options.timeZoneName !== undefined) {
71-
// We need to include timeZoneName in date format.
72-
// Check if we have locale data to able to do that.
73-
if (!(gIntl._localeData.get(resolvedLocale) && // availability of localedata
74-
Intl._metaZoneData.get(timeZone))) { // availability of metaZone for this timeZone
75-
throw new RangeError(`unsupported value "${options.timeZoneName}" for timeZone ${timeZone}. requires locale data for ${resolvedLocale}`);
76-
}
110+
// Do a timeshift to UTC to avoid explosion due to unsupprted timezone.
111+
const tsOption = jsonClone(options);
112+
tsOption.timeZone = 'UTC';
113+
_DateTimeFormat.call(this, locale, tsOption);
114+
115+
const _resolvedOptions = _get(
116+
DateTimeFormatPolyfill.prototype.__proto__ ||
117+
Object.getPrototypeOf(DateTimeFormatPolyfill.prototype),
118+
'resolvedOptions',
119+
this
120+
);
121+
122+
const resolvedLocale = _resolvedOptions.call(this).locale;
123+
124+
if (options.timeZoneName !== undefined) {
125+
// We need to include timeZoneName in date format.
126+
// Check if we have locale data to able to do that.
127+
if (!(gIntl._localeData.get(resolvedLocale) && // availability of localedata
128+
Intl._metaZoneData.get(timeZone))) { // availability of metaZone for this timeZone
129+
throw new RangeError(`unsupported value "${options.timeZoneName}" for timeZone ${timeZone}. requires locale data for ${resolvedLocale}`);
77130
}
78-
79-
// to minimize pollution everything we need to perform polyfill is wrapped under one object.
80-
this._dateTimeFormatPolyfill = {
81-
optionTimeZone: timeZone,
82-
optionTimeZoneName: options.timeZoneName,
83-
timeZoneData: timeZoneData
84-
};
85-
86-
return this;
87131
}
88132

89-
supportedLocalesOf() {
90-
// there is no need to modify behaviour of this function.
91-
return super.supportedLocalesOf();
92-
}
133+
// to minimize pollution everything we need to perform polyfill is wrapped under one object.
134+
this._dateTimeFormatPolyfill = {
135+
optionTimeZone: timeZone,
136+
optionTimeZoneName: options.timeZoneName,
137+
timeZoneData: timeZoneData
138+
};
139+
}
140+
_inherits(DateTimeFormatPolyfill, _DateTimeFormat);
141+
142+
Object.defineProperty(DateTimeFormatPolyfill.prototype, 'format', {
143+
configurable: true,
144+
value: function(date) {
145+
const _format = _get(
146+
DateTimeFormatPolyfill.prototype.__proto__ ||
147+
Object.getPrototypeOf(DateTimeFormatPolyfill.prototype),
148+
'format',
149+
this
150+
);
151+
const _resolvedOptions = _get(
152+
DateTimeFormatPolyfill.prototype.__proto__ ||
153+
Object.getPrototypeOf(DateTimeFormatPolyfill.prototype),
154+
'resolvedOptions',
155+
this
156+
);
93157

94-
format(date) {
95158
if (!this._dateTimeFormatPolyfill) {
96-
return super.format(date);
159+
return _format.call(this, date);
97160
}
98161

99162
if (date === null || date === undefined) {
@@ -108,8 +171,8 @@ export default function polyfill(globalSpace) {
108171
const timeZoneOffsetInfo = getTimeZoneOffsetInfo(polyfill.timeZoneData, date);
109172
const timeZoneOffset = timeZoneOffsetInfo.offset * 60000;
110173
const shiftedDate = new Date(date.getTime() + timeZoneOffset); // We need to format time by offseting it
111-
const shiftedFormat = super.format(shiftedDate); // offseted or shifted format
112-
const resolvedLocale = super.resolvedOptions().locale;
174+
const shiftedFormat = _format.call(this, shiftedDate); // offseted or shifted format
175+
const resolvedLocale = _resolvedOptions.call(this).locale;
113176
const doNeedToReplaceTimeZoneName = (polyfill.optionTimeZoneName !== undefined);
114177

115178
if (doNeedToReplaceTimeZoneName) {
@@ -145,20 +208,31 @@ export default function polyfill(globalSpace) {
145208

146209
return shiftedFormat;
147210
}
211+
});
212+
213+
Object.defineProperty(DateTimeFormatPolyfill.prototype, 'resolvedOptions', {
214+
writable: true,
215+
configurable: true,
216+
value: function() {
217+
const _resolvedOptions = _get(
218+
DateTimeFormatPolyfill.prototype.__proto__ ||
219+
Object.getPrototypeOf(DateTimeFormatPolyfill.prototype),
220+
'resolvedOptions',
221+
this
222+
);
148223

149-
resolvedOptions() {
150224
if (this._dateTimeFormatPolyfill) {
151-
// since we have altered timezone option for super.
225+
// since we have altered timezone option for _DateTimeFormat.
152226
// we need to correct that before returing.
153-
const options = jsonClone(super.resolvedOptions());
227+
const options = jsonClone(_resolvedOptions.call(this));
154228
options.timeZone = this._dateTimeFormatPolyfill.optionTimeZone;
155229

156230
return options;
157231
}
158232

159-
return super.resolvedOptions();
233+
return _resolvedOptions.call(this);
160234
}
161-
}
235+
});
162236

163237
gIntl.DateTimeFormat = DateTimeFormatPolyfill;
164238

test/test-complete.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ describe('Polyfill with complete package', () => {
4040
});
4141
});
4242

43+
describe('called as a function', () => {
44+
it('preserves this ability', () => {
45+
assert.doesNotThrow(() => Intl.DateTimeFormat());
46+
});
47+
});
48+
4349
describe.skip('.formatToParts(date)', () => {
4450
it('polyfilled DateTimeFormat should implement iff native DateTimeFormat implemented it', () => {
4551
const nativeDateTimeFormat = new Intl.DateTimeFormat('en', {
@@ -270,4 +276,4 @@ describe('Polyfill with complete package', () => {
270276
});
271277
});
272278
});
273-
});
279+
});

0 commit comments

Comments
 (0)