compromise-dates 插件全解析用自然语言解析日期、时间与时长【免费下载链接】compromisemodest natural-language processing项目地址: https://gitcode.com/gh_mirrors/co/compromisecompromise-dates 是 compromise 生态中最具实用价值的插件之一它把把一段人话变成结构化时间数据这件事做到了开箱即用无论是the second monday of february、2 years, 4 months, and 5 days ago还是GMT9都能解析出明确的start/endISO 时间戳。本指南以 plugins/dates/README.md 为核心结合插件源码与测试完整讲解其能力边界、API、配置项与底层解析管线读完即可在自己的前端或后端项目里接入自然语言日期解析。快速开始安装与加载compromise-dates 是独立发布的 npm 包与 compromise 主库分开安装npm install compromise compromise-dates在代码中先加载 compromise再通过nlp.plugin()注册日期插件import nlp from compromise import datePlugin from compromise-dates nlp.plugin(datePlugin) let doc nlp(the second monday of february) doc.dates().get()[0] /* { start: 2021-02-08T00:00:00.000Z, end: 2021-02-08T23:59:59.999Z} */从 package.json 可以看到插件声明了compromise 14.2.0作为 peerDependency运行时依赖spacetime时区与夏令时计算与spacetime-holiday节假日推算并提供 CJS/ESM/UMD 三种构建产物builds 目录浏览器可直接引入compromise-dates.min.js。它能解析什么能力全景插件覆盖了从最直白的书面日期到口语化时间表达的大多数场景。下表完整列出了 README 中收录的解析能力Start/End两列表示解析结果的时间范围明确日期explicit dates输入说明StartEndmarch 2nd月日March 2, 12:00amMarch 2, 11:59pm2 march日月——tues march 2星期月日——march the second自然语言数字——on the 2nd隐含月份——tuesday the 2nd日期推算——数字日期numeric dates输入说明StartEnd2020/03/02ISO 格式——2020-03-02连字符 ISO——03-02-2020英式格式——03/02月/日——2020.08.13替代 ISO——命名日期named dates输入说明StartEndtoday———tomorrow———christmas eve日历节假日Dec 24, 12:00amDec 24, 11:59pmeaster天文节假日依年份而定—q1财务季度Jan 1, 12:00amMar 31, 11:59pm时间times输入说明StartEnd2pm———2:12pm———2:12———02:12:00奇怪 ISO 时间——two oclock文字形式——before 1时间前——noon———at night非正式时段——in the morning———tomorrow evening———时区timezones输入说明StartEndeastern time非正式时区——est时区缩写——peru time地区时区——GMT9UTC/GMT 偏移——-4h小时偏移——Canada/EasternIANA 时区码——相对时长relative durations输入说明StartEndthis march———this week———this sunday———next april———this past year———second week of march第 N 周——last weekend of march———last spring季节——the saturday after next后推——推后日期punted dates输入说明StartEndin seven weeks现在时长——two days after june 6th日期时长——2 weeks from now———2 weeks after june———2 years, 4 months, and 5 days ago复杂时长——a week and a half before文字数字——a week friday习语格式——开始/结束start/end输入说明StartEndend of the week倾向结尾——start of next year倾向开头——middle of q2 last year粗略居中——日期区间date-ranges输入说明StartEndbetween june and july显式区间——from today to next halloween———aug 1 - aug 31破折号区间——22-23 February———today to next friday———during june———aug to june 1999共享区间信息——before [2019]截至某日期——by march———after february日期到无限——重复区间repeating-intervals输入说明StartEndany wednesdayN 次重复日期——any day in June区间内重复日期June 1 ..... June 30any wednesday this week———weekends in July更复杂区间——every weekday until February截至某日期的区间——API 详解.dates()主入口doc.dates()用于查找所有日期短语返回一个Dates视图继承自 compromise 的View核心实现在 src/api/dates.js。其查找逻辑位于 src/api/find/index.js先用doc.match(#Date)匹配再做一系列反例过滤——排除纯时长如20 minutes、金额百分比$5 an hour、per #Duration等最后按规则切分日期块避免30 minutes on tuesday这类短语被误判为日期。支持以下子方法.dates().get()返回精简的 start/end JSON。get(n)可传入索引取第 n 个结果源码中会过滤掉既无start也无repeat的结果因此every tuesday这类重复日期也会被保留见 dates.js。.dates().json()在 compromise 原生 json 基础上叠加日期元数据每个结果附带dates字段可用{ dates: false }关闭见 dates.js。.dates().format(fmt)把原文中的日期短语原地替换为格式化日期。格式串是 spacetime 的格式语法测试 format.test.js 给出了可运行示例let doc nlp(im going skiing two days after November 1st 2019 at 7pm) doc.dates().format({day} {month} {date-ordinal}, {time}) // im going skiing Sunday November 3rd, 7:00pm doc nlp(halloween) doc.dates().format({month} {date-ordinal}) // October 31st.dates().isBefore(iso)/.dates().isAfter(iso)仅保留早于/晚于给定 ISO 日期的结果。.dates().isSame(unit, iso)仅保留与给定日期同年、同月或同日的如isSame(month, 2021-02-01)。get()返回的DateJSON结构与 index.d.ts 一致为interface DateJSON { start: string | null // ISO 时间戳 end: string | null timezone: string | null duration: { years?, months?, days?, hours?, minutes? } // 区间跨度 unit?: string // 跨度单位day / time / year 等 repeat?: { // 重复日期如 every tuesday interval: Recordstring, number filter?: { weekDays?: Recordstring, boolean } choose?: AND | OR | null time?: string | null } }其中unit的推断逻辑很巧妙见 toJSON.js如果start恰好落在某单位year/quarter/month/week/day的起点且end落在下一单位起点就推断该区间为一个完整单位——例如jan 1 to dec 31会被识别为unit: year。.durations()时长doc.durations()匹配2 months、2mins、20min这类长度表达见 durations/index.js匹配模式为#Value #Duration (and? #Value #Duration)?并排除in 20 minutes这类日期偏移表达。get()返回形如{ minute: 30, hour: 2 }的对象。单位归一化逻辑在 durations/parse.jsm→minute、hr→hour、wk→week、qtr→quarter、yr→year等缩写映射并支持20mins这种数字字母粘连的词内拆分。测试 durations.test.js 验证了三类行为的边界nlp(in 20 mins).dates().found // true —— in 20 mins 是日期偏移 nlp(in 20 mins).durations().found // false nlp(for 20 mins).dates().found // false —— for 20 mins 是纯时长 nlp(for 20 mins).durations().found // true.times()一天中的时刻doc.times()匹配4:30pm、half past five、ten past three等时间表达见 times.js查找模式为#Time (am|pm)?。get()返回interface TimeJSON { time: string | null // 规范时间如 3:10pm 24h: string | null // 24 小时制如 15:10 hour?: number minute?: number }format(24h)可把文本时间统一为 24 小时制。时间区间如tuesday from 4 to 5pm、9-5 on tuesday也能被 range 解析 拆成{ start, end }两个时间点测试见 times.test.js。配置选项context 对象.dates()接受一个可选 context 对象来设定日期解析的基准环境这是该插件最重要的定制入口TypeScript 类型见 index.d.ts 的DateOptionsconst context { timezone: Canada/Eastern, // 默认是你的本地时区 today: 2020-02-20, // 隐含的基准日/基准年 punt: { weeks: 2 }, // after june 2nd 这类表达的隐含时长 dayStart: 8:00am, // 一天的默认开始时间 dayEnd: 5:30pm, // 一天的默认结束时间 dmy: false // 设为 true 时歧义日期按英式日月年解析 } nlp(in two days).dates(context).get() /* [{ start: 2020-02-22T08:00:00.0005:00, end: 2020-02-22T17:30:00.0005:00 }] */各选项的底层行为可以从 parse/index.js 的 context 归一化看到timezone设为false时强制使用UTC避免本地时区干扰解析到的时区信息如eastern time会覆盖该默认值。today解析今天/今年的相对基准接受 ISO 字符串、epoch 数字或 Date默认为spacetime.now(timezone)。puntafter june 2nd这类表达没有明确终点时的隐含时长默认{ weeks: 2 }。dayStart/dayEnd无显式时刻的日期默认从dayStart开始、dayEnd结束因此上面的示例中两天后返回8:00–17:30而非 0 点到 23:59。dmy控制歧义数字日期的解释见下文英美日期歧义。时区还会参与日期基准的换算当文本自带时区如in PST时parse/one/index.js 会把today的墙上时钟时间搬到目标时区保证同一天、同一时刻语义正确。解析管线的源码剖析理解插件为何能处理这么多表达关键在于其分层的解析管线。整条链路parse/index.js → range/index.js可以概括为单日期解析 区间组合两层1. 标签与查找compute 阶段插件注册了 tags、words、regex 与一个计算钩子见 plugin.js。compute/index.js 中会执行两遍正则网络匹配doMatches调用两次以链式处理2 years, 4 months and 5 days ago这类复合表达随后依次运行00-year、01-time-range、02-timezone、03-fixup四步处理最后对#DateShift表达再做两轮强化打标。2. 单日期解析parse/one任何一段日期文本都要经过分词 → 解析 → 变换三个阶段parse/one/index.jstokenize01-tokenize按 7 个维度切分文本包括 shift偏移量、counter计数、time、relative相对词、section、timezone、weekdayparse02-parse分别处理today基准日、holidays节假日、next/last相对星期/月份、yearly年度表达、explicit显式日期transform03-transform把解析出的各部件叠加到基准日期对象上例如addCounter实现second week of march这类第 N 周计算。3. 区间解析parse/range先检测重复日期every tuesday再按模板依次尝试twoTimes、combos、dateRange、oneDate四类区间range/index.js每个模板内部先doc.match(fmt.match)命中再执行自定义parse逻辑。quarter to five这类钟表时间会被特殊拦截避免误当成日期区间。最后若发现start晚于end还会自动交换两者以保证区间方向正确。4. JSON 输出toJSONtoJSON.js 通过end - start计算duration字段删除毫秒与秒并推导unit与repeat最终产出{ start, end, timezone, duration, unit?, repeat? }的扁平结构。设计决策与观点OpinionsREADME 用专门的章节记录了插件在歧义场景下的取舍这些决策对集成方至关重要一周从周一开始默认情况下一周从周一开始next week表示周一早上到周日晚上。README 说明该配置目前没有透传给 spacetime属于插件内固定行为。隐含时长Implied durationsafter October默认返回从Nov 1st开始、持续2 周的区间。可通过punt覆盖doc.dates({ punt: { month: 1 } })未来倾向Future biasMay 7th倾向返回未来最近的 5 月 7 日但在当前月份内会回退到过去日期// 假设今天是 3 月 2 日 nlp(feb 30th).dates({ today: 2021-02-01 }).get()This / Next / Last 的语义this monday裸的monday总是指它自己或即将到来的周一——周一当天说this monday是当天周二说则指下周一。this june同理6 月说指当月其他月份指最近的未来 6 月。README 也坦言未来版本可以考虑借助句子时态i paid on mondayvsi will pay on monday进一步消歧。last monday周二说last monday不是昨天而是-1 周a week ago monday同样有效this past monday才指昨天。last X若跨过周起始点可能少于 7 天例如周一说的last friday只有几天前。README 对比了同类库Wit.ai 与 chronic 返回昨天Natty 与 SugarJS 与本插件一致返回 -1 周。next wednesday周二说next wednesday不是明天而是1 周a week wednesday同样 1 周this coming wednesday才是明天。此处 Wit.ai、chronic、Natty 均返回明天SugarJS 与本插件一致返回 1 周。第 N 周Nth Week一个月的第一周或一年的第一周定义为包含周四的那一周——这是广泛采用但略显奇怪的惯例README 猜测源自军事格式且不易配置。因此first week of January的起始日可能是 12 月的某个周一而first monday of January则必然落在 1 月内。英美日期歧义默认与 JavaScript 保持一致01/02/2020按美式解析为1 月 2 日但13/01/2020会按英式解析为1 月 13 日因为 13 不可能是月份。若想强制02/03/1999的解释用dmy: truenlp(02/03/1999).dates().get() // February 3美式 nlp(02/03/1999).dates({dmy:true}).get() // March 2英式ISO 日期如1999-03-02不受该选项影响。对应测试见 dmy.test.js。季节与昼夜默认this summer返回6 月 1 日 – 9 月 1 日北半球 ISO 定义半球配置未来可能支持。lunch time等词有硬编码时刻一般情况下一天从12:00am开始、到11:59pm当天最后一毫秒结束。无效日期Invalid datescompromise 会先把看起来像日期的东西打上标签但直到解析时才校验有效性january 34th 2020→ 返回Jan 31 2020钳制到当月最后一天tomorrow at 2:62pm→ 直接返回tomorrow丢弃无效时间6th week of february→ 返回 3 月的第 2 周越界顺延遇到 DST 跳变中被跳过或重复的小时返回离 DST 变更最近的合法时间。包含/排他区间between january and march是排他的——结束于 3 月开始之前january to march是包含的——结束于 3 月的最后一天。README 承认这在日常语义中通常模棱两可。日期贪婪度Date greediness插件默认对输入文本不做假设尽力避免误报。若你能确认文本里必然包含日期可以用 compromise 的nlp.extend提高打标强度这是 README 提供的官方示例nlp.extend(function (Doc, world) { // 歧义缩写词 world.addWords({ weds: WeekDay, wed: WeekDay, sat: WeekDay, sun: WeekDay, }) world.postProcess(doc { // 把 2nd quarter 标记为日期 doc.match(#Ordinal quarter).tag(#Date) // 把 2/2 标记为日期而非分数 doc.match(/[0-9]{1,2}/[0-9]{1,2}/).tag(#Date) }) })杂项行为thursday the 16th会强制落到 16 日即便 16 日并非周四in a few hours/years按 3 小时/年计a couple of按 2 计jan 5th 2008 to Jan 6th the following year支持跨年显式引用half past 5默认按下午 5 点5pm处理。边界与限制诚实的清单README 同样坦白记录了插件做得勉强和做不到的场景集成前务必知晓处理得勉强的awkward输入说明middle of 2019/June尝试寻找大致中心返回 June 15good friday 2025尝试推算天文设定的节假日Oct 22 1975 2am in PST历史 DST 变更默认按当前 DST 规则计算不支持的doesnt do输入说明not this Saturday, but the Saturday after自引用逻辑3 years ago tomorrow口语化省略表达2100军事时间格式测试与可靠性插件在 tests 目录 下配备了 30 个测试文件覆盖am big的典型场景ambig-month、ambig-week、before-after、dmy、duration、full-iso、timezone、today、tokenizer、week等另有 false-positive.test.js 专门防守误报。测试通过 tests/_lib.js 同时验证源码版../src/plugin.js与构建产物版builds/compromise-dates.mjs确保发布包与源码行为一致。运行方式cd plugins/dates npm test # 源码测试 npm run testb # 生产构建产物测试可交互演示位于 plugins/dates/demo/index.html便于在不写代码的情况下快速验证各种输入。依赖与生态位置从 package.json 可以看到插件的技术底座spacetime承担时区、夏令时DST与所有日历算术——README 明确指出 Tokenization 与消歧由 compromise 负责时区与 DST 推算交给 spacetime数字解析复用 compromise-numbers非正式时区名eastern time则由 spacetime-informal 调和spacetime-holiday提供easter、christmas eve等节假日推算peerDependencies要求compromise 14.2.0。README 的About一节给出了作者的核心立场正则表达式太脆弱、神经网络太飘忽、商业公司不该垄断通用日期解析——他们认为基于规则与简单 NLP 的开源社区库才是构建自然语言日期解析器的最优解这也是整个 compromise 项目modest natural-language processing定位在日期领域的落地体现。总结compromise-dates 用一套清晰的标签查找 → 单日期解析 → 区间组合 → JSON 输出管线把自然语言日期解析做成了三个简单方法dates()/durations()/times()。掌握 context 配置timezone、today、punt、dayStart/dayEnd、dmy与 README 中记录的设计决策就能预判它在歧义输入下的行为从而在自己的应用中可靠地使用它。配合format()与isBefore/isAfter/isSame它足以支撑日程提醒、内容提取、报表归档等常见的时间信息处理需求。【免费下载链接】compromisemodest natural-language processing项目地址: https://gitcode.com/gh_mirrors/co/compromise创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
