@alexstukovnikov/oz-time 1.0.0 → 1.0.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/package.json +45 -48
- package/src/core/core.js +353 -0
- package/src/core/factory.js +194 -0
- package/src/index.js +22 -0
- package/src/modules/arithmetic.js +100 -0
- package/src/modules/compare.js +197 -0
- package/src/modules/duration.js +165 -0
- package/src/modules/format.js +162 -0
- package/src/modules/interval.js +190 -0
- package/src/modules/timezone.js +112 -0
- package/src/utils/calendar.js +277 -0
- package/src/utils/units.js +135 -0
- package/dist/oz-time.cjs +0 -1
- package/dist/oz-time.esm.js +0 -426
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { OzTime } from '../core/core.js';
|
|
2
|
+
import { normalizeUnit, isFixedUnit, isCalendarUnit } from '../utils/units.js';
|
|
3
|
+
import { addByFixedUnit, addByCalendarUnit } from '../utils/calendar.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Модуль арифметических операций над экземплярами {@link OzTime}.
|
|
7
|
+
*
|
|
8
|
+
* @module modules/arithmetic
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Проверяет, является ли значение экземпляром OzTime.
|
|
13
|
+
*
|
|
14
|
+
* @private
|
|
15
|
+
* @param {*} value - Проверяемое значение.
|
|
16
|
+
* @param {string} name - Имя параметра.
|
|
17
|
+
* @throws {TypeError} Выбрасывается, если значение не является экземпляром OzTime.
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
20
|
+
function assertOzTime(value, name) {
|
|
21
|
+
if (!(value instanceof OzTime)) {
|
|
22
|
+
throw new TypeError(`${name} must be OzTime`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Проверяет корректность числового значения amount.
|
|
28
|
+
*
|
|
29
|
+
* @private
|
|
30
|
+
* @param {number} amount - Количество единиц времени.
|
|
31
|
+
* @throws {TypeError} Выбрасывается, если amount не является корректным числом.
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
34
|
+
function assertAmount(amount) {
|
|
35
|
+
if (typeof amount !== 'number' || Number.isNaN(amount)) {
|
|
36
|
+
throw new TypeError('amount must be a valid number');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Возвращает новый экземпляр {@link OzTime}, у которого timestamp увеличен
|
|
42
|
+
* на указанное количество единиц времени.
|
|
43
|
+
*
|
|
44
|
+
* Поддерживает как фиксированные, так и календарные единицы времени.
|
|
45
|
+
* Исходный экземпляр не изменяется.
|
|
46
|
+
*
|
|
47
|
+
* @param {OzTime} time - Исходное значение времени.
|
|
48
|
+
* @param {number} amount - Количество единиц времени.
|
|
49
|
+
* @param {string} unit - Единица времени.
|
|
50
|
+
* @throws {TypeError} Выбрасывается, если time или amount некорректны.
|
|
51
|
+
* @returns {OzTime} Новый экземпляр OzTime с timestamp, сдвинутым вперёд.
|
|
52
|
+
* @example
|
|
53
|
+
* import { add, fromISO } from '@alexstukovnikov/oz-time';
|
|
54
|
+
*
|
|
55
|
+
* const time = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU');
|
|
56
|
+
* const result = add(time, 2, 'day');
|
|
57
|
+
* console.log(result.toISOString()); // ожидаемый результат: 2024-05-27T12:00:00.000Z
|
|
58
|
+
*/
|
|
59
|
+
export function add(time, amount, unit) {
|
|
60
|
+
assertOzTime(time, 'time');
|
|
61
|
+
assertAmount(amount);
|
|
62
|
+
|
|
63
|
+
const normalizedUnit = normalizeUnit(unit);
|
|
64
|
+
const timestamp = time.getTimestamp();
|
|
65
|
+
|
|
66
|
+
let nextTimestamp;
|
|
67
|
+
|
|
68
|
+
if (isFixedUnit(normalizedUnit)) {
|
|
69
|
+
nextTimestamp = addByFixedUnit(timestamp, amount, normalizedUnit);
|
|
70
|
+
} else if (isCalendarUnit(normalizedUnit)) {
|
|
71
|
+
nextTimestamp = addByCalendarUnit(timestamp, amount, normalizedUnit);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new OzTime(nextTimestamp, time.getTimezone(), time.getLocale());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Возвращает новый экземпляр {@link OzTime}, у которого timestamp уменьшен
|
|
79
|
+
* на указанное количество единиц времени.
|
|
80
|
+
*
|
|
81
|
+
* Исходный экземпляр не изменяется.
|
|
82
|
+
*
|
|
83
|
+
* @param {OzTime} time - Исходное значение времени.
|
|
84
|
+
* @param {number} amount - Количество единиц времени.
|
|
85
|
+
* @param {string} unit - Единица времени.
|
|
86
|
+
* @throws {TypeError} Выбрасывается, если time или amount некорректны.
|
|
87
|
+
* @returns {OzTime} Новый экземпляр OzTime с timestamp, сдвинутым назад.
|
|
88
|
+
* @example
|
|
89
|
+
* import { subtract, fromISO } from '@alexstukovnikov/oz-time';
|
|
90
|
+
*
|
|
91
|
+
* const time = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU');
|
|
92
|
+
* const result = subtract(time, 3, 'hour');
|
|
93
|
+
* console.log(result.toISOString()); // ожидаемый результат: 2024-05-25T09:00:00.000Z
|
|
94
|
+
*/
|
|
95
|
+
export function subtract(time, amount, unit) {
|
|
96
|
+
assertOzTime(time, 'time');
|
|
97
|
+
assertAmount(amount);
|
|
98
|
+
|
|
99
|
+
return add(time, -amount, unit);
|
|
100
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { OzTime } from '../core/core.js';
|
|
2
|
+
import { normalizeUnit } from '../utils/units.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Модуль сравнений для экземпляров {@link OzTime}.
|
|
6
|
+
*
|
|
7
|
+
* @module modules/compare
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Проверяет, является ли значение экземпляром OzTime.
|
|
12
|
+
*
|
|
13
|
+
* @private
|
|
14
|
+
* @param {*} value - Проверяемое значение.
|
|
15
|
+
* @param {string} name - Имя параметра.
|
|
16
|
+
* @throws {TypeError} Выбрасывается, если значение не является экземпляром OzTime.
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
function assertOzTime(value, name) {
|
|
20
|
+
if (!(value instanceof OzTime)) {
|
|
21
|
+
throw new TypeError(`${name} must be OzTime`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Обрезает timestamp до заданной точности.
|
|
27
|
+
*
|
|
28
|
+
* @private
|
|
29
|
+
* @param {number} timestamp - Исходный timestamp в миллисекундах.
|
|
30
|
+
* @param {string} unit - Единица точности.
|
|
31
|
+
* @throws {Error} Выбрасывается, если единица не поддерживается.
|
|
32
|
+
* @returns {number} Timestamp, обрезанный до заданной единицы времени.
|
|
33
|
+
*/
|
|
34
|
+
function truncateToUnit(timestamp, unit) {
|
|
35
|
+
const normalizedUnit = normalizeUnit(unit);
|
|
36
|
+
const d = new Date(timestamp);
|
|
37
|
+
|
|
38
|
+
switch (normalizedUnit) {
|
|
39
|
+
case 'millisecond':
|
|
40
|
+
return d.getTime();
|
|
41
|
+
|
|
42
|
+
case 'second':
|
|
43
|
+
d.setUTCMilliseconds(0);
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case 'minute':
|
|
47
|
+
d.setUTCSeconds(0, 0);
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case 'hour':
|
|
51
|
+
d.setUTCMinutes(0, 0, 0);
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 'day':
|
|
55
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
case 'month':
|
|
59
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
60
|
+
d.setUTCDate(1);
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case 'year':
|
|
64
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
65
|
+
d.setUTCDate(1);
|
|
66
|
+
d.setUTCMonth(0);
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
default:
|
|
70
|
+
throw new Error(`Unsupported unit for truncateToUnit: ${unit}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return d.getTime();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Проверяет, равны ли два значения времени с учётом заданной точности.
|
|
78
|
+
*
|
|
79
|
+
* @param {OzTime} a - Первое значение.
|
|
80
|
+
* @param {OzTime} b - Второе значение.
|
|
81
|
+
* @param {string} [unit='millisecond'] - Точность сравнения.
|
|
82
|
+
* @throws {TypeError} Выбрасывается, если хотя бы один аргумент не является экземпляром OzTime.
|
|
83
|
+
* @returns {boolean} `true`, если значения равны на заданной точности.
|
|
84
|
+
* @example
|
|
85
|
+
* import { isSame, fromISO } from '@alexstukovnikov/oz-time';
|
|
86
|
+
*
|
|
87
|
+
* const a = fromISO('2024-05-25T12:00:00.100Z');
|
|
88
|
+
* const b = fromISO('2024-05-25T12:00:00.900Z');
|
|
89
|
+
* console.log(isSame(a, b, 'second')); // ожидаемый результат: true
|
|
90
|
+
*/
|
|
91
|
+
export function isSame(a, b, unit = 'millisecond') {
|
|
92
|
+
assertOzTime(a, 'a');
|
|
93
|
+
assertOzTime(b, 'b');
|
|
94
|
+
|
|
95
|
+
const tsA = truncateToUnit(a.getTimestamp(), unit);
|
|
96
|
+
const tsB = truncateToUnit(b.getTimestamp(), unit);
|
|
97
|
+
|
|
98
|
+
return tsA === tsB;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Проверяет, находится ли первое значение раньше второго
|
|
103
|
+
* с учётом заданной точности.
|
|
104
|
+
*
|
|
105
|
+
* @param {OzTime} a - Первое значение.
|
|
106
|
+
* @param {OzTime} b - Второе значение.
|
|
107
|
+
* @param {string} [unit='millisecond'] - Точность сравнения.
|
|
108
|
+
* @throws {TypeError} Выбрасывается, если хотя бы один аргумент не является экземпляром OzTime.
|
|
109
|
+
* @returns {boolean} `true`, если первое значение раньше второго.
|
|
110
|
+
* @example
|
|
111
|
+
* import { isBefore, fromISO } from '@alexstukovnikov/oz-time';
|
|
112
|
+
*
|
|
113
|
+
* const a = fromISO('2024-05-25T12:00:00Z');
|
|
114
|
+
* const b = fromISO('2024-05-26T12:00:00Z');
|
|
115
|
+
* console.log(isBefore(a, b)); // ожидаемый результат: true
|
|
116
|
+
*/
|
|
117
|
+
export function isBefore(a, b, unit = 'millisecond') {
|
|
118
|
+
assertOzTime(a, 'a');
|
|
119
|
+
assertOzTime(b, 'b');
|
|
120
|
+
|
|
121
|
+
const tsA = truncateToUnit(a.getTimestamp(), unit);
|
|
122
|
+
const tsB = truncateToUnit(b.getTimestamp(), unit);
|
|
123
|
+
|
|
124
|
+
return tsA < tsB;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Проверяет, находится ли первое значение позже второго
|
|
129
|
+
* с учётом заданной точности.
|
|
130
|
+
*
|
|
131
|
+
* @param {OzTime} a - Первое значение.
|
|
132
|
+
* @param {OzTime} b - Второе значение.
|
|
133
|
+
* @param {string} [unit='millisecond'] - Точность сравнения.
|
|
134
|
+
* @throws {TypeError} Выбрасывается, если хотя бы один аргумент не является экземпляром OzTime.
|
|
135
|
+
* @returns {boolean} `true`, если первое значение позже второго.
|
|
136
|
+
* @example
|
|
137
|
+
* import { isAfter, fromISO } from '@alexstukovnikov/oz-time';
|
|
138
|
+
*
|
|
139
|
+
* const a = fromISO('2024-05-26T12:00:00Z');
|
|
140
|
+
* const b = fromISO('2024-05-25T12:00:00Z');
|
|
141
|
+
* console.log(isAfter(a, b)); // ожидаемый результат: true
|
|
142
|
+
*/
|
|
143
|
+
export function isAfter(a, b, unit = 'millisecond') {
|
|
144
|
+
assertOzTime(a, 'a');
|
|
145
|
+
assertOzTime(b, 'b');
|
|
146
|
+
|
|
147
|
+
const tsA = truncateToUnit(a.getTimestamp(), unit);
|
|
148
|
+
const tsB = truncateToUnit(b.getTimestamp(), unit);
|
|
149
|
+
|
|
150
|
+
return tsA > tsB;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Проверяет, попадает ли целевое значение в диапазон между двумя границами
|
|
155
|
+
* с учётом заданной точности.
|
|
156
|
+
*
|
|
157
|
+
* @param {OzTime} target - Проверяемое значение.
|
|
158
|
+
* @param {OzTime} left - Левая граница.
|
|
159
|
+
* @param {OzTime} right - Правая граница.
|
|
160
|
+
* @param {string} [unit='millisecond'] - Точность сравнения.
|
|
161
|
+
* @param {'[]'|'[)'|'(]'|'()'} [inclusivity='[]'] - Формат включённости границ.
|
|
162
|
+
* @throws {TypeError} Выбрасывается, если хотя бы один аргумент не является экземпляром OzTime.
|
|
163
|
+
* @throws {Error} Выбрасывается, если inclusivity задан некорректно.
|
|
164
|
+
* @returns {boolean} `true`, если значение находится внутри диапазона.
|
|
165
|
+
* @example
|
|
166
|
+
* import { isBetween, fromISO } from '@alexstukovnikov/oz-time';
|
|
167
|
+
*
|
|
168
|
+
* const target = fromISO('2024-05-25T12:00:00Z');
|
|
169
|
+
* const start = fromISO('2024-05-25T10:00:00Z');
|
|
170
|
+
* const end = fromISO('2024-05-25T14:00:00Z');
|
|
171
|
+
* console.log(isBetween(target, start, end)); // ожидаемый результат: true
|
|
172
|
+
*/
|
|
173
|
+
export function isBetween(target, left, right, unit = 'millisecond', inclusivity = '[]') {
|
|
174
|
+
assertOzTime(target, 'target');
|
|
175
|
+
assertOzTime(left, 'left');
|
|
176
|
+
assertOzTime(right, 'right');
|
|
177
|
+
|
|
178
|
+
const t = truncateToUnit(target.getTimestamp(), unit);
|
|
179
|
+
const l = truncateToUnit(left.getTimestamp(), unit);
|
|
180
|
+
const r = truncateToUnit(right.getTimestamp(), unit);
|
|
181
|
+
|
|
182
|
+
const min = Math.min(l, r);
|
|
183
|
+
const max = Math.max(l, r);
|
|
184
|
+
|
|
185
|
+
switch (inclusivity) {
|
|
186
|
+
case '[]':
|
|
187
|
+
return t >= min && t <= max;
|
|
188
|
+
case '[)':
|
|
189
|
+
return t >= min && t < max;
|
|
190
|
+
case '(]':
|
|
191
|
+
return t > min && t <= max;
|
|
192
|
+
case '()':
|
|
193
|
+
return t > min && t < max;
|
|
194
|
+
default:
|
|
195
|
+
throw new Error(`Invalid inclusivity value: ${inclusivity}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { isFixedUnit, unitToMilliseconds, normalizeUnit } from '../utils/units.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Модуль для работы с фиксированными длительностями.
|
|
5
|
+
*
|
|
6
|
+
* @module modules/duration
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Проверяет корректность числового значения amount.
|
|
11
|
+
*
|
|
12
|
+
* @private
|
|
13
|
+
* @param {number} amount - Количество единиц времени.
|
|
14
|
+
* @throws {TypeError} Выбрасывается, если amount не является корректным числом.
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
function assertAmount(amount) {
|
|
18
|
+
if (typeof amount !== 'number' || Number.isNaN(amount)) {
|
|
19
|
+
throw new TypeError('amount must be a valid number');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Представляет фиксированную длительность, хранящуюся в миллисекундах.
|
|
25
|
+
*
|
|
26
|
+
* @class
|
|
27
|
+
* @example
|
|
28
|
+
* import { Duration } from '@alexstukovnikov/oz-time';
|
|
29
|
+
*
|
|
30
|
+
* const duration = new Duration(3600000);
|
|
31
|
+
* console.log(duration.asHours()); // ожидаемый результат: 1
|
|
32
|
+
*/
|
|
33
|
+
export class Duration {
|
|
34
|
+
/**
|
|
35
|
+
* Создаёт экземпляр Duration.
|
|
36
|
+
*
|
|
37
|
+
* @param {number} milliseconds - Длительность в миллисекундах.
|
|
38
|
+
* @throws {TypeError} Выбрасывается, если milliseconds некорректен.
|
|
39
|
+
*/
|
|
40
|
+
constructor(milliseconds) {
|
|
41
|
+
if (typeof milliseconds !== 'number' || Number.isNaN(milliseconds)) {
|
|
42
|
+
throw new TypeError('Duration: milliseconds must be a valid number');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this._milliseconds = milliseconds;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Возвращает длительность в миллисекундах.
|
|
50
|
+
*
|
|
51
|
+
* @returns {number} Длительность в миллисекундах.
|
|
52
|
+
* @example
|
|
53
|
+
* import { duration } from '@alexstukovnikov/oz-time';
|
|
54
|
+
*
|
|
55
|
+
* const value = duration(3600000, 'millisecond');
|
|
56
|
+
* console.log(value.asMilliseconds()); // ожидаемый результат: 3600000
|
|
57
|
+
*/
|
|
58
|
+
asMilliseconds() {
|
|
59
|
+
return this._milliseconds;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Возвращает длительность в секундах.
|
|
64
|
+
*
|
|
65
|
+
* @returns {number} Длительность в секундах.
|
|
66
|
+
* @example
|
|
67
|
+
* import { duration } from '@alexstukovnikov/oz-time';
|
|
68
|
+
*
|
|
69
|
+
* const value = duration(3600000, 'millisecond');
|
|
70
|
+
* console.log(value.asSeconds()); // ожидаемый результат: 3600
|
|
71
|
+
*/
|
|
72
|
+
asSeconds() {
|
|
73
|
+
return this._milliseconds / unitToMilliseconds('second');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Возвращает длительность в минутах.
|
|
78
|
+
*
|
|
79
|
+
* @returns {number} Длительность в минутах.
|
|
80
|
+
* @example
|
|
81
|
+
* import { duration } from '@alexstukovnikov/oz-time';
|
|
82
|
+
*
|
|
83
|
+
* const value = duration(3600000, 'millisecond');
|
|
84
|
+
* console.log(value.asMinutes()); // ожидаемый результат: 60
|
|
85
|
+
*/
|
|
86
|
+
asMinutes() {
|
|
87
|
+
return this._milliseconds / unitToMilliseconds('minute');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Возвращает длительность в часах.
|
|
92
|
+
*
|
|
93
|
+
* @returns {number} Длительность в часах.
|
|
94
|
+
* @example
|
|
95
|
+
* import { duration } from '@alexstukovnikov/oz-time';
|
|
96
|
+
*
|
|
97
|
+
* const value = duration(36000000, 'millisecond');
|
|
98
|
+
* console.log(value.asHours()); // ожидаемый результат: 10
|
|
99
|
+
*/
|
|
100
|
+
asHours() {
|
|
101
|
+
return this._milliseconds / unitToMilliseconds('hour');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Возвращает длительность в днях.
|
|
106
|
+
*
|
|
107
|
+
* @returns {number} Длительность в днях.
|
|
108
|
+
* @example
|
|
109
|
+
* import { duration } from '@alexstukovnikov/oz-time';
|
|
110
|
+
*
|
|
111
|
+
* const value = duration(120, 'hour');
|
|
112
|
+
* console.log(value.asDays()); // ожидаемый результат: 5
|
|
113
|
+
*/
|
|
114
|
+
asDays() {
|
|
115
|
+
return this._milliseconds / unitToMilliseconds('day');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Возвращает новую длительность, равную сумме текущей и переданной длительности.
|
|
120
|
+
*
|
|
121
|
+
* @param {Duration} other - Вторая длительность.
|
|
122
|
+
* @throws {TypeError} Выбрасывается, если other не является экземпляром Duration.
|
|
123
|
+
* @returns {Duration} Новая длительность, равная сумме двух значений.
|
|
124
|
+
* @example
|
|
125
|
+
* import { duration } from '@alexstukovnikov/oz-time';
|
|
126
|
+
*
|
|
127
|
+
* const a = duration(1, 'second');
|
|
128
|
+
* const b = duration(2, 'second');
|
|
129
|
+
* console.log(a.add(b).asMilliseconds()); // ожидаемый результат: 3000
|
|
130
|
+
*/
|
|
131
|
+
add(other) {
|
|
132
|
+
if (!(other instanceof Duration)) {
|
|
133
|
+
throw new TypeError('Duration.add: other must be Duration');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return new Duration(this._milliseconds + other._milliseconds);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Создаёт и возвращает экземпляр {@link Duration} из числа
|
|
142
|
+
* и фиксированной единицы времени.
|
|
143
|
+
*
|
|
144
|
+
* @param {number} amount - Количество единиц времени.
|
|
145
|
+
* @param {string} unit - Фиксированная единица времени.
|
|
146
|
+
* @throws {TypeError} Выбрасывается, если amount некорректен.
|
|
147
|
+
* @throws {Error} Выбрасывается, если unit не является фиксированной единицей времени.
|
|
148
|
+
* @returns {Duration} Экземпляр Duration.
|
|
149
|
+
* @example
|
|
150
|
+
* import { duration } from '@alexstukovnikov/oz-time';
|
|
151
|
+
*
|
|
152
|
+
* const value = duration(2, 'hour');
|
|
153
|
+
* console.log(value.asMinutes()); // ожидаемый результат: 120
|
|
154
|
+
*/
|
|
155
|
+
export function duration(amount, unit) {
|
|
156
|
+
assertAmount(amount);
|
|
157
|
+
|
|
158
|
+
const normalizedUnit = normalizeUnit(unit);
|
|
159
|
+
|
|
160
|
+
if (!isFixedUnit(normalizedUnit)) {
|
|
161
|
+
throw new Error(`duration supports only fixed units: ${unit}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return new Duration(amount * unitToMilliseconds(normalizedUnit));
|
|
165
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { OzTime } from '../core/core.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Модуль форматирования экземпляров {@link OzTime}.
|
|
5
|
+
*
|
|
6
|
+
* @module modules/format
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Проверяет, является ли значение экземпляром OzTime.
|
|
11
|
+
*
|
|
12
|
+
* @private
|
|
13
|
+
* @param {*} value - Проверяемое значение.
|
|
14
|
+
* @throws {TypeError} Выбрасывается, если значение не является экземпляром OzTime.
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
function assertOzTime(value) {
|
|
18
|
+
if (!(value instanceof OzTime)) {
|
|
19
|
+
throw new TypeError('format: first argument must be OzTime');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Дополняет значение ведущими нулями до нужной длины.
|
|
25
|
+
*
|
|
26
|
+
* @private
|
|
27
|
+
* @param {string|number} value - Исходное значение.
|
|
28
|
+
* @param {number} [length=2] - Итоговая длина строки.
|
|
29
|
+
* @returns {string} Строка, дополненная ведущими нулями.
|
|
30
|
+
*/
|
|
31
|
+
function pad(value, length = 2) {
|
|
32
|
+
return String(value).padStart(length, '0');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Возвращает числовые части даты для форматирования.
|
|
37
|
+
*
|
|
38
|
+
* @private
|
|
39
|
+
* @param {OzTime} time - Экземпляр времени.
|
|
40
|
+
* @param {string} locale - Локаль форматирования.
|
|
41
|
+
* @returns {Object.<string, string>} Объект с числовыми частями даты и времени.
|
|
42
|
+
*/
|
|
43
|
+
function getNumericParts(time, locale) {
|
|
44
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
45
|
+
timeZone: time.getTimezone(),
|
|
46
|
+
year: 'numeric',
|
|
47
|
+
month: 'numeric',
|
|
48
|
+
day: 'numeric',
|
|
49
|
+
hour: 'numeric',
|
|
50
|
+
minute: '2-digit',
|
|
51
|
+
second: '2-digit',
|
|
52
|
+
hour12: false,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const parts = formatter.formatToParts(new Date(time.getTimestamp()));
|
|
56
|
+
return Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Возвращает название месяца в нужном формате.
|
|
61
|
+
*
|
|
62
|
+
* @private
|
|
63
|
+
* @param {OzTime} time - Экземпляр времени.
|
|
64
|
+
* @param {string} locale - Локаль форматирования.
|
|
65
|
+
* @param {'long'|'short'|'narrow'} length - Длина названия месяца.
|
|
66
|
+
* @returns {string} Название месяца.
|
|
67
|
+
*/
|
|
68
|
+
function getMonthName(time, locale, length) {
|
|
69
|
+
return new Intl.DateTimeFormat(locale, {
|
|
70
|
+
timeZone: time.getTimezone(),
|
|
71
|
+
month: length,
|
|
72
|
+
}).format(new Date(time.getTimestamp()));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Возвращает название дня недели в нужном формате.
|
|
77
|
+
*
|
|
78
|
+
* @private
|
|
79
|
+
* @param {OzTime} time - Экземпляр времени.
|
|
80
|
+
* @param {string} locale - Локаль форматирования.
|
|
81
|
+
* @param {'long'|'short'|'narrow'} length - Длина названия дня недели.
|
|
82
|
+
* @returns {string} Название дня недели.
|
|
83
|
+
*/
|
|
84
|
+
function getWeekdayName(time, locale, length) {
|
|
85
|
+
return new Intl.DateTimeFormat(locale, {
|
|
86
|
+
timeZone: time.getTimezone(),
|
|
87
|
+
weekday: length,
|
|
88
|
+
}).format(new Date(time.getTimestamp()));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Возвращает строковое представление экземпляра {@link OzTime}
|
|
93
|
+
* по заданному шаблону с токенами.
|
|
94
|
+
*
|
|
95
|
+
* Поддерживаются токены `YYYY`, `YY`, `MMMM`, `MMM`, `MM`, `M`, `dddd`, `ddd`,
|
|
96
|
+
* `DD`, `D`, `HH`, `H`, `hh`, `h`, `mm`, `ss`, `SSS` и `A`.
|
|
97
|
+
*
|
|
98
|
+
* @param {OzTime} time - Экземпляр времени для форматирования.
|
|
99
|
+
* @param {string} template - Шаблон форматирования.
|
|
100
|
+
* @param {string} [locale] - Необязательное переопределение локали.
|
|
101
|
+
* @throws {TypeError} Выбрасывается, если первый аргумент не является экземпляром OzTime или template некорректен.
|
|
102
|
+
* @returns {string} Отформатированная строка.
|
|
103
|
+
* @example
|
|
104
|
+
* import { format, fromISO } from '@alexstukovnikov/oz-time';
|
|
105
|
+
*
|
|
106
|
+
* const time = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU');
|
|
107
|
+
* console.log(format(time, 'DD.MM.YYYY HH:mm')); // ожидаемый результат: 25.05.2024 12:00
|
|
108
|
+
*/
|
|
109
|
+
export function format(time, template, locale) {
|
|
110
|
+
assertOzTime(time);
|
|
111
|
+
|
|
112
|
+
if (typeof template !== 'string' || template.trim() === '') {
|
|
113
|
+
throw new TypeError('format: template must be a non-empty string');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const usedLocale = locale ?? time.getLocale();
|
|
117
|
+
const parts = getNumericParts(time, usedLocale);
|
|
118
|
+
|
|
119
|
+
const year = Number(parts.year);
|
|
120
|
+
const month = Number(parts.month);
|
|
121
|
+
const day = Number(parts.day);
|
|
122
|
+
const hour24 = Number(parts.hour);
|
|
123
|
+
const minute = Number(parts.minute);
|
|
124
|
+
const second = Number(parts.second);
|
|
125
|
+
const millisecond = new Date(time.getTimestamp()).getUTCMilliseconds();
|
|
126
|
+
|
|
127
|
+
const hour12base = hour24 % 12;
|
|
128
|
+
const hour12 = hour12base === 0 ? 12 : hour12base;
|
|
129
|
+
const meridiem = hour24 >= 12 ? 'PM' : 'AM';
|
|
130
|
+
|
|
131
|
+
const tokens = {
|
|
132
|
+
YYYY: String(year),
|
|
133
|
+
YY: String(year).slice(-2),
|
|
134
|
+
|
|
135
|
+
MMMM: getMonthName(time, usedLocale, 'long'),
|
|
136
|
+
MMM: getMonthName(time, usedLocale, 'short'),
|
|
137
|
+
MM: pad(month),
|
|
138
|
+
M: String(month),
|
|
139
|
+
|
|
140
|
+
dddd: getWeekdayName(time, usedLocale, 'long'),
|
|
141
|
+
ddd: getWeekdayName(time, usedLocale, 'short'),
|
|
142
|
+
|
|
143
|
+
DD: pad(day),
|
|
144
|
+
D: String(day),
|
|
145
|
+
|
|
146
|
+
HH: pad(hour24),
|
|
147
|
+
H: String(hour24),
|
|
148
|
+
|
|
149
|
+
hh: pad(hour12),
|
|
150
|
+
h: String(hour12),
|
|
151
|
+
|
|
152
|
+
mm: pad(minute),
|
|
153
|
+
ss: pad(second),
|
|
154
|
+
SSS: pad(millisecond, 3),
|
|
155
|
+
|
|
156
|
+
A: meridiem,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const tokenPattern = /YYYY|MMMM|MMM|MM|M|dddd|ddd|DD|D|HH|H|hh|h|mm|ss|SSS|YY|A/g;
|
|
160
|
+
|
|
161
|
+
return template.replace(tokenPattern, (token) => tokens[token] ?? token);
|
|
162
|
+
}
|