Skip to main content

typescript-temporal

Temporal API for TS/JS dates and times. Use when parsing, formatting or comparing dates, computing today, time zones, durations or working days.

来源信息

仓库
laurigates/claude-plugins
最近来源活动
2026年9月29日 10:14
检测到的 SKILL.md 语言
英语
星标
60
分支
6

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
created
2026-09-29T00:00:00.000Z
modified
2026-09-29T00:00:00.000Z
reviewed
2026-09-29T00:00:00.000Z
name
typescript-temporal
description
Temporal API for TS/JS dates and times. Use when parsing, formatting or comparing dates, computing today, time zones, durations or working days.
user-invocable
false
allowed-tools
Glob, Grep, Read, Bash, Edit, Write, TodoWrite, WebFetch, WebSearch
# Temporal for Dates and Times New TypeScript and JavaScript code handles dates and times with `Temporal`. `Date` packs a calendar date, an instant and the host time zone into one mutable object. The bugs that follow all type-check: yesterday's date just after midnight, every date shifted a day west of UTC, weekday indexes off by one. Temporal gives each meaning its own type, so mixing them becomes a compile error. ## Pick the type by what the value means | The value is… | Type | Example | |---|---|---| | A calendar date with no time or zone (due date, holiday, timesheet day) | `Temporal.PlainDate` | `Temporal.PlainDate.from("2026-06-15")` | | A month (billing or reporting period) | `Temporal.PlainYearMonth` | `Temporal.PlainYearMonth.from("2026-02").daysInMonth` is `28` | | A wall-clock time, or date and time, with no zone ("09:00 every weekday") | `Temporal.PlainTime` / `Temporal.PlainDateTime` | `Temporal.PlainTime.from("09:00")` | | A moment in time (log timestamp, `created_at`) | `Temporal.Instant` | `Temporal.Now.instant()` | | A moment shown or bucketed in a place | `Temporal.ZonedDateTime` with an explicit IANA zone | `instant.toZonedDateTimeISO("Europe/Helsinki")` | | A length of time | `Temporal.Duration` | `Temporal.Duration.from({ hours: 7, minutes: 15 }).total("hours")` is `7.25` | If answering "which day is this?" needs a time zone, the value is not a `PlainDate` yet. ## Convert a timestamp to a date once, at the boundary, in a named zone ```ts // Wrong: the UTC calendar date, not the user's. In Finland (UTC+2/+3) this is // yesterday from 00:00 to 02:00 local in winter and to 03:00 in summer. const utcToday = new Date().toISOString().slice(0, 10); // or .split("T")[0] // Right: one "today" seam for the whole codebase (see Testing for why it // reads Date.now()) const TZ = "Europe/Helsinki"; export const today = (tz = TZ): Temporal.PlainDate => Temporal.Instant.fromEpochMilliseconds(Date.now()).toZonedDateTimeISO(tz).toPlainDate(); const day = Temporal.Instant.from(apiTimestamp).toZonedDateTimeISO(TZ).toPlainDate(); ``` Pass the zone explicitly. With no argument, `Temporal.Now.plainDateISO()` uses the host zone, which on servers and CI runners is usually UTC. An unknown zone ID throws `RangeError`. ## Keep calendar dates out of `Date` The "UTC-midnight `Date`" pattern (`new Date("2026-06-15")` read back with `getUTC*`) stays correct only while every caller remembers the UTC getters. A single `getDate()`, `getDay()` or `toLocaleDateString()` reads it in local time, and west of UTC the date moves back a day: with `TZ=America/New_York`, `new Date("2026-06-15").getDate()` is `14`. Both getters return `number`, so the type checker cannot see the mistake. A `PlainDate` has no time or zone to misread. ## Store and transmit ISO strings Keep ISO 8601 strings (`"2026-06-15"`, `"2026-06-15T09:00:00Z"`) in JSON, databases, `chrome.storage` and URLs. Parse at the edge with `Temporal.<Type>.from(str)`, emit with `.toString()`; `JSON.stringify` produces the same string through `toJSON`. String input with an impossible date throws (`PlainDate.from("2026-02-30")` is a `RangeError`), but property-bag input clamps by default (`{ year: 2026, month: 2, day: 30 }` becomes `2026-02-28`), so pass `{ overflow: "reject" }` when validating user input. Interop with legacy `Date` and libraries happens at that same boundary: | Direction | Call | |---|---| | `Date` to `Instant` | `date.toTemporalInstant()` | | `Instant` to `Date` | `new Date(instant.epochMilliseconds)` | | Epoch ms to `Instant` | `Temporal.Instant.fromEpochMilliseconds(ms)` | ## Porting from `Date` | `Date` habit | Temporal | |---|---| | `getDay()`: 0 = Sunday … 6 = Saturday | `dayOfWeek`: 1 = Monday … 7 = Sunday; weekend is `d.dayOfWeek >= 6` | | `getMonth()` is 0-based | `month` is 1-based | | `a < b`, `a - b` | `Temporal.PlainDate.compare(a, b)`, `a.until(b)`; `<`, `-` and `"" + d` throw `TypeError` because `valueOf` throws | | `a.getTime() === b.getTime()` | `a.equals(b)`; `===` compares object identity | | `d.setDate(d.getDate() + 1)` mutates | `d.add({ days: 1 })` returns a new value | | Jan 31 + 1 month rolls into March | `PlainDate.from("2026-01-31").add({ months: 1 })` is `2026-02-28` | Template literals call `toString()` and are safe: `` `${date}` ``. ## Testing A pinned test clock reaches `Temporal.Now` only if the fake-timer library fakes it. `@sinonjs/fake-timers` added `Temporal.Now.*` faking in 15.4.0 (2026-05-05). Measured on Node 26 after pinning the clock to 2020-01-15: | Runner | `Temporal.Now.instant()` | |---|---| | Vitest 4.1.4, 4.1.6, 4.1.11, `vi.useFakeTimers()` + `vi.setSystemTime()` | real time; only `Date` is pinned | | Vitest 5.0.0 and 5.0.2 (5.0.0 release notes: "support mocking `Temporal`") | pinned | | Jest 30.5.2, `jest.useFakeTimers({ now })` | pinned; `@jest/fake-timers` 30.4.0+ requires fake-timers `^15.4.0` | | Bun 1.4.2, `setSystemTime()` | pinned | An explicit `toFake` list that leaves out `"Temporal"` also leaves it real. On runners that do not fake it, code that calls `Temporal.Now.*` directly ignores the pinned clock and nothing fails loudly. The `today()` seam above reads `Date.now()`, which every fake-timer version pins, so it behaves the same on every runner; injecting a clock (`now: () => Temporal.Instant`) works as well. `bun test` runs in UTC unless `TZ` is set. In UTC, UTC slicing and local getters agree, so both off-by-one-day bugs above pass. Run date tests under a zone east and a zone west of UTC. ## Availability Native support per MDN browser-compat-data (8.1.3, 2026-09-24): | Runtime | Native from | |---|---| | Chrome, Edge | 144 | | Firefox | 139 | | Safari | Technology Preview only; not in stable Safari or iOS | | Node.js | 26.0.0 (earlier majors: polyfill) | | Deno | 2.7 | | Bun | 1.4.0 | - **Older targets**: `temporal-polyfill` (FullCalendar, ~20 kB, defers to native Temporal when present). Import `"temporal-polyfill/global"` once at the entry point. MDN also lists `@js-temporal/polyfill`; its latest release (0.5.1, March 2025) predates Stage 4. - **Chrome extensions**: set `"minimum_chrome_version": "144"` in the manifest. The Web Store then shows older Chrome "Not compatible" instead of the extension failing with `ReferenceError: Temporal is not defined`. Existing users on older Chrome stop receiving updates. - **TypeScript**: types ship from TypeScript 6.0. Add `"ESNext"` to `lib`, or the granular `"esnext.temporal"` plus `"esnext.date"` (for `toTemporalInstant`). TypeScript 5.x has no Temporal types; with the polyfill, add `import "temporal-polyfill/types/global"`. The rest of the tsconfig belongs to `typescript-strict`. ## When `Date` still appears New code that reaches for `new Date(`, `Date.now()`, moment, date-fns, dayjs or luxon uses Temporal instead. The exception is an external API that takes or returns `Date` (a library signature, `valueAsDate`, a database driver): convert at that call site and keep Temporal on both sides. The `today()` seam's `Date.now()` is the other exception. Migrate existing `Date` code when it is touched for a date bug, not as a drive-by sweep. ## Agentic Optimizations | Context | Command | |---|---| | Runtime has native Temporal | `node -p 'typeof Temporal'` (`object`) | | Types resolve | `bunx tsc --noEmit` | | Reproduce a zone bug | `TZ=America/New_York bun test` | Find the patterns this skill replaces: ```bash rg -n "toISOString\(\)\.(slice\(0, ?10\)|split\(['\"]T['\"]\))" src rg -n 'new Date\(|Date\.now\(\)|from "(moment|date-fns|dayjs|luxon)' src rg -n '\.get(Day|Date|Month|FullYear)\(\)' src ``` ## References - MDN Temporal: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal - Proposal docs and cookbook: https://tc39.es/proposal-temporal/docs/ - `temporal-polyfill`: https://github.com/fullcalendar/temporal-polyfill - `minimum_chrome_version`: https://developer.chrome.com/docs/extensions/reference/manifest/minimum-chrome-version
在 GitHub 查看