shelving 1.162.1 → 1.164.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/package.json +1 -1
- package/util/date.d.ts +29 -39
- package/util/date.js +75 -107
- package/util/duration.d.ts +120 -0
- package/util/duration.js +179 -0
- package/util/format.d.ts +72 -10
- package/util/format.js +85 -27
- package/util/index.d.ts +1 -0
- package/util/index.js +1 -0
- package/util/number.d.ts +2 -2
- package/util/number.js +3 -3
- package/util/units.d.ts +7 -22
- package/util/units.js +24 -33
- package/util/url.js +12 -1
package/package.json
CHANGED
package/util/date.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare function getYesterday(): Date;
|
|
|
35
35
|
export declare function getToday(): Date;
|
|
36
36
|
/** Get a date representing midnight of the next day. */
|
|
37
37
|
export declare function getTomorrow(): Date;
|
|
38
|
+
/** Get a Date representing exactly midnight of the specified date. */
|
|
39
|
+
export declare function getMidnight(target?: PossibleDate, caller?: AnyCaller): Date;
|
|
40
|
+
/** Get a Date representing midnight on Monday of the specified week. */
|
|
41
|
+
export declare function getMonday(target?: PossibleDate, caller?: AnyCaller): Date;
|
|
42
|
+
/** Get a Date representing the first day of the specified month. */
|
|
43
|
+
export declare function getMonthStart(target?: PossibleDate, caller?: AnyCaller): Date;
|
|
38
44
|
/**
|
|
39
45
|
* Convert a possible date to a `Date` instance, or throw `RequiredError` if it couldn't be converted.
|
|
40
46
|
* @param value Any value that we want to parse as a valid date (defaults to `"now"`).
|
|
@@ -57,47 +63,31 @@ export declare function getTimeString(value?: unknown): string | undefined;
|
|
|
57
63
|
/** Convert a possible `Date` instance to local time string like "18:32:00", or throw `RequiredError` if it couldn't be converted. */
|
|
58
64
|
export declare function requireTimeString(value?: PossibleDate, caller?: AnyCaller): string;
|
|
59
65
|
/** List of day-of-week strings. */
|
|
60
|
-
export declare const DAYS: readonly ["
|
|
66
|
+
export declare const DAYS: readonly ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
61
67
|
/** Type listing day-of-week strings. */
|
|
62
68
|
export type Day = (typeof DAYS)[number];
|
|
63
|
-
/** Convert a `Date` instance to a day-of-week string like "
|
|
69
|
+
/** Convert a `Date` instance to a day-of-week string like "Monday" */
|
|
64
70
|
export declare function getDay(target?: PossibleDate): Day;
|
|
65
|
-
/** Get a Date representing exactly midnight of the specified date. */
|
|
66
|
-
export declare function getMidnight(target?: PossibleDate, caller?: AnyCaller): Date;
|
|
67
|
-
/** Get a Date representing midnight on Monday of the specified week. */
|
|
68
|
-
export declare function getMonday(target?: PossibleDate, caller?: AnyCaller): Date;
|
|
69
|
-
/** Return a new date that increase or decreases the number of days based on an input date. */
|
|
70
|
-
export declare function addDays(change: number, target?: PossibleDate): Date;
|
|
71
|
-
/** Return a new date that increase or decreases the number of hours based on an input date. */
|
|
72
|
-
export declare function addHours(change: number, target?: PossibleDate): Date;
|
|
73
71
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
* Return a new date that increase or decreases the month based on an input date.
|
|
73
|
+
* - February 29th is a special cased and is _rounded down_ to February 28th on non-leap years.
|
|
74
|
+
*/
|
|
75
|
+
export declare function addYears(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
|
76
|
+
/**
|
|
77
|
+
* Return a new date that increase or decreases the month based on an input date.
|
|
78
|
+
* - Note that with Javascript "rollover" semantics, adding a month when we're on e.g. 31st of August would normally roll _past_ September and return 1st October.
|
|
79
|
+
* - To avoid this we clamp the date to the end of the month if rollover happens.
|
|
78
80
|
*/
|
|
79
|
-
export declare function
|
|
80
|
-
/**
|
|
81
|
-
export declare function
|
|
82
|
-
/**
|
|
83
|
-
export declare function
|
|
84
|
-
/**
|
|
85
|
-
export declare function
|
|
86
|
-
/**
|
|
87
|
-
export declare function
|
|
88
|
-
/**
|
|
89
|
-
export declare function
|
|
90
|
-
/**
|
|
91
|
-
export declare function
|
|
92
|
-
/** Is a date in the past? */
|
|
93
|
-
export declare function isPast(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
94
|
-
/** Is a date in the future? */
|
|
95
|
-
export declare function isFuture(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
96
|
-
/** Is a date today (taking into account midnight). */
|
|
97
|
-
export declare function isToday(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
98
|
-
/** Compact when a date happens/happened, e.g. `in 10d` or `2h ago` or `in 1w` or `just now` */
|
|
99
|
-
export declare function formatWhen(target: PossibleDate, current?: PossibleDate, options?: Intl.NumberFormatOptions): string;
|
|
100
|
-
/** Compact when a date happens, e.g. `10d` or `2h` or `-1w` */
|
|
101
|
-
export declare function formatUntil(target: PossibleDate, current?: PossibleDate, options?: Intl.NumberFormatOptions): string;
|
|
102
|
-
/** Compact when a date will happen, e.g. `10d` or `2h` or `-1w` */
|
|
103
|
-
export declare function formatAgo(target: PossibleDate, current?: PossibleDate, options?: Intl.NumberFormatOptions): string;
|
|
81
|
+
export declare function addMonths(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
|
82
|
+
/** Return a new date that increase or decreases the week based on an input date. */
|
|
83
|
+
export declare function addWeeks(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
|
84
|
+
/** Return a new date that increase or decreases the day based on an input date. */
|
|
85
|
+
export declare function addDays(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
|
86
|
+
/** Return a new date that increase or decreases the hour based on an input date. */
|
|
87
|
+
export declare function addHours(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
|
88
|
+
/** Return a new date that increase or decreases the minute based on an input date. */
|
|
89
|
+
export declare function addMinutes(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
|
90
|
+
/** Return a new date that increase or decreases the minute based on an input date. */
|
|
91
|
+
export declare function addSeconds(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
|
92
|
+
/** Return a new date that increase or decreases the minute based on an input date. */
|
|
93
|
+
export declare function addMilliseconds(change: number, target?: PossibleDate, caller?: AnyCaller): Date;
|
package/util/date.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
-
import { DAY, HOUR, MONTH, SECOND, WEEK } from "./constants.js";
|
|
3
|
-
import { TIME_UNITS } from "./units.js";
|
|
4
2
|
/**
|
|
5
3
|
* Is a value a valid date?
|
|
6
4
|
* - Note: `Date` instances can be invalid (e.g. `new Date("blah blah").getTime()` returns `NaN`). These are detected and will always return `false`
|
|
@@ -75,6 +73,28 @@ export function getTomorrow() {
|
|
|
75
73
|
date.setDate(date.getDate() + 1);
|
|
76
74
|
return date;
|
|
77
75
|
}
|
|
76
|
+
/** Get a Date representing exactly midnight of the specified date. */
|
|
77
|
+
export function getMidnight(target, caller = getMidnight) {
|
|
78
|
+
const date = new Date(requireDate(target, caller));
|
|
79
|
+
date.setHours(0, 0, 0, 0);
|
|
80
|
+
return date;
|
|
81
|
+
}
|
|
82
|
+
/** Get a Date representing midnight on Monday of the specified week. */
|
|
83
|
+
export function getMonday(target, caller = getMonday) {
|
|
84
|
+
const date = getMidnight(target, caller);
|
|
85
|
+
const day = date.getDay();
|
|
86
|
+
if (day === 0)
|
|
87
|
+
date.setDate(date.getDate() - 6);
|
|
88
|
+
else if (day !== 1)
|
|
89
|
+
date.setDate(date.getDate() - (day - 1));
|
|
90
|
+
return date;
|
|
91
|
+
}
|
|
92
|
+
/** Get a Date representing the first day of the specified month. */
|
|
93
|
+
export function getMonthStart(target, caller = getMonthStart) {
|
|
94
|
+
const date = getMidnight(target, caller);
|
|
95
|
+
date.setDate(1);
|
|
96
|
+
return date;
|
|
97
|
+
}
|
|
78
98
|
/**
|
|
79
99
|
* Convert a possible date to a `Date` instance, or throw `RequiredError` if it couldn't be converted.
|
|
80
100
|
* @param value Any value that we want to parse as a valid date (defaults to `"now"`).
|
|
@@ -136,121 +156,69 @@ export function requireTimeString(value, caller = requireTimeString) {
|
|
|
136
156
|
return _time(requireDate(value, caller));
|
|
137
157
|
}
|
|
138
158
|
/** List of day-of-week strings. */
|
|
139
|
-
export const DAYS = ["
|
|
140
|
-
/** Convert a `Date` instance to a day-of-week string like "
|
|
159
|
+
export const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
160
|
+
/** Convert a `Date` instance to a day-of-week string like "Monday" */
|
|
141
161
|
export function getDay(target) {
|
|
142
162
|
return DAYS[requireDate(target, getDay).getDay()];
|
|
143
163
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Return a new date that increase or decreases the month based on an input date.
|
|
166
|
+
* - February 29th is a special cased and is _rounded down_ to February 28th on non-leap years.
|
|
167
|
+
*/
|
|
168
|
+
export function addYears(change, target, caller = addYears) {
|
|
169
|
+
const input = requireDate(target, caller);
|
|
170
|
+
const output = new Date(input);
|
|
171
|
+
output.setFullYear(output.getFullYear() + change);
|
|
172
|
+
if (input.getMonth() !== output.getMonth())
|
|
173
|
+
output.setDate(0); // Handle February 29th case.
|
|
174
|
+
return output;
|
|
149
175
|
}
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Return a new date that increase or decreases the month based on an input date.
|
|
178
|
+
* - Note that with Javascript "rollover" semantics, adding a month when we're on e.g. 31st of August would normally roll _past_ September and return 1st October.
|
|
179
|
+
* - To avoid this we clamp the date to the end of the month if rollover happens.
|
|
180
|
+
*/
|
|
181
|
+
export function addMonths(change, target, caller = addMonths) {
|
|
182
|
+
const input = requireDate(target, caller);
|
|
183
|
+
const output = new Date(input);
|
|
184
|
+
output.setMonth(output.getMonth() + change);
|
|
185
|
+
if (input.getMonth() !== output.getMonth() + change)
|
|
186
|
+
output.setDate(0); // Handle 31st rollover case.
|
|
187
|
+
return output;
|
|
188
|
+
}
|
|
189
|
+
/** Return a new date that increase or decreases the week based on an input date. */
|
|
190
|
+
export function addWeeks(change, target, caller = addWeeks) {
|
|
191
|
+
const date = new Date(requireDate(target, caller));
|
|
192
|
+
date.setDate(date.getDate() + change * 7);
|
|
158
193
|
return date;
|
|
159
194
|
}
|
|
160
|
-
/** Return a new date that increase or decreases the
|
|
161
|
-
export function addDays(change, target) {
|
|
162
|
-
const date = new Date(requireDate(target,
|
|
195
|
+
/** Return a new date that increase or decreases the day based on an input date. */
|
|
196
|
+
export function addDays(change, target, caller = addDays) {
|
|
197
|
+
const date = new Date(requireDate(target, caller));
|
|
163
198
|
date.setDate(date.getDate() + change);
|
|
164
199
|
return date;
|
|
165
200
|
}
|
|
166
|
-
/** Return a new date that increase or decreases the
|
|
167
|
-
export function addHours(change, target) {
|
|
168
|
-
const date = new Date(requireDate(target,
|
|
201
|
+
/** Return a new date that increase or decreases the hour based on an input date. */
|
|
202
|
+
export function addHours(change, target, caller = addHours) {
|
|
203
|
+
const date = new Date(requireDate(target, caller));
|
|
169
204
|
date.setHours(date.getHours() + change);
|
|
170
205
|
return date;
|
|
171
206
|
}
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
/** Count the number of days until a date. */
|
|
190
|
-
export function getDaysUntil(target, current, caller = getDaysUntil) {
|
|
191
|
-
return Math.round((requireDate(target, caller).getTime() - requireDate(current, caller).getTime()) / DAY);
|
|
192
|
-
}
|
|
193
|
-
/** Count the number of days ago a date was. */
|
|
194
|
-
export function getDaysAgo(target, current, caller = getDaysAgo) {
|
|
195
|
-
return 0 - getDaysUntil(target, current, caller);
|
|
196
|
-
}
|
|
197
|
-
/** Count the number of weeks until a date. */
|
|
198
|
-
export function getWeeksUntil(target, current, caller = getWeeksUntil) {
|
|
199
|
-
return Math.floor(getDaysUntil(target, current, caller) / 7);
|
|
200
|
-
}
|
|
201
|
-
/** Count the number of weeks ago a date was. */
|
|
202
|
-
export function getWeeksAgo(target, current, caller = getWeeksAgo) {
|
|
203
|
-
return 0 - getWeeksUntil(target, current, caller);
|
|
204
|
-
}
|
|
205
|
-
/** Is a date in the past? */
|
|
206
|
-
export function isPast(target, current, caller = isPast) {
|
|
207
|
-
return getMillisecondsUntil(target, current, caller) < 0;
|
|
208
|
-
}
|
|
209
|
-
/** Is a date in the future? */
|
|
210
|
-
export function isFuture(target, current, caller = isFuture) {
|
|
211
|
-
return getMillisecondsUntil(target, current, caller) > 0;
|
|
212
|
-
}
|
|
213
|
-
/** Is a date today (taking into account midnight). */
|
|
214
|
-
export function isToday(target, current, caller = isToday) {
|
|
215
|
-
return getDaysUntil(target, current, caller) === 0;
|
|
216
|
-
}
|
|
217
|
-
/** Get an appropriate time unit based on an amount in milliseconds. */
|
|
218
|
-
function getBestTimeUnit(ms) {
|
|
219
|
-
const abs = Math.abs(ms);
|
|
220
|
-
if (abs > 18 * MONTH)
|
|
221
|
-
return TIME_UNITS.require("year");
|
|
222
|
-
if (abs > 10 * WEEK)
|
|
223
|
-
return TIME_UNITS.require("month");
|
|
224
|
-
if (abs > 2 * WEEK)
|
|
225
|
-
return TIME_UNITS.require("week");
|
|
226
|
-
if (abs > DAY)
|
|
227
|
-
return TIME_UNITS.require("day");
|
|
228
|
-
if (abs > HOUR)
|
|
229
|
-
return TIME_UNITS.require("hour");
|
|
230
|
-
if (abs > 9949)
|
|
231
|
-
return TIME_UNITS.require("minute");
|
|
232
|
-
if (abs > SECOND)
|
|
233
|
-
return TIME_UNITS.require("second");
|
|
234
|
-
return TIME_UNITS.require("millisecond");
|
|
235
|
-
}
|
|
236
|
-
/** Compact when a date happens/happened, e.g. `in 10d` or `2h ago` or `in 1w` or `just now` */
|
|
237
|
-
export function formatWhen(target, current, options) {
|
|
238
|
-
const ms = getMillisecondsUntil(target, current, formatWhen);
|
|
239
|
-
const abs = Math.abs(ms);
|
|
240
|
-
if (abs < 30 * SECOND)
|
|
241
|
-
return "just now";
|
|
242
|
-
const unit = getBestTimeUnit(ms);
|
|
243
|
-
return ms > 0 ? `in ${unit.format(unit.from(abs), options)}` : `${unit.format(unit.from(abs), options)} ago`;
|
|
244
|
-
}
|
|
245
|
-
/** Compact when a date happens, e.g. `10d` or `2h` or `-1w` */
|
|
246
|
-
export function formatUntil(target, current, options) {
|
|
247
|
-
const ms = getMillisecondsUntil(target, current, formatUntil);
|
|
248
|
-
const unit = getBestTimeUnit(ms);
|
|
249
|
-
return unit.format(unit.from(ms), options);
|
|
250
|
-
}
|
|
251
|
-
/** Compact when a date will happen, e.g. `10d` or `2h` or `-1w` */
|
|
252
|
-
export function formatAgo(target, current, options) {
|
|
253
|
-
const ms = 0 - getMillisecondsUntil(target, current, formatAgo);
|
|
254
|
-
const unit = getBestTimeUnit(ms);
|
|
255
|
-
return unit.format(unit.from(ms), options);
|
|
207
|
+
/** Return a new date that increase or decreases the minute based on an input date. */
|
|
208
|
+
export function addMinutes(change, target, caller = addMinutes) {
|
|
209
|
+
const date = new Date(requireDate(target, caller));
|
|
210
|
+
date.setMinutes(date.getMinutes() + change);
|
|
211
|
+
return date;
|
|
212
|
+
}
|
|
213
|
+
/** Return a new date that increase or decreases the minute based on an input date. */
|
|
214
|
+
export function addSeconds(change, target, caller = addSeconds) {
|
|
215
|
+
const date = new Date(requireDate(target, caller));
|
|
216
|
+
date.setSeconds(date.getSeconds() + change);
|
|
217
|
+
return date;
|
|
218
|
+
}
|
|
219
|
+
/** Return a new date that increase or decreases the minute based on an input date. */
|
|
220
|
+
export function addMilliseconds(change, target, caller = addMilliseconds) {
|
|
221
|
+
const date = new Date(requireDate(target, caller));
|
|
222
|
+
date.setMilliseconds(date.getMilliseconds() + change);
|
|
223
|
+
return date;
|
|
256
224
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type PossibleDate } from "./date.js";
|
|
2
|
+
import type { AnyCaller } from "./function.js";
|
|
3
|
+
import { type TimeUnitKey, type Unit } from "./units.js";
|
|
4
|
+
/**
|
|
5
|
+
* Duration object.
|
|
6
|
+
* - This should be compatible with `Intl.DurationFormat` when that is available.
|
|
7
|
+
*/
|
|
8
|
+
export type Duration = {
|
|
9
|
+
readonly years?: number | undefined;
|
|
10
|
+
readonly months?: number | undefined;
|
|
11
|
+
readonly weeks?: number | undefined;
|
|
12
|
+
readonly days?: number | undefined;
|
|
13
|
+
readonly hours?: number | undefined;
|
|
14
|
+
readonly minutes?: number | undefined;
|
|
15
|
+
readonly seconds?: number | undefined;
|
|
16
|
+
readonly milliseconds?: number | undefined;
|
|
17
|
+
readonly microseconds?: number | undefined;
|
|
18
|
+
readonly nanoseconds?: number | undefined;
|
|
19
|
+
};
|
|
20
|
+
/** Get the millisecond difference between two dates. */
|
|
21
|
+
export declare function getMilliseconds(from?: PossibleDate, to?: PossibleDate, caller?: AnyCaller): number;
|
|
22
|
+
/** Count the various time units between two dates and return a `Duration` format. */
|
|
23
|
+
export declare function getDuration(from?: PossibleDate, to?: PossibleDate, caller?: AnyCaller): Duration;
|
|
24
|
+
/** Get the various time units until a certain date. */
|
|
25
|
+
export declare function getUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): Duration;
|
|
26
|
+
/** Get the various time units since a certain date. */
|
|
27
|
+
export declare function getAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): Duration;
|
|
28
|
+
/** Count the milliseconds until a date. */
|
|
29
|
+
export declare function getMillisecondsUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
30
|
+
/** Count the milliseconds since a date. */
|
|
31
|
+
export declare function getMillisecondsAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
32
|
+
/**
|
|
33
|
+
* Count the whole seconds until a date.
|
|
34
|
+
* - Rounds to the nearest whole second, i.e. `1 second 499 ms` returns `1`
|
|
35
|
+
*/
|
|
36
|
+
export declare function getSecondsUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
37
|
+
/**
|
|
38
|
+
* Count the whole seconds since a date.
|
|
39
|
+
* - Rounds to the nearest whole second, i.e. `1 second 499 ms` returns `1`
|
|
40
|
+
*/
|
|
41
|
+
export declare function getSecondsAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
42
|
+
/**
|
|
43
|
+
* Count the whole minutes until a date.
|
|
44
|
+
* - Rounds to the nearest whole minute, i.e. `1 min 29 seconds` returns `1`
|
|
45
|
+
*/
|
|
46
|
+
export declare function getMinutesUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
47
|
+
/**
|
|
48
|
+
* Count the whole minutes since a date.
|
|
49
|
+
* - Rounds to the nearest whole minute, i.e. `1 min 29 seconds` returns `1`
|
|
50
|
+
*/
|
|
51
|
+
export declare function getMinutesAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
52
|
+
/**
|
|
53
|
+
* Count the whole hours until a date.
|
|
54
|
+
* - Rounds to the nearest whole hour, i.e. `1 hour 29 minutes` returns `1`
|
|
55
|
+
*/
|
|
56
|
+
export declare function getHoursUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
57
|
+
/**
|
|
58
|
+
* Count the whole hours since a date.
|
|
59
|
+
* - Rounds to the nearest whole hour, i.e. `1 hour 29 minutes` returns `1`
|
|
60
|
+
*/
|
|
61
|
+
export declare function getHoursAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
62
|
+
/**
|
|
63
|
+
* Count the calendar days until a date.
|
|
64
|
+
* - e.g. from 23:59 to 00:01 is 1 day, even though it's only 1 minutes.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getDaysUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
67
|
+
/**
|
|
68
|
+
* Count the calendar days since a date.
|
|
69
|
+
* - Rounds to the nearest whole days.
|
|
70
|
+
*/
|
|
71
|
+
export declare function getDaysAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
72
|
+
/**
|
|
73
|
+
* Count the whole weeks until a date.
|
|
74
|
+
* - Rounds down to the nearest whole week, i.e.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getWeeksUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
77
|
+
/**
|
|
78
|
+
* Count the whole weeks since a date.
|
|
79
|
+
* - Rounds to the nearest whole week.
|
|
80
|
+
*/
|
|
81
|
+
export declare function getWeeksAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
82
|
+
/**
|
|
83
|
+
* Count the calendar months until a date.
|
|
84
|
+
* - e.g. from March 31st to April 1st is 1 month, even though it's only 1 day.
|
|
85
|
+
*/
|
|
86
|
+
export declare function getMonthsUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
87
|
+
/**
|
|
88
|
+
* Count the calendar months since a date.
|
|
89
|
+
* - e.g. from March 31st to April 1st is 1 month, even though it's only 1 day.
|
|
90
|
+
*/
|
|
91
|
+
export declare function getMonthsAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
92
|
+
/**
|
|
93
|
+
* Count the calendar years until a date.
|
|
94
|
+
* - e.g. from December 31st to January 1st is 1 year, even though it's only 1 day.
|
|
95
|
+
*/
|
|
96
|
+
export declare function getYearsUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
97
|
+
/**
|
|
98
|
+
* Count the calendar years since a date.
|
|
99
|
+
* - Note this counts calendar years, not 365-day periods.
|
|
100
|
+
* - e.g. from December 31st to January 1st is -1 years, even though it's only 1 day.
|
|
101
|
+
*/
|
|
102
|
+
export declare function getYearsAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
103
|
+
/** Is a date in the past? */
|
|
104
|
+
export declare function isPast(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
105
|
+
/** Is a date in the future? */
|
|
106
|
+
export declare function isFuture(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
107
|
+
/** Is a date today (taking into account midnight). */
|
|
108
|
+
export declare function isToday(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Get a best-fit time unit based on an amount in milliseconds.
|
|
111
|
+
* - Makes a sensible choice about the best time unit to use.
|
|
112
|
+
* - Years will be used for anything 18 months or more, e.g. `in 2 years`
|
|
113
|
+
* - Months will be used for anything 10 weeks or more, e.g. `in 14 months`
|
|
114
|
+
* - Weeks will be used for anything 2 weeks or more, e.g. `in 9 weeks`
|
|
115
|
+
* - Days will be used for anything 24 hours or more, e.g. `in 13 days`
|
|
116
|
+
* - Hours will be used for anything 90 minutes or more, e.g. `in 23 hours`
|
|
117
|
+
* - Minutes will be used for anything 1 second or more, e.g. `1 minute ago` or `in 59 minutes`
|
|
118
|
+
* - Seconds will be used for anything 1000 milliseconds or more, e.g. `in 59 seconds`
|
|
119
|
+
*/
|
|
120
|
+
export declare function getBestTimeUnit(ms: number): Unit<TimeUnitKey>;
|
package/util/duration.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { DAY, HOUR, MINUTE, MONTH, SECOND, WEEK, YEAR } from "./constants.js";
|
|
2
|
+
import { getMidnight, requireDate } from "./date.js";
|
|
3
|
+
import { TIME_UNITS } from "./units.js";
|
|
4
|
+
/** Get the millisecond difference between two dates. */
|
|
5
|
+
export function getMilliseconds(from, to, caller = getMilliseconds) {
|
|
6
|
+
return requireDate(to, caller).getTime() - requireDate(from, caller).getTime();
|
|
7
|
+
}
|
|
8
|
+
/** Count the various time units between two dates and return a `Duration` format. */
|
|
9
|
+
export function getDuration(from, to, caller = getDuration) {
|
|
10
|
+
const ms = getMilliseconds(from, to, caller);
|
|
11
|
+
return {
|
|
12
|
+
years: Math.trunc(ms / YEAR),
|
|
13
|
+
months: Math.trunc((ms % YEAR) / MONTH),
|
|
14
|
+
weeks: Math.trunc((ms % MONTH) / WEEK),
|
|
15
|
+
days: Math.trunc((ms % WEEK) / DAY),
|
|
16
|
+
hours: Math.trunc((ms % DAY) / HOUR),
|
|
17
|
+
minutes: Math.trunc((ms % HOUR) / MINUTE),
|
|
18
|
+
seconds: Math.trunc((ms % MINUTE) / SECOND),
|
|
19
|
+
milliseconds: Math.trunc((ms % SECOND) / 1),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Get the various time units until a certain date. */
|
|
23
|
+
export function getUntil(target, current = "now", caller = getUntil) {
|
|
24
|
+
return getDuration(current, target, caller);
|
|
25
|
+
}
|
|
26
|
+
/** Get the various time units since a certain date. */
|
|
27
|
+
export function getAgo(target, current = "now", caller = getAgo) {
|
|
28
|
+
return getDuration(target, current, caller);
|
|
29
|
+
}
|
|
30
|
+
/** Count the milliseconds until a date. */
|
|
31
|
+
export function getMillisecondsUntil(target, current, caller = getMillisecondsUntil) {
|
|
32
|
+
return getMilliseconds(current, target, caller);
|
|
33
|
+
}
|
|
34
|
+
/** Count the milliseconds since a date. */
|
|
35
|
+
export function getMillisecondsAgo(target, current, caller = getMillisecondsAgo) {
|
|
36
|
+
return 0 - getMillisecondsUntil(target, current, caller);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Count the whole seconds until a date.
|
|
40
|
+
* - Rounds to the nearest whole second, i.e. `1 second 499 ms` returns `1`
|
|
41
|
+
*/
|
|
42
|
+
export function getSecondsUntil(target, current, caller = getSecondsUntil) {
|
|
43
|
+
return Math.round(getMilliseconds(current, target, caller) / SECOND);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Count the whole seconds since a date.
|
|
47
|
+
* - Rounds to the nearest whole second, i.e. `1 second 499 ms` returns `1`
|
|
48
|
+
*/
|
|
49
|
+
export function getSecondsAgo(target, current, caller = getSecondsAgo) {
|
|
50
|
+
return 0 - getSecondsUntil(target, current, caller);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Count the whole minutes until a date.
|
|
54
|
+
* - Rounds to the nearest whole minute, i.e. `1 min 29 seconds` returns `1`
|
|
55
|
+
*/
|
|
56
|
+
export function getMinutesUntil(target, current, caller = getMinutesUntil) {
|
|
57
|
+
return Math.round(getMilliseconds(current, target, caller) / MINUTE);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Count the whole minutes since a date.
|
|
61
|
+
* - Rounds to the nearest whole minute, i.e. `1 min 29 seconds` returns `1`
|
|
62
|
+
*/
|
|
63
|
+
export function getMinutesAgo(target, current, caller = getMinutesAgo) {
|
|
64
|
+
return 0 - getMinutesUntil(target, current, caller);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Count the whole hours until a date.
|
|
68
|
+
* - Rounds to the nearest whole hour, i.e. `1 hour 29 minutes` returns `1`
|
|
69
|
+
*/
|
|
70
|
+
export function getHoursUntil(target, current, caller = getHoursUntil) {
|
|
71
|
+
return Math.round(getMilliseconds(current, target, caller) / HOUR);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Count the whole hours since a date.
|
|
75
|
+
* - Rounds to the nearest whole hour, i.e. `1 hour 29 minutes` returns `1`
|
|
76
|
+
*/
|
|
77
|
+
export function getHoursAgo(target, current, caller = getHoursAgo) {
|
|
78
|
+
return 0 - getHoursUntil(target, current, caller);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Count the calendar days until a date.
|
|
82
|
+
* - e.g. from 23:59 to 00:01 is 1 day, even though it's only 1 minutes.
|
|
83
|
+
*/
|
|
84
|
+
export function getDaysUntil(target, current, caller = getDaysUntil) {
|
|
85
|
+
return Math.round((getMidnight(target, caller).getTime() - getMidnight(current, caller).getTime()) / DAY);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Count the calendar days since a date.
|
|
89
|
+
* - Rounds to the nearest whole days.
|
|
90
|
+
*/
|
|
91
|
+
export function getDaysAgo(target, current, caller = getDaysAgo) {
|
|
92
|
+
return 0 - getDaysUntil(target, current, caller);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Count the whole weeks until a date.
|
|
96
|
+
* - Rounds down to the nearest whole week, i.e.
|
|
97
|
+
*/
|
|
98
|
+
export function getWeeksUntil(target, current, caller = getWeeksUntil) {
|
|
99
|
+
return Math.trunc(getDaysUntil(target, current, caller) / 7);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Count the whole weeks since a date.
|
|
103
|
+
* - Rounds to the nearest whole week.
|
|
104
|
+
*/
|
|
105
|
+
export function getWeeksAgo(target, current, caller = getWeeksAgo) {
|
|
106
|
+
return 0 - getWeeksUntil(target, current, caller);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Count the calendar months until a date.
|
|
110
|
+
* - e.g. from March 31st to April 1st is 1 month, even though it's only 1 day.
|
|
111
|
+
*/
|
|
112
|
+
export function getMonthsUntil(target, current, caller = getMonthsUntil) {
|
|
113
|
+
const t = requireDate(target, caller);
|
|
114
|
+
const c = requireDate(current, caller);
|
|
115
|
+
const years = t.getFullYear() - c.getFullYear();
|
|
116
|
+
const months = t.getMonth() - c.getMonth();
|
|
117
|
+
return years * 12 + months;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Count the calendar months since a date.
|
|
121
|
+
* - e.g. from March 31st to April 1st is 1 month, even though it's only 1 day.
|
|
122
|
+
*/
|
|
123
|
+
export function getMonthsAgo(target, current, caller = getMonthsAgo) {
|
|
124
|
+
return 0 - getMonthsUntil(target, current, caller);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Count the calendar years until a date.
|
|
128
|
+
* - e.g. from December 31st to January 1st is 1 year, even though it's only 1 day.
|
|
129
|
+
*/
|
|
130
|
+
export function getYearsUntil(target, current, caller = getYearsUntil) {
|
|
131
|
+
return requireDate(target, caller).getFullYear() - requireDate(current, caller).getFullYear();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Count the calendar years since a date.
|
|
135
|
+
* - Note this counts calendar years, not 365-day periods.
|
|
136
|
+
* - e.g. from December 31st to January 1st is -1 years, even though it's only 1 day.
|
|
137
|
+
*/
|
|
138
|
+
export function getYearsAgo(target, current, caller = getYearsAgo) {
|
|
139
|
+
return 0 - getYearsUntil(target, current, caller);
|
|
140
|
+
}
|
|
141
|
+
/** Is a date in the past? */
|
|
142
|
+
export function isPast(target, current, caller = isPast) {
|
|
143
|
+
return getMilliseconds(current, target, caller) < 0;
|
|
144
|
+
}
|
|
145
|
+
/** Is a date in the future? */
|
|
146
|
+
export function isFuture(target, current, caller = isFuture) {
|
|
147
|
+
return getMilliseconds(current, target, caller) > 0;
|
|
148
|
+
}
|
|
149
|
+
/** Is a date today (taking into account midnight). */
|
|
150
|
+
export function isToday(target, current, caller = isToday) {
|
|
151
|
+
return getDaysUntil(target, current, caller) === 0;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get a best-fit time unit based on an amount in milliseconds.
|
|
155
|
+
* - Makes a sensible choice about the best time unit to use.
|
|
156
|
+
* - Years will be used for anything 18 months or more, e.g. `in 2 years`
|
|
157
|
+
* - Months will be used for anything 10 weeks or more, e.g. `in 14 months`
|
|
158
|
+
* - Weeks will be used for anything 2 weeks or more, e.g. `in 9 weeks`
|
|
159
|
+
* - Days will be used for anything 24 hours or more, e.g. `in 13 days`
|
|
160
|
+
* - Hours will be used for anything 90 minutes or more, e.g. `in 23 hours`
|
|
161
|
+
* - Minutes will be used for anything 1 second or more, e.g. `1 minute ago` or `in 59 minutes`
|
|
162
|
+
* - Seconds will be used for anything 1000 milliseconds or more, e.g. `in 59 seconds`
|
|
163
|
+
*/
|
|
164
|
+
export function getBestTimeUnit(ms) {
|
|
165
|
+
const abs = Math.abs(ms);
|
|
166
|
+
if (abs > 18 * MONTH)
|
|
167
|
+
return TIME_UNITS.require("year");
|
|
168
|
+
if (abs > 10 * WEEK)
|
|
169
|
+
return TIME_UNITS.require("month");
|
|
170
|
+
if (abs > DAY)
|
|
171
|
+
return TIME_UNITS.require("day");
|
|
172
|
+
if (abs > HOUR)
|
|
173
|
+
return TIME_UNITS.require("hour");
|
|
174
|
+
if (abs > MINUTE * 90)
|
|
175
|
+
return TIME_UNITS.require("minute");
|
|
176
|
+
if (abs > SECOND)
|
|
177
|
+
return TIME_UNITS.require("second");
|
|
178
|
+
return TIME_UNITS.require("millisecond");
|
|
179
|
+
}
|
package/util/format.d.ts
CHANGED
|
@@ -1,24 +1,62 @@
|
|
|
1
1
|
import { type ImmutableArray } from "./array.js";
|
|
2
2
|
import { type PossibleDate } from "./date.js";
|
|
3
|
+
import { type Duration } from "./duration.js";
|
|
3
4
|
import { type ImmutableObject } from "./object.js";
|
|
4
5
|
import { type PossibleURL } from "./url.js";
|
|
5
|
-
/**
|
|
6
|
-
export
|
|
7
|
-
/** Format a number with a short suffix, e.g. `1,000 kg` */
|
|
8
|
-
export declare function formatQuantity(num: number, suffix: string, options?: Intl.NumberFormatOptions): string;
|
|
6
|
+
/** Options we use for number formatting. */
|
|
7
|
+
export type NumberOptions = Omit<Intl.NumberFormatOptions, "style" | "unit" | "unitDisplay" | "currency" | "currencyDisplay" | "currencySign">;
|
|
9
8
|
/** Format a number (based on the user's browser language settings). */
|
|
10
|
-
export declare function formatNumber(num: number, options?:
|
|
11
|
-
/** Format a number
|
|
12
|
-
export declare function
|
|
9
|
+
export declare function formatNumber(num: number, options?: NumberOptions): string;
|
|
10
|
+
/** Format a number range (based on the user's browser language settings). */
|
|
11
|
+
export declare function formatRange(from: number, to: number, options?: NumberOptions): string;
|
|
12
|
+
/** Options for quantity formatting. */
|
|
13
|
+
export interface QuantityOptions extends Omit<Intl.NumberFormatOptions, "style" | "unit" | "currency" | "currencyDisplay" | "currencySign"> {
|
|
14
|
+
/**
|
|
15
|
+
* String for one of this thing, e.g. `product` or `item` or `sheep`
|
|
16
|
+
* - Used for `unitDisplay: "long"` formatting.
|
|
17
|
+
* - Defaults to unit reference, e.g. "minute"
|
|
18
|
+
*/
|
|
19
|
+
readonly one?: string;
|
|
20
|
+
/**
|
|
21
|
+
* String for several of this thing, e.g. `products` or `items` or `sheep`
|
|
22
|
+
* - Used for `unitDisplay: "long"` formatting.
|
|
23
|
+
* - Defaults to `one + "s"`
|
|
24
|
+
*/
|
|
25
|
+
readonly many?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Abbreviation for this thing, e.g. `products` or `items` or `sheep` (defaults to `one` + "s").
|
|
28
|
+
* - Used for `unitDisplay: "narrow"` formatting.
|
|
29
|
+
* - Defaults to unit reference, e.g. "minute"
|
|
30
|
+
*/
|
|
31
|
+
readonly abbr?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Format a quantity of a given unit.
|
|
35
|
+
*
|
|
36
|
+
* - Javascript has built-in support for formatting a number of different units.
|
|
37
|
+
* - Unfortunately the list of supported units changes in different browsers.
|
|
38
|
+
* - Ideally we want to format units using the built-in formatting so things like translation and internationalisation are covered.
|
|
39
|
+
* - But we want provide fallback formatting for unsupported units, and do something _good enough_ job in most cases.
|
|
40
|
+
*/
|
|
41
|
+
export declare function formatUnit(num: number, unit: string, options?: QuantityOptions): string;
|
|
42
|
+
/** Options we use for currency formatting. */
|
|
43
|
+
export type CurrencyOptions = Omit<Intl.NumberFormatOptions, "style" | "unit" | "unitDisplay" | "currency">;
|
|
44
|
+
/**
|
|
45
|
+
* Format a currency amount (based on the user's browser language settings).
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatCurrency(amount: number, currency: string, options?: CurrencyOptions): string;
|
|
48
|
+
/** Options we use for percent formatting. */
|
|
49
|
+
export type PercentOptions = Omit<Intl.NumberFormatOptions, "style" | "unit" | "unitDisplay" | "currency" | "currencyDisplay" | "currencySign">;
|
|
13
50
|
/**
|
|
14
51
|
* Format a percentage (combines `getPercent()` and `formatQuantity()` for convenience).
|
|
15
52
|
* - Defaults to showing no decimal places.
|
|
16
53
|
* - Defaults to rounding closer to zero (so that 99.99% is shown as 99%).
|
|
54
|
+
* - Javascript's built-in percent formatting works on the `0` zero to `1` range. This uses `getPercent()` which works on `0` to `100` for convenience.
|
|
17
55
|
*
|
|
18
|
-
* @param numerator Number representing the amount of progress.
|
|
19
|
-
* @param denumerator The number representing the whole amount.
|
|
56
|
+
* @param numerator Number representing the amount of progress (e.g. `50`).
|
|
57
|
+
* @param denumerator The number representing the whole amount (defaults to 100).
|
|
20
58
|
*/
|
|
21
|
-
export declare function formatPercent(numerator: number, denumerator
|
|
59
|
+
export declare function formatPercent(numerator: number, denumerator?: number, options?: PercentOptions): string;
|
|
22
60
|
/**
|
|
23
61
|
* Format an unknown object as a string.
|
|
24
62
|
* - Use the custom `.toString()` function if it exists (don't use built in `Object.prototype.toString` because it's useless.
|
|
@@ -50,3 +88,27 @@ export declare function formatURL(possible: PossibleURL, base?: PossibleURL): st
|
|
|
50
88
|
* - Everything else returns `"Unknown"`
|
|
51
89
|
*/
|
|
52
90
|
export declare function formatValue(value: unknown): string;
|
|
91
|
+
/**
|
|
92
|
+
* Compact best-fit when a date happens/happened, e.g. `in 10d` or `2h ago` or `in 1w` or `just now`
|
|
93
|
+
* - See `getBestTimeUnit()` for details on how the best-fit unit is chosen.
|
|
94
|
+
* - But: anything under 30 seconds will show `just now`, which makes more sense in most UIs.
|
|
95
|
+
*/
|
|
96
|
+
export declare function formatWhen(target: PossibleDate, current?: PossibleDate, options?: Intl.NumberFormatOptions): string;
|
|
97
|
+
/** Compact when a date happens, e.g. `10d` or `2h` or `-1w` */
|
|
98
|
+
export declare function formatUntil(target: PossibleDate, current?: PossibleDate, options?: Intl.NumberFormatOptions): string;
|
|
99
|
+
/** Compact when a date will happen, e.g. `10d` or `2h` or `-1w` */
|
|
100
|
+
export declare function formatAgo(target: PossibleDate, current?: PossibleDate, options?: Intl.NumberFormatOptions): string;
|
|
101
|
+
/**
|
|
102
|
+
* This roughly corresponds to `Intl.DurationFormatOptions`
|
|
103
|
+
* @todo Use `Intl.DurationFormatOptions` instead it's available in TS lib.
|
|
104
|
+
*/
|
|
105
|
+
interface DurationFormatOptions {
|
|
106
|
+
style?: "short" | "long" | "narrow";
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Format a duration as a string, e.g. `1 year, 2 months, 3 days` or `1y 2m 3d`
|
|
110
|
+
* @todo Use `Intl.DurationFormat().format()` instead it's more widely supported and is available in TS lib.
|
|
111
|
+
*/
|
|
112
|
+
export declare function formatDuration(duration: Duration, options?: DurationFormatOptions): string;
|
|
113
|
+
export declare function _getDurationStrings(duration: Duration, options?: Intl.NumberFormatOptions): Iterable<string>;
|
|
114
|
+
export {};
|
package/util/format.js
CHANGED
|
@@ -1,49 +1,64 @@
|
|
|
1
1
|
import { isArray } from "./array.js";
|
|
2
|
-
import {
|
|
2
|
+
import { SECOND } from "./constants.js";
|
|
3
3
|
import { isDate, requireDate } from "./date.js";
|
|
4
|
+
import { getBestTimeUnit, getMilliseconds } from "./duration.js";
|
|
4
5
|
import { getPercent } from "./number.js";
|
|
5
6
|
import { isObject } from "./object.js";
|
|
7
|
+
import { TIME_UNITS } from "./units.js";
|
|
6
8
|
import { requireURL } from "./url.js";
|
|
7
|
-
/** Format a number range (based on the user's browser language settings). */
|
|
8
|
-
export function formatRange(min, max, options) {
|
|
9
|
-
return `${formatNumber(min, options)}${NNBSP}–${NNBSP}${formatNumber(max, options)}`;
|
|
10
|
-
}
|
|
11
|
-
/** Format a number with a short suffix, e.g. `1,000 kg` */
|
|
12
|
-
export function formatQuantity(num, suffix, options) {
|
|
13
|
-
const o = { unitDisplay: "short", ...options, style: "decimal" };
|
|
14
|
-
const str = formatNumber(num, o);
|
|
15
|
-
const sep = o.unitDisplay === "narrow" ? "" : NNBSP;
|
|
16
|
-
return `${str}${sep}${suffix}`;
|
|
17
|
-
}
|
|
18
9
|
/** Format a number (based on the user's browser language settings). */
|
|
19
10
|
export function formatNumber(num, options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
return Intl.NumberFormat(undefined, options).format(num);
|
|
12
|
+
}
|
|
13
|
+
/** Format a number range (based on the user's browser language settings). */
|
|
14
|
+
export function formatRange(from, to, options) {
|
|
15
|
+
return Intl.NumberFormat(undefined, options).formatRange(from, to);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Format a quantity of a given unit.
|
|
19
|
+
*
|
|
20
|
+
* - Javascript has built-in support for formatting a number of different units.
|
|
21
|
+
* - Unfortunately the list of supported units changes in different browsers.
|
|
22
|
+
* - Ideally we want to format units using the built-in formatting so things like translation and internationalisation are covered.
|
|
23
|
+
* - But we want provide fallback formatting for unsupported units, and do something _good enough_ job in most cases.
|
|
24
|
+
*/
|
|
25
|
+
export function formatUnit(num, unit, options) {
|
|
26
|
+
// Check if the unit is supported by the browser.
|
|
27
|
+
if (Intl.supportedValuesOf("unit").includes(unit))
|
|
28
|
+
return Intl.NumberFormat(undefined, { ...options, style: "unit", unit }).format(num);
|
|
29
|
+
// Otherwise, use the default number format.
|
|
30
|
+
const str = Intl.NumberFormat(undefined, { ...options, style: "decimal" }).format(num);
|
|
31
|
+
const { unitDisplay, abbr = unit, one = unit, many = `${one}s` } = options ?? {};
|
|
32
|
+
if (unitDisplay === "long")
|
|
33
|
+
return `${str} ${str === "1" ? one : many}`;
|
|
34
|
+
return `${str}${unitDisplay === "narrow" ? "" : " "}${abbr}`; // "short" is the default.
|
|
23
35
|
}
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Format a currency amount (based on the user's browser language settings).
|
|
38
|
+
*/
|
|
39
|
+
export function formatCurrency(amount, currency, options) {
|
|
40
|
+
return Intl.NumberFormat(undefined, {
|
|
41
|
+
style: "currency",
|
|
42
|
+
currency,
|
|
27
43
|
...options,
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
return `${qty}${NNBSP}${num === 1 ? one : many}`;
|
|
44
|
+
}).format(amount);
|
|
31
45
|
}
|
|
32
46
|
/**
|
|
33
47
|
* Format a percentage (combines `getPercent()` and `formatQuantity()` for convenience).
|
|
34
48
|
* - Defaults to showing no decimal places.
|
|
35
49
|
* - Defaults to rounding closer to zero (so that 99.99% is shown as 99%).
|
|
50
|
+
* - Javascript's built-in percent formatting works on the `0` zero to `1` range. This uses `getPercent()` which works on `0` to `100` for convenience.
|
|
36
51
|
*
|
|
37
|
-
* @param numerator Number representing the amount of progress.
|
|
38
|
-
* @param denumerator The number representing the whole amount.
|
|
52
|
+
* @param numerator Number representing the amount of progress (e.g. `50`).
|
|
53
|
+
* @param denumerator The number representing the whole amount (defaults to 100).
|
|
39
54
|
*/
|
|
40
55
|
export function formatPercent(numerator, denumerator, options) {
|
|
41
|
-
return
|
|
56
|
+
return Intl.NumberFormat(undefined, {
|
|
57
|
+
style: "percent",
|
|
42
58
|
maximumFractionDigits: 0,
|
|
43
|
-
roundingMode: "
|
|
59
|
+
roundingMode: "floor",
|
|
44
60
|
...options,
|
|
45
|
-
|
|
46
|
-
});
|
|
61
|
+
}).format(getPercent(numerator, denumerator) / 100);
|
|
47
62
|
}
|
|
48
63
|
/**
|
|
49
64
|
* Format an unknown object as a string.
|
|
@@ -133,3 +148,46 @@ export function formatValue(value) {
|
|
|
133
148
|
return formatObject(value);
|
|
134
149
|
return "Unknown";
|
|
135
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Compact best-fit when a date happens/happened, e.g. `in 10d` or `2h ago` or `in 1w` or `just now`
|
|
153
|
+
* - See `getBestTimeUnit()` for details on how the best-fit unit is chosen.
|
|
154
|
+
* - But: anything under 30 seconds will show `just now`, which makes more sense in most UIs.
|
|
155
|
+
*/
|
|
156
|
+
export function formatWhen(target, current, options) {
|
|
157
|
+
const ms = getMilliseconds(current, target, formatWhen);
|
|
158
|
+
const abs = Math.abs(ms);
|
|
159
|
+
if (abs < 30 * SECOND)
|
|
160
|
+
return "just now";
|
|
161
|
+
const unit = getBestTimeUnit(ms);
|
|
162
|
+
return ms > 0 ? `in ${unit.format(unit.from(abs), options)}` : `${unit.format(unit.from(abs), options)} ago`;
|
|
163
|
+
}
|
|
164
|
+
/** Compact when a date happens, e.g. `10d` or `2h` or `-1w` */
|
|
165
|
+
export function formatUntil(target, current, options) {
|
|
166
|
+
const ms = getMilliseconds(current, target, formatUntil);
|
|
167
|
+
const unit = getBestTimeUnit(ms);
|
|
168
|
+
return unit.format(unit.from(ms), options);
|
|
169
|
+
}
|
|
170
|
+
/** Compact when a date will happen, e.g. `10d` or `2h` or `-1w` */
|
|
171
|
+
export function formatAgo(target, current, options) {
|
|
172
|
+
const ms = getMilliseconds(target, current, formatAgo);
|
|
173
|
+
const unit = getBestTimeUnit(ms);
|
|
174
|
+
return unit.format(unit.from(ms), options);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Format a duration as a string, e.g. `1 year, 2 months, 3 days` or `1y 2m 3d`
|
|
178
|
+
* @todo Use `Intl.DurationFormat().format()` instead it's more widely supported and is available in TS lib.
|
|
179
|
+
*/
|
|
180
|
+
export function formatDuration(duration, options) {
|
|
181
|
+
// Map `DurationFormatOptions` to `NumberFormatOptions`
|
|
182
|
+
const style = options?.style ?? "short";
|
|
183
|
+
return new Intl.ListFormat(undefined, { style, type: "unit" }).format(_getDurationStrings(duration, { ...options, style: "unit", unitDisplay: style }));
|
|
184
|
+
}
|
|
185
|
+
export function* _getDurationStrings(duration, options) {
|
|
186
|
+
for (const key of TIME_KEYS) {
|
|
187
|
+
const value = duration[`${key}s`];
|
|
188
|
+
if (typeof value === "number" && value !== 0)
|
|
189
|
+
yield TIME_UNITS.require(key)?.format(value, options);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Keys we loop through in the right order.
|
|
193
|
+
const TIME_KEYS = ["year", "month", "week", "day", "hour", "minute", "second", "millisecond"];
|
package/util/index.d.ts
CHANGED
package/util/index.js
CHANGED
package/util/number.d.ts
CHANGED
|
@@ -92,9 +92,9 @@ export declare function wrapNumber(num: number, min: number, max: number): numbe
|
|
|
92
92
|
* Get a number as a percentage of another number.
|
|
93
93
|
*
|
|
94
94
|
* @param numerator Number representing the amount of progress.
|
|
95
|
-
* @param denumerator The number representing the whole amount.
|
|
95
|
+
* @param denumerator The number representing the whole amount (defaults to 100).
|
|
96
96
|
*/
|
|
97
|
-
export declare function getPercent(numerator: number, denumerator
|
|
97
|
+
export declare function getPercent(numerator: number, denumerator?: number): number;
|
|
98
98
|
/** Sum an iterable set of numbers and return the total. */
|
|
99
99
|
export declare function sumNumbers(nums: Iterable<number>): number;
|
|
100
100
|
/** Find the number that's closest to a target in an iterable set of numbers. */
|
package/util/number.js
CHANGED
|
@@ -144,10 +144,10 @@ export function wrapNumber(num, min, max) {
|
|
|
144
144
|
* Get a number as a percentage of another number.
|
|
145
145
|
*
|
|
146
146
|
* @param numerator Number representing the amount of progress.
|
|
147
|
-
* @param denumerator The number representing the whole amount.
|
|
147
|
+
* @param denumerator The number representing the whole amount (defaults to 100).
|
|
148
148
|
*/
|
|
149
|
-
export function getPercent(numerator, denumerator) {
|
|
150
|
-
return
|
|
149
|
+
export function getPercent(numerator, denumerator = 100) {
|
|
150
|
+
return denumerator === 100 ? numerator : (100 / denumerator) * numerator;
|
|
151
151
|
}
|
|
152
152
|
/** Sum an iterable set of numbers and return the total. */
|
|
153
153
|
export function sumNumbers(nums) {
|
package/util/units.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type QuantityOptions } from "./format.js";
|
|
1
2
|
import { ImmutableMap, type MapKey } from "./map.js";
|
|
2
3
|
import type { ImmutableObject } from "./object.js";
|
|
3
4
|
/** Conversion from one unit to another (either an amount to multiple by, or a function to convert). */
|
|
@@ -6,18 +7,10 @@ type Conversion = number | ((num: number) => number);
|
|
|
6
7
|
type Conversions<T extends string> = {
|
|
7
8
|
readonly [K in T]?: Conversion;
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
-
/** Short abbreviation for this unit, e.g. `km` (defaults to first letter of `id`). */
|
|
11
|
-
readonly abbr?: string;
|
|
12
|
-
/** Singular name for this unit, e.g. `kilometer` (defaults to `id` + "s"). */
|
|
13
|
-
readonly one?: string;
|
|
14
|
-
/** Plural name for this unit, e.g. `kilometers` (defaults to `id`). */
|
|
15
|
-
readonly many?: string;
|
|
10
|
+
interface UnitProps<T extends string> extends QuantityOptions {
|
|
16
11
|
/** Conversions to other units (typically needs at least the base conversion, unless it's already the base unit). */
|
|
17
12
|
readonly to?: Conversions<T>;
|
|
18
|
-
|
|
19
|
-
readonly options?: Readonly<Intl.NumberFormatOptions> | undefined;
|
|
20
|
-
};
|
|
13
|
+
}
|
|
21
14
|
/** Represent a unit. */
|
|
22
15
|
export declare class Unit<K extends string> {
|
|
23
16
|
private readonly _to;
|
|
@@ -25,23 +18,15 @@ export declare class Unit<K extends string> {
|
|
|
25
18
|
readonly list: UnitList<K>;
|
|
26
19
|
/** String key for this unit, e.g. `kilometer` */
|
|
27
20
|
readonly key: K;
|
|
28
|
-
/**
|
|
29
|
-
readonly
|
|
30
|
-
/** Singular name for this unit, e.g. `kilometer` (defaults to `id`). */
|
|
31
|
-
readonly one: string;
|
|
32
|
-
/** Plural name for this unit, e.g. `kilometers` (defaults to `singular` + "s"). */
|
|
33
|
-
readonly many: string;
|
|
34
|
-
/** Possible options for formatting these units with `Intl.NumberFormat` (`.unit` can be specified if different from key, but is not required). */
|
|
35
|
-
readonly options: Readonly<Intl.NumberFormatOptions> | undefined;
|
|
36
|
-
/** Title for this unit (uses format `abbr (plural)`, e.g. `fl oz (US fluid ounces)`) */
|
|
37
|
-
get title(): string;
|
|
21
|
+
/** Possible options for formatting these units. */
|
|
22
|
+
readonly options: Readonly<QuantityOptions> | undefined;
|
|
38
23
|
constructor(
|
|
39
24
|
/** `UnitList` this unit belongs to. */
|
|
40
25
|
list: UnitList<K>,
|
|
41
26
|
/** String key for this unit, e.g. `kilometer` */
|
|
42
27
|
key: K,
|
|
43
28
|
/** Props to configure this unit. */
|
|
44
|
-
{
|
|
29
|
+
{ to, ...options }: UnitProps<K>);
|
|
45
30
|
/** Convert an amount from this unit to another unit. */
|
|
46
31
|
to(amount: number, targetKey?: K): number;
|
|
47
32
|
/** Convert an amount from another unit to this unit. */
|
|
@@ -53,7 +38,7 @@ export declare class Unit<K extends string> {
|
|
|
53
38
|
* - Uses `Intl.NumberFormat` if this is a supported unit (so e.g. `ounce` is translated to e.g. `Unze` in German).
|
|
54
39
|
* - Polyfills unsupported units to use long/short form based on `options.unitDisplay`.
|
|
55
40
|
*/
|
|
56
|
-
format(amount: number, options?:
|
|
41
|
+
format(amount: number, options?: QuantityOptions): string;
|
|
57
42
|
}
|
|
58
43
|
/**
|
|
59
44
|
* Represent a list of units.
|
package/util/units.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { ValueError } from "../error/ValueError.js";
|
|
3
3
|
import { DAY, HOUR, MILLION, MINUTE, MONTH, NNBSP, SECOND, WEEK, YEAR } from "./constants.js";
|
|
4
|
-
import {
|
|
4
|
+
import { formatUnit } from "./format.js";
|
|
5
5
|
import { ImmutableMap } from "./map.js";
|
|
6
6
|
import { getProps } from "./object.js";
|
|
7
7
|
/** Convert an amount using a `Conversion. */
|
|
@@ -15,30 +15,17 @@ export class Unit {
|
|
|
15
15
|
list;
|
|
16
16
|
/** String key for this unit, e.g. `kilometer` */
|
|
17
17
|
key;
|
|
18
|
-
/**
|
|
19
|
-
abbr;
|
|
20
|
-
/** Singular name for this unit, e.g. `kilometer` (defaults to `id`). */
|
|
21
|
-
one;
|
|
22
|
-
/** Plural name for this unit, e.g. `kilometers` (defaults to `singular` + "s"). */
|
|
23
|
-
many;
|
|
24
|
-
/** Possible options for formatting these units with `Intl.NumberFormat` (`.unit` can be specified if different from key, but is not required). */
|
|
18
|
+
/** Possible options for formatting these units. */
|
|
25
19
|
options;
|
|
26
|
-
/** Title for this unit (uses format `abbr (plural)`, e.g. `fl oz (US fluid ounces)`) */
|
|
27
|
-
get title() {
|
|
28
|
-
return `${this.abbr} (${this.many})`;
|
|
29
|
-
}
|
|
30
20
|
constructor(
|
|
31
21
|
/** `UnitList` this unit belongs to. */
|
|
32
22
|
list,
|
|
33
23
|
/** String key for this unit, e.g. `kilometer` */
|
|
34
24
|
key,
|
|
35
25
|
/** Props to configure this unit. */
|
|
36
|
-
{
|
|
26
|
+
{ to, ...options }) {
|
|
37
27
|
this.list = list;
|
|
38
28
|
this.key = key;
|
|
39
|
-
this.abbr = abbr;
|
|
40
|
-
this.one = one;
|
|
41
|
-
this.many = many;
|
|
42
29
|
this.options = options;
|
|
43
30
|
this._to = to;
|
|
44
31
|
}
|
|
@@ -83,13 +70,7 @@ export class Unit {
|
|
|
83
70
|
* - Polyfills unsupported units to use long/short form based on `options.unitDisplay`.
|
|
84
71
|
*/
|
|
85
72
|
format(amount, options) {
|
|
86
|
-
|
|
87
|
-
if (Intl.supportedValuesOf("unit").includes(this.key))
|
|
88
|
-
return formatNumber(amount, { style: "unit", unitDisplay: "short", ...this.options, ...options, unit: this.key });
|
|
89
|
-
// Otherwise, use the default number format.
|
|
90
|
-
// If unitDisplay is "long" use the singular/plural form.
|
|
91
|
-
const o = { style: "decimal", unitDisplay: "short", ...this.options, ...options };
|
|
92
|
-
return o.unitDisplay === "long" ? pluralizeQuantity(amount, this.one, this.many, o) : formatQuantity(amount, this.abbr, o);
|
|
73
|
+
return formatUnit(amount, this.key, { ...this.options, ...options });
|
|
93
74
|
}
|
|
94
75
|
}
|
|
95
76
|
/**
|
|
@@ -185,14 +166,14 @@ const TIME_OPTIONS = {
|
|
|
185
166
|
};
|
|
186
167
|
/** Time units. */
|
|
187
168
|
export const TIME_UNITS = new UnitList({
|
|
188
|
-
millisecond: { abbr: "ms"
|
|
189
|
-
second: { to: { millisecond: SECOND }
|
|
190
|
-
minute: { to: { millisecond: MINUTE }
|
|
191
|
-
hour: { to: { millisecond: HOUR }
|
|
192
|
-
day: { to: { millisecond: DAY }
|
|
193
|
-
week: { to: { millisecond: WEEK }
|
|
194
|
-
month: { to: { millisecond: MONTH }
|
|
195
|
-
year: { to: { millisecond: YEAR }
|
|
169
|
+
millisecond: { ...TIME_OPTIONS, abbr: "ms" },
|
|
170
|
+
second: { ...TIME_OPTIONS, to: { millisecond: SECOND } },
|
|
171
|
+
minute: { ...TIME_OPTIONS, to: { millisecond: MINUTE } },
|
|
172
|
+
hour: { ...TIME_OPTIONS, to: { millisecond: HOUR } },
|
|
173
|
+
day: { ...TIME_OPTIONS, to: { millisecond: DAY } },
|
|
174
|
+
week: { ...TIME_OPTIONS, to: { millisecond: WEEK } },
|
|
175
|
+
month: { ...TIME_OPTIONS, to: { millisecond: MONTH } },
|
|
176
|
+
year: { ...TIME_OPTIONS, to: { millisecond: YEAR } },
|
|
196
177
|
});
|
|
197
178
|
/** Length units. */
|
|
198
179
|
export const LENGTH_UNITS = new UnitList({
|
|
@@ -293,6 +274,16 @@ export const TEMPERATURE_UNITS = new UnitList({
|
|
|
293
274
|
many: "degrees Celsius",
|
|
294
275
|
to: { fahrenheit: n => n * (9 / 5) + 32, kelvin: n => n + 273.15 },
|
|
295
276
|
},
|
|
296
|
-
fahrenheit: {
|
|
297
|
-
|
|
277
|
+
fahrenheit: {
|
|
278
|
+
abbr: "°F",
|
|
279
|
+
one: "degree Fahrenheit",
|
|
280
|
+
many: "degrees Fahrenheit",
|
|
281
|
+
to: { celsius: n => (n - 32) * (5 / 9) },
|
|
282
|
+
},
|
|
283
|
+
kelvin: {
|
|
284
|
+
abbr: "°K",
|
|
285
|
+
one: "degree Kelvin",
|
|
286
|
+
many: "degrees Kelvin",
|
|
287
|
+
to: { celsius: n => n - 273.15 },
|
|
288
|
+
},
|
|
298
289
|
});
|
package/util/url.js
CHANGED
|
@@ -4,6 +4,17 @@ import { isData } from "./data.js";
|
|
|
4
4
|
import { notNullish } from "./null.js";
|
|
5
5
|
import { getProps } from "./object.js";
|
|
6
6
|
import { getString, isString } from "./string.js";
|
|
7
|
+
function parseURL(value, base) {
|
|
8
|
+
const ctor = URL;
|
|
9
|
+
if (typeof ctor.parse === "function")
|
|
10
|
+
return ctor.parse(value, base);
|
|
11
|
+
try {
|
|
12
|
+
return new URL(value, base);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
7
18
|
/** Is an unknown value a URL object? */
|
|
8
19
|
export function isURL(value) {
|
|
9
20
|
return value instanceof URL;
|
|
@@ -16,7 +27,7 @@ export function assertURL(value, caller = assertURL) {
|
|
|
16
27
|
/** Convert a possible URL to a URL, or return `undefined` if conversion fails. */
|
|
17
28
|
export function getURL(possible, base = _BASE) {
|
|
18
29
|
if (notNullish(possible))
|
|
19
|
-
return isURL(possible) ? possible :
|
|
30
|
+
return isURL(possible) ? possible : parseURL(possible, base) || undefined;
|
|
20
31
|
}
|
|
21
32
|
const _BASE = typeof document === "object" ? document.baseURI : undefined;
|
|
22
33
|
/** Convert a possible URL to a URL, or throw `RequiredError` if conversion fails. */
|