@jsenv/navi 0.29.182 → 0.29.183
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1375,7 +1375,7 @@ const formatCompactNumber = (value, lang) => {
|
|
|
1375
1375
|
* Formats a date as a human-readable day string.
|
|
1376
1376
|
*
|
|
1377
1377
|
* @param {Date} date
|
|
1378
|
-
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"numeric"|{ weekday?: "long"|"short"|"narrow", month?: "long"|"short"|"narrow"|"numeric" }, year?: boolean|"auto", now?: Date }} [options]
|
|
1378
|
+
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"numeric"|{ weekday?: "long"|"short"|"narrow", month?: "long"|"short"|"narrow"|"numeric" }, year?: boolean|"auto", now?: Date, timeZone?: string }} [options]
|
|
1379
1379
|
* A string spells the weekday and the month the same way. An object spells
|
|
1380
1380
|
* them apart, each key defaulting to `"long"`: a narrow card usually wants
|
|
1381
1381
|
* the weekday whole (it is the reading anchor) and the month abbreviated (it
|
|
@@ -1387,6 +1387,12 @@ const formatCompactNumber = (value, lang) => {
|
|
|
1387
1387
|
* ("30/07", the day/month order still following the locale), `"auto"` drops
|
|
1388
1388
|
* it only when the date is in the current year (`now`'s year). The spelled
|
|
1389
1389
|
* formats never write the year, so they ignore it.
|
|
1390
|
+
* @param {string} [options.timeZone]
|
|
1391
|
+
* IANA zone the instant is worded in ("Europe/Paris"); defaults to the
|
|
1392
|
+
* runtime's own zone. The case is a server wording an instant for readers
|
|
1393
|
+
* in a known zone — a game at 00:30 Paris must not be dated the previous
|
|
1394
|
+
* day just because the process clock runs on UTC. `year: "auto"` reads both
|
|
1395
|
+
* years in that zone too.
|
|
1390
1396
|
*
|
|
1391
1397
|
* @example
|
|
1392
1398
|
* formatDay(new Date(), { lang: "fr" }) // "lundi 11 mai" (long, default)
|
|
@@ -1403,17 +1409,19 @@ const formatDay = (
|
|
|
1403
1409
|
format = "long",
|
|
1404
1410
|
year = true,
|
|
1405
1411
|
now = new Date(),
|
|
1412
|
+
timeZone,
|
|
1406
1413
|
} = {},
|
|
1407
1414
|
) => {
|
|
1408
1415
|
if (format === "numeric") {
|
|
1409
1416
|
const yearWritten =
|
|
1410
1417
|
year === "auto"
|
|
1411
|
-
? date
|
|
1418
|
+
? readYear(date, timeZone) !== readYear(now, timeZone)
|
|
1412
1419
|
: year !== false;
|
|
1413
1420
|
return new Intl.DateTimeFormat(lang, {
|
|
1414
1421
|
day: "2-digit",
|
|
1415
1422
|
month: "2-digit",
|
|
1416
1423
|
...(yearWritten ? { year: "numeric" } : {}),
|
|
1424
|
+
timeZone,
|
|
1417
1425
|
}).format(date);
|
|
1418
1426
|
}
|
|
1419
1427
|
const { weekday = "long", month = "long" } =
|
|
@@ -1422,6 +1430,7 @@ const formatDay = (
|
|
|
1422
1430
|
weekday, // "long", "short", or "narrow"
|
|
1423
1431
|
day: "numeric",
|
|
1424
1432
|
month,
|
|
1433
|
+
timeZone,
|
|
1425
1434
|
}).format(date);
|
|
1426
1435
|
};
|
|
1427
1436
|
|
|
@@ -1587,20 +1596,22 @@ const formatDayRelative = (offset, lang) => {
|
|
|
1587
1596
|
|
|
1588
1597
|
const formatMonth = (
|
|
1589
1598
|
date,
|
|
1590
|
-
{ lang = getRuntimeLang(), format = "long" } = {},
|
|
1599
|
+
{ lang = getRuntimeLang(), format = "long", timeZone } = {},
|
|
1591
1600
|
) => {
|
|
1592
1601
|
return new Intl.DateTimeFormat(lang, {
|
|
1593
1602
|
month: format, // "long", "short", or "narrow"
|
|
1594
1603
|
year: "numeric",
|
|
1604
|
+
timeZone,
|
|
1595
1605
|
}).format(date);
|
|
1596
1606
|
};
|
|
1597
1607
|
|
|
1598
1608
|
/**
|
|
1599
1609
|
* Formats a date as "lun. 11 mai, 14:30" (long), "11 mai, 14:30" (short), "11/05, 14:30" (narrow).
|
|
1610
|
+
* `timeZone` words the instant in that IANA zone instead of the runtime's own.
|
|
1600
1611
|
*/
|
|
1601
1612
|
const formatDatetime = (
|
|
1602
1613
|
date,
|
|
1603
|
-
{ lang = getRuntimeLang(), format = "long" } = {},
|
|
1614
|
+
{ lang = getRuntimeLang(), format = "long", timeZone } = {},
|
|
1604
1615
|
) => {
|
|
1605
1616
|
if (format === "long") {
|
|
1606
1617
|
return new Intl.DateTimeFormat(lang, {
|
|
@@ -1609,6 +1620,7 @@ const formatDatetime = (
|
|
|
1609
1620
|
month: "long",
|
|
1610
1621
|
hour: "2-digit",
|
|
1611
1622
|
minute: "2-digit",
|
|
1623
|
+
timeZone,
|
|
1612
1624
|
}).format(date);
|
|
1613
1625
|
}
|
|
1614
1626
|
if (format === "narrow") {
|
|
@@ -1617,6 +1629,7 @@ const formatDatetime = (
|
|
|
1617
1629
|
month: "2-digit",
|
|
1618
1630
|
hour: "2-digit",
|
|
1619
1631
|
minute: "2-digit",
|
|
1632
|
+
timeZone,
|
|
1620
1633
|
}).format(date);
|
|
1621
1634
|
}
|
|
1622
1635
|
// "short": no weekday
|
|
@@ -1625,16 +1638,22 @@ const formatDatetime = (
|
|
|
1625
1638
|
month: "short",
|
|
1626
1639
|
hour: "2-digit",
|
|
1627
1640
|
minute: "2-digit",
|
|
1641
|
+
timeZone,
|
|
1628
1642
|
}).format(date);
|
|
1629
1643
|
};
|
|
1630
1644
|
|
|
1631
1645
|
/**
|
|
1632
1646
|
* Formats a date as "14:30".
|
|
1647
|
+
* `timeZone` words the instant in that IANA zone instead of the runtime's own.
|
|
1633
1648
|
*/
|
|
1634
|
-
const formatTime = (
|
|
1649
|
+
const formatTime = (
|
|
1650
|
+
date,
|
|
1651
|
+
{ lang = getRuntimeLang(), timeZone } = {},
|
|
1652
|
+
) => {
|
|
1635
1653
|
return new Intl.DateTimeFormat(lang, {
|
|
1636
1654
|
hour: "2-digit",
|
|
1637
1655
|
minute: "2-digit",
|
|
1656
|
+
timeZone,
|
|
1638
1657
|
}).format(date);
|
|
1639
1658
|
};
|
|
1640
1659
|
|
|
@@ -1647,11 +1666,16 @@ const formatTime = (date, lang) => {
|
|
|
1647
1666
|
* A Date, a ms timestamp, or an "HH:MM"/"HH:MM:SS" string. Only the clock
|
|
1648
1667
|
* time is read. A nullish value renders the "--:--" placeholder; an
|
|
1649
1668
|
* unparseable one is returned as-is, stringified.
|
|
1650
|
-
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact"|"timestring", pad?: boolean, precision?: "hour"|"minute" }} [options]
|
|
1669
|
+
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact"|"timestring", pad?: boolean, precision?: "hour"|"minute", timeZone?: string }} [options]
|
|
1651
1670
|
* The options `<Time type="time">` takes: `"timestring"` is the clock
|
|
1652
1671
|
* "14:30"; the other formats write the time as a duration-shaped phrase —
|
|
1653
1672
|
* see {@link formatMinuteDuration}'s `clockStyle` for what `pad` and
|
|
1654
1673
|
* `precision` shape in `format="compact"`.
|
|
1674
|
+
* @param {string} [options.timeZone]
|
|
1675
|
+
* IANA zone the instant's clock is read in ("Europe/Paris"); defaults to
|
|
1676
|
+
* the runtime's own zone. Only applies to a Date/timestamp — an
|
|
1677
|
+
* "HH:MM" string is already a wall-clock reading, there is nothing to
|
|
1678
|
+
* move to another zone, so it ignores this.
|
|
1655
1679
|
*
|
|
1656
1680
|
* @example
|
|
1657
1681
|
* formatTimeOfDay(date, { lang: "fr" }) // "14 heures 30" (long, default)
|
|
@@ -1666,6 +1690,7 @@ const formatTimeOfDay = (
|
|
|
1666
1690
|
format = "long",
|
|
1667
1691
|
pad = true,
|
|
1668
1692
|
precision = pad ? "minute" : "hour",
|
|
1693
|
+
timeZone,
|
|
1669
1694
|
} = {},
|
|
1670
1695
|
) => {
|
|
1671
1696
|
if (value === undefined || value === null) {
|
|
@@ -1676,16 +1701,20 @@ const formatTimeOfDay = (
|
|
|
1676
1701
|
if (!date || isNaN(date.getTime())) {
|
|
1677
1702
|
return String(value);
|
|
1678
1703
|
}
|
|
1704
|
+
// An "HH:MM" string is a wall-clock reading, not an instant — re-reading
|
|
1705
|
+
// it in another zone would shift what the caller already spelled out.
|
|
1706
|
+
const zone = typeof value === "string" ? undefined : timeZone;
|
|
1679
1707
|
if (format === "timestring") {
|
|
1680
|
-
return formatTime(date, lang);
|
|
1708
|
+
return formatTime(date, { lang, timeZone: zone });
|
|
1681
1709
|
}
|
|
1682
|
-
const
|
|
1710
|
+
const { hours, minutes } = readClock(date, zone);
|
|
1711
|
+
const totalMinutes = hours * 60 + minutes;
|
|
1683
1712
|
// clockStyle: this is always a time-of-day here, never a duration — keeps
|
|
1684
1713
|
// a zero hour instead of dropping it (midnight would otherwise be
|
|
1685
1714
|
// indistinguishable from an actual 5-minute duration), and in
|
|
1686
1715
|
// format="compact" also zero-pads a single-digit hour so "5h30"/"0h05"
|
|
1687
1716
|
// read as "05h30"/"00h05", closer to a "HH:MM" clock.
|
|
1688
|
-
if (
|
|
1717
|
+
if (hours !== 0 || format !== "long") {
|
|
1689
1718
|
// At midnight, short/narrow/compact keep the "0 h"/"0h" hour part —
|
|
1690
1719
|
// "0 h et 5 min"/"0h 5min"/"00h05" — rather than substituting a
|
|
1691
1720
|
// translated "midnight" word, which would look out of place squeezed
|
|
@@ -1730,7 +1759,7 @@ const formatTimeOfDay = (
|
|
|
1730
1759
|
const parts = new Intl.DurationFormat(lang, {
|
|
1731
1760
|
style: "long",
|
|
1732
1761
|
hoursDisplay: "always",
|
|
1733
|
-
}).formatToParts({ hours: 0, minutes
|
|
1762
|
+
}).formatToParts({ hours: 0, minutes });
|
|
1734
1763
|
let hourGroupReplaced = false;
|
|
1735
1764
|
return parts
|
|
1736
1765
|
.map((part) => {
|
|
@@ -1758,11 +1787,12 @@ const formatTimeOfDay = (
|
|
|
1758
1787
|
* @param {Date|number|string} to
|
|
1759
1788
|
* Each bound accepts what {@link formatTimeOfDay} accepts; a nullish bound
|
|
1760
1789
|
* renders its "--:--" placeholder.
|
|
1761
|
-
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact"|"timestring", pad?: boolean, precision?: "hour"|"minute", separator?: string }} [options]
|
|
1790
|
+
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"compact"|"timestring", pad?: boolean, precision?: "hour"|"minute", separator?: string, timeZone?: string }} [options]
|
|
1762
1791
|
* `precision` writes both bounds at this precision instead of the one the
|
|
1763
1792
|
* pair calls for. `separator` defaults to the `"time.range_separator"` navi
|
|
1764
1793
|
* text (an en dash), tightened against both bounds in `format="compact"` —
|
|
1765
|
-
* where the span is one short token — and spaced out otherwise.
|
|
1794
|
+
* where the span is one short token — and spaced out otherwise. `timeZone`
|
|
1795
|
+
* reads both bounds' clocks in that IANA zone — see {@link formatTimeOfDay}.
|
|
1766
1796
|
*
|
|
1767
1797
|
* @example
|
|
1768
1798
|
* formatTimeRange("08:00", "10:00", { lang: "fr", format: "compact", pad: false }) // "8h–10h"
|
|
@@ -1775,11 +1805,12 @@ const formatTimeRange = (
|
|
|
1775
1805
|
lang = getRuntimeLang(),
|
|
1776
1806
|
format = "long",
|
|
1777
1807
|
pad = true,
|
|
1778
|
-
|
|
1808
|
+
timeZone,
|
|
1809
|
+
precision = resolveTimeRangePrecision(from, to, { format, pad, timeZone }),
|
|
1779
1810
|
separator = naviI18n("time.range_separator", undefined, { lang }),
|
|
1780
1811
|
} = {},
|
|
1781
1812
|
) => {
|
|
1782
|
-
const boundOptions = { lang, format, pad, precision };
|
|
1813
|
+
const boundOptions = { lang, format, pad, precision, timeZone };
|
|
1783
1814
|
const fromText = formatTimeOfDay(from, boundOptions);
|
|
1784
1815
|
const toText = formatTimeOfDay(to, boundOptions);
|
|
1785
1816
|
if (format === "compact") {
|
|
@@ -1793,13 +1824,23 @@ const formatTimeRange = (
|
|
|
1793
1824
|
// of them does. Only ever a question for the unpadded compact clock — the
|
|
1794
1825
|
// padded one always writes "08h00", and the spelled-out formats name their
|
|
1795
1826
|
// units, leaving no shape for the eye to trip on.
|
|
1796
|
-
const resolveTimeRangePrecision = (
|
|
1827
|
+
const resolveTimeRangePrecision = (
|
|
1828
|
+
from,
|
|
1829
|
+
to,
|
|
1830
|
+
{ format, pad, timeZone },
|
|
1831
|
+
) => {
|
|
1797
1832
|
if (pad || format !== "compact") {
|
|
1798
1833
|
return "minute";
|
|
1799
1834
|
}
|
|
1800
1835
|
const hasMinutes = (value) => {
|
|
1801
1836
|
const date = toTimeOfDay(value);
|
|
1802
|
-
|
|
1837
|
+
if (!date || isNaN(date.getTime())) {
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
// Same rule as formatTimeOfDay: a string is a wall-clock reading, only
|
|
1841
|
+
// an instant is re-read in `timeZone`.
|
|
1842
|
+
const zone = typeof value === "string" ? undefined : timeZone;
|
|
1843
|
+
return readClock(date, zone).minutes !== 0;
|
|
1803
1844
|
};
|
|
1804
1845
|
return hasMinutes(from) || hasMinutes(to) ? "minute" : "hour";
|
|
1805
1846
|
};
|
|
@@ -2438,5 +2479,42 @@ const toTimeOfDay = (value) => {
|
|
|
2438
2479
|
});
|
|
2439
2480
|
};
|
|
2440
2481
|
|
|
2482
|
+
// Reads the wall-clock hour/minute of an instant — in the runtime's own zone
|
|
2483
|
+
// by default, in `timeZone` when given. A Date only carries local getters, so
|
|
2484
|
+
// another zone's clock has to come out of Intl parts (hourCycle h23 so
|
|
2485
|
+
// midnight reads hour 0, never 24).
|
|
2486
|
+
const readClock = (date, timeZone) => {
|
|
2487
|
+
if (!timeZone) {
|
|
2488
|
+
return { hours: date.getHours(), minutes: date.getMinutes() };
|
|
2489
|
+
}
|
|
2490
|
+
const parts = new Intl.DateTimeFormat("en", {
|
|
2491
|
+
timeZone,
|
|
2492
|
+
hour: "numeric",
|
|
2493
|
+
minute: "numeric",
|
|
2494
|
+
hourCycle: "h23",
|
|
2495
|
+
}).formatToParts(date);
|
|
2496
|
+
let hours = 0;
|
|
2497
|
+
let minutes = 0;
|
|
2498
|
+
for (const part of parts) {
|
|
2499
|
+
if (part.type === "hour") {
|
|
2500
|
+
hours = Number(part.value);
|
|
2501
|
+
} else if (part.type === "minute") {
|
|
2502
|
+
minutes = Number(part.value);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
return { hours, minutes };
|
|
2506
|
+
};
|
|
2507
|
+
|
|
2508
|
+
// Reads the calendar year of an instant in `timeZone` (the runtime's own zone
|
|
2509
|
+
// when not given) — what formatDay's `year: "auto"` compares.
|
|
2510
|
+
const readYear = (date, timeZone) => {
|
|
2511
|
+
if (!timeZone) {
|
|
2512
|
+
return date.getFullYear();
|
|
2513
|
+
}
|
|
2514
|
+
return Number(
|
|
2515
|
+
new Intl.DateTimeFormat("en", { timeZone, year: "numeric" }).format(date),
|
|
2516
|
+
);
|
|
2517
|
+
};
|
|
2518
|
+
|
|
2441
2519
|
export { formatDatePlaceholder, formatDatetime, formatDatetimePlaceholder, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatMonthPlaceholder, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, formatWeekPlaceholder, getRelativeDay, resolveTimeRangePrecision, toDate, toTimeOfDay };
|
|
2442
2520
|
//# sourceMappingURL=jsenv_navi_format_time.js.map
|