指南 · 进阶
版本基线 Luxon 3.x。把 Luxon 用进真实项目:时区与
keepLocalTime、基于 Intl 的本地化、Duration的asvsshiftTo、Interval区间运算、fromFormat严格解析。
一、时区:同一时刻 vs 同一钟点
Luxon 时区基于原生 Intl,能直接用任意 IANA 时区。setZone 默认保持绝对时刻,只换展示时区:
const local = DateTime.local(2024, 5, 15, 12);
local.zoneName; // 'America/New_York'
local.toISO(); // '2024-05-15T12:00:00.000-04:00'
const tokyo = local.setZone("Asia/Tokyo");
tokyo.toISO(); // '2024-05-16T01:00:00.000+09:00'(同一时刻,东京是次日凌晨 1 点)
local.toMillis() === tokyo.toMillis(); // true加 { keepLocalTime: true } 则相反:保持当地钟点不变,底层时间戳改变:
const kept = local.setZone("Asia/Tokyo", { keepLocalTime: true });
kept.toISO(); // '2024-05-15T12:00:00.000+09:00'(钟点还是 12 点,但变成东京的 12 点)
local.toMillis() === kept.toMillis(); // false其它常用:
local.toUTC(); // 转 UTC
DateTime.utc(2024, 5, 15); // 直接在 UTC 构造
local.offset; // -240(分钟)
local.offsetNameShort; // 'EDT'
local.isInDST; // true / false全局默认时区
Settings.defaultZone = "Asia/Tokyo" 让之后所有 DateTime.local() 默认用东京时区;恢复用 "system",UTC 用 "utc"。
二、本地化:一行切语言,全靠 Intl
const dt = DateTime.local(2024, 5, 15, 14, 30);
dt.setLocale("zh").toLocaleString(DateTime.DATE_FULL); // '2024年5月15日'
dt.setLocale("fr").toLocaleString(DateTime.DATETIME_MED); // '15 mai 2024, 14:30'
dt.setLocale("de").toLocaleString(DateTime.DATE_FULL); // '15. Mai 2024'
// 构造时指定
DateTime.fromISO("2024-05-15", { locale: "ja" });
// 全局默认
Settings.defaultLocale = "zh";预设常量本质是 Intl.DateTimeFormat 选项对象,可解构定制;也可直接传自定义 Intl 选项:
dt.toLocaleString({ ...DateTime.DATE_SHORT, weekday: "long" }); // 'Wednesday, 5/15/2024'
dt.toLocaleString({ month: "long", day: "numeric" }); // 'May 15'数字系统也能配(如孟加拉数字、阿拉伯数字):
dt.reconfigure({ locale: "it", numberingSystem: "beng" })
.toLocaleString(DateTime.DATE_FULL); // '१५ maggio २०२४' 形态toFormat / fromFormat 默认 en-US
DateTime.fromFormat 与 DateTime#toFormat 默认回退 en-US(因为常用于对 locale 不敏感的接口场景)。要非英文,必须显式 setLocale 或传 { locale }。这与跟随系统的 toLocaleString 不同。
三、Duration:as 取数字,shiftTo 重分配
import { Duration } from "luxon";
const dur = Duration.fromObject({ hours: 2, minutes: 7 });
dur.as("seconds"); // 7620(数字标量)
dur.shiftTo("hours", "minutes").toObject(); // { hours: 2, minutes: 7 }(Duration)
Duration.fromObject({ minutes: 90 }).shiftTo("hours", "minutes").toObject();
//=> { hours: 1, minutes: 30 }
dur.plus({ minutes: 3 }).toObject(); // { hours: 2, minutes: 10 }
dur.negate().toObject(); // { hours: -2, minutes: -7 }
Duration.fromMillis(90000).rescale().toObject(); // { minutes: 1, seconds: 30 }
dur.toISO(); // 'PT2H7M'记牢区别:as(unit) 返回数字;shiftTo(...units) / normalize() / rescale() 返回新的 Duration。
四、Interval:区间运算
Interval 锚定起止点,长度按需重算,区间运算丰富:
import { Interval, DateTime } from "luxon";
const i1 = Interval.fromDateTimes(
DateTime.fromISO("2024-05-15T09:00"),
DateTime.fromISO("2024-05-15T12:00")
);
const i2 = Interval.fromDateTimes(
DateTime.fromISO("2024-05-15T11:00"),
DateTime.fromISO("2024-05-15T14:00")
);
i1.contains(DateTime.fromISO("2024-05-15T10:00")); // true
i1.overlaps(i2); // true
i1.intersection(i2); // 11:00–12:00
i1.union(i2); // 09:00–14:00
i1.length("hours"); // 3
i1.splitBy({ hours: 1 }).length; // 3(切成 3 个 Interval)
i1.divideEqually(3).length; // 3(等分成 3 段)用 Interval 避免信息损失
diff('months').as('days') 是 casual 近似(1 月≈30 天),可能不等于真实天数。Interval.length('days') 每次查询都基于起止点重算,得到的是真实值——需要精确跨单位时优先用 Interval。
五、fromFormat:严格按 token 解析
人类输入的自定义格式用 fromFormat,它严格匹配(与 Moment 的宽松不同):
DateTime.fromFormat("May 25 1982", "LLLL dd yyyy"); // ok
DateTime.fromFormat("mai 25 1982", "LLLL dd yyyy", { locale: "fr" }); // 本地化解析并非所有 token 都能用于解析:带歧义的时区名 ZZZZ/ZZZZZ、单字母月/星期、含偏移名的宏 token(ttt/FFFF)不可解析。两位数年份按 Settings.twoDigitCutoffYear(默认 60)判世纪:'60'→2060、'61'→1961。
调试解析失败用 fromFormatExplain:
DateTime.fromFormatExplain("Aug 6 1982", "MMMM d yyyy");
// 返回 { input, tokens, regex, matches, result, zone }
// 这里 MMMM 期待完整月名而输入是缩写 → matches 为空,一眼看出不匹配进入 指南 · 专家:有效性模型与 throwOnInvalid、相对时间与 Intl 兼容性、Intl/ICU 环境要求、与 Moment/Day.js/date-fns 的取舍。