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
1 change: 1 addition & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default defineConfig({
],
title: 'date-and-time',
description: 'The simplest, most intuitive date and time library',
disable404Route: true,
logo: {
src: './docs/assets/logo.png',
alt: 'date-and-time',
Expand Down
9 changes: 9 additions & 0 deletions docs/404.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: '404'
template: splash
editUrl: false
pagefind: false
hero:
title: '404'
tagline: Page not found. Check the URL or try using the search bar.
---
6 changes: 3 additions & 3 deletions docs/api/format.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ interface FormatterOptions {
calendar?: 'gregory' | 'buddhist';
hour12?: 'h11' | 'h12';
hour24?: 'h23' | 'h24';
plugins?: FormatterPlugin[];
plugins?: (FormatterPluginObject | FormatterPlugin)[];
}
```

Expand Down Expand Up @@ -276,10 +276,10 @@ format(midnight, 'H:mm', { hour24: 'h24' });

### plugins

**Type**: `FormatterPlugin[]`
**Type**: `(FormatterPluginObject | FormatterPlugin)[]`
**Default**: `undefined`

Enables additional format tokens provided by plugins. Plugins extend the formatter with special tokens that are not included in the core library.
Enables additional format tokens provided by plugins. Plugins extend the formatter with special tokens that are not included in the core library. Each entry may also be a plain object literal annotated with `FormatterPluginObject`, which rejects keys that collide with built-in tokens at compile time. Entries that are not annotated this way are not checked, so an entry that defines a built-in token such as `YYYY` overrides it. `FormatterPlugin` is deprecated, kept only for compatibility with existing code, and will be removed in the next major version. See the [Plugins](../plugins) guide for details.

```typescript
import { format } from 'date-and-time';
Expand Down
6 changes: 3 additions & 3 deletions docs/api/parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ interface ParserOptions {
hour24?: 'h23' | 'h24';
ignoreCase?: boolean;
defaultDate?: ParsedComponents;
plugins?: ParserPlugin[];
plugins?: (ParserPluginObject | ParserPlugin)[];
}
```

Expand Down Expand Up @@ -342,10 +342,10 @@ parse('12:30', 'HH:mm', { defaultDate: { Y: 2024, M: 3, D: 15, Z: -540 }, timeZo

### plugins

**Type**: `ParserPlugin[]`
**Type**: `(ParserPluginObject | ParserPlugin)[]`
**Default**: `undefined`

Enables additional parse tokens provided by plugins. Plugins extend the parser with special tokens that are not included in the core library.
Enables additional parse tokens provided by plugins. Plugins extend the parser with special tokens that are not included in the core library. Unlike a format token, a parse token can only set one of the date components that the built-in parser already provides (year, month, day, hour, minute, second, millisecond, meridiem, and time zone offset); a token that sets none of them just skips the matching text. Each entry may also be a plain object literal annotated with `ParserPluginObject`, which rejects keys that collide with built-in tokens at compile time. Entries that are not annotated this way are not checked, so an entry that defines a built-in token such as `YYYY` overrides it. `ParserPlugin` is deprecated, kept only for compatibility with existing code, and will be removed in the next major version. See the [Plugins](../plugins) guide for details.

```typescript
import { parse } from 'date-and-time';
Expand Down
8 changes: 7 additions & 1 deletion docs/content.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { docsSchema } from '@astrojs/starlight/schema';
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';

export const collections = {
docs: defineCollection({
Expand All @@ -10,4 +10,10 @@ export const collections = {
}),
schema: docsSchema(),
}),
// Starlight reads this optional collection for UI-string overrides. A collection with no
// entries is absent from Astro's data store and logs a warning, so register one empty `en` entry.
i18n: defineCollection({
loader: () => ({ en: {} }),
schema: i18nSchema(),
}),
};
26 changes: 25 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,28 @@ The following plugins are now obsolete as they have been integrated into the mai
- `timespan`
- `timezone`

The custom plugin feature that existed up to 3.x is not yet supported at this time.
The custom plugin feature (`date.extend(...)`) that existed up to 3.x has been replaced by the `plugins` option, which accepts a plain object literal instead of mutating a global singleton. Below is a `Formatter` example. A `Parser` plugin is passed the same way, but its tokens are more limited: they can only set the date components the built-in parser already provides. See the Plugins guide for details on writing your own plugin.

```typescript
// 3.x
date.extend({
formatter: {
Q: function (d) { return String(Math.floor(d.getMonth() / 3) + 1); }
}
});
date.format(new Date(), 'YYYY [Q]Q');
```

```typescript
// 4.x
import { format } from 'date-and-time';
import type { DateLike, FormatterPluginObject } from 'date-and-time/plugin';

const quarter: FormatterPluginObject = {
Q: (d: DateLike) => String((d.getMonth() / 3 | 0) + 1)
};

format(new Date(), 'YYYY [Q]Q', { plugins: [quarter] });
```

The 3.x `extend` silently ignored any key that collided with a built-in token, leaving the built-in behavior in place. With `FormatterPluginObject`/`ParserPluginObject`, the same collision (such as `YYYY` or `MM`) is rejected at compile time instead. Note that an untyped plain object is not checked against built-in tokens, and custom plugins are searched before the built-in tokens; this differs from 3.x and can silently override a built-in token if the same key is reused.
49 changes: 49 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,55 @@ const foobar = require('date-and-time/plugins/foobar');
format(new Date(), 'ddd, MMM DD YYYY', { plugins: [foobar.formatter] });
```

## Writing Your Own Plugin

For a quick, one-off token, you can pass a plain object literal instead of a plugin module. Annotate it with `FormatterPluginObject` (for `format`) or `ParserPluginObject` (for `parse`, `preparse`, and `isValid`), and any key that collides with a built-in token (such as `YYYY` or `MM`) is rejected at compile time.

```typescript
import { format } from 'date-and-time';
import type { DateLike, FormatterPluginObject } from 'date-and-time/plugin';

const quarter: FormatterPluginObject = {
Q: (d: DateLike) => String((d.getMonth() / 3 | 0) + 1)
};

format(new Date(2025, 3, 1), 'YYYY [Q]Q', { plugins: [quarter] });
// => 2025 Q2
```

```typescript
// @ts-expect-error - `YYYY` is a built-in token and cannot be redefined this way
const invalid: FormatterPluginObject = { YYYY: () => 'nope' };
```

Parser plugins are more limited than formatter plugins. A formatter token can return any string, but a parser token can only supply one of the date components that the built-in parser already reads: year (`Y`), month (`M`), day (`D`), 24-hour (`H`), meridiem (`A`), 12-hour (`h`), minute (`m`), second (`s`), millisecond (`S`), and time zone offset (`Z`). There is no way to add a new component.

A parser token receives the remaining input string and returns a `value`, the `length` of the text it consumed, and the `token` naming the component that `value` is applied to. The `exec` helper builds this result from a regular expression, and its third argument is the component. A `length` of `0` means the token did not match, and parsing stops there. A result without a `token` only consumes its text and its `value` is discarded, which is how the `day-of-week` plugin skips a day name. Any name other than the components above is rejected by the type definitions and ignored at run time.

The following token reads a day with an English ordinal suffix, such as `23rd`; the bundled `ordinal` plugin below provides the same token. The regular expression matches only the number, so `length` is increased by 2 to consume the suffix as well, while `value` is applied to the day component:

```typescript
import { parse } from 'date-and-time';
import { exec } from 'date-and-time/plugin';
import type { ParserPluginObject } from 'date-and-time/plugin';

const ordinal: ParserPluginObject = {
DDD: (str: string) => {
const result = exec(/^\d\d?(?=st|nd|rd|th)/, str, 'D');

if (result.length > 0) {
result.length += 2;
}
return result;
}
};

parse('August 23rd, 2025', 'MMMM DDD, YYYY', { plugins: [ordinal] });
// => Sat Aug 23 2025 00:00:00 GMT-0700
```

Objects passed to `plugins` without one of these annotations are not checked against built-in tokens. Since custom plugins are searched before the built-in tokens, a key such as `YYYY` in such an object silently overrides the built-in token. The `FormatterPlugin` and `ParserPlugin` types, which `plugins` also accepts, are deprecated, kept only for compatibility with existing code, and will be removed in the next major version.

## day-of-week

This plugin adds tokens to the `Parser` for reading the day of the week. Since the day of the week does not provide information that identifies a specific date, it is a meaningless token, but it can be used to skip that portion when the string you want to read contains a day of the week.
Expand Down
Loading
Loading