@prairielearn/formatter 2.0.2 → 2.1.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/CHANGELOG.md +7 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/interval.d.ts +8 -2
- package/dist/interval.d.ts.map +1 -1
- package/dist/interval.js +11 -8
- package/dist/interval.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +5 -0
- package/src/interval.ts +14 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @prairielearn/formatter
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9c91665: Add options for full part names and first only in formatInterval
|
|
8
|
+
- 7b937fb: Remove unused exports, add `@knipignore` for intentionally public exports, and re-export newly used symbols from `@prairielearn/formatter`.
|
|
9
|
+
|
|
3
10
|
## 2.0.2
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { formatDate, formatDateFriendly, formatDateRangeFriendly, formatDateWithinRange, formatDateYMD, formatDateYMDHM, formatTz, } from './date.js';
|
|
2
|
-
export { formatInterval, formatIntervalHM, formatIntervalMinutes, formatIntervalRelative, makeInterval, } from './interval.js';
|
|
1
|
+
export { formatDate, formatDateFriendly, formatDateHMS, formatDateRangeFriendly, formatDateWithinRange, formatDateYMD, formatDateYMDHM, formatTz, } from './date.js';
|
|
2
|
+
export { DAY_IN_MILLISECONDS, formatInterval, formatIntervalHM, formatIntervalMinutes, formatIntervalRelative, HOUR_IN_MILLISECONDS, makeInterval, MINUTE_IN_MILLISECONDS, SECOND_IN_MILLISECONDS, } from './interval.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,aAAa,EACb,eAAe,EACf,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,uBAAuB,EACvB,qBAAqB,EACrB,aAAa,EACb,eAAe,EACf,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC","sourcesContent":["export {\n formatDate,\n formatDateFriendly,\n formatDateHMS,\n formatDateRangeFriendly,\n formatDateWithinRange,\n formatDateYMD,\n formatDateYMDHM,\n formatTz,\n} from './date.js';\nexport {\n DAY_IN_MILLISECONDS,\n formatInterval,\n formatIntervalHM,\n formatIntervalMinutes,\n formatIntervalRelative,\n HOUR_IN_MILLISECONDS,\n makeInterval,\n MINUTE_IN_MILLISECONDS,\n SECOND_IN_MILLISECONDS,\n} from './interval.js';\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { formatDate, formatDateFriendly, formatDateRangeFriendly, formatDateWithinRange, formatDateYMD, formatDateYMDHM, formatTz, } from './date.js';
|
|
2
|
-
export { formatInterval, formatIntervalHM, formatIntervalMinutes, formatIntervalRelative, makeInterval, } from './interval.js';
|
|
1
|
+
export { formatDate, formatDateFriendly, formatDateHMS, formatDateRangeFriendly, formatDateWithinRange, formatDateYMD, formatDateYMDHM, formatTz, } from './date.js';
|
|
2
|
+
export { DAY_IN_MILLISECONDS, formatInterval, formatIntervalHM, formatIntervalMinutes, formatIntervalRelative, HOUR_IN_MILLISECONDS, makeInterval, MINUTE_IN_MILLISECONDS, SECOND_IN_MILLISECONDS, } from './interval.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,aAAa,EACb,eAAe,EACf,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,uBAAuB,EACvB,qBAAqB,EACrB,aAAa,EACb,eAAe,EACf,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC","sourcesContent":["export {\n formatDate,\n formatDateFriendly,\n formatDateHMS,\n formatDateRangeFriendly,\n formatDateWithinRange,\n formatDateYMD,\n formatDateYMDHM,\n formatTz,\n} from './date.js';\nexport {\n DAY_IN_MILLISECONDS,\n formatInterval,\n formatIntervalHM,\n formatIntervalMinutes,\n formatIntervalRelative,\n HOUR_IN_MILLISECONDS,\n makeInterval,\n MINUTE_IN_MILLISECONDS,\n SECOND_IN_MILLISECONDS,\n} from './interval.js';\n"]}
|
package/dist/interval.d.ts
CHANGED
|
@@ -17,12 +17,18 @@ export declare function makeInterval({ days, hours, minutes, seconds }: {
|
|
|
17
17
|
seconds?: number;
|
|
18
18
|
}): number;
|
|
19
19
|
/**
|
|
20
|
-
* Format an interval (in milliseconds) to a human-readable string like '3 h 40
|
|
20
|
+
* Format an interval (in milliseconds) to a human-readable string like '3 h 40 min'.
|
|
21
21
|
*
|
|
22
22
|
* @param interval Time interval in milliseconds.
|
|
23
|
+
* @param options
|
|
24
|
+
* @param options.fullPartNames Whether to use full part names (e.g. '3 hours 40 minutes' instead of '3 h 40 m'). Default is false.
|
|
25
|
+
* @param options.firstOnly Whether to return only the first non-zero part of the interval (e.g. '3 h' instead of '3 h 40 m'). Default is false.
|
|
23
26
|
* @returns Human-readable string representing the interval.
|
|
24
27
|
*/
|
|
25
|
-
export declare function formatInterval(interval: number
|
|
28
|
+
export declare function formatInterval(interval: number, { fullPartNames, firstOnly }?: {
|
|
29
|
+
firstOnly?: boolean | undefined;
|
|
30
|
+
fullPartNames?: boolean | undefined;
|
|
31
|
+
}): string;
|
|
26
32
|
/**
|
|
27
33
|
* Format an interval (in milliseconds) to a human-readable string like 'Until 6
|
|
28
34
|
* minutes before the session start time'.
|
package/dist/interval.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interval.d.ts","sourceRoot":"","sources":["../src/interval.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAC3C,eAAO,MAAM,sBAAsB,QAA8B,CAAC;AAClE,eAAO,MAAM,oBAAoB,QAA8B,CAAC;AAChE,eAAO,MAAM,mBAAmB,QAA4B,CAAC;AAE7D;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAQ,EACR,KAAS,EACT,OAAW,EACX,OAAW,EACZ,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAET;AAED
|
|
1
|
+
{"version":3,"file":"interval.d.ts","sourceRoot":"","sources":["../src/interval.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAC3C,eAAO,MAAM,sBAAsB,QAA8B,CAAC;AAClE,eAAO,MAAM,oBAAoB,QAA8B,CAAC;AAChE,eAAO,MAAM,mBAAmB,QAA4B,CAAC;AAE7D;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAQ,EACR,KAAS,EACT,OAAW,EACX,OAAW,EACZ,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAET;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,EAAE,aAAqB,EAAE,SAAiB,EAAE;;;CAAK,GAChD,MAAM,CA2BR;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,EAC3C,SAAS,EAAE,MAAM,GAChB,MAAM,CAUR;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,EAAE,MAAc,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAsB,GAC3D,MAAM,CAKR;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ9D","sourcesContent":["export const SECOND_IN_MILLISECONDS = 1000;\nexport const MINUTE_IN_MILLISECONDS = 60 * SECOND_IN_MILLISECONDS;\nexport const HOUR_IN_MILLISECONDS = 60 * MINUTE_IN_MILLISECONDS;\nexport const DAY_IN_MILLISECONDS = 24 * HOUR_IN_MILLISECONDS;\n\n/**\n * Makes an interval (in milliseconds).\n * @param param\n * @param param.days The number of days in the interval.\n * @param param.hours The number of hours in the interval.\n * @param param.minutes The number of minutes in the interval.\n * @param param.seconds The number of seconds in the interval.\n */\nexport function makeInterval({\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n}): number {\n return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000;\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string like '3 h 40 min'.\n *\n * @param interval Time interval in milliseconds.\n * @param options\n * @param options.fullPartNames Whether to use full part names (e.g. '3 hours 40 minutes' instead of '3 h 40 m'). Default is false.\n * @param options.firstOnly Whether to return only the first non-zero part of the interval (e.g. '3 h' instead of '3 h 40 m'). Default is false.\n * @returns Human-readable string representing the interval.\n */\nexport function formatInterval(\n interval: number,\n { fullPartNames = false, firstOnly = false } = {},\n): string {\n const sign = interval < 0 ? '-' : '';\n\n const days = Math.floor(Math.abs(interval) / DAY_IN_MILLISECONDS);\n const hours = Math.floor(Math.abs(interval) / HOUR_IN_MILLISECONDS) % 24;\n const mins = Math.floor(Math.abs(interval) / MINUTE_IN_MILLISECONDS) % 60;\n const secs = Math.floor(Math.abs(interval) / SECOND_IN_MILLISECONDS) % 60;\n\n const parts: string[] = [];\n\n if (days > 0) {\n parts.push(`${sign}${days} ${fullPartNames ? (days === 1 ? 'day' : 'days') : 'd'}`);\n }\n if (hours > 0) {\n parts.push(`${sign}${hours} ${fullPartNames ? (hours === 1 ? 'hour' : 'hours') : 'h'}`);\n }\n if (mins > 0) {\n parts.push(`${sign}${mins} ${fullPartNames ? (mins === 1 ? 'minute' : 'minutes') : 'min'}`);\n }\n if (secs > 0) {\n parts.push(`${sign}${secs} ${fullPartNames ? (secs === 1 ? 'second' : 'seconds') : 's'}`);\n }\n if (parts.length === 0) {\n parts.push(`0 ${fullPartNames ? 'seconds' : 's'}`);\n }\n\n return firstOnly ? parts[0] : parts.join(' ');\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string like 'Until 6\n * minutes before the session start time'.\n *\n * @param interval Time interval in milliseconds relative to `reference` (positive intervals are after `reference`).\n * @param prefix The prefix to use, must be 'Until' or 'From' (or lowercase versions of these).\n * @param reference The reference time, for example 'session start time'.\n * @returns Human-readable string representing the interval.\n */\nexport function formatIntervalRelative(\n interval: number,\n prefix: 'Until' | 'until' | 'From' | 'from',\n reference: string,\n): string {\n if (interval > 0) {\n return `${prefix} ${formatInterval(interval)} after ${reference}`;\n } else if (interval < 0) {\n return `${prefix} ${formatInterval(-interval)} before ${reference}`;\n } else if (interval === 0) {\n return `${prefix} ${reference}`;\n } else {\n return `Invalid interval: ${interval}`;\n }\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string like HH:MM or +HH:MM.\n *\n * @param interval Time interval in milliseconds.\n * @param options\n * @param options.signed Whether to include the sign in the output.\n * @returns Human-readable string representing the interval in minutes.\n */\nexport function formatIntervalHM(\n interval: number,\n { signed = false }: { signed?: boolean } = { signed: false },\n): string {\n const sign = interval < 0 ? '-' : interval > 0 ? (signed ? '+' : '') : '';\n const hours = Math.floor(Math.abs(interval) / HOUR_IN_MILLISECONDS);\n const mins = Math.floor(Math.abs(interval) / MINUTE_IN_MILLISECONDS) % 60;\n return `${sign}${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`;\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string with the number of minutes, like '7 minutes' or '1 minute'.\n *\n * @param interval Time interval in milliseconds.\n * @returns Human-readable string representing the interval in minutes.\n */\nexport function formatIntervalMinutes(interval: number): string {\n const sign = interval < 0 ? '-' : '';\n const minutes = Math.ceil(Math.abs(interval / MINUTE_IN_MILLISECONDS));\n if (minutes === 1) {\n return `${sign}1 minute`;\n } else {\n return `${sign}${minutes} minutes`;\n }\n}\n"]}
|
package/dist/interval.js
CHANGED
|
@@ -14,12 +14,15 @@ export function makeInterval({ days = 0, hours = 0, minutes = 0, seconds = 0, })
|
|
|
14
14
|
return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Format an interval (in milliseconds) to a human-readable string like '3 h 40
|
|
17
|
+
* Format an interval (in milliseconds) to a human-readable string like '3 h 40 min'.
|
|
18
18
|
*
|
|
19
19
|
* @param interval Time interval in milliseconds.
|
|
20
|
+
* @param options
|
|
21
|
+
* @param options.fullPartNames Whether to use full part names (e.g. '3 hours 40 minutes' instead of '3 h 40 m'). Default is false.
|
|
22
|
+
* @param options.firstOnly Whether to return only the first non-zero part of the interval (e.g. '3 h' instead of '3 h 40 m'). Default is false.
|
|
20
23
|
* @returns Human-readable string representing the interval.
|
|
21
24
|
*/
|
|
22
|
-
export function formatInterval(interval) {
|
|
25
|
+
export function formatInterval(interval, { fullPartNames = false, firstOnly = false } = {}) {
|
|
23
26
|
const sign = interval < 0 ? '-' : '';
|
|
24
27
|
const days = Math.floor(Math.abs(interval) / DAY_IN_MILLISECONDS);
|
|
25
28
|
const hours = Math.floor(Math.abs(interval) / HOUR_IN_MILLISECONDS) % 24;
|
|
@@ -27,21 +30,21 @@ export function formatInterval(interval) {
|
|
|
27
30
|
const secs = Math.floor(Math.abs(interval) / SECOND_IN_MILLISECONDS) % 60;
|
|
28
31
|
const parts = [];
|
|
29
32
|
if (days > 0) {
|
|
30
|
-
parts.push(`${sign}${days} d`);
|
|
33
|
+
parts.push(`${sign}${days} ${fullPartNames ? (days === 1 ? 'day' : 'days') : 'd'}`);
|
|
31
34
|
}
|
|
32
35
|
if (hours > 0) {
|
|
33
|
-
parts.push(`${sign}${hours} h`);
|
|
36
|
+
parts.push(`${sign}${hours} ${fullPartNames ? (hours === 1 ? 'hour' : 'hours') : 'h'}`);
|
|
34
37
|
}
|
|
35
38
|
if (mins > 0) {
|
|
36
|
-
parts.push(`${sign}${mins} min`);
|
|
39
|
+
parts.push(`${sign}${mins} ${fullPartNames ? (mins === 1 ? 'minute' : 'minutes') : 'min'}`);
|
|
37
40
|
}
|
|
38
41
|
if (secs > 0) {
|
|
39
|
-
parts.push(`${sign}${secs} s`);
|
|
42
|
+
parts.push(`${sign}${secs} ${fullPartNames ? (secs === 1 ? 'second' : 'seconds') : 's'}`);
|
|
40
43
|
}
|
|
41
44
|
if (parts.length === 0) {
|
|
42
|
-
parts.push(
|
|
45
|
+
parts.push(`0 ${fullPartNames ? 'seconds' : 's'}`);
|
|
43
46
|
}
|
|
44
|
-
return parts.join(' ');
|
|
47
|
+
return firstOnly ? parts[0] : parts.join(' ');
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
50
|
* Format an interval (in milliseconds) to a human-readable string like 'Until 6
|
package/dist/interval.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interval.js","sourceRoot":"","sources":["../src/interval.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAC3C,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,sBAAsB,CAAC;AAClE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,sBAAsB,CAAC;AAChE,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,GAAG,oBAAoB,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,IAAI,GAAG,CAAC,EACR,KAAK,GAAG,CAAC,EACT,OAAO,GAAG,CAAC,EACX,OAAO,GAAG,CAAC,GAMZ,EAAU;IACT,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC;AAAA,CACrE;AAED
|
|
1
|
+
{"version":3,"file":"interval.js","sourceRoot":"","sources":["../src/interval.ts"],"names":[],"mappings":"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","sourcesContent":["export const SECOND_IN_MILLISECONDS = 1000;\nexport const MINUTE_IN_MILLISECONDS = 60 * SECOND_IN_MILLISECONDS;\nexport const HOUR_IN_MILLISECONDS = 60 * MINUTE_IN_MILLISECONDS;\nexport const DAY_IN_MILLISECONDS = 24 * HOUR_IN_MILLISECONDS;\n\n/**\n * Makes an interval (in milliseconds).\n * @param param\n * @param param.days The number of days in the interval.\n * @param param.hours The number of hours in the interval.\n * @param param.minutes The number of minutes in the interval.\n * @param param.seconds The number of seconds in the interval.\n */\nexport function makeInterval({\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: {\n days?: number;\n hours?: number;\n minutes?: number;\n seconds?: number;\n}): number {\n return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000;\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string like '3 h 40 min'.\n *\n * @param interval Time interval in milliseconds.\n * @param options\n * @param options.fullPartNames Whether to use full part names (e.g. '3 hours 40 minutes' instead of '3 h 40 m'). Default is false.\n * @param options.firstOnly Whether to return only the first non-zero part of the interval (e.g. '3 h' instead of '3 h 40 m'). Default is false.\n * @returns Human-readable string representing the interval.\n */\nexport function formatInterval(\n interval: number,\n { fullPartNames = false, firstOnly = false } = {},\n): string {\n const sign = interval < 0 ? '-' : '';\n\n const days = Math.floor(Math.abs(interval) / DAY_IN_MILLISECONDS);\n const hours = Math.floor(Math.abs(interval) / HOUR_IN_MILLISECONDS) % 24;\n const mins = Math.floor(Math.abs(interval) / MINUTE_IN_MILLISECONDS) % 60;\n const secs = Math.floor(Math.abs(interval) / SECOND_IN_MILLISECONDS) % 60;\n\n const parts: string[] = [];\n\n if (days > 0) {\n parts.push(`${sign}${days} ${fullPartNames ? (days === 1 ? 'day' : 'days') : 'd'}`);\n }\n if (hours > 0) {\n parts.push(`${sign}${hours} ${fullPartNames ? (hours === 1 ? 'hour' : 'hours') : 'h'}`);\n }\n if (mins > 0) {\n parts.push(`${sign}${mins} ${fullPartNames ? (mins === 1 ? 'minute' : 'minutes') : 'min'}`);\n }\n if (secs > 0) {\n parts.push(`${sign}${secs} ${fullPartNames ? (secs === 1 ? 'second' : 'seconds') : 's'}`);\n }\n if (parts.length === 0) {\n parts.push(`0 ${fullPartNames ? 'seconds' : 's'}`);\n }\n\n return firstOnly ? parts[0] : parts.join(' ');\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string like 'Until 6\n * minutes before the session start time'.\n *\n * @param interval Time interval in milliseconds relative to `reference` (positive intervals are after `reference`).\n * @param prefix The prefix to use, must be 'Until' or 'From' (or lowercase versions of these).\n * @param reference The reference time, for example 'session start time'.\n * @returns Human-readable string representing the interval.\n */\nexport function formatIntervalRelative(\n interval: number,\n prefix: 'Until' | 'until' | 'From' | 'from',\n reference: string,\n): string {\n if (interval > 0) {\n return `${prefix} ${formatInterval(interval)} after ${reference}`;\n } else if (interval < 0) {\n return `${prefix} ${formatInterval(-interval)} before ${reference}`;\n } else if (interval === 0) {\n return `${prefix} ${reference}`;\n } else {\n return `Invalid interval: ${interval}`;\n }\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string like HH:MM or +HH:MM.\n *\n * @param interval Time interval in milliseconds.\n * @param options\n * @param options.signed Whether to include the sign in the output.\n * @returns Human-readable string representing the interval in minutes.\n */\nexport function formatIntervalHM(\n interval: number,\n { signed = false }: { signed?: boolean } = { signed: false },\n): string {\n const sign = interval < 0 ? '-' : interval > 0 ? (signed ? '+' : '') : '';\n const hours = Math.floor(Math.abs(interval) / HOUR_IN_MILLISECONDS);\n const mins = Math.floor(Math.abs(interval) / MINUTE_IN_MILLISECONDS) % 60;\n return `${sign}${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`;\n}\n\n/**\n * Format an interval (in milliseconds) to a human-readable string with the number of minutes, like '7 minutes' or '1 minute'.\n *\n * @param interval Time interval in milliseconds.\n * @returns Human-readable string representing the interval in minutes.\n */\nexport function formatIntervalMinutes(interval: number): string {\n const sign = interval < 0 ? '-' : '';\n const minutes = Math.ceil(Math.abs(interval / MINUTE_IN_MILLISECONDS));\n if (minutes === 1) {\n return `${sign}1 minute`;\n } else {\n return `${sign}${minutes} minutes`;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/formatter",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@prairielearn/tsconfig": "^2.0.0",
|
|
25
25
|
"@types/node": "^24.10.9",
|
|
26
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
26
|
+
"@typescript/native-preview": "^7.0.0-dev.20260203.1",
|
|
27
27
|
"@vitest/coverage-v8": "^4.0.18",
|
|
28
28
|
"tsx": "^4.21.0",
|
|
29
29
|
"typescript": "^5.9.3",
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export {
|
|
2
2
|
formatDate,
|
|
3
3
|
formatDateFriendly,
|
|
4
|
+
formatDateHMS,
|
|
4
5
|
formatDateRangeFriendly,
|
|
5
6
|
formatDateWithinRange,
|
|
6
7
|
formatDateYMD,
|
|
@@ -8,9 +9,13 @@ export {
|
|
|
8
9
|
formatTz,
|
|
9
10
|
} from './date.js';
|
|
10
11
|
export {
|
|
12
|
+
DAY_IN_MILLISECONDS,
|
|
11
13
|
formatInterval,
|
|
12
14
|
formatIntervalHM,
|
|
13
15
|
formatIntervalMinutes,
|
|
14
16
|
formatIntervalRelative,
|
|
17
|
+
HOUR_IN_MILLISECONDS,
|
|
15
18
|
makeInterval,
|
|
19
|
+
MINUTE_IN_MILLISECONDS,
|
|
20
|
+
SECOND_IN_MILLISECONDS,
|
|
16
21
|
} from './interval.js';
|
package/src/interval.ts
CHANGED
|
@@ -26,12 +26,18 @@ export function makeInterval({
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Format an interval (in milliseconds) to a human-readable string like '3 h 40
|
|
29
|
+
* Format an interval (in milliseconds) to a human-readable string like '3 h 40 min'.
|
|
30
30
|
*
|
|
31
31
|
* @param interval Time interval in milliseconds.
|
|
32
|
+
* @param options
|
|
33
|
+
* @param options.fullPartNames Whether to use full part names (e.g. '3 hours 40 minutes' instead of '3 h 40 m'). Default is false.
|
|
34
|
+
* @param options.firstOnly Whether to return only the first non-zero part of the interval (e.g. '3 h' instead of '3 h 40 m'). Default is false.
|
|
32
35
|
* @returns Human-readable string representing the interval.
|
|
33
36
|
*/
|
|
34
|
-
export function formatInterval(
|
|
37
|
+
export function formatInterval(
|
|
38
|
+
interval: number,
|
|
39
|
+
{ fullPartNames = false, firstOnly = false } = {},
|
|
40
|
+
): string {
|
|
35
41
|
const sign = interval < 0 ? '-' : '';
|
|
36
42
|
|
|
37
43
|
const days = Math.floor(Math.abs(interval) / DAY_IN_MILLISECONDS);
|
|
@@ -42,22 +48,22 @@ export function formatInterval(interval: number): string {
|
|
|
42
48
|
const parts: string[] = [];
|
|
43
49
|
|
|
44
50
|
if (days > 0) {
|
|
45
|
-
parts.push(`${sign}${days} d`);
|
|
51
|
+
parts.push(`${sign}${days} ${fullPartNames ? (days === 1 ? 'day' : 'days') : 'd'}`);
|
|
46
52
|
}
|
|
47
53
|
if (hours > 0) {
|
|
48
|
-
parts.push(`${sign}${hours} h`);
|
|
54
|
+
parts.push(`${sign}${hours} ${fullPartNames ? (hours === 1 ? 'hour' : 'hours') : 'h'}`);
|
|
49
55
|
}
|
|
50
56
|
if (mins > 0) {
|
|
51
|
-
parts.push(`${sign}${mins} min`);
|
|
57
|
+
parts.push(`${sign}${mins} ${fullPartNames ? (mins === 1 ? 'minute' : 'minutes') : 'min'}`);
|
|
52
58
|
}
|
|
53
59
|
if (secs > 0) {
|
|
54
|
-
parts.push(`${sign}${secs} s`);
|
|
60
|
+
parts.push(`${sign}${secs} ${fullPartNames ? (secs === 1 ? 'second' : 'seconds') : 's'}`);
|
|
55
61
|
}
|
|
56
62
|
if (parts.length === 0) {
|
|
57
|
-
parts.push(
|
|
63
|
+
parts.push(`0 ${fullPartNames ? 'seconds' : 's'}`);
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
return parts.join(' ');
|
|
66
|
+
return firstOnly ? parts[0] : parts.join(' ');
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
/**
|