From d5afa73b506d244199e29b5f47f97edef1f80df4 Mon Sep 17 00:00:00 2001 From: knowledgecode Date: Tue, 22 Sep 2026 16:30:42 +0900 Subject: [PATCH 1/3] fix: preserve default-export interop for CJS locale/numeral/timezone modules require() on the CJS build of a locale, numeral, or timezone module lost the ability to be used as the value it exports once terser/esbuild's default-export handling flattened module.exports.default onto module.exports itself. Add a renderChunk plugin that re-defines module.exports.default to point back at module.exports so both require('...') and require('...').default keep working, and generate a matching .d.cts with `export =` via a dts renderChunk plugin so the CommonJS types line up. Also wire tests/build into prepublishOnly so a broken interop or missing .d.cts fails the build before publish. --- docs/guide/installation.mdx | 16 ++++++++++ package.json | 35 +++++++++++++++------- rollup.config.ts | 40 +++++++++++++++++++++---- tests/build/cjs-default-interop.spec.ts | 36 ++++++++++++++++++++++ 4 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 tests/build/cjs-default-interop.spec.ts diff --git a/docs/guide/installation.mdx b/docs/guide/installation.mdx index d312f6e..4488f84 100644 --- a/docs/guide/installation.mdx +++ b/docs/guide/installation.mdx @@ -80,6 +80,14 @@ format(new Date(), 'D MMMM YYYY', { locale: fr }); For a complete list of all supported locales with import examples, see [Supported Locales](../locales). +CommonJS's `require()` returns the same locale object as the ESM default export: + +```typescript +const ja = require('date-and-time/locales/ja'); + +format(new Date(), 'YYYY年M月D日', { locale: ja }); +``` + ### Timezone Usage Pass an IANA timezone name string directly to any function that accepts a timezone option: @@ -102,6 +110,14 @@ format(new Date(), 'DD/MM/YYYY', { numeral: arab }); // => ٠٨/٠٧/٢٠٢٥ ``` +The same applies to numeral systems under CommonJS: + +```typescript +const arab = require('date-and-time/numerals/arab'); + +format(new Date(), 'DD/MM/YYYY', { numeral: arab }); +``` + ## Plugin Imports Some advanced features are available as plugins: diff --git a/package.json b/package.json index 143bf05..2988408 100644 --- a/package.json +++ b/package.json @@ -30,14 +30,24 @@ "require": "./dist/timezone.cjs" }, "./locales/*": { - "types": "./dist/locales/*.d.ts", - "import": "./dist/locales/*.js", - "require": "./dist/locales/*.cjs" + "import": { + "types": "./dist/locales/*.d.ts", + "default": "./dist/locales/*.js" + }, + "require": { + "types": "./dist/locales/*.d.cts", + "default": "./dist/locales/*.cjs" + } }, "./numerals/*": { - "types": "./dist/numerals/*.d.ts", - "import": "./dist/numerals/*.js", - "require": "./dist/numerals/*.cjs" + "import": { + "types": "./dist/numerals/*.d.ts", + "default": "./dist/numerals/*.js" + }, + "require": { + "types": "./dist/numerals/*.d.cts", + "default": "./dist/numerals/*.cjs" + } }, "./plugins/*": { "types": "./dist/plugins/*.d.ts", @@ -45,9 +55,14 @@ "require": "./dist/plugins/*.cjs" }, "./timezones/*": { - "types": "./dist/timezones/*.d.ts", - "import": "./dist/timezones/*.js", - "require": "./dist/timezones/*.cjs" + "import": { + "types": "./dist/timezones/*.d.ts", + "default": "./dist/timezones/*.js" + }, + "require": { + "types": "./dist/timezones/*.d.cts", + "default": "./dist/timezones/*.cjs" + } } }, "files": [ @@ -64,7 +79,7 @@ "docs:dev": "astro dev", "docs:preview": "astro preview", "lint": "eslint", - "prepublishOnly": "npm run build", + "prepublishOnly": "npm run build && vitest run tests/build", "test": "vitest run", "test:coverage": "vitest run --coverage", "timezone": "tsx tools/timezone.ts", diff --git a/rollup.config.ts b/rollup.config.ts index 7ce0826..f94773f 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -1,3 +1,4 @@ +import type { Plugin } from 'rollup'; import alias from '@rollup/plugin-alias'; import esbuild from 'rollup-plugin-esbuild'; import terser from '@rollup/plugin-terser'; @@ -9,29 +10,43 @@ import { fileURLToPath } from 'node:url'; const outputDir = (input: string) => input.replace(/^src/g, 'dist').replace(/\/[^/]*$/g, ''); const replacePath = (input: string) => input.replace(/(^src\/|\.ts$)/g, ''); +const fixCjsDefaultInterop = (): Plugin => ({ + name: 'fix-cjs-default-interop', + renderChunk: (code, _chunk, outputOptions) => { + if (outputOptions.format !== 'cjs') { + return null; + } + return { + code: `${code}Object.defineProperty(module.exports, 'default', { value: module.exports, enumerable: false });\n`, + map: null + }; + } +}); + const ts = () => { const plugins = [ alias({ entries: [{ find: '@', replacement: resolve(dirname(fileURLToPath(import.meta.url)), 'src') }] }), esbuild({ minify: false, target: 'es2021' }), terser() ]; - const config = (input: string | Record, outputDir: string) => ({ + const defaultExportPlugins = [...plugins, fixCjsDefaultInterop()]; + const config = (input: string | Record, outputDir: string, entryPlugins = plugins) => ({ input, output: [ { dir: outputDir, format: 'es' }, { dir: outputDir, format: 'cjs', entryFileNames: '[name].cjs' } ], - plugins + plugins: entryPlugins }); return [ config('src/index.ts', 'dist'), config('src/plugin.ts', 'dist'), config('src/timezone.ts', 'dist'), - config(Object.fromEntries(globSync('src/numerals/**/*.ts').map(input => [replacePath(input), input])), 'dist'), - globSync('src/locales/**/*.ts').map(input => config(input, outputDir(input))), + config(Object.fromEntries(globSync('src/numerals/**/*.ts').map(input => [replacePath(input), input])), 'dist', defaultExportPlugins), + globSync('src/locales/**/*.ts').map(input => config(input, outputDir(input), defaultExportPlugins)), globSync('src/plugins/**/*.ts').map(input => config(input, outputDir(input))), - config(Object.fromEntries(globSync('src/timezones/**/*.ts').map(input => [replacePath(input), input])), 'dist') + config(Object.fromEntries(globSync('src/timezones/**/*.ts').map(input => [replacePath(input), input])), 'dist', defaultExportPlugins) ].flat(); }; @@ -40,20 +55,33 @@ const types = () => { alias({ entries: [{ find: '@', replacement: resolve(dirname(fileURLToPath(import.meta.url)), 'src') }] }), dts() ]; + const cjsExportEquals = (): Plugin => ({ + name: 'cjs-dts-export-equals', + renderChunk: (code) => code.replace(/export \{ (\w+) as default \};\n?$/, 'export = $1;\n') + }); + const cjsPlugins = [...plugins, cjsExportEquals()]; const config = (input: string | Record, outputDir: string) => ({ input, output: { dir: outputDir }, plugins }); + const cjsConfig = (input: string | Record, outputDir: string) => ({ + input, + output: { dir: outputDir, entryFileNames: '[name].d.cts' }, + plugins: cjsPlugins + }); return [ config('src/index.ts', 'dist'), config('src/plugin.ts', 'dist'), config('src/timezone.ts', 'dist'), config(Object.fromEntries(globSync('src/numerals/**/*.ts').map(input => [replacePath(input), input])), 'dist'), + cjsConfig(Object.fromEntries(globSync('src/numerals/**/*.ts').map(input => [replacePath(input), input])), 'dist'), globSync('src/locales/**/*.ts').map(input => config(input, outputDir(input))), + globSync('src/locales/**/*.ts').map(input => cjsConfig(input, outputDir(input))), globSync('src/plugins/**/*.ts').map(input => config(input, outputDir(input))), - config(Object.fromEntries(globSync('src/timezones/**/*.ts').map(input => [replacePath(input), input])), 'dist') + config(Object.fromEntries(globSync('src/timezones/**/*.ts').map(input => [replacePath(input), input])), 'dist'), + cjsConfig(Object.fromEntries(globSync('src/timezones/**/*.ts').map(input => [replacePath(input), input])), 'dist') ].flat(); }; diff --git a/tests/build/cjs-default-interop.spec.ts b/tests/build/cjs-default-interop.spec.ts new file mode 100644 index 0000000..916d655 --- /dev/null +++ b/tests/build/cjs-default-interop.spec.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from 'vitest'; +import { existsSync, readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import type { Locale } from '@/locale.ts'; +import type { Numeral } from '@/numeral.ts'; + +const require = createRequire(import.meta.url); +const dist = (path: string) => new URL(`../../dist/${path}`, import.meta.url); + +const built = ['locales/en.d.cts', 'numerals/latn.d.cts'].every((path) => existsSync(dist(path))); + +describe.skipIf(!built)('CJS default export interop', () => { + test('locale module: require() keeps returning the value directly and via .default', () => { + const mod = require('../../dist/locales/ja.cjs') as Locale & { default: unknown }; + expect(typeof mod.getLocale).toBe('function'); + expect(mod.default).toBe(mod); + expect(Object.keys(mod)).not.toContain('default'); + }); + + test('numeral module: require() keeps returning the value directly and via .default', () => { + const mod = require('../../dist/numerals/arab.cjs') as Numeral & { default: unknown }; + expect(typeof mod.encode).toBe('function'); + expect(mod.default).toBe(mod); + expect(Object.keys(mod)).not.toContain('default'); + }); + + test('plugin module (named export only): does not gain an unexpected default property', () => { + const mod = require('../../dist/plugins/ordinal.cjs') as { default?: unknown }; + expect(mod.default).toBeUndefined(); + }); + + test('locale/numeral modules each generate a matching .d.cts using export=', () => { + expect(readFileSync(dist('locales/ja.d.cts'), 'utf8')).toMatch(/^export = \w+;$/m); + expect(readFileSync(dist('numerals/arab.d.cts'), 'utf8')).toMatch(/^export = \w+;$/m); + }); +}); From cd2037f57c11e20f34fcc3f0e78a565c6594441d Mon Sep 17 00:00:00 2001 From: knowledgecode Date: Tue, 22 Sep 2026 16:30:59 +0900 Subject: [PATCH 2/3] docs: fix incorrect examples in API and guide docs - Aug 23, 2025 is a Saturday, not a Friday; correct the day-of-week shown in parse() example outputs across parse.md and quick-start.md, and likewise Aug 23, 1970 (a Sunday, not a Saturday). Drop the now-redundant ignoreCase example that duplicated the corrected one. - Fix the escaped-bracket example in format.md: the closing bracket was escaped in the wrong position (`...]\\` instead of `...\\]`). - Fix the plugin import example in installation.mdx: plugins export a named `formatter` (and `parser`), not a default export, and the microsecond plugin was replaced by zonename to match its actual token in the example. --- docs/api/format.md | 2 +- docs/api/parse.md | 35 ++++++++++++++++------------------- docs/guide/installation.mdx | 7 +++---- docs/guide/quick-start.md | 4 ++-- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/docs/api/format.md b/docs/api/format.md index 585f8f4..05e58a3 100644 --- a/docs/api/format.md +++ b/docs/api/format.md @@ -359,7 +359,7 @@ format(date, 'ddd, DD MMM YYYY HH:mm:ss ZZ'); // => Sat, 23 Aug 2025 14:30:45 +09:00 // Log timestamp -format(date, '\\[YYYY-MM-DD HH:mm:ss.SSS]\\'); +format(date, '\\[YYYY-MM-DD HH:mm:ss.SSS\\]'); // => [2025-08-23 14:30:45.123] // File naming diff --git a/docs/api/parse.md b/docs/api/parse.md index 8b6c40e..fff235d 100644 --- a/docs/api/parse.md +++ b/docs/api/parse.md @@ -29,13 +29,13 @@ import { parse } from 'date-and-time'; // Basic date parsing parse('2025-08-23', 'YYYY-MM-DD'); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 parse('08/23/2025', 'MM/DD/YYYY'); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 parse('23.08.2025', 'DD.MM.YYYY'); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 // Time parsing parse('14:30:45', 'HH:mm:ss'); @@ -46,7 +46,7 @@ parse('2:30:45 PM', 'h:mm:ss A'); // Combined date and time parse('2025-08-23 14:30:45', 'YYYY-MM-DD HH:mm:ss'); -// => Fri Aug 23 2025 14:30:45 GMT-0700 +// => Sat Aug 23 2025 14:30:45 GMT-0700 ``` ## Format Tokens @@ -153,7 +153,7 @@ import es from 'date-and-time/locales/es'; // Spanish parsing parse('23 de agosto de 2025', 'D [de] MMMM [de] YYYY', { locale: es }); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 ``` For a complete list of all supported locales with import examples, see [Supported Locales](../locales). @@ -172,18 +172,18 @@ import { parse } from 'date-and-time'; // Parse using an IANA timezone name string parse('2025-08-23 14:30:00', 'YYYY-MM-DD HH:mm:ss', { timeZone: 'Asia/Tokyo' }); -// => Fri Aug 23 2025 14:30:00 GMT+0900 +// => Sat Aug 23 2025 14:30:00 GMT+0900 // Parse in UTC parse('2025-08-23 14:30:00', 'YYYY-MM-DD HH:mm:ss', { timeZone: 'UTC' }); -// => Fri Aug 23 2025 14:30:00 GMT+0000 +// => Sat Aug 23 2025 14:30:00 GMT+0000 // Timezone offset in input takes precedence over timeZone option parse('2025-08-23 14:30:00 +0300', 'YYYY-MM-DD HH:mm:ss Z', { timeZone: 'Asia/Tokyo' }); -// => Fri Aug 23 2025 14:30:00 GMT+0300 (Asia/Tokyo timeZone is ignored) +// => Sat Aug 23 2025 14:30:00 GMT+0300 (Asia/Tokyo timeZone is ignored) parse('2025-08-23T14:30:00 +05:00', 'YYYY-MM-DD[T]HH:mm:ss ZZ', { timeZone: 'America/New_York' }); -// => Fri Aug 23 2025 14:30:00 GMT+0500 (America/New_York timeZone is ignored) +// => Sat Aug 23 2025 14:30:00 GMT+0500 (America/New_York timeZone is ignored) ``` For a complete list of all supported timezones, see [Supported Timezones](../timezones). @@ -229,11 +229,11 @@ import { parse } from 'date-and-time'; // Gregorian calendar (default) parse('August 23, 2025', 'MMMM D, YYYY'); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 // Buddhist calendar (543 years behind) parse('August 23, 2568', 'MMMM D, YYYY', { calendar: 'buddhist' }); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 ``` ### hour12 @@ -290,10 +290,7 @@ parse('august 23, 2025', 'MMMM D, YYYY'); // Case-insensitive parse('AUGUST 23, 2025', 'MMMM D, YYYY', { ignoreCase: true }); -// => Fri Aug 23 2025 00:00:00 GMT-0700 - -parse('fri aug 23 2025', 'ddd MMM DD YYYY', { ignoreCase: true }); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 ``` ### defaultDate @@ -389,7 +386,7 @@ parse('14:30:45', 'HH:mm:ss'); // Only date - defaults to 00:00:00 parse('2025-08-23', 'YYYY-MM-DD'); -// => Fri Aug 23 2025 00:00:00 GMT-0700 +// => Sat Aug 23 2025 00:00:00 GMT-0700 // Year and month - defaults to 1st day parse('2025-08', 'YYYY-MM'); @@ -528,7 +525,7 @@ parse('samedi, 23 août 2025 à 14:30:45', 'dddd, D MMMM YYYY [à] HH:mm:ss', { locale: fr, timeZone: 'Europe/Paris' }); -// => Fri Aug 23 2025 14:30:45 GMT+0200 +// => Sat Aug 23 2025 14:30:45 GMT+0200 ``` ### Business and Technical Formats @@ -538,7 +535,7 @@ import { parse } from 'date-and-time'; // ISO 8601 format parse('2025-08-23T14:30:45.123Z', 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]', { timeZone: 'UTC' }); -// => Fri Aug 23 2025 14:30:45 GMT+0000 +// => Sat Aug 23 2025 14:30:45 GMT+0000 // RFC 2822 format parse('Sat, 23 Aug 2025 14:30:45 +0900', 'ddd, DD MMM YYYY HH:mm:ss ZZ'); @@ -608,7 +605,7 @@ const timestamp = parse(logLine, ' YYYY-MM-DD HH:mm:ss.SSS ...'); // For different log formats const syslogLine = 'Aug 23 14:30:45 server: Process started'; const syslogTimestamp = parse(syslogLine, 'MMM DD HH:mm:ss...'); -// => Sat Aug 23 1970 14:30:45 GMT-0700 +// => Sun Aug 23 1970 14:30:45 GMT-0700 ``` ### API Responses diff --git a/docs/guide/installation.mdx b/docs/guide/installation.mdx index 4488f84..30cf565 100644 --- a/docs/guide/installation.mdx +++ b/docs/guide/installation.mdx @@ -125,13 +125,12 @@ Some advanced features are available as plugins: ```typescript import { format } from 'date-and-time'; // Import specific plugins -import microsecond from 'date-and-time/plugins/microsecond'; -import ordinal from 'date-and-time/plugins/ordinal'; -import zonename from 'date-and-time/plugins/zonename'; +import { formatter as ordinal } from 'date-and-time/plugins/ordinal'; +import { formatter as zonename } from 'date-and-time/plugins/zonename'; // Use plugin-specific tokens with plugins specified in options format(new Date(), 'MMMM DDD, YYYY', { plugins: [ordinal] }); // with ordinal plugin -format(new Date(), 'HH:mm:ss.SSSSSS', { plugins: [microsecond] }); // with microsecond plugin +format(new Date(), 'YYYY-MM-DD HH:mm:ss z', { plugins: [zonename], timeZone: 'Asia/Tokyo' }); // with zonename plugin ``` ## CDN Usage diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index dfe622a..7480967 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -19,7 +19,7 @@ console.log(formatted); // Parse a date string const parsed = parse('2025/08/23 14:30:45', 'YYYY/MM/DD HH:mm:ss'); console.log(parsed); -// => Fri Aug 23 2025 14:30:45 GMT+0900 +// => Sat Aug 23 2025 14:30:45 GMT+0900 ``` ## Common Format Patterns @@ -98,7 +98,7 @@ format(date, 'YYYY-MM-DD HH:mm:ss [UTC]', { timeZone: 'UTC' }); // Parsing in timezone parse('2025-08-23 23:30:45', 'YYYY-MM-DD HH:mm:ss', { timeZone: 'Asia/Tokyo' }); -// => Fri Aug 23 2025 23:30:45 GMT+0900 +// => Sat Aug 23 2025 23:30:45 GMT+0900 ``` For a complete list of all supported timezones, see [Supported Timezones](../timezones). From 95c27fc636b035881734211cc4ef93c90276ccfe Mon Sep 17 00:00:00 2001 From: knowledgecode Date: Tue, 22 Sep 2026 16:31:07 +0900 Subject: [PATCH 3/3] feat: add date-and-time and date-and-time-migration agent skills Add two self-contained Agent Skills for AI coding agents: `date-and-time` covers writing v4 code (formatting, parsing, timezones, locales, plugins, date arithmetic, durations), and `date-and-time-migration` covers migrating a project from v3 to v4. Either can be installed on its own via `npx skills add` and neither depends on `docs/`. Document them in the README with install instructions (skills CLI and manual copy) and a pointer from the v3->v4 migration section. --- README.md | 36 +++- skills/date-and-time-migration/SKILL.md | 174 ++++++++++++++++++ .../references/api-mapping.md | 141 ++++++++++++++ .../references/locales-and-plugins.md | 161 ++++++++++++++++ skills/date-and-time/SKILL.md | 118 ++++++++++++ skills/date-and-time/references/options.md | 112 +++++++++++ skills/date-and-time/references/plugins.md | 78 ++++++++ skills/date-and-time/references/tokens.md | 71 +++++++ 8 files changed, 890 insertions(+), 1 deletion(-) create mode 100644 skills/date-and-time-migration/SKILL.md create mode 100644 skills/date-and-time-migration/references/api-mapping.md create mode 100644 skills/date-and-time-migration/references/locales-and-plugins.md create mode 100644 skills/date-and-time/SKILL.md create mode 100644 skills/date-and-time/references/options.md create mode 100644 skills/date-and-time/references/plugins.md create mode 100644 skills/date-and-time/references/tokens.md diff --git a/README.md b/README.md index 1af3378..e581b3a 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,46 @@ Version `4.x` has been completely rewritten in TypeScript and some features from - Tree shaking is now supported - Supports `ES2021` and no longer supports older browsers -For details, please refer to [migration.md](https://github.com/knowledgecode/date-and-time/blob/master/docs/migration.md). +For details, please refer to [migration.md](https://github.com/knowledgecode/date-and-time/blob/master/docs/migration.md). If you use an AI coding agent, the `date-and-time-migration` skill can carry out the migration; see [Agent Skills](#agent-skills). ## API For comprehensive documentation and examples, visit: **[GitHub Pages](https://knowledgecode.github.io/date-and-time/)** +## Agent Skills + +This repository ships two [Agent Skills](https://agentskills.io) that help AI coding agents (Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, and others) work with this library: + +| Skill | Use it to | +|-------|-----------| +| [`date-and-time`](https://github.com/knowledgecode/date-and-time/tree/master/skills/date-and-time) | Write code with date-and-time v4: formatting, parsing, timezones, locales, plugins, date arithmetic, durations | +| [`date-and-time-migration`](https://github.com/knowledgecode/date-and-time/tree/master/skills/date-and-time-migration) | Migrate a project from date-and-time v3 to v4 | + +Each skill is self-contained, so you can install either one on its own. The skills are not part of the npm package. + +### Install + +With the [`skills`](https://github.com/vercel-labs/skills) CLI: + +```shell +# Both skills, into the current project +npx skills add knowledgecode/date-and-time --skill date-and-time --skill date-and-time-migration + +# One skill, globally, for a specific agent (Claude Code here) +npx skills add knowledgecode/date-and-time --skill date-and-time -g -a claude-code + +# See what the repository offers before installing +npx skills add knowledgecode/date-and-time --list +``` + +Or copy a skill directory by hand into your agent's skills directory (`.claude/skills/` for Claude Code, `.agents/skills/` for agents that follow the shared convention): + +```shell +git clone --depth 1 https://github.com/knowledgecode/date-and-time.git +mkdir -p .claude/skills +cp -R date-and-time/skills/date-and-time .claude/skills/ +``` + ## License MIT diff --git a/skills/date-and-time-migration/SKILL.md b/skills/date-and-time-migration/SKILL.md new file mode 100644 index 0000000..bf1a395 --- /dev/null +++ b/skills/date-and-time-migration/SKILL.md @@ -0,0 +1,174 @@ +--- +name: date-and-time-migration +description: Migrate a JavaScript or TypeScript project from date-and-time v3 to v4 (npm package date-and-time). Use when a codebase uses v3 APIs such as the default export, date.locale, date.plugin, date.extend, formatTZ, parseTZ, timeSpan, or a boolean UTC argument, or when the user asks to upgrade date-and-time to v4. For writing new v4 code, use the date-and-time skill instead. +--- + +# Migrating date-and-time from v3 to v4 + +v4 is a TypeScript rewrite, not a drop-in upgrade. There is no default export and no global state: locale, plugins, and timezone are passed per call. Targets 4.x. Official guide: https://github.com/knowledgecode/date-and-time/blob/master/docs/migration.md + +How to migrate is the user's choice, so confirm it before editing (see "Decisions to confirm" in step 1). Unless the user says otherwise, keep the project's language and module system: a JavaScript project stays JavaScript and a CommonJS project stays CommonJS, because v4 works with both `require` and `import`. + +## Workflow + +Copy this checklist and tick each item as you finish it: + +``` +- [ ] 1. Inventory v3 usage, tell the user its scope, and confirm the approach +- [ ] 2. Upgrade the package +- [ ] 3. Rewrite imports +- [ ] 4. Rewrite calls (references/api-mapping.md) +- [ ] 5. Replace locale, plugin, and extend usage (references/locales-and-plugins.md) +- [ ] 6. Run the tests and the checks for your language until they are clean +- [ ] 7. Re-scan for leftovers and for silent behavior changes +``` + +### 1. Inventory + +Use `git grep` from the project root (it skips `node_modules` and ignored files). Outside a git repository, use `grep -rnE ... --exclude-dir=node_modules` with the same patterns. Keep the single quotes. + +```shell +# A. Every file that uses the library +git grep -nE 'date-and-time' -- '*.js' '*.jsx' '*.mjs' '*.cjs' '*.ts' '*.tsx' '*.vue' + +# B. v3 import paths and builds (locale/ and plugin/ are now locales/ and plugins/; the UMD and esm/ builds are gone) +git grep -nE 'date-and-time/(locale|plugin)/|date-and-time(\.es)?(\.min)?\.js|esm/date-and-time' -- '*.js' '*.jsx' '*.mjs' '*.cjs' '*.ts' '*.tsx' '*.vue' '*.html' + +# C. v3-only APIs (only hits in files from A count, because other libraries also have .plugin() and .locale() calls) +git grep -nE '(formatTZ|parseTZ|transformTZ|addYearsTZ|addMonthsTZ|addDaysTZ|timeSpan)|\.(locale|extend|plugin)\(' -- '*.js' '*.jsx' '*.mjs' '*.cjs' '*.ts' '*.tsx' '*.vue' + +# D. Boolean UTC argument (a heuristic: calls split over several lines are missed) +git grep -nE '(format|parse|preparse|isValid|transform|addYears|addMonths|addDays)\(.*true\)' -- '*.js' '*.jsx' '*.mjs' '*.cjs' '*.ts' '*.tsx' '*.vue' + +# E. subtract calls, whose meaning changed +git grep -nE 'subtract\(' -- '*.js' '*.jsx' '*.mjs' '*.cjs' '*.ts' '*.tsx' '*.vue' +``` + +Before editing, tell the user how many files and call sites each category found, then confirm the decisions below. Ask them together, and state the default you will use for anything the user leaves open. Use a question tool if your environment has one; otherwise ask in plain text. + +**Decisions to confirm** + +| Decision | Default if the user has no preference | +|----------|---------------------------------------| +| Language and module system: stay as they are, or convert (JavaScript to TypeScript, CommonJS to ES modules) | Stay as they are | +| Import style: named imports, or `import * as date from 'date-and-time'` for a minimal diff | Named imports | +| Locale: pass `{ locale }` at every call, or one wrapper module that binds it (only when the code called `date.locale(...)` at startup) | One wrapper module | +| Verification: run the one-off TypeScript checker on JavaScript files (step 6; it may download TypeScript) | Ask before running it; always run the project's tests | +| Locale codes without a one-to-one replacement (`pt`, `jv`, `pa-in`; see [references/locales-and-plugins.md](references/locales-and-plugins.md)) | Ask; never substitute silently | + +### 2. Upgrade + +```shell +npm install date-and-time@4 +``` + +Use the project's package manager if it is not npm. Compare the project's Node.js version with the package's `engines` field, and remember that v4 targets ES2021: it does not support older browsers. + +The v3 type declarations are replaced by the v4 ones. A TypeScript project therefore starts reporting removed APIs, but a plain JavaScript project gets no errors at all, so it relies on the grep inventory (step 1), the checker in step 6, and the tests. + +### 3. Imports + +| v3 | v4 | +|----|----| +| `import date from 'date-and-time'`, then `date.format(...)` | `import { format } from 'date-and-time'`, then `format(...)`. For a minimal diff, `import * as date from 'date-and-time'` keeps `date.format(...)` working. | +| `const date = require('date-and-time')` | `const { format } = require('date-and-time')`. The v4 CommonJS module has the same named exports, so `date.format(...)` keeps working for functions that still exist. | +| `import ja from 'date-and-time/locale/ja'` | `import ja from 'date-and-time/locales/ja'` (plural), passed per call as `{ locale: ja }`. Some codes were renamed; see [references/locales-and-plugins.md](references/locales-and-plugins.md). | +| `require('date-and-time/locale/ja')` | `const ja = require('date-and-time/locales/ja')`, passed directly. | +| `import ordinal from 'date-and-time/plugin/ordinal'` | `import { formatter, parser } from 'date-and-time/plugins/ordinal'` (plural), passed per call in `plugins` | +| `require('date-and-time/plugin/ordinal')` | `const ordinal = require('date-and-time/plugins/ordinal')`, then `{ plugins: [ordinal.formatter] }` or `[ordinal.parser]` | +| `date-and-time.min.js` with the global `date`, or `esm/date-and-time.es.js` | There is no global or UMD build. Use ES modules: `import { format } from 'https://cdn.jsdelivr.net/npm/date-and-time/dist/index.js'` inside `