date-fns 葡萄牙语(pt)Locale 全解析:format/parse 令牌快照与底层实现指南
【免费下载链接】date-fns⏳ Modern JavaScript date utility library ⌛️项目地址: https://gitcode.com/gh_mirrors/da/date-fns
导读
本指南以 date-fns 仓库中 葡萄牙语 locale 快照文档 为骨架,系统讲解ptlocale 在format、parse、formatDistance、formatDistanceStrict、formatRelative、formatDuration六大 API 下的完整行为。你将掌握葡萄牙语序数(1º、2º)、月份/星期缩写、灵活时段(da manhã、da madrugada)等本地化令牌的输入输出规则,并深入理解这些输出是如何由 localize/index.ts、match/index.ts、formatLong/index.ts 等源码实现的。
一、pt Locale 的基本信息与加载方式
葡萄牙语 locale 在仓库中的完整实现位于pkgs/core/src/locale/pt/,通过 index.ts 导出:
export const pt: Locale = { code: "pt", formatDistance: formatDistance, formatLong: formatLong, formatRelative: formatRelative, localize: localize, match: match, options: { weekStartsOn: 0 /* Sunday */, firstWeekContainsDate: 4, }, };code: "pt":符合 ISO 639-1 的语言代码(巴西葡萄牙语为独立的pt-BRlocale,位于 pkgs/core/src/locale/pt-BR 同目录结构)。options.weekStartsOn: 0:一周从星期日开始,这会直接影响formatRelative中 "lastWeek"/"nextWeek" 的判定边界。options.firstWeekContainsDate: 4:一年中第一个包含 1 月 4 日的周为第 1 周(这是葡萄牙语地区常用的周编号约定,与部分国家使用的 1 月 1 日规则不同)。这两项配置在 format/index.ts 中通过options?.locale?.options?.weekStartsOn等取值链路被实际消费。
加载使用方式:pt已被自动索引导出(见 pkgs/core/src/locale/index.ts 的export * from "./pt/index.ts"),可直接从date-fns/locale导入:
import { pt } from "date-fns/locale"; import { format, parse, formatDistance } from "date-fns"; format(new Date(1987, 1, 11), "PPPP", { locale: pt }); // => "quarta-feira, 11 de fevereiro de 1987"快照表格中的时区基线:所有示例日期均以
T12:13:14.015Z等 UTC 时间戳给出,测试在固定时区下运行,format与parse结果均为确定性输出,可用于回归验证。
二、format 与 parse 令牌快照:葡萄牙语序数与本地化文本
快照文档的核心是一张覆盖全部令牌形态的对照表,其输出由 localize/index.ts 定义,解析侧由 match/index.ts 配合实现。以下按类别逐项解读。
1. 序数令牌(带o后缀)
葡萄牙语的序数规则非常统一:任何数字后面直接追加º(阳性序数符号)。这一规则在源码中由ordinalNumber函数实现:
const ordinalNumber: LocalizeFn<number> = (dirtyNumber, _options) => { const number = Number(dirtyNumber); return number + "º"; };因此所有*o令牌(年份、季度、月份、星期、时、分、秒、日)的 format 结果都形如1º、11º、42º、365º:
| 令牌 | 含义 | format 示例 | parse 回读 |
|---|---|---|---|
yo | 日历年份 | 1987º/5º | 1987-01-01/0005-01-01 |
Yo | 本地周编号年份 | 1987º/4º | 1987-01-04/0004-01-04 |
Qo/qo | 季度(格式化/独立) | 1º/2º | 对应季度首日 |
Mo/Lo | 月份(格式化/独立) | 1º~12º | 对应月份 1 日 |
wo/Io | 本地周/ISO 周 | 1º/49º | 周起始日 |
do | 日 | 1º/11º/28º | 对应日期 |
Do | 年中的日 | 42º/365º | 对应日期 |
ho/Ho/Ko/ko | 小时(1-12/0-23/0-11/1-24) | 11º/23º | 对应时刻 |
mo/so | 分 / 秒 | 1º/55º | 对应时刻 |
eo/io/co | 本地/ISO/独立星期几 | 2º(周一)、6º(周五) | 对应日期 |
注意Yo与yo的差异:以0005-01-01为例,Yo输出4º且 parse 回读为0004-01-04——这是因为本地周编号年份按firstWeekContainsDate: 4规则归入上一年度第 1 周。
2. 月份:三种宽度 × 两种上下文
月份数据定义于 localize/index.ts:
const monthValues = { narrow: ["j", "f", "m", "a", "m", "j", "j", "a", "s", "o", "n", "d"], abbreviated: ["jan", "fev", "mar", "abr", "mai", "jun", "jul", "ago", "set", "out", "nov", "dez"], wide: ["janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"], };| 令牌 | 宽度 | format 示例 |
|---|---|---|
MMMM/LLLL | wide | janeiro、fevereiro、março、abril……dezembro |
MMM/LLL | abbreviated | jan、fev、mar、abr、mai、jun、jul、ago、set、out、nov、dez |
MMMMM/LLLLL | narrow | j、f、m、a、m、j、j、a、s、o、n、d |
窄宽度(narrow)的重要陷阱:由于多个月份首字母相同,MMMMM/LLLLL的 format 结果存在歧义——如2019-03-11与2019-05-11都输出m,2019-06-11、2019-07-11都输出j。因此快照中这些行的parse 结果不可靠(例如m被解析回2019-03-01、j被解析回2019-01-01),实战中不要用窄宽度令牌做解析。对应地,match/index.ts 的parseMonthPatterns中narrow与any两组正则正是这种有损匹配的来源(any用^ja、^mar、^ab等更长的前缀消歧,narrow只能靠首字母)。
3. 星期:格式化与独立两种上下文
localize/index.ts 定义了星期数据:
const dayValues = { narrow: ["d", "s", "t", "q", "q", "s", "s"], short: ["dom", "seg", "ter", "qua", "qui", "sex", "sáb"], abbreviated: ["dom", "seg", "ter", "qua", "qui", "sex", "sáb"], wide: ["domingo", "segunda-feira", "terça-feira", "quarta-feira", "quinta-feira", "sexta-feira", "sábado"], };| 令牌 | 宽度 | format 示例(以周一2019-02-11为例) |
|---|---|---|
EEEE/iiii/eeee/cccc | wide | segunda-feira |
E/EE/EEE/iii/eee/ccc | abbreviated | seg |
EEEEE/iiiii/eeeee/ccccc | narrow | s |
EEEEEE/iiiiii/eeeeee/cccccc | short | seg |
io/eo/co | 序数(ISO/本地/独立) | 1º(周一)/2º(本地周) |
关键差异点:
E*(格式化)、i*(ISO 星期)、e*(本地星期)、c*(独立星期)四套令牌共用同一份本地化文本,区别仅在编号体系与上下文。- 窄宽度
EEEEE同样存在歧义:周一与周五都输出s,快照显示其 parse 回读都落在2019-02-11(周一),说明窄宽度星期不应作为唯一解析依据。 - 本地星期序号
eo中周一为2º(因为weekStartsOn: 0星期日为 1),ISO 序号io中周一为1º。
4. 时段(Day Period):AM/PM、中午/午夜与灵活时段
葡萄牙语对一天时段的划分是本地化最丰富的部分,定义于 localize/index.ts:
a/b系列(AM/PM、AM/PM+noon/midnight):AM/PM或小写am/pm;aaaaa/bbbbb(narrow)只输出AM/PM,且快照中对应parse 结果为Invalid Date——因为窄宽度无法被 match/index.ts 的matchDayPeriodPatterns唯一识别。B系列(灵活时段):这是葡萄牙语特色,按一天 24 小时划分:
| 时段 | 输入时刻 | format 结果 | parse 回读基准时刻 |
|---|---|---|---|
da manhã(早晨) | 11:13 | da manhã | 04:00 |
da tarde(下午) | 14:13 | da tarde | 12:00 |
da noite(晚上) | 19:13 | da noite | 17:00 |
da madrugada(凌晨) | 02:13 | da madrugada | 00:00 |
注意B系列是格式化上下文(formattingDayPeriodValues),介词da与 morning/afternoon/evening/night 的阳阴性搭配由该映射表固定;独立上下文(standalone)则使用不带介词的manhã、tarde、noite、madrugada。
5. 本地化长日期/长时间令牌(P、p 系列)
formatLong/index.ts 定义了这些组合令牌的底层格式:
const dateFormats = { full: "EEEE, d 'de' MMMM 'de' y", long: "d 'de' MMMM 'de' y", medium: "d 'de' MMM 'de' y", short: "dd/MM/y", }; const timeFormats = { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }; const dateTimeFormats = { full: "{{date}} 'às' {{time}}", long: "{{date}} 'às' {{time}}", medium: "{{date}}, {{time}}", short: "{{date}}, {{time}}", };| 令牌 | format 示例(1987-02-11) | 说明 |
|---|---|---|
P | 11/02/1987 | dd/MM/y,日/月/年顺序 |
PP | 11 de fev de 1987 | 短月份 +de连接词 |
PPP | 11 de fevereiro de 1987 | 全月名 +de连接词 |
PPPP | quarta-feira, 11 de fevereiro de 1987 | 星期 + 逗号 + 全月名 |
p | 12:13 | HH:mm |
pp | 12:13:14 | HH:mm:ss |
ppp/pppp | 12:13:14 GMT+0/12:13:14 GMT+00:00 | 含时区名,快照中parse 均为Errored |
Pp/PPpp/PPPppp/PPPPpppp | 日期 + 逗号/às+ 时间 | 组合令牌 |
两个实战要点:
de与às是字面量(用单引号包裹),PPPppp/PPPPpppp组合中日期与时间用às(意为 "at")连接,这是葡萄牙语自然语言的固定表达。ppp/pppp及其组合的 parse 结果在快照中全部标记为Errored——当timeFormats.full包含zzzz/z(时区名)时,parse 无法解析GMT+0这类时区表示。实战中解析长时间令牌时避免依赖时区名。
三、formatDistance:口语化距离表达
快照## formatDistance节设定基准时刻为2000 年 1 月 1 日 00:00,覆盖过去与未来两个方向。其文案与逻辑实现在 formatDistance/index.ts:
const formatDistanceLocale = { lessThanXSeconds: { one: "menos de um segundo", other: "menos de {{count}} segundos" }, xSeconds: { one: "1 segundo", other: "{{count}} segundos" }, halfAMinute: "meio minuto", lessThanXMinutes: { one: "menos de um minuto", other: "menos de {{count}} minutos" }, xMinutes: { one: "1 minuto", other: "{{count}} minutos" }, aboutXHours: { one: "aproximadamente 1 hora", other: "aproximadamente {{count}} horas" }, xHours: { one: "1 hora", other: "{{count}} horas" }, xDays: { one: "1 dia", other: "{{count}} dias" }, aboutXWeeks: { one: "aproximadamente 1 semana", other: "aproximadamente {{count}} semanas" }, xWeeks: { one: "1 semana", other: "{{count}} semanas" }, aboutXMonths: { one: "aproximadamente 1 mês", other: "aproximadamente {{count}} meses" }, xMonths: { one: "1 mês", other: "{{count}} meses" }, aboutXYears: { one: "aproximadamente 1 ano", other: "aproximadamente {{count}} anos" }, xYears: { one: "1 ano", other: "{{count}} anos" }, overXYears: { one: "mais de 1 ano", other: "mais de {{count}} anos" }, almostXYears: { one: "quase 1 ano", other: "quase {{count}} anos" }, };单复数规则:count === 1用单数形式(1 ano、1 mês、1 dia),否则用复数(anos、meses、dias)。{{count}}由函数在运行时替换为数字:
export const formatDistance: FormatDistanceFn = (token, count, options) => { let result; const tokenValue = formatDistanceLocale[token]; if (typeof tokenValue === "string") { result = tokenValue; } else if (count === 1) { result = tokenValue.one; } else { result = tokenValue.other.replace("{{count}}", String(count)); } if (options?.addSuffix) { if (options.comparison && options.comparison > 0) { return "daqui a " + result; // 未来 } else { return "há " + result; // 过去 } } return result; };快照中的行为可归纳为:
- 未来方向(
addSuffix: true且comparison > 0):前缀daqui a("in …"),如daqui a 6 anos、daqui a 30 minutos。 - 过去方向:前缀
há("… ago"),如há 2 dias、há aproximadamente 1 hora。 includeSeconds: true:当差值小于 1 分钟时细化秒级表达——menos de 5 segundos、menos de 10 segundos、menos de 20 segundos、meio minuto(半分钟)。- 约数词:
aproximadamente(约)、mais de(超过)、quase(几乎)分别对应aboutX*、overXYears、almostXYears令牌族。注意快照中2001-06-01输出mais de 1 ano(over),而2001-02-01输出aproximadamente 1 ano(about),这是formatDistance内部按日差计算后选取不同令牌的结果。
四、formatDistanceStrict:精确距离与强制单位
## formatDistanceStrict节输出的是不含约数的精确值,并展示unit: "hour"强制单位的效果:
| 输入日期 | 默认结果 | addSuffix: true | 强制hour单位 |
|---|---|---|---|
| 2006-01-01 | 6 anos | daqui a 6 anos | 52608 horas |
| 2000-01-02 | 1 dia | daqui a 1 dia | 24 horas |
| 2000-01-01T00:45 | 45 minutos | daqui a 45 minutos | 1 hora |
| 2000-01-01T00:00:00 | 0 segundos | há 0 segundos | 0 horas |
| 1999-12-31T23:59:55 | 5 segundos | há 5 segundos | 0 horas |
与formatDistance的要点差异:
- 无约数:差值 45 分钟直接输出
45 minutos(formatDistance会四舍五入为aproximadamente 1 hora)。 - 默认单位选择按跨度:月/年/日/时/分/秒精确换算(
formatDistanceStrict内部实现见 pkgs/core/src/formatDistanceStrict/index.ts)。 - 强制单位时(如
unit: "hour")所有差值统一折算成小时取整:6 anos ≈ 52608 horas(含闰年 2004 的 366 天),45 minutos折算后按取整规则输出1 hora,不足 1 小时的15 minutos输出0 horas。 - 后缀规则与 formatDistance 一致:未来加
daqui a,过去加há,如há 0 segundos。
五、formatRelative:相对日期表达
## formatRelative节设定基准时刻为 2000-01-01 00:00,输出由 formatRelative/index.ts 驱动:
const formatRelativeLocale = { lastWeek: (date) => { const weekday = date.getDay(); const last = weekday === 0 || weekday === 6 ? "último" : "última"; return "'" + last + "' eeee 'às' p"; }, yesterday: "'ontem às' p", today: "'hoje às' p", tomorrow: "'amanhã às' p", nextWeek: "eeee 'às' p", other: "P", };快照对应关系:
| 输入日期 | 相对关系 | 结果 |
|---|---|---|
| 2000-01-10 | 超出下周 | 10/01/2000(other→P) |
| 2000-01-05 | 下周内 | quarta-feira às 00:00 |
| 2000-01-02 | 明天 | amanhã às 00:00 |
| 2000-01-01 | 今天 | hoje às 00:00 |
| 1999-12-31 | 昨天 | ontem às 00:00 |
| 1999-12-27 | 上周内 | última segunda-feira às 00:00 |
| 1999-12-21 | 超出上周 | 21/12/1999(other→P) |
两个语言细节值得注意:
lastWeek是函数而非字符串:因为葡萄牙语的 "上星期几" 需要按名词性别变体——星期日(domingo)与星期六(sábado)是阳性名词,用último;其余星期几是阴性名词(segunda-feira等以-feira结尾),用última。这正是快照中出现última segunda-feira的原因。nextWeek不加定冠词:直接输出quarta-feira às 00:00。
六、formatDuration:时长格式化
## formatDuration节验证了Duration对象到葡萄牙语字符串的映射,底层复用 formatDistance/index.ts 的xYears、xMonths、xWeeks、xDays、xHours、xMinutes、xSeconds令牌:
| Duration | 结果 |
|---|---|
{years: 0}/{years: 1}/{years: 2} | 0 anos/1 ano/2 anos |
{months: 0}/{months: 1}/{months: 2} | 0 meses/1 mês/2 meses |
{weeks: 0}/{weeks: 1}/{weeks: 2} | 0 semanas/1 semana/2 semanas |
{days: 0}/{days: 1}/{days: 2} | 0 dias/1 dia/2 dias |
{hours: 0}/{hours: 1}/{hours: 2} | 0 horas/1 hora/2 horas |
{minutes: 0}/{minutes: 1}/{minutes: 2} | 0 minutos/1 minuto/2 minutos |
{seconds: 0}/{seconds: 1}/{seconds: 2} | 0 segundos/1 segundo/2 segundos |
单复数规则贯穿始终:数量为 1 时用单数(1 mês、1 dia、1 hora),其余(含 0)用复数(meses、dias、horas)。注意0与大于 1 的数字一样走复数分支。
七、源码佐证:pt 的数据结构、正则匹配与整体调用链
1. 本地化数据的两种用途(format 与 parse 各司其职)
ptlocale 内部以五个模块协作(见 pkgs/core/src/locale/pt/index.ts 的导入):
localize(localize/index.ts):format 侧,负责把数值(月份序号、星期序号等)翻译成葡萄牙语文本,所有值按narrow/abbreviated/wide三种宽度组织,quarter还带有argumentCallback: (quarter) => quarter - 1做数组下标偏移。match(match/index.ts):parse 侧,负责把葡萄牙语文本匹配回数值。每类单元都提供match*Patterns(从文本到正则)与parse*Patterns(从正则到数值)两组模式:
const matchMonthPatterns = { narrow: /^[jfmasond]/i, abbreviated: /^(jan|fev|mar|abr|mai|jun|jul|ago|set|out|nov|dez)/i, wide: /^(janeiro|fevereiro|março|abril|maio|junho|julho|agosto|setembro|outubro|novembro|dezembro)/i, };formatLong(formatLong/index.ts):定义P/p系列组合令牌的底层格式串。formatDistance与formatRelative:分别支撑口语化距离与相对时间表达。
所有函数都通过通用构建器(buildLocalizeFn、buildMatchFn、buildMatchPatternFn、buildFormatLongFn)生成,保证与 pkgs/core/src/locale/types.ts 中定义的Locale接口一致——该接口要求每个 locale 必须实现code、formatDistance、formatRelative、localize、formatLong、match六个成员。
2. 配置项如何影响行为
pt的options(weekStartsOn: 0、firstWeekContainsDate: 4)在核心函数中被读取。以 format/index.ts 为例:
options?.locale?.options?.firstWeekContainsDate ?? defaultOptions.locale?.options?.firstWeekContainsDate ?? /* 默认值 */, options?.locale?.options?.weekStartsOn ?? defaultOptions.locale?.options?.weekStartsOn ?? /* 默认值 */,这意味着:
weekStartsOn: 0决定了formatRelative中今天/昨天/上周/下周的边界划分;firstWeekContainsDate: 4决定了Yo、wo等周编号令牌的分界(如0005-01-01的Yo归入0004年第 1 周)。
如果显式传入的options中带weekStartsOn等覆盖值,会优先于 locale 内置配置(源码取值顺序为:显式 options → locale options → 默认值)。
3. 与 pt-BR 的并列关系
仓库中葡萄牙语存在两个独立 locale:pt(葡萄牙本土)与pt-BR(巴西),二者目录并列于 pkgs/core/src/locale。快照所展示的pt使用1º序数、da manhã时段表达与PT地区周规则;若目标用户为巴西,应选择pt-BR,两者在拼写(如某些词汇差异)与周起始约定上可能不同,选错 locale 会直接影响format与formatDistance的输出。
八、实战建议与快照使用场景
- 做回归测试基准:
snapshot.md是 date-fns 为每个 locale 自动生成的确定性快照(仓库中pkgs/core/src/locale/下每个语言目录均有同名文件)。升级依赖或修改 locale 源码后,可用快照表逐行比对 format/parse 输出是否漂移。 - 解析输入规范化:用户输入的葡萄牙语日期文本(如
11 de fevereiro de 1987)可直接交给parse,其内部会调用match模块的正则按wide宽度匹配全月名。避免窄宽度:MMMMM/EEEEE的多义性(多个月份/星期共享首字母)会导致回读错误,而aaaaa/bbbbb的 narrow AM/PM 甚至直接返回Invalid Date。 - 时区名令牌慎用于 parse:
ppp/pppp(含GMT+0)在快照中 parse 均为Errored;如需可解析的完整时间,使用pp(HH:mm:ss)或自行附加时区处理。 - 序数风格统一:葡萄牙语所有序数统一追加
º(阳性质),ordinalNumber实现简单直接,不存在英语st/nd/rd/th那种按数字结尾变化的复杂性。 - 距离表达的语义选择:需要口语化约数用
formatDistance(会产出aproximadamente/mais de/quase),需要精确数值用formatDistanceStrict(可按unit强制折算为单一单位)。
总结
ptlocale 快照文档(pkgs/core/src/locale/pt/snapshot.md)完整覆盖了 date-fns 六大格式化 API 在葡萄牙语下的行为:*o序数统一追加º、月份/星期三档宽度、da manhã/da madrugada等灵活时段、de/às连接词的长日期表达、daqui a/há后缀的距离句式,以及último/última按星期名词性别变化的相对时间。这些输出与 localize、match、formatLong、formatDistance、formatRelative 五个实现模块一一对应,并受weekStartsOn: 0、firstWeekContainsDate: 4两个选项约束。理解快照与源码的映射关系,既能让你准确预测任何日期在ptlocale 下的格式化结果,也能在遇到 parse 回读异常(窄宽度歧义、时区名无法解析)时快速定位根因。
【免费下载链接】date-fns⏳ Modern JavaScript date utility library ⌛️项目地址: https://gitcode.com/gh_mirrors/da/date-fns
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考