├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── constants.ts
├── convert.ts
├── convert_test.ts
├── datetime.ts
├── datetime_test.ts
├── diff.ts
├── docs
├── _config.yml
├── format.md
├── index.md
├── math.md
├── parse.md
├── quick_tour.md
├── timezone.md
└── utils.md
├── format.ts
├── format_test.ts
├── local_time.ts
├── locale.ts
├── locale_test.ts
├── mod.ts
├── parse_date.ts
├── parse_date_test.ts
├── timezone.ts
├── timezone_test.ts
├── types.ts
├── utils.ts
├── zoned_time.ts
└── zoned_time_test.ts
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: tests (${{ matrix.os }})
8 | runs-on: ${{ matrix.os }}
9 | strategy:
10 | matrix:
11 | os: [ubuntu-latest, windows-latest, macOS-latest]
12 | fail-fast: true
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: download deno
16 | uses: denoland/setup-deno@main
17 | with:
18 | deno-version: "1.11.0"
19 |
20 | - name: check format
21 | if: matrix.os == 'ubuntu-latest'
22 | run: deno fmt --check
23 |
24 | - name: check linting
25 | if: matrix.os == 'ubuntu-latest'
26 | run: deno lint
27 |
28 | - name: run tests
29 | run: deno test
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vim
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.eol": "\n",
3 | "files.trimTrailingWhitespace": true,
4 | "typescript.format.semicolons": "remove",
5 | "typescript.preferences.quoteStyle": "single",
6 | "[javascript]": {
7 | "editor.defaultFormatter": "vscode.typescript-language-features",
8 | "editor.formatOnSave": false
9 | },
10 | "[typescript]": {
11 | "editor.defaultFormatter": "vscode.typescript-language-features",
12 | "editor.formatOnSave": true,
13 | "editor.codeActionsOnSave": {
14 | "source.organizeImports": true
15 | }
16 | },
17 | "[typescriptreact]": {
18 | "editor.defaultFormatter": "vscode.typescript-language-features",
19 | "editor.formatOnSave": true,
20 | "editor.codeActionsOnSave": {
21 | "source.organizeImports": true
22 | }
23 | },
24 | "[rust]": {
25 | "editor.defaultFormatter": "rust-lang.rust",
26 | "editor.formatOnSave": true
27 | },
28 | "deno.enable": true,
29 | "deno.unstable": true,
30 | "deno.suggest.imports.hosts": { "http://deno.land": true }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Takuro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ptera
2 |
3 | [](https://github.com/Tak-Iwamoto/ptera/actions/workflows/ci.yml)
4 | [](https://opensource.org/licenses/MIT)
5 |
6 |

7 |
8 | Ptera is DateTime library for Deno.
9 |
10 | Fully Written in Deno.
11 |
12 | Heavily inspired by the great libraries
13 | [Luxon](https://github.com/moment/luxon),
14 | [Day.js](https://github.com/iamkun/dayjs),
15 | [Moment.js](https://github.com/moment/moment).
16 |
17 | ## Features
18 |
19 | - Immutable, chainable
20 | - Parsing and Formatting
21 | - Timezone and Intl support
22 |
23 | ## Getting Started
24 |
25 | ### API
26 |
27 | ```typescript
28 | import { datetime } from "https://deno.land/x/ptera/mod.ts";
29 |
30 | datetime("2021-06-30T21:15:30.200");
31 |
32 | // timezone
33 | datetime().toZonedTime("Asia/Tokyo");
34 |
35 | // locale
36 | datetime().setLocale("fr");
37 |
38 | // add, subtract
39 | datetime().add({ year: 1 });
40 | datetime().subtract({ day: 1 });
41 | ```
42 |
43 | ### Documentation
44 |
45 | https://tak-iwamoto.github.io/ptera/
46 |
--------------------------------------------------------------------------------
/constants.ts:
--------------------------------------------------------------------------------
1 | export const MILLISECONDS_IN_DAY = 86400000;
2 | export const MILLISECONDS_IN_HOUR = 3600000;
3 | export const MILLISECONDS_IN_MINUTE = 60000;
4 |
--------------------------------------------------------------------------------
/convert.ts:
--------------------------------------------------------------------------------
1 | import { MILLISECONDS_IN_DAY } from "./constants.ts";
2 | import { adjustedTS } from "./diff.ts";
3 | import { DateObj } from "./types.ts";
4 |
5 | export function dateToArray(
6 | dateObj: DateObj,
7 | option?: { jsMonth: boolean },
8 | ): [
9 | number,
10 | number,
11 | number,
12 | number,
13 | number,
14 | number,
15 | number,
16 | ] {
17 | const { year, month, day, hour, minute, second, millisecond } = dateObj;
18 | return [
19 | year,
20 | option?.jsMonth ? month - 1 : month,
21 | day ?? 0,
22 | hour ?? 0,
23 | minute ?? 0,
24 | second ?? 0,
25 | millisecond ?? 0,
26 | ];
27 | }
28 |
29 | export function dateToTS(dateObj: DateObj): number {
30 | return Date.UTC(...dateToArray(dateObj, { jsMonth: true }));
31 | }
32 |
33 | export function dateToJSDate(
34 | date: DateObj,
35 | ): Date {
36 | return new Date(dateToTS(date));
37 | }
38 |
39 | export function jsDateToDate(jsDate: Date): DateObj {
40 | return {
41 | year: jsDate.getUTCFullYear(),
42 | month: jsDate.getUTCMonth() + 1,
43 | day: jsDate.getUTCDate(),
44 | hour: jsDate.getUTCHours(),
45 | minute: jsDate.getUTCMinutes(),
46 | second: jsDate.getUTCSeconds(),
47 | millisecond: jsDate.getUTCMilliseconds(),
48 | };
49 | }
50 |
51 | export function arrayToDate(dateArray: number[]): DateObj {
52 | const result = [NaN, 1, 1, 0, 0, 0, 0];
53 | for (const [i, v] of dateArray.entries()) {
54 | result[i] = v;
55 | }
56 | const year = result[0];
57 | const month = result[1];
58 | const day = result[2];
59 | const hour = result[3];
60 | const minute = result[4];
61 | const second = result[5];
62 | const millisecond = result[6];
63 | return { year, month, day, hour, minute, second, millisecond };
64 | }
65 |
66 | export function tsToDate(ts: number, option?: { isLocal: boolean }): DateObj {
67 | const date = new Date(ts);
68 | if (option && option.isLocal) {
69 | return {
70 | year: date.getFullYear(),
71 | month: date.getMonth() + 1,
72 | day: date.getDate(),
73 | hour: date.getHours(),
74 | minute: date.getMinutes(),
75 | second: date.getSeconds(),
76 | millisecond: date.getMilliseconds(),
77 | };
78 | }
79 | return {
80 | year: date.getUTCFullYear(),
81 | month: date.getUTCMonth() + 1,
82 | day: date.getUTCDate(),
83 | hour: date.getUTCHours(),
84 | minute: date.getUTCMinutes(),
85 | second: date.getUTCSeconds(),
86 | millisecond: date.getUTCMilliseconds(),
87 | };
88 | }
89 |
90 | export function dayOfYearToDate(dayOfYear: number, year: number) {
91 | const ts = adjustedTS({
92 | year,
93 | month: 1,
94 | day: 1,
95 | hour: 0,
96 | minute: 0,
97 | second: 0,
98 | millisecond: 0,
99 | }, {
100 | day: dayOfYear - 1,
101 | }, { positive: true });
102 | return tsToDate(ts);
103 | }
104 |
105 | export function dateToWeekDay(dateObj: DateObj): number {
106 | const jsDate = dateToJSDate(dateObj);
107 | return jsDate.getUTCDay();
108 | }
109 |
110 | export function dateToDayOfYear(dateObj: DateObj): number {
111 | const jsDate = dateToJSDate(dateObj);
112 | const utc = jsDate.getTime();
113 |
114 | jsDate.setUTCMonth(0, 1);
115 | jsDate.setUTCHours(0, 0, 0, 0);
116 | const startOfYear = jsDate.getTime();
117 |
118 | const diff = utc - startOfYear;
119 | return Math.floor(diff / MILLISECONDS_IN_DAY) + 1;
120 | }
121 |
--------------------------------------------------------------------------------
/convert_test.ts:
--------------------------------------------------------------------------------
1 | import { assertEquals } from "https://deno.land/std@0.95.0/testing/asserts.ts";
2 | import { arrayToDate } from "./convert.ts";
3 |
4 | Deno.test("arrayToDate", () => {
5 | const tests = [
6 | {
7 | input: [2021],
8 | expected: {
9 | year: 2021,
10 | month: 1,
11 | day: 1,
12 | hour: 0,
13 | minute: 0,
14 | second: 0,
15 | millisecond: 0,
16 | },
17 | },
18 | {
19 | input: [2021, 12],
20 | expected: {
21 | year: 2021,
22 | month: 12,
23 | day: 1,
24 | hour: 0,
25 | minute: 0,
26 | second: 0,
27 | millisecond: 0,
28 | },
29 | },
30 | {
31 | input: [2021, 12, 15],
32 | expected: {
33 | year: 2021,
34 | month: 12,
35 | day: 15,
36 | hour: 0,
37 | minute: 0,
38 | second: 0,
39 | millisecond: 0,
40 | },
41 | },
42 | {
43 | input: [2021, 12, 15, 13],
44 | expected: {
45 | year: 2021,
46 | month: 12,
47 | day: 15,
48 | hour: 13,
49 | minute: 0,
50 | second: 0,
51 | millisecond: 0,
52 | },
53 | },
54 | {
55 | input: [2021, 12, 15, 13, 30],
56 | expected: {
57 | year: 2021,
58 | month: 12,
59 | day: 15,
60 | hour: 13,
61 | minute: 30,
62 | second: 0,
63 | millisecond: 0,
64 | },
65 | },
66 | {
67 | input: [2021, 12, 15, 13, 30, 40],
68 | expected: {
69 | year: 2021,
70 | month: 12,
71 | day: 15,
72 | hour: 13,
73 | minute: 30,
74 | second: 40,
75 | millisecond: 0,
76 | },
77 | },
78 | {
79 | input: [2021, 12, 15, 13, 30, 40, 10],
80 | expected: {
81 | year: 2021,
82 | month: 12,
83 | day: 15,
84 | hour: 13,
85 | minute: 30,
86 | second: 40,
87 | millisecond: 10,
88 | },
89 | },
90 | {
91 | input: [2021, 12, 15, 13, 30, 40, 10, 30],
92 | expected: {
93 | year: 2021,
94 | month: 12,
95 | day: 15,
96 | hour: 13,
97 | minute: 30,
98 | second: 40,
99 | millisecond: 10,
100 | },
101 | },
102 | ];
103 |
104 | tests.forEach((t) => {
105 | assertEquals(arrayToDate(t.input), t.expected);
106 | });
107 | });
108 |
--------------------------------------------------------------------------------
/datetime.ts:
--------------------------------------------------------------------------------
1 | import { adjustedTS } from "./diff.ts";
2 | import { formatDate, formatDateObj } from "./format.ts";
3 | import { getLocalName } from "./local_time.ts";
4 | import { tzOffset } from "./timezone.ts";
5 | import { toOtherZonedTime, zonedTimeToUTC } from "./zoned_time.ts";
6 | import { Locale } from "./locale.ts";
7 | import { parseDateStr, parseISO } from "./parse_date.ts";
8 | import {
9 | arrayToDate,
10 | dateToArray,
11 | dateToDayOfYear,
12 | dateToJSDate,
13 | dateToTS,
14 | dateToWeekDay,
15 | tsToDate,
16 | } from "./convert.ts";
17 | import {
18 | daysInMonth,
19 | INVALID_DATE,
20 | isLeapYear,
21 | isValidDate,
22 | weeksInWeekYear,
23 | } from "./utils.ts";
24 | import { DateArray, DateDiff, DateObj, Option, Timezone } from "./types.ts";
25 | import {
26 | MILLISECONDS_IN_DAY,
27 | MILLISECONDS_IN_HOUR,
28 | MILLISECONDS_IN_MINUTE,
29 | } from "./constants.ts";
30 |
31 | export type DateArg = Partial | Date | number[] | string | number;
32 |
33 | function isDateObj(arg: DateArg): arg is DateObj {
34 | return (arg as DateObj).year !== undefined;
35 | }
36 |
37 | function isArray(arg: DateArg): arg is number[] {
38 | return (Array.isArray(arg));
39 | }
40 |
41 | function parseArg(date: DateArg): DateObj {
42 | if (typeof date === "number") {
43 | return tsToDate(date, { isLocal: true });
44 | }
45 |
46 | if (date instanceof Date) {
47 | return tsToDate(date.getTime(), { isLocal: true });
48 | }
49 |
50 | if (isDateObj(date)) {
51 | return date;
52 | }
53 |
54 | if (isArray(date)) {
55 | return arrayToDate(date);
56 | }
57 |
58 | if (typeof date === "string") {
59 | const parsed = parseISO(date);
60 | const offset = parsed.offsetMillisec;
61 | if (!offset || offset === 0) return parsed;
62 |
63 | const normalizeSign = offset > 0 ? false : true;
64 | return tsToDate(
65 | adjustedTS(parsed, { millisecond: offset }, { positive: normalizeSign }),
66 | );
67 | }
68 |
69 | return INVALID_DATE;
70 | }
71 |
72 | export type DateTimeOption = Omit