@naturalcycles/js-lib 15.84.0 → 15.84.1
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/dist/datetime/localTime.js +44 -14
- package/dist/promise/pGradualQueue.js +1 -0
- package/dist/promise/pQueue.js +1 -0
- package/dist/promise/pTimeout.js +1 -0
- package/package.json +1 -3
- package/src/datetime/localTime.ts +49 -14
- package/src/promise/pGradualQueue.ts +2 -1
- package/src/promise/pQueue.ts +2 -1
- package/src/promise/pTimeout.ts +2 -1
- package/src/typeFest.ts +6 -6
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { comparators } from '../array/sort.js';
|
|
2
2
|
import { _assert } from '../error/assert.js';
|
|
3
|
+
import { Intl2 } from '../intl/intl.js';
|
|
3
4
|
import { localDate } from './localDate.js';
|
|
4
5
|
import { _ms } from './time.util.js';
|
|
5
6
|
// oxlint-disable-next-line import/no-cycle -- intentional cycle
|
|
@@ -69,15 +70,7 @@ export class LocalTime {
|
|
|
69
70
|
* @experimental
|
|
70
71
|
*/
|
|
71
72
|
inTimezone(tz) {
|
|
72
|
-
|
|
73
|
-
return new WallTime({
|
|
74
|
-
year: d.getFullYear(),
|
|
75
|
-
month: d.getMonth() + 1,
|
|
76
|
-
day: d.getDate(),
|
|
77
|
-
hour: d.getHours(),
|
|
78
|
-
minute: d.getMinutes(),
|
|
79
|
-
second: d.getSeconds(),
|
|
80
|
-
});
|
|
73
|
+
return new WallTime(getWallComponentsInTimezone(this.$date, tz));
|
|
81
74
|
}
|
|
82
75
|
/**
|
|
83
76
|
* UTC offset is the opposite of "timezone offset" - it's the number of minutes to add
|
|
@@ -96,8 +89,13 @@ export class LocalTime {
|
|
|
96
89
|
getUTCOffsetMinutes(tz) {
|
|
97
90
|
if (tz) {
|
|
98
91
|
// based on: https://stackoverflow.com/a/53652131/4919972
|
|
92
|
+
// The wall clock in `tz` is interpreted as UTC (not as host-local time, like the
|
|
93
|
+
// stackoverflow answer does): `wall = utc + offset`, so `offset = Date.UTC(wall) - now`.
|
|
94
|
+
// This makes the result independent of the host timezone (the host-local interpretation
|
|
95
|
+
// returned offsets relative to the host, and was distorted by the host's DST gaps).
|
|
99
96
|
const nowTime = this.$date.getTime();
|
|
100
|
-
const
|
|
97
|
+
const { year, month, day, hour, minute, second } = getWallComponentsInTimezone(this.$date, tz);
|
|
98
|
+
const tzTime = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
101
99
|
return Math.round((tzTime - nowTime) / 60000) || 0;
|
|
102
100
|
}
|
|
103
101
|
return -this.$date.getTimezoneOffset() || 0;
|
|
@@ -120,10 +118,12 @@ export class LocalTime {
|
|
|
120
118
|
*/
|
|
121
119
|
getUTCOffsetString(tz) {
|
|
122
120
|
const minutes = this.getUTCOffsetMinutes(tz);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
const
|
|
121
|
+
// Sign is taken from minutes (not truncated hours), and minutes are abs-ed,
|
|
122
|
+
// so negative fractional offsets like Newfoundland -03:30 don't render as `-03:-30`
|
|
123
|
+
const sign = minutes < 0 ? '-' : '+';
|
|
124
|
+
const absMinutes = Math.abs(minutes);
|
|
125
|
+
const h = String(Math.trunc(absMinutes / 60)).padStart(2, '0');
|
|
126
|
+
const m = String(absMinutes % 60).padStart(2, '0');
|
|
127
127
|
return `${sign}${h}:${m}`;
|
|
128
128
|
}
|
|
129
129
|
get(unit) {
|
|
@@ -995,6 +995,36 @@ function differenceInMonths(a, b) {
|
|
|
995
995
|
const anchor2 = addMonths(a, wholeMonthDiff + sign).getTime();
|
|
996
996
|
return -(wholeMonthDiff + ((b.getTime() - anchor) / (anchor2 - anchor)) * sign);
|
|
997
997
|
}
|
|
998
|
+
const dateTimeObjectKeys = new Set([
|
|
999
|
+
'year',
|
|
1000
|
+
'month',
|
|
1001
|
+
'day',
|
|
1002
|
+
'hour',
|
|
1003
|
+
'minute',
|
|
1004
|
+
'second',
|
|
1005
|
+
]);
|
|
1006
|
+
// Intl.DateTimeFormat construction is expensive (~30µs), while a cached formatter
|
|
1007
|
+
// (via Intl2) converts in ~3µs. Unlike `date.toLocaleString(...)` + `new Date(string)`
|
|
1008
|
+
// round-trip, formatToParts is also immune to the host timezone's DST gaps.
|
|
1009
|
+
function getWallComponentsInTimezone(date, tz) {
|
|
1010
|
+
const fmt = Intl2.DateTimeFormat('en-US', {
|
|
1011
|
+
timeZone: tz,
|
|
1012
|
+
year: 'numeric',
|
|
1013
|
+
month: 'numeric',
|
|
1014
|
+
day: 'numeric',
|
|
1015
|
+
hour: 'numeric',
|
|
1016
|
+
minute: 'numeric',
|
|
1017
|
+
second: 'numeric',
|
|
1018
|
+
hourCycle: 'h23',
|
|
1019
|
+
});
|
|
1020
|
+
const dto = {};
|
|
1021
|
+
for (const { type, value } of fmt.formatToParts(date)) {
|
|
1022
|
+
if (dateTimeObjectKeys.has(type)) {
|
|
1023
|
+
dto[type] = Number(value);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return dto;
|
|
1027
|
+
}
|
|
998
1028
|
const localTimeFactory = new LocalTimeFactory();
|
|
999
1029
|
export const localTime = localTimeFactory.fromInput.bind(localTimeFactory);
|
|
1000
1030
|
// The line below is the blackest of black magic I have ever written in 2024.
|
package/dist/promise/pQueue.js
CHANGED
package/dist/promise/pTimeout.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/js-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "15.84.
|
|
4
|
+
"version": "15.84.1",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"tslib": "^2"
|
|
7
7
|
},
|
|
@@ -14,11 +14,9 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@types/crypto-js": "^4",
|
|
18
17
|
"@types/node": "^26",
|
|
19
18
|
"@types/semver": "^7",
|
|
20
19
|
"typescript": "^7",
|
|
21
|
-
"crypto-js": "^4",
|
|
22
20
|
"dayjs": "^1",
|
|
23
21
|
"@naturalcycles/dev-lib": "0.0.0"
|
|
24
22
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { comparators } from '../array/sort.js'
|
|
2
2
|
import { _assert } from '../error/assert.js'
|
|
3
|
+
import { Intl2 } from '../intl/intl.js'
|
|
3
4
|
import type {
|
|
4
5
|
IANATimezone,
|
|
5
6
|
Inclusiveness,
|
|
@@ -108,15 +109,7 @@ export class LocalTime {
|
|
|
108
109
|
* @experimental
|
|
109
110
|
*/
|
|
110
111
|
inTimezone(tz: IANATimezone): WallTime {
|
|
111
|
-
|
|
112
|
-
return new WallTime({
|
|
113
|
-
year: d.getFullYear(),
|
|
114
|
-
month: d.getMonth() + 1,
|
|
115
|
-
day: d.getDate(),
|
|
116
|
-
hour: d.getHours(),
|
|
117
|
-
minute: d.getMinutes(),
|
|
118
|
-
second: d.getSeconds(),
|
|
119
|
-
})
|
|
112
|
+
return new WallTime(getWallComponentsInTimezone(this.$date, tz))
|
|
120
113
|
}
|
|
121
114
|
|
|
122
115
|
/**
|
|
@@ -136,8 +129,13 @@ export class LocalTime {
|
|
|
136
129
|
getUTCOffsetMinutes(tz?: IANATimezone): NumberOfMinutes {
|
|
137
130
|
if (tz) {
|
|
138
131
|
// based on: https://stackoverflow.com/a/53652131/4919972
|
|
132
|
+
// The wall clock in `tz` is interpreted as UTC (not as host-local time, like the
|
|
133
|
+
// stackoverflow answer does): `wall = utc + offset`, so `offset = Date.UTC(wall) - now`.
|
|
134
|
+
// This makes the result independent of the host timezone (the host-local interpretation
|
|
135
|
+
// returned offsets relative to the host, and was distorted by the host's DST gaps).
|
|
139
136
|
const nowTime = this.$date.getTime()
|
|
140
|
-
const
|
|
137
|
+
const { year, month, day, hour, minute, second } = getWallComponentsInTimezone(this.$date, tz)
|
|
138
|
+
const tzTime = Date.UTC(year, month - 1, day, hour, minute, second)
|
|
141
139
|
return Math.round((tzTime - nowTime) / 60000) || 0
|
|
142
140
|
}
|
|
143
141
|
|
|
@@ -163,10 +161,12 @@ export class LocalTime {
|
|
|
163
161
|
*/
|
|
164
162
|
getUTCOffsetString(tz: IANATimezone): string {
|
|
165
163
|
const minutes = this.getUTCOffsetMinutes(tz)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
164
|
+
// Sign is taken from minutes (not truncated hours), and minutes are abs-ed,
|
|
165
|
+
// so negative fractional offsets like Newfoundland -03:30 don't render as `-03:-30`
|
|
166
|
+
const sign = minutes < 0 ? '-' : '+'
|
|
167
|
+
const absMinutes = Math.abs(minutes)
|
|
168
|
+
const h = String(Math.trunc(absMinutes / 60)).padStart(2, '0')
|
|
169
|
+
const m = String(absMinutes % 60).padStart(2, '0')
|
|
170
170
|
return `${sign}${h}:${m}`
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -1165,6 +1165,41 @@ function differenceInMonths(a: Date, b: Date): number {
|
|
|
1165
1165
|
return -(wholeMonthDiff + ((b.getTime() - anchor) / (anchor2 - anchor)) * sign)
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
|
+
const dateTimeObjectKeys = new Set<keyof DateTimeObject>([
|
|
1169
|
+
'year',
|
|
1170
|
+
'month',
|
|
1171
|
+
'day',
|
|
1172
|
+
'hour',
|
|
1173
|
+
'minute',
|
|
1174
|
+
'second',
|
|
1175
|
+
])
|
|
1176
|
+
|
|
1177
|
+
// Intl.DateTimeFormat construction is expensive (~30µs), while a cached formatter
|
|
1178
|
+
// (via Intl2) converts in ~3µs. Unlike `date.toLocaleString(...)` + `new Date(string)`
|
|
1179
|
+
// round-trip, formatToParts is also immune to the host timezone's DST gaps.
|
|
1180
|
+
function getWallComponentsInTimezone(date: Date, tz: IANATimezone): DateTimeObject {
|
|
1181
|
+
const fmt = Intl2.DateTimeFormat('en-US', {
|
|
1182
|
+
timeZone: tz,
|
|
1183
|
+
year: 'numeric',
|
|
1184
|
+
month: 'numeric',
|
|
1185
|
+
day: 'numeric',
|
|
1186
|
+
hour: 'numeric',
|
|
1187
|
+
minute: 'numeric',
|
|
1188
|
+
second: 'numeric',
|
|
1189
|
+
hourCycle: 'h23',
|
|
1190
|
+
})
|
|
1191
|
+
|
|
1192
|
+
const dto = {} as DateTimeObject
|
|
1193
|
+
|
|
1194
|
+
for (const { type, value } of fmt.formatToParts(date)) {
|
|
1195
|
+
if (dateTimeObjectKeys.has(type as keyof DateTimeObject)) {
|
|
1196
|
+
dto[type as keyof DateTimeObject] = Number(value)
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
return dto
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1168
1203
|
interface LocalTimeFn extends LocalTimeFactory {
|
|
1169
1204
|
(d: LocalTimeInput): LocalTime
|
|
1170
1205
|
}
|
|
@@ -159,7 +159,8 @@ function runSafe<R>(fn: AsyncFunction<R>): Promise<R> {
|
|
|
159
159
|
return fn()
|
|
160
160
|
} catch (err) {
|
|
161
161
|
// Handle synchronous throws
|
|
162
|
-
|
|
162
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
163
|
+
return Promise.reject(err)
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
|
package/src/promise/pQueue.ts
CHANGED
|
@@ -150,7 +150,8 @@ function runSafe<R>(fn: AsyncFunction<R>): Promise<R> {
|
|
|
150
150
|
return fn()
|
|
151
151
|
} catch (err) {
|
|
152
152
|
// Handle synchronous throws - ensure inFlight is decremented
|
|
153
|
-
|
|
153
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
154
|
+
return Promise.reject(err)
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
|
package/src/promise/pTimeout.ts
CHANGED
|
@@ -108,7 +108,8 @@ export async function pTimeout<T>(
|
|
|
108
108
|
try {
|
|
109
109
|
resolve(await fn(signal))
|
|
110
110
|
} catch (err) {
|
|
111
|
-
|
|
111
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
112
|
+
reject(err)
|
|
112
113
|
} finally {
|
|
113
114
|
clearTimeout(timer)
|
|
114
115
|
}
|
package/src/typeFest.ts
CHANGED
|
@@ -57,9 +57,9 @@ export type Except<ObjectType, KeysType extends keyof ObjectType> = {
|
|
|
57
57
|
* When you use a type that will iterate through an object that has indexed keys and explicitly defined keys you end up with a type where only the indexed keys are kept. This is because `keyof` of an indexed type always returns `string | number | symbol`, because every key is possible in that object. With this type, you can save the indexed keys and reinject them later, like in the second example below.
|
|
58
58
|
*/
|
|
59
59
|
export type PickIndexSignature<ObjectType> = {
|
|
60
|
-
[
|
|
61
|
-
? KeyType
|
|
62
|
-
|
|
60
|
+
[
|
|
61
|
+
KeyType in keyof ObjectType as {} extends Record<KeyType, unknown> ? KeyType : never
|
|
62
|
+
]: ObjectType[KeyType]
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
@@ -73,9 +73,9 @@ export type PickIndexSignature<ObjectType> = {
|
|
|
73
73
|
* (The actual value type, `unknown`, is irrelevant and could be any type. Only the key type matters.)
|
|
74
74
|
*/
|
|
75
75
|
export type OmitIndexSignature<ObjectType> = {
|
|
76
|
-
[
|
|
77
|
-
? never
|
|
78
|
-
|
|
76
|
+
[
|
|
77
|
+
KeyType in keyof ObjectType as {} extends Record<KeyType, unknown> ? never : KeyType
|
|
78
|
+
]: ObjectType[KeyType]
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|