Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/api/format.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 16 additions & 19 deletions docs/api/parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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
Expand Down Expand Up @@ -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).
Expand All @@ -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).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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
Expand All @@ -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');
Expand Down Expand Up @@ -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
Expand Down
23 changes: 19 additions & 4 deletions docs/guide/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -102,20 +110,27 @@ 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:

```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
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down
35 changes: 25 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,39 @@
"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",
"import": "./dist/plugins/*.js",
"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": [
Expand All @@ -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",
Expand Down
40 changes: 34 additions & 6 deletions rollup.config.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<string, string>, outputDir: string) => ({
const defaultExportPlugins = [...plugins, fixCjsDefaultInterop()];
const config = (input: string | Record<string, string>, 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();
};

Expand All @@ -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<string, string>, outputDir: string) => ({
input,
output: { dir: outputDir },
plugins
});
const cjsConfig = (input: string | Record<string, string>, 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();
};

Expand Down
Loading
Loading