@invariant--labs/foundation 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pnp.cjs +22192 -0
- package/.pnp.loader.mjs +2126 -0
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +527 -0
- package/README.md +3 -0
- package/eslint.config.mjs +52 -0
- package/invariant.json +22 -0
- package/jest/jest.config.base.ts +30 -0
- package/jest/jest.spec.base.ts +7 -0
- package/jest.config.js +49 -0
- package/package.json +99 -0
- package/src/core/application/index.ts +1 -0
- package/src/core/application/persistence/index.ts +3 -0
- package/src/core/application/persistence/query-builder.ts +2 -0
- package/src/core/application/persistence/read-projection.ts +2 -0
- package/src/core/application/persistence/repository.ts +23 -0
- package/src/core/domain/entities/aggregate-root.ts +34 -0
- package/src/core/domain/entities/entity.spec.ts +43 -0
- package/src/core/domain/entities/entity.ts +29 -0
- package/src/core/domain/entities/identifier.spec.ts +34 -0
- package/src/core/domain/entities/identifier.ts +16 -0
- package/src/core/domain/entities/index.ts +5 -0
- package/src/core/domain/entities/projection.ts +7 -0
- package/src/core/domain/entities/unique-entity-id.ts +9 -0
- package/src/core/domain/events/domain-event.ts +7 -0
- package/src/core/domain/events/index.ts +1 -0
- package/src/core/domain/index.ts +3 -0
- package/src/core/domain/value-objects/index.ts +2 -0
- package/src/core/domain/value-objects/range.ts +4 -0
- package/src/core/domain/value-objects/value-object.spec.ts +45 -0
- package/src/core/domain/value-objects/value-object.ts +17 -0
- package/src/core/errors/command-failure.error.spec.ts +30 -0
- package/src/core/errors/command-failure.error.ts +9 -0
- package/src/core/errors/command-filter.error.ts +3 -0
- package/src/core/errors/detailed.error.ts +25 -0
- package/src/core/errors/domain.error.spec.ts +27 -0
- package/src/core/errors/domain.error.ts +9 -0
- package/src/core/errors/entity-not-found.error.spec.ts +32 -0
- package/src/core/errors/entity-not-found.error.ts +9 -0
- package/src/core/errors/fake-implementation.error.spec.ts +27 -0
- package/src/core/errors/fake-implementation.error.ts +15 -0
- package/src/core/errors/index.ts +8 -0
- package/src/core/errors/query-failure.error.ts +8 -0
- package/src/core/errors/too-many-results.error.ts +3 -0
- package/src/core/index.ts +4 -0
- package/src/core/infrastructure/config/config.provider.ts +78 -0
- package/src/core/infrastructure/config/config.service.ts +25 -0
- package/src/core/infrastructure/config/index.ts +2 -0
- package/src/core/infrastructure/messaging/fake/fake-messaging.service.ts +17 -0
- package/src/core/infrastructure/messaging/fake/fake-queue-manager.service.ts +9 -0
- package/src/core/infrastructure/messaging/fake/fake-queue-messaging.service.ts +9 -0
- package/src/core/infrastructure/messaging/fake/index.ts +3 -0
- package/src/core/infrastructure/messaging/index.ts +6 -0
- package/src/core/infrastructure/messaging/messaging.service.ts +3 -0
- package/src/core/infrastructure/messaging/queue-manager.service.ts +6 -0
- package/src/core/infrastructure/messaging/queue-messaging.service.ts +3 -0
- package/src/core/infrastructure/messaging/rabbitmq/index.ts +2 -0
- package/src/core/infrastructure/messaging/rabbitmq/rabbit-messaging.service.ts +11 -0
- package/src/core/infrastructure/messaging/rabbitmq/rabbit-queue-messaging.service.ts +11 -0
- package/src/core/infrastructure/messaging/types.ts +28 -0
- package/src/core/infrastructure/persistence/errors/index.ts +2 -0
- package/src/core/infrastructure/persistence/errors/model-to-entity-conversion.error.ts +9 -0
- package/src/core/infrastructure/persistence/errors/persistence.error.ts +8 -0
- package/src/core/infrastructure/persistence/in-memory/fake.repository.ts +79 -0
- package/src/core/infrastructure/persistence/in-memory/in-memory.query-builder.ts +4 -0
- package/src/core/infrastructure/persistence/in-memory/in-memory.repository.spec.ts +50 -0
- package/src/core/infrastructure/persistence/in-memory/in-memory.repository.ts +81 -0
- package/src/core/infrastructure/persistence/in-memory/index.ts +3 -0
- package/src/core/infrastructure/persistence/index.ts +4 -0
- package/src/core/infrastructure/persistence/read-side/in-memory.query-builder.ts +4 -0
- package/src/core/infrastructure/persistence/read-side/index.ts +1 -0
- package/src/core/infrastructure/persistence/read-side/knex/index.ts +2 -0
- package/src/core/infrastructure/persistence/read-side/knex/knex-types.definition.ts +13 -0
- package/src/core/infrastructure/persistence/read-side/knex/knex.query-builder.ts +70 -0
- package/src/core/infrastructure/persistence/read-side/knex-query-builder.ts +70 -0
- package/src/core/infrastructure/persistence/read-side/knex-types.definition.ts +13 -0
- package/src/core/infrastructure/persistence/write-side/aggregate-typeorm-repository.ts +87 -0
- package/src/core/infrastructure/persistence/write-side/entity-typeorm-repository.ts +64 -0
- package/src/core/infrastructure/persistence/write-side/in-memory.repository.ts +82 -0
- package/src/core/infrastructure/persistence/write-side/index.ts +1 -0
- package/src/core/infrastructure/persistence/write-side/model-attributes.ts +4 -0
- package/src/core/infrastructure/persistence/write-side/orm-embedded-mapper.ts +11 -0
- package/src/core/infrastructure/persistence/write-side/orm-mapper.ts +11 -0
- package/src/core/infrastructure/persistence/write-side/typeorm/aggregate-typeorm.repository.ts +87 -0
- package/src/core/infrastructure/persistence/write-side/typeorm/entity-typeorm.repository.ts +64 -0
- package/src/core/infrastructure/persistence/write-side/typeorm/index.ts +5 -0
- package/src/core/infrastructure/persistence/write-side/typeorm/model-attributes.ts +4 -0
- package/src/core/infrastructure/persistence/write-side/typeorm/orm-embedded.mapper.ts +11 -0
- package/src/core/infrastructure/persistence/write-side/typeorm/orm.mapper.ts +11 -0
- package/src/core/types/architecture-layer.ts +52 -0
- package/src/core/types/array-element.ts +5 -0
- package/src/core/types/index.ts +2 -0
- package/src/index.ts +30 -0
- package/src/modules/config/config.module.ts +9 -0
- package/src/modules/config/index.ts +2 -0
- package/src/modules/graphql/index.ts +1 -0
- package/src/modules/graphql/paginated-response.object-type.ts +23 -0
- package/src/modules/healthcheck/healthcheck-out.dto.ts +43 -0
- package/src/modules/healthcheck/index.ts +1 -0
- package/src/modules/knex/index.ts +3 -0
- package/src/modules/knex/knex-core.module.ts +30 -0
- package/src/modules/knex/knex.decorator.ts +5 -0
- package/src/modules/knex/knex.interface.ts +12 -0
- package/src/modules/knex/knex.module.ts +14 -0
- package/src/modules/knex/knex.token.ts +2 -0
- package/src/modules/logger/app-logger.ts +28 -0
- package/src/modules/logger/index.ts +5 -0
- package/src/modules/logger/log.ts +26 -0
- package/src/modules/logger/logger.module.ts +47 -0
- package/src/modules/logger/logger.service.ts +131 -0
- package/src/modules/logger/transports/console-color.transport.ts +41 -0
- package/src/modules/logger/transports/console-json.transport.ts +23 -0
- package/src/modules/logger/transports/console.transport.ts +10 -0
- package/src/modules/logger/transports/fake.transport.ts +18 -0
- package/src/modules/logger/transports/index.ts +5 -0
- package/src/modules/logger/transports/logger-transport.ts +22 -0
- package/src/modules/messaging/index.ts +2 -0
- package/src/modules/messaging/messaging.module.ts +92 -0
- package/src/modules/queue/default-queue-name.resolver.ts +5 -0
- package/src/modules/queue/index.ts +3 -0
- package/src/modules/queue/queue.module.ts +73 -0
- package/src/modules/queue/rabbit-queue-manager.service.ts +67 -0
- package/src/nestjs/errors/handlers/error-handler.ts +38 -0
- package/src/nestjs/errors/handlers/error-handler.type.ts +23 -0
- package/src/nestjs/errors/handlers/generic-error-handler.ts +59 -0
- package/src/nestjs/errors/handlers/handle-errors.decorator.ts +43 -0
- package/src/nestjs/errors/handlers/index.ts +5 -0
- package/src/nestjs/errors/handlers/validation-error-handler.ts +46 -0
- package/src/nestjs/errors/index.ts +2 -0
- package/src/nestjs/errors/parsers/axios-metadata.parser.ts +21 -0
- package/src/nestjs/errors/parsers/error-metadata.definition.ts +1 -0
- package/src/nestjs/errors/parsers/index.ts +5 -0
- package/src/nestjs/errors/parsers/knex-metadata.parser.ts +18 -0
- package/src/nestjs/errors/parsers/typeorm-metadata.parser.ts +19 -0
- package/src/nestjs/errors/parsers/workos-metadata.parser.ts +17 -0
- package/src/nestjs/http/decorators/index.ts +1 -0
- package/src/nestjs/http/decorators/log-app-ctx.decorator.ts +32 -0
- package/src/nestjs/http/dtos/date-range.dto.ts +15 -0
- package/src/nestjs/http/dtos/index.ts +1 -0
- package/src/nestjs/http/filters/all-exceptions.filter.ts +92 -0
- package/src/nestjs/http/filters/command-failure.filter.ts +12 -0
- package/src/nestjs/http/filters/index.ts +4 -0
- package/src/nestjs/http/filters/rpc-exceptions.filter.ts +10 -0
- package/src/nestjs/http/filters/too-many-results.filter.ts +12 -0
- package/src/nestjs/http/index.ts +6 -0
- package/src/nestjs/http/interceptors/index.ts +2 -0
- package/src/nestjs/http/interceptors/log-app-ctx.interceptor.ts +109 -0
- package/src/nestjs/http/interceptors/serialize-output.interceptor.ts +19 -0
- package/src/nestjs/http/middleware/http-logger.middleware.ts +31 -0
- package/src/nestjs/http/middleware/index.ts +2 -0
- package/src/nestjs/http/middleware/logger.middleware.ts +21 -0
- package/src/nestjs/http/swagger/index.ts +1 -0
- package/src/nestjs/http/swagger/swagger.ts +44 -0
- package/src/nestjs/index.ts +2 -0
- package/src/testing/command-bus.stub.ts +33 -0
- package/src/testing/event-bus.stub.ts +56 -0
- package/src/testing/event-publisher.stub.ts +24 -0
- package/src/testing/fake-logger.ts +20 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/query-bus.stub.ts +27 -0
- package/src/testing/stub.spec.ts +250 -0
- package/src/testing/stub.ts +170 -0
- package/src/testing/stubs/command-bus.stub.ts +33 -0
- package/src/testing/stubs/event-bus.stub.ts +56 -0
- package/src/testing/stubs/event-publisher.stub.ts +24 -0
- package/src/testing/stubs/index.ts +5 -0
- package/src/testing/stubs/query-bus.stub.ts +27 -0
- package/src/testing/stubs/stub.ts +170 -0
- package/src/utils/array.spec.ts +29 -0
- package/src/utils/array.ts +10 -0
- package/src/utils/base64.spec.ts +18 -0
- package/src/utils/base64.ts +6 -0
- package/src/utils/collection.ts +4 -0
- package/src/utils/common.ts +13 -0
- package/src/utils/csv.ts +49 -0
- package/src/utils/date/date-range.ts +4 -0
- package/src/utils/date/date.spec.ts +100 -0
- package/src/utils/date/date.ts +177 -0
- package/src/utils/date/diff.spec.ts +66 -0
- package/src/utils/date/diffYear.spec.ts +23 -0
- package/src/utils/date/fillMissingRangeValues.spec.ts +523 -0
- package/src/utils/date/groubBy.spec.ts +183 -0
- package/src/utils/date/index.ts +2 -0
- package/src/utils/date/isSame.spec.ts +111 -0
- package/src/utils/file.spec.ts +66 -0
- package/src/utils/file.ts +5 -0
- package/src/utils/hash-key.ts +23 -0
- package/src/utils/index.ts +14 -0
- package/src/utils/invariant.ts +3 -0
- package/src/utils/iso-date.ts +11 -0
- package/src/utils/object.spec.ts +18 -0
- package/src/utils/object.ts +6 -0
- package/src/utils/paginated.ts +36 -0
- package/src/utils/string.spec.ts +10 -0
- package/src/utils/string.ts +19 -0
- package/src/utils/type.ts +9 -0
- package/src/utils/xml.ts +6 -0
- package/src/validation/ensure-array.decorator.ts +5 -0
- package/src/validation/index.ts +7 -0
- package/src/validation/is-iso-date.decorator.spec.ts +29 -0
- package/src/validation/is-iso-date.decorator.ts +30 -0
- package/src/validation/is-less-than-or-equal.decorator.spec.ts +30 -0
- package/src/validation/is-less-than-or-equal.decorator.ts +52 -0
- package/src/validation/is-less-than.decorator.spec.ts +36 -0
- package/src/validation/is-less-than.decorator.ts +52 -0
- package/src/validation/is-more-than-or-equal.decorator.spec.ts +30 -0
- package/src/validation/is-more-than-or-equal.decorator.ts +52 -0
- package/src/validation/is-more-than.decorator.spec.ts +36 -0
- package/src/validation/is-more-than.decorator.ts +52 -0
- package/src/validation/is-time-string.decorator.spec.ts +35 -0
- package/src/validation/is-time-string.decorator.ts +29 -0
- package/tsconfig.build.json +6 -0
- package/tsconfig.json +34 -0
- package/tsconfig.spec.json +14 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { arrayContainsDate, compareDates, composeDate, daysBetweenDates, isValidDate, makeDateRange } from "./date";
|
|
2
|
+
|
|
3
|
+
describe("makeDateRange", () => {
|
|
4
|
+
it("should include from and to", () => {
|
|
5
|
+
const from = new Date("2021-07-13");
|
|
6
|
+
const to = new Date("2021-07-15");
|
|
7
|
+
|
|
8
|
+
expect(makeDateRange(from, to)).toEqual([new Date("2021-07-13"), new Date("2021-07-14"), new Date("2021-07-15")]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should not make a range if to is before from", () => {
|
|
12
|
+
const from = new Date("2021-07-29");
|
|
13
|
+
const to = new Date("2021-07-28");
|
|
14
|
+
|
|
15
|
+
expect(makeDateRange(from, to)).toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("compareDates", () => {
|
|
20
|
+
it("should make correct comparisons", () => {
|
|
21
|
+
expect(compareDates(new Date("2021-06-15"), new Date("2021-06-17"))).toBeLessThan(0);
|
|
22
|
+
expect(compareDates(new Date("2021-06-15"), new Date("2021-06-13"))).toBeGreaterThan(0);
|
|
23
|
+
expect(compareDates(new Date("2021-06-15"), new Date("2021-06-15"))).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should order an array of dates ASC", () => {
|
|
27
|
+
const array = [new Date("2021-06-17"), new Date("2021-06-13"), new Date("2021-06-19"), new Date("2021-06-15")];
|
|
28
|
+
|
|
29
|
+
expect(array.sort(compareDates)).toEqual([
|
|
30
|
+
new Date("2021-06-13"),
|
|
31
|
+
new Date("2021-06-15"),
|
|
32
|
+
new Date("2021-06-17"),
|
|
33
|
+
new Date("2021-06-19"),
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("arrayContainsDate", () => {
|
|
39
|
+
it("should find a date in an array of dates", () => {
|
|
40
|
+
const date = new Date("2021-06-15");
|
|
41
|
+
const array = [new Date("2021-06-13"), new Date("2021-06-15"), new Date("2021-06-17")];
|
|
42
|
+
|
|
43
|
+
expect(arrayContainsDate(array, date)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should not find a date if no date is the same", () => {
|
|
47
|
+
const date = new Date("2021-06-15");
|
|
48
|
+
const array = [new Date("2021-06-13"), new Date("2021-06-15T22:00:00"), new Date("2021-06-17")];
|
|
49
|
+
|
|
50
|
+
expect(arrayContainsDate(array, date)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("daysBetween dates", () => {
|
|
55
|
+
it("sould compute the number of days between dates", () => {
|
|
56
|
+
const date1 = new Date("2021-06-15");
|
|
57
|
+
const date2 = new Date("2021-06-11");
|
|
58
|
+
|
|
59
|
+
expect(daysBetweenDates(date1, date2)).toBe(4);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should floor the result of the computation", () => {
|
|
63
|
+
const date1 = new Date("2021-06-15T12:00:00.000Z");
|
|
64
|
+
const date2 = new Date("2021-06-11");
|
|
65
|
+
|
|
66
|
+
expect(daysBetweenDates(date1, date2)).toBe(4);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("composeDate", () => {
|
|
71
|
+
describe("should compose a date from a string and a timezone", () => {
|
|
72
|
+
it("Asia/Ho_Chi_Minh", () => {
|
|
73
|
+
const dateStr = "2021-06-15 12:00:00";
|
|
74
|
+
const format = "YYYY-MM-DD HH:mm:ss";
|
|
75
|
+
const timezone = "Asia/Ho_Chi_Minh"; // UTC+7
|
|
76
|
+
|
|
77
|
+
expect(composeDate(dateStr, format, timezone)).toEqual(new Date("2021-06-15T05:00:00.000Z"));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("UTC", () => {
|
|
81
|
+
const dateStr = "15/06/2021 12:00";
|
|
82
|
+
const format = "DD/MM/YYYY HH:mm";
|
|
83
|
+
const timezone = "UTC";
|
|
84
|
+
|
|
85
|
+
expect(composeDate(dateStr, format, timezone)).toEqual(new Date("2021-06-15T12:00:00.000Z"));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("isValidDate", () => {
|
|
91
|
+
it("should return true if the date is valid", () => {
|
|
92
|
+
const date = new Date("2021-06-15");
|
|
93
|
+
expect(isValidDate(date)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should return false if the date is invalid", () => {
|
|
97
|
+
const date = new Date("invalid");
|
|
98
|
+
expect(isValidDate(date)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import dayjs, { extend } from "dayjs";
|
|
2
|
+
import customParseFormat from "dayjs/plugin/customParseFormat";
|
|
3
|
+
import quarterPlugin from "dayjs/plugin/quarterOfYear";
|
|
4
|
+
import relativeTime from "dayjs/plugin/relativeTime";
|
|
5
|
+
import timezone from "dayjs/plugin/timezone";
|
|
6
|
+
import utc from "dayjs/plugin/utc";
|
|
7
|
+
|
|
8
|
+
import { maxBy, minBy } from "../array";
|
|
9
|
+
import { RecursiveKeyOf } from "../common";
|
|
10
|
+
import { get } from "../object";
|
|
11
|
+
import { DateRange } from "./date-range";
|
|
12
|
+
|
|
13
|
+
extend(quarterPlugin);
|
|
14
|
+
extend(timezone);
|
|
15
|
+
extend(utc);
|
|
16
|
+
extend(relativeTime);
|
|
17
|
+
extend(customParseFormat);
|
|
18
|
+
|
|
19
|
+
export const makeDateRange = (from: Date, to: Date) => {
|
|
20
|
+
const range: Date[] = [];
|
|
21
|
+
|
|
22
|
+
for (let current = new Date(from); current <= to; current.setDate(current.getDate() + 1)) {
|
|
23
|
+
range.push(new Date(current));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return range;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const MS_IN_DAY = 86_400_000;
|
|
30
|
+
|
|
31
|
+
export const daysBetweenDates = (date1: Date, date2: Date): number =>
|
|
32
|
+
Math.floor(Math.abs(date2.getTime() - date1.getTime()) / MS_IN_DAY);
|
|
33
|
+
|
|
34
|
+
export const compareDates = (date1: Date, date2: Date): number => date1.getTime() - date2.getTime();
|
|
35
|
+
|
|
36
|
+
export const arrayContainsDate = (dateArray: Date[], date: Date): boolean =>
|
|
37
|
+
dateArray.some((d) => compareDates(date, d) === 0);
|
|
38
|
+
|
|
39
|
+
export const isSame = (date: Date, compareDate: Date, type?: dayjs.QUnitType): boolean =>
|
|
40
|
+
dayjs(date).isSame(compareDate, type);
|
|
41
|
+
|
|
42
|
+
export const isSameOrBefore = (date: Date, compareDate: Date): boolean =>
|
|
43
|
+
date.getTime() <= compareDate.getTime();
|
|
44
|
+
|
|
45
|
+
export const isBefore = (date: Date, compareDate: Date): boolean =>
|
|
46
|
+
date.getTime() < compareDate.getTime();
|
|
47
|
+
|
|
48
|
+
export const isSameOrAfter = (date: Date, compareDate: Date): boolean =>
|
|
49
|
+
date.getTime() >= compareDate.getTime();
|
|
50
|
+
|
|
51
|
+
export const isAfter = (date: Date, compareDate: Date): boolean =>
|
|
52
|
+
date.getTime() > compareDate.getTime();
|
|
53
|
+
|
|
54
|
+
export const diffDay = (date: Date, compareDate: Date): number =>
|
|
55
|
+
Math.ceil((compareDate.getTime() - date.getTime()) / MS_IN_DAY);
|
|
56
|
+
|
|
57
|
+
export const diffYear = (date1: Date, date2: Date) =>
|
|
58
|
+
dayjs(date1).diff(date2, "year", true);
|
|
59
|
+
|
|
60
|
+
export const diff = (date: Date, compareDate: Date, unit?: dayjs.QUnitType | dayjs.OpUnitType, float?: boolean) =>
|
|
61
|
+
dayjs(date).diff(compareDate, unit, float);
|
|
62
|
+
|
|
63
|
+
export type GroupUnit = "day" | "month" | "quarter" | "year";
|
|
64
|
+
|
|
65
|
+
export type GroupDate<T> = {
|
|
66
|
+
beginOfUnit: Date;
|
|
67
|
+
endOfUnit: Date;
|
|
68
|
+
values: T[];
|
|
69
|
+
unit: GroupUnit;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const makeGroupKey = (date: Date, unit: GroupUnit): string => {
|
|
73
|
+
const y = date.getFullYear();
|
|
74
|
+
const m = date.getMonth();
|
|
75
|
+
switch (unit) {
|
|
76
|
+
case "day": return `${y}-${m}-${date.getDate()}`;
|
|
77
|
+
case "month": return `${y}-${m}-m`;
|
|
78
|
+
case "quarter": return `${y}-${Math.floor(m / 3)}-q`;
|
|
79
|
+
case "year": return `${y}`;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function groupBy<T extends object>(array: T[], key: RecursiveKeyOf<T>, unit: GroupUnit): GroupDate<T>[] {
|
|
84
|
+
const groups: Map<string, GroupDate<T>> = new Map();
|
|
85
|
+
|
|
86
|
+
array.forEach((item) => {
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
88
|
+
const date = get(item, key);
|
|
89
|
+
if (!(date instanceof Date)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const groupKey = makeGroupKey(date, unit);
|
|
94
|
+
|
|
95
|
+
const group = groups.get(groupKey) || {
|
|
96
|
+
beginOfUnit: dayjs(date).startOf(unit).toDate(),
|
|
97
|
+
endOfUnit: dayjs(date).endOf(unit).toDate(),
|
|
98
|
+
values: [],
|
|
99
|
+
unit,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
group.values.push(item);
|
|
103
|
+
groups.set(groupKey, group);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return Array.from(groups.values());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function fillMissingRangeValues<T extends object>(
|
|
110
|
+
array: GroupDate<T>[],
|
|
111
|
+
unit: GroupUnit,
|
|
112
|
+
dateRange?: DateRange,
|
|
113
|
+
): GroupDate<T>[] {
|
|
114
|
+
const groups: Map<string, GroupDate<T>> = new Map(
|
|
115
|
+
array.map((item) => {
|
|
116
|
+
return [makeGroupKey(item.beginOfUnit, unit), item];
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const minDate = dateRange?.from
|
|
121
|
+
? dayjs(dateRange.from).startOf(unit).toDate()
|
|
122
|
+
: dayjs(minBy(array, "beginOfUnit")?.beginOfUnit).startOf(unit).toDate();
|
|
123
|
+
|
|
124
|
+
const maxDate = dateRange?.to
|
|
125
|
+
? dayjs(dateRange.to).endOf(unit).toDate()
|
|
126
|
+
: dayjs(maxBy(array, "beginOfUnit")?.beginOfUnit).endOf(unit).toDate();
|
|
127
|
+
|
|
128
|
+
for (let date = new Date(minDate); isSameOrBefore(date, maxDate); date = add(date, 1, unit)) {
|
|
129
|
+
const groupKey = makeGroupKey(date, unit);
|
|
130
|
+
const group = groups.get(groupKey) || {
|
|
131
|
+
beginOfUnit: dayjs(date).startOf(unit).toDate(),
|
|
132
|
+
endOfUnit: dayjs(date).endOf(unit).toDate(),
|
|
133
|
+
values: [],
|
|
134
|
+
unit,
|
|
135
|
+
};
|
|
136
|
+
groups.set(groupKey, group);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Array.from(groups.values());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type DateUnit = "quarter" | "year" | "month" | "day";
|
|
143
|
+
type TimeUnit = DateUnit | "hour" | "minute" | "second" | "millisecond";
|
|
144
|
+
|
|
145
|
+
export const startOf = (date: Date, unit: DateUnit) => dayjs(date).startOf(unit).toDate();
|
|
146
|
+
|
|
147
|
+
export const endOf = (date: Date, unit: DateUnit) => dayjs(date).endOf(unit).toDate();
|
|
148
|
+
|
|
149
|
+
export const subtract = (date: Date, n: number, unit: TimeUnit) => dayjs(date).subtract(n, unit).toDate();
|
|
150
|
+
|
|
151
|
+
export const add = (date: Date, n: number, unit: TimeUnit) => dayjs(date).add(n, unit).toDate();
|
|
152
|
+
|
|
153
|
+
export const getQuarter = (date: Date) => dayjs(date).quarter();
|
|
154
|
+
|
|
155
|
+
export const randomDate = (start: Date, end: Date) =>
|
|
156
|
+
new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
|
|
157
|
+
|
|
158
|
+
export const format = (date: Date, fmt: string, timezone?: string) => {
|
|
159
|
+
const d = dayjs(date);
|
|
160
|
+
if (!d.isValid()) {return "";}
|
|
161
|
+
|
|
162
|
+
if (timezone) {
|
|
163
|
+
try { return d.tz(timezone).format(fmt); } catch { /* fall through */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return d.format(fmt);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const toNow = (date: Date) => dayjs(date).toNow();
|
|
170
|
+
|
|
171
|
+
export const fromNow = (date: Date) => dayjs(date).fromNow();
|
|
172
|
+
|
|
173
|
+
export const composeDate = (dateStr: string, fmt: string, timezone: string) => {
|
|
174
|
+
try { return dayjs.tz(dateStr, fmt, timezone).toDate(); } catch { return new Date(dateStr); }
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const isValidDate = (date: Date) => dayjs(date).isValid();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { add, diff } from "./date";
|
|
2
|
+
|
|
3
|
+
const NOW = Date.now();
|
|
4
|
+
|
|
5
|
+
const ONE_MS = 1;
|
|
6
|
+
const ONE_SECOND = ONE_MS * 1000;
|
|
7
|
+
const ONE_MINUTE = ONE_SECOND * 60;
|
|
8
|
+
const ONE_HOUR = ONE_MINUTE * 60;
|
|
9
|
+
|
|
10
|
+
describe("diff", () => {
|
|
11
|
+
describe("millisecond", () => {
|
|
12
|
+
it("should return diff millisecond", () => {
|
|
13
|
+
const result = diff(new Date(NOW + ONE_MS), new Date(NOW), "millisecond");
|
|
14
|
+
|
|
15
|
+
expect(result).toEqual(1);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("second", () => {
|
|
20
|
+
it("should return diff second", () => {
|
|
21
|
+
const result = diff(new Date(NOW + ONE_SECOND), new Date(NOW), "second");
|
|
22
|
+
|
|
23
|
+
expect(result).toEqual(1);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("minute", () => {
|
|
28
|
+
it("should return diff minute", () => {
|
|
29
|
+
const result = diff(new Date(NOW + ONE_MINUTE), new Date(NOW), "minute");
|
|
30
|
+
|
|
31
|
+
expect(result).toEqual(1);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("hour", () => {
|
|
36
|
+
it("should return diff hour", () => {
|
|
37
|
+
const result = diff(new Date(NOW + ONE_HOUR), new Date(NOW), "hour");
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual(1);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("day", () => {
|
|
44
|
+
it("should return diff day", () => {
|
|
45
|
+
const result = diff(add(new Date(NOW), 1, "day"), new Date(NOW), "day");
|
|
46
|
+
|
|
47
|
+
expect(result).toEqual(1);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("month", () => {
|
|
52
|
+
it("should return diff month", () => {
|
|
53
|
+
const result = diff(add(new Date(NOW), 1, "month"), new Date(NOW), "month");
|
|
54
|
+
|
|
55
|
+
expect(result).toEqual(1);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("year", () => {
|
|
60
|
+
it("should return diff year", () => {
|
|
61
|
+
const result = diff(add(new Date(NOW), 1, "year"), new Date(NOW), "year");
|
|
62
|
+
|
|
63
|
+
expect(result).toEqual(1);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { diffYear } from "./date";
|
|
2
|
+
|
|
3
|
+
describe("diffYear", () => {
|
|
4
|
+
it("should return correct value if firstDate is of normal year", () => {
|
|
5
|
+
const firstDate = new Date(2022, 1, 1); // 2022 is normal year
|
|
6
|
+
const expectedResult = 1.25;
|
|
7
|
+
const secondDate = new Date(firstDate.getTime() + expectedResult * 365 * 24 * 60 * 60 * 1000);
|
|
8
|
+
|
|
9
|
+
const duration = diffYear(secondDate, firstDate);
|
|
10
|
+
|
|
11
|
+
expect(duration).toBeCloseTo(expectedResult, 1);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should return correct value if firstDate is of leap year", () => {
|
|
15
|
+
const firstDate = new Date(2016, 1, 1); // 2020 is leap year
|
|
16
|
+
const expectedResult = 1.25;
|
|
17
|
+
const secondDate = new Date(firstDate.getTime() + expectedResult * 366 * 24 * 60 * 60 * 1000);
|
|
18
|
+
|
|
19
|
+
const duration = diffYear(secondDate, firstDate);
|
|
20
|
+
|
|
21
|
+
expect(duration).toBeCloseTo(expectedResult, 1);
|
|
22
|
+
});
|
|
23
|
+
});
|