flowerbi-dates 2.0.0
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/.prettierignore +25 -0
- package/.prettierrc +7 -0
- package/README.md +3 -0
- package/dist/fillDates.d.ts +99 -0
- package/dist/fillDates.js +175 -0
- package/dist/fillDates.js.map +1 -0
- package/dist/fillDates.test.d.ts +1 -0
- package/dist/fillDates.test.js +211 -0
- package/dist/fillDates.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +4 -0
- package/package.json +28 -0
- package/src/fillDates.test.ts +234 -0
- package/src/fillDates.ts +217 -0
- package/src/index.ts +1 -0
- package/tsconfig.json +21 -0
package/.prettierignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
**/build/**
|
|
2
|
+
**/coverage/**
|
|
3
|
+
**/dist/**
|
|
4
|
+
**/built/**
|
|
5
|
+
|
|
6
|
+
*.tf
|
|
7
|
+
*.sln
|
|
8
|
+
*.deployproj
|
|
9
|
+
*.targets
|
|
10
|
+
*.gitignore
|
|
11
|
+
*.png
|
|
12
|
+
*.svg
|
|
13
|
+
*.config
|
|
14
|
+
*.ps1
|
|
15
|
+
*.ico
|
|
16
|
+
*.lock
|
|
17
|
+
*.jsbeautifyrc
|
|
18
|
+
*.xml
|
|
19
|
+
*.csv
|
|
20
|
+
*.tsbuildinfo
|
|
21
|
+
*.eslintignore
|
|
22
|
+
*.env
|
|
23
|
+
*.bat
|
|
24
|
+
*.prettierignore
|
|
25
|
+
*.scss.d.ts
|
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Moment } from "moment";
|
|
2
|
+
export declare type FillDate = Date | string | number | Moment;
|
|
3
|
+
/**
|
|
4
|
+
* Three operations on dates used by {@link fillDates}.
|
|
5
|
+
*/
|
|
6
|
+
export declare type FillDateType = {
|
|
7
|
+
/** Round the given date down to the nearest whole unit (e.g. start of month, quarter, year) */
|
|
8
|
+
round(d: Moment): Moment;
|
|
9
|
+
/** Format the given date to a string */
|
|
10
|
+
format(d: Moment): string;
|
|
11
|
+
/** Increment the date by the unit. The given date will already be rounded down. */
|
|
12
|
+
increment(d: Moment): Moment;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Standard built-in date types. To customise, implement the {@link FillDateType} interface.
|
|
16
|
+
*/
|
|
17
|
+
export declare const dateTypes: {
|
|
18
|
+
readonly days: FillDateType;
|
|
19
|
+
readonly months: FillDateType;
|
|
20
|
+
readonly quarters: FillDateType;
|
|
21
|
+
readonly years: FillDateType;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Examines a set of dates and chooses the most specific type that includes them all. If
|
|
25
|
+
* all fall on Jan, 1 then `years` is chosen, and so on.
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectDateType(dates: Moment[]): FillDateType;
|
|
28
|
+
/** Options for {@link fillDates} function. */
|
|
29
|
+
export interface FillDatesOptions<T, R> {
|
|
30
|
+
/** The records to base the filled list on. */
|
|
31
|
+
records: T[];
|
|
32
|
+
/** The operations to use for rounding, incrementing and formatting dates. */
|
|
33
|
+
type?: FillDateType;
|
|
34
|
+
/** Extracts a date value from a record in the input list. */
|
|
35
|
+
getDate(record: T): FillDate;
|
|
36
|
+
/**
|
|
37
|
+
* Generate a record for a date, from the formatted {@link dateText} and
|
|
38
|
+
* the input record for that date, if any.
|
|
39
|
+
*/
|
|
40
|
+
fill(dateText: string, record: T | undefined): R;
|
|
41
|
+
/**
|
|
42
|
+
* The minimum date to generate. It will be rounded down by the {@link type}
|
|
43
|
+
* so doesn't need to be on an exact boundary.
|
|
44
|
+
*/
|
|
45
|
+
min?: FillDate;
|
|
46
|
+
/**
|
|
47
|
+
* The maximum date to generate. It will be rounded down by the {@link type}
|
|
48
|
+
* so doesn't need to be on an exact boundary.
|
|
49
|
+
*/
|
|
50
|
+
max?: FillDate;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* When querying for a time series chart, e.g. x-axis is _Month_ and y-axis is
|
|
54
|
+
* _Total Sales_, there may be months where nothing was sold so they are
|
|
55
|
+
* missing from the list of records.
|
|
56
|
+
*
|
|
57
|
+
* To render a proper time-series, we need these gaps to be filled in with
|
|
58
|
+
* runs of fake records that give zero amounts for those months. e.g.
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* const filled = fillDates({
|
|
62
|
+
* records: [
|
|
63
|
+
* { date: '2020-04-01', totalSales: 10 },
|
|
64
|
+
* { date: '2020-06-01', totalSales: 4 },
|
|
65
|
+
* { date: '2020-07-01', totalSales: 9 },
|
|
66
|
+
* ],
|
|
67
|
+
* type: dateTypes.months,
|
|
68
|
+
* getDate: rec => rec.date,
|
|
69
|
+
* fill: (label, rec) => ({
|
|
70
|
+
* label,
|
|
71
|
+
* totalSales: 0,
|
|
72
|
+
* ...rec
|
|
73
|
+
* })
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* In the above example we add a `label` property to all the records, and
|
|
78
|
+
* for the records that fill the gaps we set the `totalSales` property to 0.
|
|
79
|
+
* For the real records, `...rec` will copy the real value of `totalSales`.
|
|
80
|
+
*
|
|
81
|
+
* To do this, we need to know:
|
|
82
|
+
*
|
|
83
|
+
* - how to round a date to the start of a unit (year, month, quarter),
|
|
84
|
+
* - how to increment a date by that unit,
|
|
85
|
+
* - how to format a date to a string for display.
|
|
86
|
+
*
|
|
87
|
+
* These operations are encapsulated by the {@link FillDateType} interface.
|
|
88
|
+
* Several built-in types are provided in {@link dateTypes}, but you can
|
|
89
|
+
* implement your own.
|
|
90
|
+
*
|
|
91
|
+
* Optionally you can also pass `min` and `max` dates, which will cause
|
|
92
|
+
* extra records to be added at the start and end of the range if necessary.
|
|
93
|
+
*
|
|
94
|
+
* If you don't pass a `type`, a suitable type will be detected based on
|
|
95
|
+
* how the input record dates fall on unit boundaries.
|
|
96
|
+
*/
|
|
97
|
+
export declare function fillDates<T, R>({ records, getDate, fill, min, max, type }: FillDatesOptions<T, R>): R[];
|
|
98
|
+
/** @deprecated */
|
|
99
|
+
export declare function smartDates<T, R>(records: T[], min: FillDate | undefined, max: FillDate | undefined, getDate: (record: T) => FillDate, fill: (dateText: string, record: T | undefined) => R): R[];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
3
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
4
|
+
if (ar || !(i in from)) {
|
|
5
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
6
|
+
ar[i] = from[i];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.smartDates = exports.fillDates = exports.detectDateType = exports.dateTypes = void 0;
|
|
16
|
+
var moment_1 = __importDefault(require("moment"));
|
|
17
|
+
function parseDate(val) {
|
|
18
|
+
// Ensure a numeric year is not interpreted as offset from 1970!
|
|
19
|
+
if (typeof val === "number") {
|
|
20
|
+
val = "" + val;
|
|
21
|
+
}
|
|
22
|
+
return (0, moment_1.default)(val);
|
|
23
|
+
}
|
|
24
|
+
var days = {
|
|
25
|
+
round: function (d) {
|
|
26
|
+
return d.clone().startOf("day");
|
|
27
|
+
},
|
|
28
|
+
format: function (d) {
|
|
29
|
+
return d.format("ll");
|
|
30
|
+
},
|
|
31
|
+
increment: function (d) {
|
|
32
|
+
return d.clone().add(1, "day");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var months = {
|
|
36
|
+
round: function (d) {
|
|
37
|
+
return d.clone().startOf("month");
|
|
38
|
+
},
|
|
39
|
+
format: function (d) {
|
|
40
|
+
return d.format("MMM YYYY");
|
|
41
|
+
},
|
|
42
|
+
increment: function (d) {
|
|
43
|
+
return d.clone().add(1, "month");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var quarters = {
|
|
47
|
+
round: function (d) {
|
|
48
|
+
return d.clone().startOf("quarter");
|
|
49
|
+
},
|
|
50
|
+
format: function (d) {
|
|
51
|
+
var monthFirst = d.format("MMM");
|
|
52
|
+
var monthLast = d.clone().add(2, "months").format("MMM");
|
|
53
|
+
var year = d.format("YYYY");
|
|
54
|
+
return "".concat(monthFirst, "-").concat(monthLast, " ").concat(year);
|
|
55
|
+
},
|
|
56
|
+
increment: function (d) {
|
|
57
|
+
return d.clone().add(3, "months");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var years = {
|
|
61
|
+
round: function (d) {
|
|
62
|
+
return d.clone().startOf("year");
|
|
63
|
+
},
|
|
64
|
+
format: function (d) {
|
|
65
|
+
return d.format("YYYY");
|
|
66
|
+
},
|
|
67
|
+
increment: function (d) {
|
|
68
|
+
return d.clone().add(1, "year");
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Standard built-in date types. To customise, implement the {@link FillDateType} interface.
|
|
73
|
+
*/
|
|
74
|
+
exports.dateTypes = {
|
|
75
|
+
days: days,
|
|
76
|
+
months: months,
|
|
77
|
+
quarters: quarters,
|
|
78
|
+
years: years
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Examines a set of dates and chooses the most specific type that includes them all. If
|
|
82
|
+
* all fall on Jan, 1 then `years` is chosen, and so on.
|
|
83
|
+
*/
|
|
84
|
+
function detectDateType(dates) {
|
|
85
|
+
return !dates.every(function (x) { return x.date() === 1; }) ? exports.dateTypes.days :
|
|
86
|
+
!dates.every(function (x) { return x.month() % 3 === 0; }) ? exports.dateTypes.months :
|
|
87
|
+
!dates.every(function (x) { return x.month() === 0; }) ? exports.dateTypes.quarters :
|
|
88
|
+
exports.dateTypes.years;
|
|
89
|
+
}
|
|
90
|
+
exports.detectDateType = detectDateType;
|
|
91
|
+
/**
|
|
92
|
+
* When querying for a time series chart, e.g. x-axis is _Month_ and y-axis is
|
|
93
|
+
* _Total Sales_, there may be months where nothing was sold so they are
|
|
94
|
+
* missing from the list of records.
|
|
95
|
+
*
|
|
96
|
+
* To render a proper time-series, we need these gaps to be filled in with
|
|
97
|
+
* runs of fake records that give zero amounts for those months. e.g.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const filled = fillDates({
|
|
101
|
+
* records: [
|
|
102
|
+
* { date: '2020-04-01', totalSales: 10 },
|
|
103
|
+
* { date: '2020-06-01', totalSales: 4 },
|
|
104
|
+
* { date: '2020-07-01', totalSales: 9 },
|
|
105
|
+
* ],
|
|
106
|
+
* type: dateTypes.months,
|
|
107
|
+
* getDate: rec => rec.date,
|
|
108
|
+
* fill: (label, rec) => ({
|
|
109
|
+
* label,
|
|
110
|
+
* totalSales: 0,
|
|
111
|
+
* ...rec
|
|
112
|
+
* })
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* In the above example we add a `label` property to all the records, and
|
|
117
|
+
* for the records that fill the gaps we set the `totalSales` property to 0.
|
|
118
|
+
* For the real records, `...rec` will copy the real value of `totalSales`.
|
|
119
|
+
*
|
|
120
|
+
* To do this, we need to know:
|
|
121
|
+
*
|
|
122
|
+
* - how to round a date to the start of a unit (year, month, quarter),
|
|
123
|
+
* - how to increment a date by that unit,
|
|
124
|
+
* - how to format a date to a string for display.
|
|
125
|
+
*
|
|
126
|
+
* These operations are encapsulated by the {@link FillDateType} interface.
|
|
127
|
+
* Several built-in types are provided in {@link dateTypes}, but you can
|
|
128
|
+
* implement your own.
|
|
129
|
+
*
|
|
130
|
+
* Optionally you can also pass `min` and `max` dates, which will cause
|
|
131
|
+
* extra records to be added at the start and end of the range if necessary.
|
|
132
|
+
*
|
|
133
|
+
* If you don't pass a `type`, a suitable type will be detected based on
|
|
134
|
+
* how the input record dates fall on unit boundaries.
|
|
135
|
+
*/
|
|
136
|
+
function fillDates(_a) {
|
|
137
|
+
var records = _a.records, getDate = _a.getDate, fill = _a.fill, min = _a.min, max = _a.max, type = _a.type;
|
|
138
|
+
records = __spreadArray([], records, true);
|
|
139
|
+
records.sort(function (x, y) { return parseDate(getDate(x)).diff(parseDate(getDate(y))); });
|
|
140
|
+
type = type !== null && type !== void 0 ? type : detectDateType(records.map(function (d) { return parseDate(getDate(d)); }));
|
|
141
|
+
var results = [];
|
|
142
|
+
var latest = undefined;
|
|
143
|
+
var lower = min ? type.round(parseDate(min)) : undefined;
|
|
144
|
+
for (var _i = 0, records_1 = records; _i < records_1.length; _i++) {
|
|
145
|
+
var record = records_1[_i];
|
|
146
|
+
var current = parseDate(getDate(record));
|
|
147
|
+
for (;;) {
|
|
148
|
+
latest = latest ? type.increment(latest) : lower;
|
|
149
|
+
if (!latest || latest >= current) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
results.push(fill(type.format(latest), undefined));
|
|
153
|
+
}
|
|
154
|
+
results.push(fill(type.format(current), record));
|
|
155
|
+
latest = current;
|
|
156
|
+
}
|
|
157
|
+
if (latest && max) {
|
|
158
|
+
var upper = type.round(parseDate(max));
|
|
159
|
+
for (;;) {
|
|
160
|
+
latest = type.increment(latest);
|
|
161
|
+
if (latest > upper) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
results.push(fill(type.format(latest), undefined));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return results;
|
|
168
|
+
}
|
|
169
|
+
exports.fillDates = fillDates;
|
|
170
|
+
/** @deprecated */
|
|
171
|
+
function smartDates(records, min, max, getDate, fill) {
|
|
172
|
+
return fillDates({ records: records, min: min, max: max, getDate: getDate, fill: fill });
|
|
173
|
+
}
|
|
174
|
+
exports.smartDates = smartDates;
|
|
175
|
+
//# sourceMappingURL=fillDates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fillDates.js","sourceRoot":"","sources":["../src/fillDates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
var fillDates_1 = require("./fillDates");
|
|
18
|
+
var moment_1 = __importDefault(require("moment"));
|
|
19
|
+
test("detects days", function () {
|
|
20
|
+
expect((0, fillDates_1.detectDateType)([
|
|
21
|
+
(0, moment_1.default)("2021-04-01"),
|
|
22
|
+
(0, moment_1.default)("2020-11-01"),
|
|
23
|
+
(0, moment_1.default)("2020-11-02"),
|
|
24
|
+
(0, moment_1.default)("2022-07-01"),
|
|
25
|
+
])).toBe(fillDates_1.dateTypes.days);
|
|
26
|
+
});
|
|
27
|
+
test("detects months", function () {
|
|
28
|
+
expect((0, fillDates_1.detectDateType)([
|
|
29
|
+
(0, moment_1.default)("2021-04-01"),
|
|
30
|
+
(0, moment_1.default)("2020-11-01"),
|
|
31
|
+
(0, moment_1.default)("2022-07-01"),
|
|
32
|
+
])).toBe(fillDates_1.dateTypes.months);
|
|
33
|
+
});
|
|
34
|
+
test("detects quarters", function () {
|
|
35
|
+
expect((0, fillDates_1.detectDateType)([
|
|
36
|
+
(0, moment_1.default)("2021-04-01"),
|
|
37
|
+
(0, moment_1.default)("2020-10-01"),
|
|
38
|
+
(0, moment_1.default)("2022-07-01"),
|
|
39
|
+
])).toBe(fillDates_1.dateTypes.quarters);
|
|
40
|
+
});
|
|
41
|
+
test("detects years", function () {
|
|
42
|
+
expect((0, fillDates_1.detectDateType)([
|
|
43
|
+
(0, moment_1.default)("2021-01-01"),
|
|
44
|
+
(0, moment_1.default)("2020-01-01"),
|
|
45
|
+
(0, moment_1.default)("2022-01-01"),
|
|
46
|
+
])).toBe(fillDates_1.dateTypes.years);
|
|
47
|
+
});
|
|
48
|
+
test("handles empty list", function () {
|
|
49
|
+
expect((0, fillDates_1.fillDates)({
|
|
50
|
+
records: [],
|
|
51
|
+
getDate: function (x) { return 0; },
|
|
52
|
+
fill: function () { return undefined; }
|
|
53
|
+
})).toStrictEqual([]);
|
|
54
|
+
});
|
|
55
|
+
test("leaves complete months alone", function () {
|
|
56
|
+
expect((0, fillDates_1.fillDates)({
|
|
57
|
+
records: [
|
|
58
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
59
|
+
{ date: "2020-05-01", totalSales: 4 },
|
|
60
|
+
{ date: "2020-06-01", totalSales: 9 },
|
|
61
|
+
{ date: "2020-07-01", totalSales: 3 },
|
|
62
|
+
],
|
|
63
|
+
getDate: function (rec) { return rec.date; },
|
|
64
|
+
fill: function (label, rec) { return (__assign({ label: label, totalSales: 0 }, rec)); },
|
|
65
|
+
min: "2020-04-12",
|
|
66
|
+
max: "2020-07-06"
|
|
67
|
+
})).toStrictEqual([
|
|
68
|
+
{ label: "Apr 2020", date: "2020-04-01", totalSales: 10 },
|
|
69
|
+
{ label: "May 2020", date: "2020-05-01", totalSales: 4 },
|
|
70
|
+
{ label: "Jun 2020", date: "2020-06-01", totalSales: 9 },
|
|
71
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 3 },
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
test("fills in missing months", function () {
|
|
75
|
+
expect((0, fillDates_1.fillDates)({
|
|
76
|
+
records: [
|
|
77
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
78
|
+
{ date: "2020-06-01", totalSales: 4 },
|
|
79
|
+
{ date: "2020-07-01", totalSales: 9 },
|
|
80
|
+
{ date: "2020-11-01", totalSales: 3 },
|
|
81
|
+
],
|
|
82
|
+
getDate: function (rec) { return rec.date; },
|
|
83
|
+
fill: function (label, rec) { return (__assign({ label: label, totalSales: 0 }, rec)); }
|
|
84
|
+
})).toStrictEqual([
|
|
85
|
+
{ label: "Apr 2020", date: "2020-04-01", totalSales: 10 },
|
|
86
|
+
{ label: "May 2020", totalSales: 0 },
|
|
87
|
+
{ label: "Jun 2020", date: "2020-06-01", totalSales: 4 },
|
|
88
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 9 },
|
|
89
|
+
{ label: "Aug 2020", totalSales: 0 },
|
|
90
|
+
{ label: "Sep 2020", totalSales: 0 },
|
|
91
|
+
{ label: "Oct 2020", totalSales: 0 },
|
|
92
|
+
{ label: "Nov 2020", date: "2020-11-01", totalSales: 3 },
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
test("fills in missing months and min/max range", function () {
|
|
96
|
+
expect((0, fillDates_1.fillDates)({
|
|
97
|
+
records: [
|
|
98
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
99
|
+
{ date: "2020-06-01", totalSales: 4 },
|
|
100
|
+
{ date: "2020-07-01", totalSales: 9 },
|
|
101
|
+
{ date: "2020-11-01", totalSales: 3 },
|
|
102
|
+
],
|
|
103
|
+
getDate: function (rec) { return rec.date; },
|
|
104
|
+
fill: function (label, rec) { return (__assign({ label: label, totalSales: 0 }, rec)); },
|
|
105
|
+
min: "2020-02-15",
|
|
106
|
+
max: "2020-12-02"
|
|
107
|
+
})).toStrictEqual([
|
|
108
|
+
{ label: "Feb 2020", totalSales: 0 },
|
|
109
|
+
{ label: "Mar 2020", totalSales: 0 },
|
|
110
|
+
{ label: "Apr 2020", date: "2020-04-01", totalSales: 10 },
|
|
111
|
+
{ label: "May 2020", totalSales: 0 },
|
|
112
|
+
{ label: "Jun 2020", date: "2020-06-01", totalSales: 4 },
|
|
113
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 9 },
|
|
114
|
+
{ label: "Aug 2020", totalSales: 0 },
|
|
115
|
+
{ label: "Sep 2020", totalSales: 0 },
|
|
116
|
+
{ label: "Oct 2020", totalSales: 0 },
|
|
117
|
+
{ label: "Nov 2020", date: "2020-11-01", totalSales: 3 },
|
|
118
|
+
{ label: "Dec 2020", totalSales: 0 },
|
|
119
|
+
]);
|
|
120
|
+
});
|
|
121
|
+
test("leaves complete quarters alone", function () {
|
|
122
|
+
expect((0, fillDates_1.fillDates)({
|
|
123
|
+
records: [
|
|
124
|
+
{ date: "2020-07-01", totalSales: 10 },
|
|
125
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
126
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
127
|
+
{ date: "2021-04-01", totalSales: 3 },
|
|
128
|
+
],
|
|
129
|
+
getDate: function (rec) { return rec.date; },
|
|
130
|
+
fill: function (label, rec) { return (__assign({ label: label, totalSales: 0 }, rec)); },
|
|
131
|
+
min: "2020-07-12",
|
|
132
|
+
max: "2020-04-06"
|
|
133
|
+
})).toStrictEqual([
|
|
134
|
+
{ label: "Jul-Sep 2020", date: "2020-07-01", totalSales: 10 },
|
|
135
|
+
{ label: "Oct-Dec 2020", date: "2020-10-01", totalSales: 4 },
|
|
136
|
+
{ label: "Jan-Mar 2021", date: "2021-01-01", totalSales: 9 },
|
|
137
|
+
{ label: "Apr-Jun 2021", date: "2021-04-01", totalSales: 3 },
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
test("fills in missing quarters", function () {
|
|
141
|
+
expect((0, fillDates_1.fillDates)({
|
|
142
|
+
records: [
|
|
143
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
144
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
145
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
146
|
+
{ date: "2021-10-01", totalSales: 3 },
|
|
147
|
+
],
|
|
148
|
+
type: fillDates_1.dateTypes.quarters,
|
|
149
|
+
getDate: function (rec) { return rec.date; },
|
|
150
|
+
fill: function (label, rec) { return (__assign({ label: label, totalSales: 0 }, rec)); }
|
|
151
|
+
})).toStrictEqual([
|
|
152
|
+
{ label: "Apr-Jun 2020", date: "2020-04-01", totalSales: 10 },
|
|
153
|
+
{ label: "Jul-Sep 2020", totalSales: 0 },
|
|
154
|
+
{ label: "Oct-Dec 2020", date: "2020-10-01", totalSales: 4 },
|
|
155
|
+
{ label: "Jan-Mar 2021", date: "2021-01-01", totalSales: 9 },
|
|
156
|
+
{ label: "Apr-Jun 2021", totalSales: 0 },
|
|
157
|
+
{ label: "Jul-Sep 2021", totalSales: 0 },
|
|
158
|
+
{ label: "Oct-Dec 2021", date: "2021-10-01", totalSales: 3 },
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
test("fills in missing quarters and min/max range", function () {
|
|
162
|
+
expect((0, fillDates_1.fillDates)({
|
|
163
|
+
records: [
|
|
164
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
165
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
166
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
167
|
+
{ date: "2021-10-01", totalSales: 3 },
|
|
168
|
+
],
|
|
169
|
+
type: fillDates_1.dateTypes.quarters,
|
|
170
|
+
getDate: function (rec) { return rec.date; },
|
|
171
|
+
fill: function (label, rec) { return (__assign({ label: label, totalSales: 0 }, rec)); },
|
|
172
|
+
min: "2019-10-10",
|
|
173
|
+
max: "2022-01-06"
|
|
174
|
+
})).toStrictEqual([
|
|
175
|
+
{ label: "Oct-Dec 2019", totalSales: 0 },
|
|
176
|
+
{ label: "Jan-Mar 2020", totalSales: 0 },
|
|
177
|
+
{ label: "Apr-Jun 2020", date: "2020-04-01", totalSales: 10 },
|
|
178
|
+
{ label: "Jul-Sep 2020", totalSales: 0 },
|
|
179
|
+
{ label: "Oct-Dec 2020", date: "2020-10-01", totalSales: 4 },
|
|
180
|
+
{ label: "Jan-Mar 2021", date: "2021-01-01", totalSales: 9 },
|
|
181
|
+
{ label: "Apr-Jun 2021", totalSales: 0 },
|
|
182
|
+
{ label: "Jul-Sep 2021", totalSales: 0 },
|
|
183
|
+
{ label: "Oct-Dec 2021", date: "2021-10-01", totalSales: 3 },
|
|
184
|
+
{ label: "Jan-Mar 2022", totalSales: 0 },
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
test("explicit type overrides detection", function () {
|
|
188
|
+
expect((0, fillDates_1.fillDates)({
|
|
189
|
+
records: [
|
|
190
|
+
{ date: "2020-07-01", totalSales: 10 },
|
|
191
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
192
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
193
|
+
{ date: "2021-04-01", totalSales: 3 },
|
|
194
|
+
],
|
|
195
|
+
getDate: function (rec) { return rec.date; },
|
|
196
|
+
fill: function (label, rec) { return (__assign({ label: label, totalSales: 0 }, rec)); },
|
|
197
|
+
type: fillDates_1.dateTypes.months
|
|
198
|
+
})).toStrictEqual([
|
|
199
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 10 },
|
|
200
|
+
{ label: "Aug 2020", totalSales: 0 },
|
|
201
|
+
{ label: "Sep 2020", totalSales: 0 },
|
|
202
|
+
{ label: "Oct 2020", date: "2020-10-01", totalSales: 4 },
|
|
203
|
+
{ label: "Nov 2020", totalSales: 0 },
|
|
204
|
+
{ label: "Dec 2020", totalSales: 0 },
|
|
205
|
+
{ label: "Jan 2021", date: "2021-01-01", totalSales: 9 },
|
|
206
|
+
{ label: "Feb 2021", totalSales: 0 },
|
|
207
|
+
{ label: "Mar 2021", totalSales: 0 },
|
|
208
|
+
{ label: "Apr 2021", date: "2021-04-01", totalSales: 3 },
|
|
209
|
+
]);
|
|
210
|
+
});
|
|
211
|
+
//# sourceMappingURL=fillDates.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fillDates.test.js","sourceRoot":"","sources":["../src/fillDates.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./fillDates";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./fillDates"), exports);
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAA4B"}
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flowerbi-dates",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"author": "Daniel Earwicker <dan@earwicker.com>",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "FlowerBI: ultra-minimal BI analytics query and UI tools",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/jest": "^27.4.0",
|
|
11
|
+
"jest": "^27.4.7",
|
|
12
|
+
"moment": "^2.29.1",
|
|
13
|
+
"prettier": "^2.5.1",
|
|
14
|
+
"ts-jest": "^27.1.3",
|
|
15
|
+
"typedoc": "^0.22.11",
|
|
16
|
+
"typescript": "^4.5.5"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"moment": "^2.29.1"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"watch": "tsc -w",
|
|
24
|
+
"test": "jest",
|
|
25
|
+
"docs": "typedoc --excludePrivate --readme home.md --out ../../../docs/typedoc/flowerbi src",
|
|
26
|
+
"prepare": "npm run test && npm run build && npm run docs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { fillDates, dateTypes, detectDateType } from "./fillDates";
|
|
2
|
+
import moment from "moment";
|
|
3
|
+
|
|
4
|
+
test("detects days", () => {
|
|
5
|
+
expect(detectDateType([
|
|
6
|
+
moment("2021-04-01"),
|
|
7
|
+
moment("2020-11-01"),
|
|
8
|
+
moment("2020-11-02"),
|
|
9
|
+
moment("2022-07-01"),
|
|
10
|
+
])).toBe(dateTypes.days);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("detects months", () => {
|
|
14
|
+
expect(detectDateType([
|
|
15
|
+
moment("2021-04-01"),
|
|
16
|
+
moment("2020-11-01"),
|
|
17
|
+
moment("2022-07-01"),
|
|
18
|
+
])).toBe(dateTypes.months);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("detects quarters", () => {
|
|
22
|
+
expect(detectDateType([
|
|
23
|
+
moment("2021-04-01"),
|
|
24
|
+
moment("2020-10-01"),
|
|
25
|
+
moment("2022-07-01"),
|
|
26
|
+
])).toBe(dateTypes.quarters);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("detects years", () => {
|
|
30
|
+
expect(detectDateType([
|
|
31
|
+
moment("2021-01-01"),
|
|
32
|
+
moment("2020-01-01"),
|
|
33
|
+
moment("2022-01-01"),
|
|
34
|
+
])).toBe(dateTypes.years);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("handles empty list", () => {
|
|
38
|
+
expect(fillDates({
|
|
39
|
+
records: [],
|
|
40
|
+
getDate: x => 0,
|
|
41
|
+
fill: () => undefined
|
|
42
|
+
})).toStrictEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("leaves complete months alone", () => {
|
|
46
|
+
expect(fillDates({
|
|
47
|
+
records: [
|
|
48
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
49
|
+
{ date: "2020-05-01", totalSales: 4 },
|
|
50
|
+
{ date: "2020-06-01", totalSales: 9 },
|
|
51
|
+
{ date: "2020-07-01", totalSales: 3 },
|
|
52
|
+
],
|
|
53
|
+
getDate: rec => rec.date,
|
|
54
|
+
fill: (label, rec) => ({
|
|
55
|
+
label,
|
|
56
|
+
totalSales: 0,
|
|
57
|
+
...rec
|
|
58
|
+
}),
|
|
59
|
+
min: "2020-04-12",
|
|
60
|
+
max: "2020-07-06"
|
|
61
|
+
})).toStrictEqual([
|
|
62
|
+
{ label: "Apr 2020", date: "2020-04-01", totalSales: 10 },
|
|
63
|
+
{ label: "May 2020", date: "2020-05-01", totalSales: 4 },
|
|
64
|
+
{ label: "Jun 2020", date: "2020-06-01", totalSales: 9 },
|
|
65
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 3 },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("fills in missing months", () => {
|
|
70
|
+
expect(fillDates({
|
|
71
|
+
records: [
|
|
72
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
73
|
+
{ date: "2020-06-01", totalSales: 4 },
|
|
74
|
+
{ date: "2020-07-01", totalSales: 9 },
|
|
75
|
+
{ date: "2020-11-01", totalSales: 3 },
|
|
76
|
+
],
|
|
77
|
+
getDate: rec => rec.date,
|
|
78
|
+
fill: (label, rec) => ({
|
|
79
|
+
label,
|
|
80
|
+
totalSales: 0,
|
|
81
|
+
...rec
|
|
82
|
+
})
|
|
83
|
+
})).toStrictEqual([
|
|
84
|
+
{ label: "Apr 2020", date: "2020-04-01", totalSales: 10 },
|
|
85
|
+
{ label: "May 2020", totalSales: 0 },
|
|
86
|
+
{ label: "Jun 2020", date: "2020-06-01", totalSales: 4 },
|
|
87
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 9 },
|
|
88
|
+
{ label: "Aug 2020", totalSales: 0 },
|
|
89
|
+
{ label: "Sep 2020", totalSales: 0 },
|
|
90
|
+
{ label: "Oct 2020", totalSales: 0 },
|
|
91
|
+
{ label: "Nov 2020", date: "2020-11-01", totalSales: 3 },
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("fills in missing months and min/max range", () => {
|
|
96
|
+
expect(fillDates({
|
|
97
|
+
records: [
|
|
98
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
99
|
+
{ date: "2020-06-01", totalSales: 4 },
|
|
100
|
+
{ date: "2020-07-01", totalSales: 9 },
|
|
101
|
+
{ date: "2020-11-01", totalSales: 3 },
|
|
102
|
+
],
|
|
103
|
+
getDate: rec => rec.date,
|
|
104
|
+
fill: (label, rec) => ({
|
|
105
|
+
label,
|
|
106
|
+
totalSales: 0,
|
|
107
|
+
...rec
|
|
108
|
+
}),
|
|
109
|
+
min: "2020-02-15",
|
|
110
|
+
max: "2020-12-02"
|
|
111
|
+
})).toStrictEqual([
|
|
112
|
+
{ label: "Feb 2020", totalSales: 0 },
|
|
113
|
+
{ label: "Mar 2020", totalSales: 0 },
|
|
114
|
+
{ label: "Apr 2020", date: "2020-04-01", totalSales: 10 },
|
|
115
|
+
{ label: "May 2020", totalSales: 0 },
|
|
116
|
+
{ label: "Jun 2020", date: "2020-06-01", totalSales: 4 },
|
|
117
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 9 },
|
|
118
|
+
{ label: "Aug 2020", totalSales: 0 },
|
|
119
|
+
{ label: "Sep 2020", totalSales: 0 },
|
|
120
|
+
{ label: "Oct 2020", totalSales: 0 },
|
|
121
|
+
{ label: "Nov 2020", date: "2020-11-01", totalSales: 3 },
|
|
122
|
+
{ label: "Dec 2020", totalSales: 0 },
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("leaves complete quarters alone", () => {
|
|
127
|
+
expect(fillDates({
|
|
128
|
+
records: [
|
|
129
|
+
{ date: "2020-07-01", totalSales: 10 },
|
|
130
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
131
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
132
|
+
{ date: "2021-04-01", totalSales: 3 },
|
|
133
|
+
],
|
|
134
|
+
getDate: rec => rec.date,
|
|
135
|
+
fill: (label, rec) => ({
|
|
136
|
+
label,
|
|
137
|
+
totalSales: 0,
|
|
138
|
+
...rec
|
|
139
|
+
}),
|
|
140
|
+
min: "2020-07-12",
|
|
141
|
+
max: "2020-04-06"
|
|
142
|
+
})).toStrictEqual([
|
|
143
|
+
{ label: "Jul-Sep 2020", date: "2020-07-01", totalSales: 10 },
|
|
144
|
+
{ label: "Oct-Dec 2020", date: "2020-10-01", totalSales: 4 },
|
|
145
|
+
{ label: "Jan-Mar 2021", date: "2021-01-01", totalSales: 9 },
|
|
146
|
+
{ label: "Apr-Jun 2021", date: "2021-04-01", totalSales: 3 },
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("fills in missing quarters", () => {
|
|
151
|
+
expect(fillDates({
|
|
152
|
+
records: [
|
|
153
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
154
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
155
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
156
|
+
{ date: "2021-10-01", totalSales: 3 },
|
|
157
|
+
],
|
|
158
|
+
type: dateTypes.quarters,
|
|
159
|
+
getDate: rec => rec.date,
|
|
160
|
+
fill: (label, rec) => ({
|
|
161
|
+
label,
|
|
162
|
+
totalSales: 0,
|
|
163
|
+
...rec
|
|
164
|
+
})
|
|
165
|
+
})).toStrictEqual([
|
|
166
|
+
{ label: "Apr-Jun 2020", date: "2020-04-01", totalSales: 10 },
|
|
167
|
+
{ label: "Jul-Sep 2020", totalSales: 0 },
|
|
168
|
+
{ label: "Oct-Dec 2020", date: "2020-10-01", totalSales: 4 },
|
|
169
|
+
{ label: "Jan-Mar 2021", date: "2021-01-01", totalSales: 9 },
|
|
170
|
+
{ label: "Apr-Jun 2021", totalSales: 0 },
|
|
171
|
+
{ label: "Jul-Sep 2021", totalSales: 0 },
|
|
172
|
+
{ label: "Oct-Dec 2021", date: "2021-10-01", totalSales: 3 },
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("fills in missing quarters and min/max range", () => {
|
|
177
|
+
expect(fillDates({
|
|
178
|
+
records: [
|
|
179
|
+
{ date: "2020-04-01", totalSales: 10 },
|
|
180
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
181
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
182
|
+
{ date: "2021-10-01", totalSales: 3 },
|
|
183
|
+
],
|
|
184
|
+
type: dateTypes.quarters,
|
|
185
|
+
getDate: rec => rec.date,
|
|
186
|
+
fill: (label, rec) => ({
|
|
187
|
+
label,
|
|
188
|
+
totalSales: 0,
|
|
189
|
+
...rec
|
|
190
|
+
}),
|
|
191
|
+
min: "2019-10-10",
|
|
192
|
+
max: "2022-01-06"
|
|
193
|
+
})).toStrictEqual([
|
|
194
|
+
{ label: "Oct-Dec 2019", totalSales: 0 },
|
|
195
|
+
{ label: "Jan-Mar 2020", totalSales: 0 },
|
|
196
|
+
{ label: "Apr-Jun 2020", date: "2020-04-01", totalSales: 10 },
|
|
197
|
+
{ label: "Jul-Sep 2020", totalSales: 0 },
|
|
198
|
+
{ label: "Oct-Dec 2020", date: "2020-10-01", totalSales: 4 },
|
|
199
|
+
{ label: "Jan-Mar 2021", date: "2021-01-01", totalSales: 9 },
|
|
200
|
+
{ label: "Apr-Jun 2021", totalSales: 0 },
|
|
201
|
+
{ label: "Jul-Sep 2021", totalSales: 0 },
|
|
202
|
+
{ label: "Oct-Dec 2021", date: "2021-10-01", totalSales: 3 },
|
|
203
|
+
{ label: "Jan-Mar 2022", totalSales: 0 },
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("explicit type overrides detection", () => {
|
|
208
|
+
expect(fillDates({
|
|
209
|
+
records: [
|
|
210
|
+
{ date: "2020-07-01", totalSales: 10 },
|
|
211
|
+
{ date: "2020-10-01", totalSales: 4 },
|
|
212
|
+
{ date: "2021-01-01", totalSales: 9 },
|
|
213
|
+
{ date: "2021-04-01", totalSales: 3 },
|
|
214
|
+
],
|
|
215
|
+
getDate: rec => rec.date,
|
|
216
|
+
fill: (label, rec) => ({
|
|
217
|
+
label,
|
|
218
|
+
totalSales: 0,
|
|
219
|
+
...rec
|
|
220
|
+
}),
|
|
221
|
+
type: dateTypes.months
|
|
222
|
+
})).toStrictEqual([
|
|
223
|
+
{ label: "Jul 2020", date: "2020-07-01", totalSales: 10 },
|
|
224
|
+
{ label: "Aug 2020", totalSales: 0 },
|
|
225
|
+
{ label: "Sep 2020", totalSales: 0 },
|
|
226
|
+
{ label: "Oct 2020", date: "2020-10-01", totalSales: 4 },
|
|
227
|
+
{ label: "Nov 2020", totalSales: 0 },
|
|
228
|
+
{ label: "Dec 2020", totalSales: 0 },
|
|
229
|
+
{ label: "Jan 2021", date: "2021-01-01", totalSales: 9 },
|
|
230
|
+
{ label: "Feb 2021", totalSales: 0 },
|
|
231
|
+
{ label: "Mar 2021", totalSales: 0 },
|
|
232
|
+
{ label: "Apr 2021", date: "2021-04-01", totalSales: 3 },
|
|
233
|
+
]);
|
|
234
|
+
});
|
package/src/fillDates.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import moment, { Moment } from "moment";
|
|
2
|
+
|
|
3
|
+
export type FillDate = Date | string | number | Moment;
|
|
4
|
+
|
|
5
|
+
function parseDate(val: FillDate) {
|
|
6
|
+
// Ensure a numeric year is not interpreted as offset from 1970!
|
|
7
|
+
if (typeof val === "number") {
|
|
8
|
+
val = "" + val;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return moment(val);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Three operations on dates used by {@link fillDates}.
|
|
16
|
+
*/
|
|
17
|
+
export type FillDateType = {
|
|
18
|
+
/** Round the given date down to the nearest whole unit (e.g. start of month, quarter, year) */
|
|
19
|
+
round(d: Moment): Moment;
|
|
20
|
+
/** Format the given date to a string */
|
|
21
|
+
format(d: Moment): string;
|
|
22
|
+
/** Increment the date by the unit. The given date will already be rounded down. */
|
|
23
|
+
increment(d: Moment): Moment;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const days: FillDateType = {
|
|
27
|
+
round(d) {
|
|
28
|
+
return d.clone().startOf("day");
|
|
29
|
+
},
|
|
30
|
+
format(d) {
|
|
31
|
+
return d.format("ll");
|
|
32
|
+
},
|
|
33
|
+
increment(d) {
|
|
34
|
+
return d.clone().add(1, "day");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const months: FillDateType = {
|
|
39
|
+
round(d) {
|
|
40
|
+
return d.clone().startOf("month");
|
|
41
|
+
},
|
|
42
|
+
format(d) {
|
|
43
|
+
return d.format("MMM YYYY");
|
|
44
|
+
},
|
|
45
|
+
increment(d) {
|
|
46
|
+
return d.clone().add(1, "month");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const quarters: FillDateType = {
|
|
51
|
+
round(d) {
|
|
52
|
+
return d.clone().startOf("quarter");
|
|
53
|
+
},
|
|
54
|
+
format(d) {
|
|
55
|
+
const monthFirst = d.format("MMM");
|
|
56
|
+
const monthLast = d.clone().add(2, "months").format("MMM");
|
|
57
|
+
const year = d.format("YYYY");
|
|
58
|
+
return `${monthFirst}-${monthLast} ${year}`;
|
|
59
|
+
},
|
|
60
|
+
increment(d) {
|
|
61
|
+
return d.clone().add(3, "months");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const years: FillDateType = {
|
|
66
|
+
round(d) {
|
|
67
|
+
return d.clone().startOf("year");
|
|
68
|
+
},
|
|
69
|
+
format(d) {
|
|
70
|
+
return d.format("YYYY");
|
|
71
|
+
},
|
|
72
|
+
increment(d) {
|
|
73
|
+
return d.clone().add(1, "year");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Standard built-in date types. To customise, implement the {@link FillDateType} interface.
|
|
79
|
+
*/
|
|
80
|
+
export const dateTypes = {
|
|
81
|
+
days, months, quarters, years
|
|
82
|
+
} as const;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Examines a set of dates and chooses the most specific type that includes them all. If
|
|
86
|
+
* all fall on Jan, 1 then `years` is chosen, and so on.
|
|
87
|
+
*/
|
|
88
|
+
export function detectDateType(dates: Moment[]): FillDateType {
|
|
89
|
+
return !dates.every(x => x.date() === 1) ? dateTypes.days :
|
|
90
|
+
!dates.every(x => x.month() % 3 === 0) ? dateTypes.months :
|
|
91
|
+
!dates.every(x => x.month() === 0) ? dateTypes.quarters :
|
|
92
|
+
dateTypes.years;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Options for {@link fillDates} function. */
|
|
96
|
+
export interface FillDatesOptions<T, R> {
|
|
97
|
+
/** The records to base the filled list on. */
|
|
98
|
+
records: T[];
|
|
99
|
+
/** The operations to use for rounding, incrementing and formatting dates. */
|
|
100
|
+
type?: FillDateType;
|
|
101
|
+
/** Extracts a date value from a record in the input list. */
|
|
102
|
+
getDate(record: T): FillDate;
|
|
103
|
+
/**
|
|
104
|
+
* Generate a record for a date, from the formatted {@link dateText} and
|
|
105
|
+
* the input record for that date, if any.
|
|
106
|
+
*/
|
|
107
|
+
fill(dateText: string, record: T | undefined): R;
|
|
108
|
+
/**
|
|
109
|
+
* The minimum date to generate. It will be rounded down by the {@link type}
|
|
110
|
+
* so doesn't need to be on an exact boundary.
|
|
111
|
+
*/
|
|
112
|
+
min?: FillDate;
|
|
113
|
+
/**
|
|
114
|
+
* The maximum date to generate. It will be rounded down by the {@link type}
|
|
115
|
+
* so doesn't need to be on an exact boundary.
|
|
116
|
+
*/
|
|
117
|
+
max?: FillDate;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* When querying for a time series chart, e.g. x-axis is _Month_ and y-axis is
|
|
122
|
+
* _Total Sales_, there may be months where nothing was sold so they are
|
|
123
|
+
* missing from the list of records.
|
|
124
|
+
*
|
|
125
|
+
* To render a proper time-series, we need these gaps to be filled in with
|
|
126
|
+
* runs of fake records that give zero amounts for those months. e.g.
|
|
127
|
+
*
|
|
128
|
+
* ```ts
|
|
129
|
+
* const filled = fillDates({
|
|
130
|
+
* records: [
|
|
131
|
+
* { date: '2020-04-01', totalSales: 10 },
|
|
132
|
+
* { date: '2020-06-01', totalSales: 4 },
|
|
133
|
+
* { date: '2020-07-01', totalSales: 9 },
|
|
134
|
+
* ],
|
|
135
|
+
* type: dateTypes.months,
|
|
136
|
+
* getDate: rec => rec.date,
|
|
137
|
+
* fill: (label, rec) => ({
|
|
138
|
+
* label,
|
|
139
|
+
* totalSales: 0,
|
|
140
|
+
* ...rec
|
|
141
|
+
* })
|
|
142
|
+
* });
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* In the above example we add a `label` property to all the records, and
|
|
146
|
+
* for the records that fill the gaps we set the `totalSales` property to 0.
|
|
147
|
+
* For the real records, `...rec` will copy the real value of `totalSales`.
|
|
148
|
+
*
|
|
149
|
+
* To do this, we need to know:
|
|
150
|
+
*
|
|
151
|
+
* - how to round a date to the start of a unit (year, month, quarter),
|
|
152
|
+
* - how to increment a date by that unit,
|
|
153
|
+
* - how to format a date to a string for display.
|
|
154
|
+
*
|
|
155
|
+
* These operations are encapsulated by the {@link FillDateType} interface.
|
|
156
|
+
* Several built-in types are provided in {@link dateTypes}, but you can
|
|
157
|
+
* implement your own.
|
|
158
|
+
*
|
|
159
|
+
* Optionally you can also pass `min` and `max` dates, which will cause
|
|
160
|
+
* extra records to be added at the start and end of the range if necessary.
|
|
161
|
+
*
|
|
162
|
+
* If you don't pass a `type`, a suitable type will be detected based on
|
|
163
|
+
* how the input record dates fall on unit boundaries.
|
|
164
|
+
*/
|
|
165
|
+
export function fillDates<T, R>(
|
|
166
|
+
{ records, getDate, fill, min, max, type }: FillDatesOptions<T, R>
|
|
167
|
+
) {
|
|
168
|
+
records = [...records];
|
|
169
|
+
records.sort((x, y) => parseDate(getDate(x)).diff(parseDate(getDate(y))));
|
|
170
|
+
|
|
171
|
+
type = type ?? detectDateType(records.map(d => parseDate(getDate(d))));
|
|
172
|
+
|
|
173
|
+
const results: R[] = [];
|
|
174
|
+
let latest: Moment | undefined = undefined;
|
|
175
|
+
const lower = min ? type.round(parseDate(min)) : undefined;
|
|
176
|
+
|
|
177
|
+
for (const record of records) {
|
|
178
|
+
const current = parseDate(getDate(record));
|
|
179
|
+
|
|
180
|
+
for (;;) {
|
|
181
|
+
latest = latest ? type.increment(latest) : lower;
|
|
182
|
+
if (!latest || latest >= current) {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
results.push(fill(type.format(latest), undefined));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
results.push(fill(type.format(current), record));
|
|
190
|
+
latest = current;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (latest && max) {
|
|
194
|
+
const upper = type.round(parseDate(max))
|
|
195
|
+
for (;;) {
|
|
196
|
+
latest = type.increment(latest);
|
|
197
|
+
if (latest > upper) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
results.push(fill(type.format(latest), undefined));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return results;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** @deprecated */
|
|
209
|
+
export function smartDates<T, R>(
|
|
210
|
+
records: T[],
|
|
211
|
+
min: FillDate | undefined,
|
|
212
|
+
max: FillDate | undefined,
|
|
213
|
+
getDate: (record: T) => FillDate,
|
|
214
|
+
fill: (dateText: string, record: T | undefined) => R
|
|
215
|
+
) {
|
|
216
|
+
return fillDates({ records, min, max, getDate, fill });
|
|
217
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./fillDates";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es5",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"module": "commonjs",
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"outDir": "dist",
|
|
16
|
+
"declaration": true,
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"jsx": "react"
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"]
|
|
21
|
+
}
|