Skip to content

Commit 0fc166e

Browse files
julienweemeli
andauthored
fix(bundle): Fix FluentDateTime and FluentNumber primitive conversions (#641)
* Do not require scope argument in FluentNumber & FluentDateTime toString() calls * Add FluentDateTime.p.[Symbol.toPrimitive]() * Add test suite using code from firefox-devtools/profiler@9c8fb55 --------- Co-authored-by: Eemeli Aro <[email protected]>
1 parent 2c2463b commit 0fc166e

File tree

3 files changed

+112
-33
lines changed

3 files changed

+112
-33
lines changed

fluent-bundle/src/types.ts

+26-18
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,16 @@ export class FluentNumber extends FluentType<number> {
108108
/**
109109
* Format this `FluentNumber` to a string.
110110
*/
111-
toString(scope: Scope): string {
112-
try {
113-
const nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts);
114-
return nf.format(this.value);
115-
} catch (err) {
116-
scope.reportError(err);
117-
return this.value.toString(10);
111+
toString(scope?: Scope): string {
112+
if (scope) {
113+
try {
114+
const nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts);
115+
return nf.format(this.value);
116+
} catch (err) {
117+
scope.reportError(err);
118+
}
118119
}
120+
return this.value.toString(10);
119121
}
120122
}
121123

@@ -190,6 +192,10 @@ export class FluentDateTime extends FluentType<number | Date | TemporalObject> {
190192
this.opts = opts;
191193
}
192194

195+
[Symbol.toPrimitive](hint: "number" | "string" | "default"): string | number {
196+
return hint === "string" ? this.toString() : this.toNumber();
197+
}
198+
193199
/**
194200
* Convert this `FluentDateTime` to a number.
195201
* Note that this isn't always possible due to the nature of Temporal objects.
@@ -214,18 +220,20 @@ export class FluentDateTime extends FluentType<number | Date | TemporalObject> {
214220
/**
215221
* Format this `FluentDateTime` to a string.
216222
*/
217-
toString(scope: Scope): string {
218-
try {
219-
const dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts);
220-
return dtf.format(
221-
this.value as Parameters<Intl.DateTimeFormat["format"]>[0]
222-
);
223-
} catch (err) {
224-
scope.reportError(err);
225-
if (typeof this.value === "number" || this.value instanceof Date) {
226-
return new Date(this.value).toISOString();
223+
toString(scope?: Scope): string {
224+
if (scope) {
225+
try {
226+
const dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts);
227+
return dtf.format(
228+
this.value as Parameters<Intl.DateTimeFormat["format"]>[0]
229+
);
230+
} catch (err) {
231+
scope.reportError(err);
227232
}
228-
return this.value.toString();
229233
}
234+
if (typeof this.value === "number" || this.value instanceof Date) {
235+
return new Date(this.value).toISOString();
236+
}
237+
return this.value.toString();
230238
}
231239
}

fluent-bundle/test/functions_runtime_test.js

+81-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
import assert from "assert";
21
import ftl from "@fluent/dedent";
2+
import assert from "assert";
33

4-
import { FluentBundle } from "../esm/bundle.js";
5-
import { FluentResource } from "../esm/resource.js";
6-
import { FluentNumber } from "../esm/types.js";
4+
import {
5+
FluentBundle,
6+
FluentDateTime,
7+
FluentNumber,
8+
FluentResource,
9+
} from "../esm/index.js";
710

811
suite("Runtime-specific functions", function () {
9-
let bundle, errs;
10-
11-
setup(function () {
12-
errs = [];
13-
});
14-
1512
suite("passing into the constructor", function () {
13+
let bundle, errs;
14+
1615
suiteSetup(function () {
1716
bundle = new FluentBundle("en-US", {
1817
useIsolating: false,
@@ -33,6 +32,7 @@ suite("Runtime-specific functions", function () {
3332
}
3433
`)
3534
);
35+
errs = [];
3636
});
3737

3838
test("works for strings", function () {
@@ -56,4 +56,75 @@ suite("Runtime-specific functions", function () {
5656
assert.strictEqual(errs.length, 0);
5757
});
5858
});
59+
60+
suite("firefox-devtools/profiler@9c8fb55", () => {
61+
/** @type {FluentBundle} */
62+
let bundle;
63+
64+
suiteSetup(() => {
65+
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
66+
const ONE_YEAR_IN_MS = 365 * ONE_DAY_IN_MS;
67+
68+
const DATE_FORMATS = {
69+
thisDay: { hour: "numeric", minute: "numeric" },
70+
thisYear: {
71+
month: "short",
72+
day: "numeric",
73+
hour: "numeric",
74+
minute: "numeric",
75+
},
76+
ancient: {
77+
year: "numeric",
78+
month: "short",
79+
day: "numeric",
80+
},
81+
};
82+
83+
const SHORTDATE = args => {
84+
const date = args[0];
85+
const nowTimestamp = Number(new Date("2025-02-15T12:00"));
86+
87+
const timeDifference = nowTimestamp - +date;
88+
if (timeDifference < 0 || timeDifference > ONE_YEAR_IN_MS) {
89+
return new FluentDateTime(date, DATE_FORMATS.ancient);
90+
}
91+
if (timeDifference > ONE_DAY_IN_MS) {
92+
return new FluentDateTime(date, DATE_FORMATS.thisYear);
93+
}
94+
return new FluentDateTime(date, DATE_FORMATS.thisDay);
95+
};
96+
97+
const messages = ftl`\nkey = { SHORTDATE($date) }\n`;
98+
const resource = new FluentResource(messages);
99+
bundle = new FluentBundle("en-US", { functions: { SHORTDATE } });
100+
bundle.addResource(resource);
101+
});
102+
103+
test("works with difference in hours", function () {
104+
const msg = bundle.getMessage("key");
105+
const date = new Date("2025-02-15T10:30");
106+
const errs = [];
107+
const val = bundle.formatPattern(msg.value, { date }, errs);
108+
assert.strictEqual(val, "10:30 AM");
109+
assert.strictEqual(errs.length, 0);
110+
});
111+
112+
test("works with difference in days", function () {
113+
const msg = bundle.getMessage("key");
114+
const date = new Date("2025-02-03T10:30");
115+
const errs = [];
116+
const val = bundle.formatPattern(msg.value, { date }, errs);
117+
assert.strictEqual(val, "Feb 3, 10:30 AM");
118+
assert.strictEqual(errs.length, 0);
119+
});
120+
121+
test("works with difference in years", function () {
122+
const msg = bundle.getMessage("key");
123+
const date = new Date("2023-02-03T10:30");
124+
const errs = [];
125+
const val = bundle.formatPattern(msg.value, { date }, errs);
126+
assert.strictEqual(val, "Feb 3, 2023");
127+
assert.strictEqual(errs.length, 0);
128+
});
129+
});
59130
});

fluent-bundle/test/temporal_test.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ suite("Temporal support", function () {
6666

6767
test("can be converted to a number", function () {
6868
arg = new FluentDateTime(arg);
69-
assert.strictEqual(arg.toNumber(), 0);
69+
assert.strictEqual(+arg, 0);
7070
});
7171
});
7272

@@ -94,7 +94,7 @@ suite("Temporal support", function () {
9494

9595
test("can be converted to a number", function () {
9696
arg = new FluentDateTime(arg);
97-
assert.strictEqual(arg.toNumber(), 0);
97+
assert.strictEqual(+arg, 0);
9898
});
9999
});
100100

@@ -123,7 +123,7 @@ suite("Temporal support", function () {
123123

124124
test("can be converted to a number", function () {
125125
arg = new FluentDateTime(arg);
126-
assert.strictEqual(arg.toNumber(), 0);
126+
assert.strictEqual(+arg, 0);
127127
});
128128
});
129129
}
@@ -171,7 +171,7 @@ suite("Temporal support", function () {
171171

172172
test("cannot be converted to a number", function () {
173173
arg = new FluentDateTime(arg);
174-
assert.throws(() => arg.toNumber(), TypeError);
174+
assert.throws(() => +arg, TypeError);
175175
});
176176
});
177177

@@ -203,7 +203,7 @@ suite("Temporal support", function () {
203203

204204
test("cannot be converted to a number", function () {
205205
arg = new FluentDateTime(arg);
206-
assert.throws(() => arg.toNumber(), TypeError);
206+
assert.throws(() => +arg, TypeError);
207207
});
208208
});
209209
});

0 commit comments

Comments
 (0)