diff --git a/.prettierignore b/.prettierignore index 5f89f25..8a58e65 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,10 @@ dist npm .cache -.nyc_output +.* *.log +*.md +*.json +benchmark +browser-test +rollup.config.js diff --git a/.prettierrc.json b/.prettierrc.json index e8bef1d..2bdd97e 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,9 +1,16 @@ { - "parser": "typescript", - "useTabs": true, - "tabWidth": 4, - "singleQuote": true, - "trailingComma": "all", - "bracketSpacing": false, - "printWidth": 120 + "overrides": [ + { + "files": "*.{js,jsx,ts,tsx}", + "options": { + "parser": "typescript", + "useTabs": true, + "tabWidth": 4, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": false, + "printWidth": 120 + } + } + ] } diff --git a/.travis.yml b/.travis.yml index a17d228..beb63ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ notifications: email: false node_js: - - '12' + - "12" jobs: include: @@ -34,4 +34,3 @@ jobs: provider: script skip_cleanup: true script: sh scripts/deploy.sh $TRAVIS_TAG - diff --git a/README.md b/README.md index 3323637..98b93e2 100644 --- a/README.md +++ b/README.md @@ -5,110 +5,106 @@ [![NPM Version](https://img.shields.io/npm/v/ts-date.svg)](https://www.npmjs.com/package/ts-date) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) - **ts-date** is a `Date` library written in Typescript for Typescript Main difference from most javascript `Date` libraries is: -* you will never get `"Invalid Date"`, if you follow types -* literally no overhead under native `Date`, take a look at [benchmarks](https://github.com/standy/ts-date/tree/master/benchmark) -* provides tree-shakeable pure functions - +- you will never get `"Invalid Date"`, if you follow types +- literally no overhead under native `Date`, take a look at [benchmarks](https://github.com/standy/ts-date/tree/master/benchmark) +- provides tree-shakeable pure functions ## Usage ```js -import { parse, format, addMonth } from 'ts-date/esm/locale/en'; -const date = parse('1st August 2017', 'Do MMMM YYYY'); -const result = format(addMonth(date, 1), 'Do MMMM YYYY'); // 1st September 2017 +import { parse, format, addMonth } from "ts-date/esm/locale/en"; +const date = parse("1st August 2017", "Do MMMM YYYY"); +const result = format(addMonth(date, 1), "Do MMMM YYYY"); // 1st September 2017 ``` - - ## ES6 modules and CommonJS -To get full benefit from [tree shaking](https://webpack.js.org/guides/tree-shaking/) you can import from `ts-date/esm/*` -See [resolve.alias](https://webpack.js.org/configuration/resolve/#resolve-alias) for Webpack, -or [rollup-plugin-alias](https://github.com/rollup/rollup-plugin-alias) for Rollup - +To get full benefit from [tree shaking](https://webpack.js.org/guides/tree-shaking/) you can import from `ts-date/esm/*` +See [resolve.alias](https://webpack.js.org/configuration/resolve/#resolve-alias) for Webpack, +or [rollup-plugin-alias](https://github.com/rollup/rollup-plugin-alias) for Rollup ## Locales There is different import for each locale: `ts-date/locale/*` For now there is `en` and `ru` -:warning: Directly `ts-date` exports without any locale - +:warning: Directly `ts-date` exports without any locale +## Compare type system with `momentjs` +With `momentjs` you have no warnings here: -## Compare type system with `momentjs` -With `momentjs` you have no warnings here: ```js -import * as moment from 'moment'; +import * as moment from "moment"; function someDateProcessing(isoDate: string): string { - const m = moment(isoDate); - return m.format('YYYY-MM-DD'); // "Invalid date" + const m = moment(isoDate); + return m.format("YYYY-MM-DD"); // "Invalid date" } -someDateProcessing('The Day After Tomorrow'); +someDateProcessing("The Day After Tomorrow"); ``` -With **ts-date** you forced to make checks or add a `null` as posible result +With **ts-date** you forced to make checks or add a `null` as posible result + ```js -import { format, parseIso } from 'ts-date'; +import { format, parseIso } from "ts-date"; function dateProcessingWithSafetyBelt(pleaseIsoDate: string): string { - const d = parseIso(pleaseIsoDate); // Type is 'ValidDate | null' - - // Warning here: - return format(d, 'YYYY-MM-DD'); // Type is 'string | null' - // TS2322:Type 'string | null' is not assignable to type 'string'. - - // To avoid warning should: - // - change function type to 'string | null' - // - throw error - // - or return another magic string explicitly - if (d === null) { - throw new TypeError(`ISO 8601 format expected`); - } - d; // Type is 'ValidDate' - return format(d, 'YYYY-MM-DD'); // Type is 'string' + const d = parseIso(pleaseIsoDate); // Type is 'ValidDate | null' + + // Warning here: + return format(d, "YYYY-MM-DD"); // Type is 'string | null' + // TS2322:Type 'string | null' is not assignable to type 'string'. + + // To avoid warning should: + // - change function type to 'string | null' + // - throw error + // - or return another magic string explicitly + if (d === null) { + throw new TypeError(`ISO 8601 format expected`); + } + d; // Type is 'ValidDate' + return format(d, "YYYY-MM-DD"); // Type is 'string' } ``` - - - ## ValidDate type + `ValidDate` type – the immutable wrapper type under `Date`, actually `ValidDate` becomes a `Date` after compile `ValidDate` creation occurs through methods which will return `null` instead of `Date("Invalid Date")` + ```js -import { parseIso, format } from 'ts-date/locale/en'; -const d = parseIso('2021-12-21'); // ValidDate | null -format(d, 'Do MMMM YYYY'); // Type is 'string | null' +import { parseIso, format } from "ts-date/locale/en"; +const d = parseIso("2021-12-21"); // ValidDate | null +format(d, "Do MMMM YYYY"); // Type is 'string | null' if (d) { - d; // ValidDate - format(d, 'Do MMMM YYYY'); // Type is 'string' - // no "Invalid Date" option here + d; // ValidDate + format(d, "Do MMMM YYYY"); // Type is 'string' + // no "Invalid Date" option here } else { - d; // null - format(null, 'Do MMMM YYYY'); // Type is 'null' + d; // null + format(null, "Do MMMM YYYY"); // Type is 'null' } ``` + Since `ValidDate` is `Date`, you can use some `Date` methods: + ```js -const d = parseIso('2021-12-21'); +const d = parseIso("2021-12-21"); if (d) { - d.getDate() // 21 + d.getDate(); // 21 } ``` + To make `ValidDate` immutable, all methods for `Date` mutation are banned in type: + ```js -d.setDate(0) // Typescript will warn here +d.setDate(0); // Typescript will warn here ``` - - ## Browser support [![Build Status](https://saucelabs.com/browser-matrix/standy.svg)](https://saucelabs.com/beta/builds/5fe7f4ad4e9c4f87842ea80e0a497eb5) @@ -116,160 +112,165 @@ d.setDate(0) // Typescript will warn here Should work fine without polyfills in every modern browser and IE9+ Chrome 5+, Edge, Firefox 4.0+, IE 9+, Opera 12+, Safari 5+ - - # Api **NOTE**: Mostly methods will return `null` for `null` or invalid input ### Tokens -This tokens can be used for parsing and formatting dates: -| token | meaning | example | -|:-------------|:---------------------|:------------------| -| YYYY | 4 digit year | 2018 | -| YY | 2 digit year | 18 | -| MMMM | month | January, December | -| MMM | short month | Jan, Dec | -| MM, M | month number | 01, 1 | -| DD, D | day of month | 02, 2 | -| dddd | day of week | Friday, Sunday | -| ddd | short day of week | Fri, Sun | -| dd | 2 letter day of week | Fr, Su | -| HH, H | hour-24 | 0..24 | -| hh, h | hour-12 | 0..12 | -| A | meridiem | AM, PM | -| a | meridiem | am, pm | -| aa | meridiem | a.m., p.m. | -| mm, m | minute | 0..59 | -| ss, s | second | 0..59 | -| SSS, SS, S | millisecond | 0..999 | -| Z | timezone | -12:00..+12:00 | -| ZZ | timezone | -1200..+1200 | +This tokens can be used for parsing and formatting dates: +| token | meaning | example | +| :--------- | :------------------- | :---------------- | +| YYYY | 4 digit year | 2018 | +| YY | 2 digit year | 18 | +| MMMM | month | January, December | +| MMM | short month | Jan, Dec | +| MM, M | month number | 01, 1 | +| DD, D | day of month | 02, 2 | +| dddd | day of week | Friday, Sunday | +| ddd | short day of week | Fri, Sun | +| dd | 2 letter day of week | Fr, Su | +| HH, H | hour-24 | 0..24 | +| hh, h | hour-12 | 0..12 | +| A | meridiem | AM, PM | +| a | meridiem | am, pm | +| aa | meridiem | a.m., p.m. | +| mm, m | minute | 0..59 | +| ss, s | second | 0..59 | +| SSS, SS, S | millisecond | 0..999 | +| Z | timezone | -12:00..+12:00 | +| ZZ | timezone | -1200..+1200 | ## Date parsing and creation - ### parse(date: string, template: string): ValidDate | null + Parse date by template using tokens + ```js -parse('2018 July 12', 'YYYY MMMM D'); // = Date(2018-07-12) +parse("2018 July 12", "YYYY MMMM D"); // = Date(2018-07-12) ``` - ### parseIso(dateIso: string): ValidDate | null + Parse most of ISO 8601 formats + ```js -parseIso('2018-06-12T19:30'); // = Date(2018-06-12T19:30) +parseIso("2018-06-12T19:30"); // = Date(2018-06-12T19:30) ``` - ### fromDate(date: Date | number): ValidDate | null + Creates `ValidDate` from `Date` object Similar to `isValidDate`, but returns new valid date or null - ### newValidDate(...args): ValidDate -Create `ValidDate`, same signature as `new Date(...)` +Create `ValidDate`, same signature as `new Date(...)` ### isValidDate(date: Date): boolean -Type guard for `ValidDate`, returns `true` if date is valid - +Type guard for `ValidDate`, returns `true` if date is valid ## Date formatting - ### format(date: ValidDate, template: string): string + Format by template using tokens + ```js -format(new Date('2018-07-12'), 'YYYY MMMM D'); // = '2018 July 12' +format(new Date("2018-07-12"), "YYYY MMMM D"); // = '2018 July 12' ``` - ### formatDateIso(ValidDate): string -Format as `YYYY-MM-DD` ISO string +Format as `YYYY-MM-DD` ISO string ### formatDateTimeIso(ValidDate): string -Format as `YYYY-MM-DD[T]HH:MM` ISO string +Format as `YYYY-MM-DD[T]HH:MM` ISO string ### formatLocalIso(ValidDate): string -Format as `YYYY-MM-DD[T]HH:MM:SS.sss` ISO string - +Format as `YYYY-MM-DD[T]HH:MM:SS.sss` ISO string ## Date manipulations - ### add\[Units](date: ValidDate, amount: number): ValidDate + Adding fixed amount of units. First argument should be `ValidDate`, `null` or either. Result will be same type as input ```js -addMilliseconds -addSeconds -addMinutes -addHours -addDate -addMonth -addYear +addMilliseconds; +addSeconds; +addMinutes; +addHours; +addDate; +addMonth; +addYear; ``` - ### reset\[Units](date: ValidDate): ValidDate + Reset to default all units after method's name unit + ```js -resetYear -resetMonth -resetISOWeek -resetDate -resetHours -resetMinutes -resetSeconds +resetYear; +resetMonth; +resetISOWeek; +resetDate; +resetHours; +resetMinutes; +resetSeconds; ``` + Example: + ```js -resetYear(newValidDate(2017, 5, 30, 12, 30)) // = Date(2017-01-01) +resetYear(newValidDate(2017, 5, 30, 12, 30)); // = Date(2017-01-01) ``` - ### diff\[Units](d1: ValidDate, d2: ValidDate): number + Return whole amount of [units] between first and second date, same as you expect from `d1 - d2` In case one of arguments is `null` or `Date("Invalid Date")`, result is `null` + ```js -diffMilliseconds -diffSeconds -diffMinutes -diffHours -diffDate -diffMonth -diffYear +diffMilliseconds; +diffSeconds; +diffMinutes; +diffHours; +diffDate; +diffMonth; +diffYear; ``` + Example: + ```js -diffDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 12)) // = 9 -diffDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 20)) // = 8 +diffDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 12)); // = 9 +diffDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 20)); // = 8 ``` - ### diffCalendar[Units](d1: ValidDate, d2: ValidDate): number + Enumerate units between dates + ```js -diffCalendarDate -diffCalendarMonth -diffCalendarYear +diffCalendarDate; +diffCalendarMonth; +diffCalendarYear; ``` + Example: + ```js -diffCalendarDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 12)) // = 9 -diffCalendarDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 20)) // = 9 <-- different from diffDate +diffCalendarDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 12)); // = 9 +diffCalendarDate(new Date(2018, 5, 10, 18), new Date(2018, 5, 1, 20)); // = 9 <-- different from diffDate function isToday(d: ValidDate) { - return diffCalendarDate(d, newValidDate()) === 0; + return diffCalendarDate(d, newValidDate()) === 0; } ``` - diff --git a/package.json b/package.json index 24f9894..dc224c7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "rollup -c", "dev": "rollup -c -w", - "prettify": "prettier --write \"**/*.ts\"", + "prettify": "prettier --write \"**/*.{ts,js,yml,md}\"", "release": "standard-version", "release-canary": "standard-version --prerelease canary", "github-release": "conventional-github-releaser", diff --git a/src/format/create-format.ts b/src/format/create-format.ts index c56e886..c87b259 100644 --- a/src/format/create-format.ts +++ b/src/format/create-format.ts @@ -6,7 +6,7 @@ import {isValidDate} from '../default-exports'; type Token = string | Formatter; function splitToTokens(template: string, formatters: FormatterObj): Token[] { - const RX_TOKENS = /*@__PURE__*/tokensRx(formatters); + const RX_TOKENS = /*@__PURE__*/ tokensRx(formatters); const tokens = template.match(RX_TOKENS) as string[]; // this regexp cant fail because of "|." diff --git a/src/format/format.test.ts b/src/format/format.test.ts index 5ab476c..3412042 100644 --- a/src/format/format.test.ts +++ b/src/format/format.test.ts @@ -79,7 +79,7 @@ describe('format', function() { it('correct extending format', function() { extendFormat({ season: date => { - const index = Math.floor((date.getMonth() + 1) % 12 / 3); + const index = Math.floor(((date.getMonth() + 1) % 12) / 3); return ['winter', 'spring', 'summer', 'autumn'][index]; }, }); @@ -100,7 +100,7 @@ describe('format', function() { ]; for (let i = 0; i < checks.length; i++) { const [date, correctResult] = checks[i]; - assert.equal(format(date, template), correctResult) + assert.equal(format(date, template), correctResult); } }); }); diff --git a/src/parse/create-parse.ts b/src/parse/create-parse.ts index 6091394..82ad3d9 100644 --- a/src/parse/create-parse.ts +++ b/src/parse/create-parse.ts @@ -10,7 +10,7 @@ function escapeRegExp(text: string) { } export function createParse(parsers: ParserObj): ParseByTemplateFn { - const RX_TOKENS = /*@__PURE__*/tokensRx(parsers); + const RX_TOKENS = /*@__PURE__*/ tokensRx(parsers); return function parse(dateStr: string, template: string): ValidDate | null { const tokens = template.match(RX_TOKENS) as string[]; diff --git a/src/utils/test-utils.ts b/src/utils/test-utils.ts index 84534a4..2e66d28 100644 --- a/src/utils/test-utils.ts +++ b/src/utils/test-utils.ts @@ -18,4 +18,3 @@ export function randomTimezone(): string { } return ''; } -