Skip to content

Commit

Permalink
feat: asDate support custom format and timezone using tempo
Browse files Browse the repository at this point in the history
  • Loading branch information
Th1nkK1D committed Nov 12, 2024
1 parent 0f9cfc2 commit 5e1ba2a
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 8 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@formkit/tempo": "^0.1.2",
"@sinclair/typebox": "^0.33.19",
"d3-dsv": "^3.0.1"
},
Expand Down
59 changes: 53 additions & 6 deletions src/transformer/as-date.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,67 @@
import {
applyOffset,
format,
offset,
parse,
type Format,
} from '@formkit/tempo';
import { Date, type DateOptions } from '@sinclair/typebox';
import { createTransformer } from './create-transformer';

export type { DateOptions };

/**
* Create date transformer. Only accept {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format | Date-time string format}.
* @param options - Validation options (see {@link DateOptions})
* Options for Tempo to parse and format date string.
*/
export interface TempoOptions {
/**
* The format that should be used to parse and format the date.
* @defaultValue ISO 8601 format
* @see {@link https://tempo.formkit.com/#format-tokens | Tempo Format Tokens}
*/
format?: Format;
/**
* Timezone for the decode's input and encode's output date string.
* @defaultValue UTC
*/
timezone?: string;
}

/**
* Create date transformer.
* Using {@link https://tempo.formkit.com | Tempo} to parse and format date string.
* @param options - {@link TempoOptions} for parsing/formatting and {@link DateOptions} for validation
* @remarks Without format option, asDate expects {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format | ISO 8601 format}
* @example
* ```ts
* Column('createdAt', asDate());
* Column('createdAt', asDate({
* format: 'DD/MM/YYYY',
* timezone: 'Asia/Bangkok'
* }));
* ```
*/
export function asDate(options?: DateOptions) {
export function asDate(options: TempoOptions & DateOptions = {}) {
const {
format: formatOption,
timezone = 'UTC',
...validateOptions
} = options;

return createTransformer(
(str) => new global.Date(str),
(date) => date.toISOString(),
Date(options),
(str) => {
const localDate = parse(str, formatOption);
return applyOffset(localDate, offset(localDate, timezone));
},
(date) =>
format({
date,
format: formatOption ?? 'YYYY-MM-DDTHH:mm:ss',
tz: timezone,
}) +
(formatOption === undefined
? `.${date.getMilliseconds().toString().padStart(3, '0')}Z`
: ''),
Date(validateOptions),
);
}
52 changes: 50 additions & 2 deletions tests/transformer/as-date.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ describe('default', () => {
it.each(['13/11/1996', 'not date'])(
'should throw when decode %p',
(value) => {
expect(() => Decode(asDate(), value)).toThrow('Expected Date');
expect(() => Decode(asDate(), value)).toThrow(
'Non ISO 8601 compliant date',
);
},
);

it('should encode date as ISO string', () => {
it('should encode date as string', () => {
const date = new Date();
const output = Encode(asDate(), date);
expect(output).toBe(date.toISOString());
Expand All @@ -36,6 +38,52 @@ describe('default', () => {
expect(() => Encode(asDate(), value)).toThrow('Expected Date');
},
);

it('should use custom format if given', () => {
const input = '13/11/1996';
const tfm = asDate({
format: 'DD/MM/YYYY',
});

const date = Decode(tfm, input);
expect(date).toBeValidDate();
expect(date.getDate()).toBe(13);
expect(date.getMonth()).toBe(10);
expect(date.getFullYear()).toBe(1996);

const string = Encode(tfm, date);
expect(string).toBe(input);
});

it('should use UTC timezone by default', () => {
const input = '1996-11-13T00:00';
const tfm = asDate();

const date = Decode(tfm, input);
expect(date).toBeValidDate();
expect(date.getMonth()).toBe(10);
expect(date.getDate()).toBe(13);
expect(date.getHours()).toBe(0);

const string = Encode(tfm, date);
expect(string).toStartWith(input);
});

it('should use custom timezone if given', () => {
const input = '1996-11-13T00:00';
const tfm = asDate({
timezone: 'Asia/Bangkok',
});

const date = Decode(tfm, input);
expect(date).toBeValidDate();
expect(date.getMonth()).toBe(10);
expect(date.getDate()).toBe(12);
expect(date.getHours()).toBe(17);

const string = Encode(tfm, date);
expect(string).toStartWith(input);
});
});

describe('optional', () => {
Expand Down

0 comments on commit 5e1ba2a

Please sign in to comment.