chronos-ts 1.1.0 → 2.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/README.md +249 -443
- package/dist/core/chronos.d.ts +460 -0
- package/dist/core/chronos.js +1259 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +19 -0
- package/dist/core/interval.d.ts +289 -0
- package/dist/core/interval.js +689 -0
- package/dist/core/period-collection.d.ts +205 -0
- package/dist/core/period-collection.js +562 -0
- package/dist/core/period.d.ts +428 -0
- package/dist/core/period.js +1007 -0
- package/dist/core/timezone.d.ts +289 -0
- package/dist/core/timezone.js +671 -0
- package/dist/index.d.ts +50 -4
- package/dist/index.js +148 -22
- package/dist/locales/index.d.ts +66 -0
- package/dist/locales/index.js +847 -0
- package/dist/types/index.d.ts +428 -0
- package/dist/types/index.js +71 -0
- package/dist/utils/index.d.ts +127 -0
- package/dist/utils/index.js +656 -0
- package/package.json +19 -3
- package/dist/interval.d.ts +0 -61
- package/dist/interval.js +0 -82
- package/dist/period.d.ts +0 -196
- package/dist/period.js +0 -365
- package/dist/precision.d.ts +0 -24
- package/dist/precision.js +0 -46
- package/dist/utils.d.ts +0 -190
- package/dist/utils.js +0 -374
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ChronosTimezone - Timezone handling and conversions
|
|
4
|
+
* @module ChronosTimezone
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Timezones = exports.ChronosTimezone = exports.TIMEZONES = void 0;
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Timezone Data
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Common timezone identifiers
|
|
13
|
+
*/
|
|
14
|
+
exports.TIMEZONES = {
|
|
15
|
+
// UTC
|
|
16
|
+
UTC: 'UTC',
|
|
17
|
+
GMT: 'GMT',
|
|
18
|
+
// Americas
|
|
19
|
+
'America/New_York': 'America/New_York',
|
|
20
|
+
'America/Chicago': 'America/Chicago',
|
|
21
|
+
'America/Denver': 'America/Denver',
|
|
22
|
+
'America/Los_Angeles': 'America/Los_Angeles',
|
|
23
|
+
'America/Phoenix': 'America/Phoenix',
|
|
24
|
+
'America/Anchorage': 'America/Anchorage',
|
|
25
|
+
'America/Toronto': 'America/Toronto',
|
|
26
|
+
'America/Vancouver': 'America/Vancouver',
|
|
27
|
+
'America/Mexico_City': 'America/Mexico_City',
|
|
28
|
+
'America/Sao_Paulo': 'America/Sao_Paulo',
|
|
29
|
+
'America/Buenos_Aires': 'America/Buenos_Aires',
|
|
30
|
+
'America/Lima': 'America/Lima',
|
|
31
|
+
'America/Bogota': 'America/Bogota',
|
|
32
|
+
// Europe
|
|
33
|
+
'Europe/London': 'Europe/London',
|
|
34
|
+
'Europe/Paris': 'Europe/Paris',
|
|
35
|
+
'Europe/Berlin': 'Europe/Berlin',
|
|
36
|
+
'Europe/Madrid': 'Europe/Madrid',
|
|
37
|
+
'Europe/Rome': 'Europe/Rome',
|
|
38
|
+
'Europe/Amsterdam': 'Europe/Amsterdam',
|
|
39
|
+
'Europe/Brussels': 'Europe/Brussels',
|
|
40
|
+
'Europe/Vienna': 'Europe/Vienna',
|
|
41
|
+
'Europe/Warsaw': 'Europe/Warsaw',
|
|
42
|
+
'Europe/Prague': 'Europe/Prague',
|
|
43
|
+
'Europe/Moscow': 'Europe/Moscow',
|
|
44
|
+
'Europe/Istanbul': 'Europe/Istanbul',
|
|
45
|
+
'Europe/Athens': 'Europe/Athens',
|
|
46
|
+
'Europe/Helsinki': 'Europe/Helsinki',
|
|
47
|
+
'Europe/Stockholm': 'Europe/Stockholm',
|
|
48
|
+
'Europe/Oslo': 'Europe/Oslo',
|
|
49
|
+
'Europe/Copenhagen': 'Europe/Copenhagen',
|
|
50
|
+
'Europe/Dublin': 'Europe/Dublin',
|
|
51
|
+
'Europe/Zurich': 'Europe/Zurich',
|
|
52
|
+
// Asia
|
|
53
|
+
'Asia/Tokyo': 'Asia/Tokyo',
|
|
54
|
+
'Asia/Shanghai': 'Asia/Shanghai',
|
|
55
|
+
'Asia/Hong_Kong': 'Asia/Hong_Kong',
|
|
56
|
+
'Asia/Singapore': 'Asia/Singapore',
|
|
57
|
+
'Asia/Seoul': 'Asia/Seoul',
|
|
58
|
+
'Asia/Taipei': 'Asia/Taipei',
|
|
59
|
+
'Asia/Bangkok': 'Asia/Bangkok',
|
|
60
|
+
'Asia/Jakarta': 'Asia/Jakarta',
|
|
61
|
+
'Asia/Manila': 'Asia/Manila',
|
|
62
|
+
'Asia/Kuala_Lumpur': 'Asia/Kuala_Lumpur',
|
|
63
|
+
'Asia/Ho_Chi_Minh': 'Asia/Ho_Chi_Minh',
|
|
64
|
+
'Asia/Dubai': 'Asia/Dubai',
|
|
65
|
+
'Asia/Kolkata': 'Asia/Kolkata',
|
|
66
|
+
'Asia/Mumbai': 'Asia/Mumbai',
|
|
67
|
+
'Asia/Karachi': 'Asia/Karachi',
|
|
68
|
+
'Asia/Dhaka': 'Asia/Dhaka',
|
|
69
|
+
'Asia/Tehran': 'Asia/Tehran',
|
|
70
|
+
'Asia/Riyadh': 'Asia/Riyadh',
|
|
71
|
+
'Asia/Jerusalem': 'Asia/Jerusalem',
|
|
72
|
+
// Australia/Pacific
|
|
73
|
+
'Australia/Sydney': 'Australia/Sydney',
|
|
74
|
+
'Australia/Melbourne': 'Australia/Melbourne',
|
|
75
|
+
'Australia/Brisbane': 'Australia/Brisbane',
|
|
76
|
+
'Australia/Perth': 'Australia/Perth',
|
|
77
|
+
'Australia/Adelaide': 'Australia/Adelaide',
|
|
78
|
+
'Pacific/Auckland': 'Pacific/Auckland',
|
|
79
|
+
'Pacific/Fiji': 'Pacific/Fiji',
|
|
80
|
+
'Pacific/Honolulu': 'Pacific/Honolulu',
|
|
81
|
+
// Africa
|
|
82
|
+
'Africa/Cairo': 'Africa/Cairo',
|
|
83
|
+
'Africa/Johannesburg': 'Africa/Johannesburg',
|
|
84
|
+
'Africa/Lagos': 'Africa/Lagos',
|
|
85
|
+
'Africa/Nairobi': 'Africa/Nairobi',
|
|
86
|
+
'Africa/Casablanca': 'Africa/Casablanca',
|
|
87
|
+
};
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// ChronosTimezone Class
|
|
90
|
+
// ============================================================================
|
|
91
|
+
/**
|
|
92
|
+
* ChronosTimezone - Handles timezone operations and conversions
|
|
93
|
+
*
|
|
94
|
+
* This class provides comprehensive timezone handling including:
|
|
95
|
+
* - Timezone information retrieval
|
|
96
|
+
* - Offset calculations
|
|
97
|
+
* - DST detection
|
|
98
|
+
* - Timezone conversions
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* // Get timezone info
|
|
103
|
+
* const tz = ChronosTimezone.create('America/New_York');
|
|
104
|
+
* console.log(tz.offset); // -5 or -4 depending on DST
|
|
105
|
+
*
|
|
106
|
+
* // Check DST
|
|
107
|
+
* console.log(tz.isDST(new Date())); // true/false
|
|
108
|
+
*
|
|
109
|
+
* // Convert between timezones
|
|
110
|
+
* const utcDate = new Date();
|
|
111
|
+
* const localDate = ChronosTimezone.convert(utcDate, 'UTC', 'America/New_York');
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
class ChronosTimezone {
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Constructor
|
|
117
|
+
// ============================================================================
|
|
118
|
+
/**
|
|
119
|
+
* Create a new ChronosTimezone
|
|
120
|
+
*/
|
|
121
|
+
constructor(identifier = 'UTC') {
|
|
122
|
+
this._originalOffset = null;
|
|
123
|
+
this._cachedOffset = null;
|
|
124
|
+
this._cachedDate = null;
|
|
125
|
+
const normalized = this._normalizeIdentifier(identifier);
|
|
126
|
+
this._identifier = normalized.identifier;
|
|
127
|
+
this._originalOffset = normalized.originalOffset;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Normalize timezone identifier
|
|
131
|
+
*/
|
|
132
|
+
_normalizeIdentifier(identifier) {
|
|
133
|
+
// Handle UTC aliases
|
|
134
|
+
if (identifier.toUpperCase() === 'Z' ||
|
|
135
|
+
identifier.toUpperCase() === 'GMT') {
|
|
136
|
+
return { identifier: 'UTC', originalOffset: null };
|
|
137
|
+
}
|
|
138
|
+
// Handle offset strings like +05:30, -08:00
|
|
139
|
+
if (/^[+-]\d{2}:\d{2}$/.test(identifier)) {
|
|
140
|
+
// Store original offset and convert to Etc/GMT for internal use
|
|
141
|
+
const offsetHours = this._parseOffsetString(identifier);
|
|
142
|
+
const etcGmt = `Etc/GMT${offsetHours >= 0 ? '-' : '+'}${Math.abs(Math.floor(offsetHours))}`;
|
|
143
|
+
return { identifier: etcGmt, originalOffset: identifier };
|
|
144
|
+
}
|
|
145
|
+
return { identifier, originalOffset: null };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Parse offset string to hours
|
|
149
|
+
*/
|
|
150
|
+
_parseOffsetString(offset) {
|
|
151
|
+
const match = offset.match(/^([+-])(\d{2}):(\d{2})$/);
|
|
152
|
+
if (!match)
|
|
153
|
+
return 0;
|
|
154
|
+
const sign = match[1] === '+' ? 1 : -1;
|
|
155
|
+
const hours = parseInt(match[2], 10);
|
|
156
|
+
const minutes = parseInt(match[3], 10);
|
|
157
|
+
return sign * (hours + minutes / 60);
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Static Factory Methods
|
|
161
|
+
// ============================================================================
|
|
162
|
+
/**
|
|
163
|
+
* Create a timezone instance
|
|
164
|
+
*/
|
|
165
|
+
static create(identifier = 'UTC') {
|
|
166
|
+
return new ChronosTimezone(identifier);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Create UTC timezone
|
|
170
|
+
*/
|
|
171
|
+
static utc() {
|
|
172
|
+
return new ChronosTimezone('UTC');
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create timezone from local system timezone
|
|
176
|
+
*/
|
|
177
|
+
static local() {
|
|
178
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
179
|
+
return new ChronosTimezone(tz);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Create timezone from offset in hours
|
|
183
|
+
*/
|
|
184
|
+
static fromOffset(offsetHours) {
|
|
185
|
+
const sign = offsetHours >= 0 ? '+' : '-';
|
|
186
|
+
const absOffset = Math.abs(offsetHours);
|
|
187
|
+
const hours = Math.floor(absOffset);
|
|
188
|
+
const minutes = Math.round((absOffset - hours) * 60);
|
|
189
|
+
const offsetString = `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
|
190
|
+
return new ChronosTimezone(offsetString);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get the local system timezone identifier
|
|
194
|
+
*/
|
|
195
|
+
static localIdentifier() {
|
|
196
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
197
|
+
}
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Getters
|
|
200
|
+
// ============================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Get timezone identifier
|
|
203
|
+
* Returns the original offset string if created from an offset, otherwise returns the IANA identifier
|
|
204
|
+
*/
|
|
205
|
+
get identifier() {
|
|
206
|
+
var _a;
|
|
207
|
+
return (_a = this._originalOffset) !== null && _a !== void 0 ? _a : this._identifier;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get the internal IANA timezone identifier (used for Intl operations)
|
|
211
|
+
*/
|
|
212
|
+
get ianaIdentifier() {
|
|
213
|
+
return this._identifier;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get timezone name (alias for identifier)
|
|
217
|
+
*/
|
|
218
|
+
get name() {
|
|
219
|
+
var _a;
|
|
220
|
+
return (_a = this._originalOffset) !== null && _a !== void 0 ? _a : this._identifier;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get timezone abbreviation for a given date
|
|
224
|
+
*/
|
|
225
|
+
getAbbreviation(date = new Date()) {
|
|
226
|
+
var _a;
|
|
227
|
+
try {
|
|
228
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
229
|
+
timeZone: this._identifier,
|
|
230
|
+
timeZoneName: 'short',
|
|
231
|
+
});
|
|
232
|
+
const parts = formatter.formatToParts(date);
|
|
233
|
+
const tzPart = parts.find((p) => p.type === 'timeZoneName');
|
|
234
|
+
return (_a = tzPart === null || tzPart === void 0 ? void 0 : tzPart.value) !== null && _a !== void 0 ? _a : this._identifier;
|
|
235
|
+
}
|
|
236
|
+
catch (_b) {
|
|
237
|
+
return this._identifier;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get full timezone name for a given date
|
|
242
|
+
*/
|
|
243
|
+
getFullName(date = new Date()) {
|
|
244
|
+
var _a;
|
|
245
|
+
try {
|
|
246
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
247
|
+
timeZone: this._identifier,
|
|
248
|
+
timeZoneName: 'long',
|
|
249
|
+
});
|
|
250
|
+
const parts = formatter.formatToParts(date);
|
|
251
|
+
const tzPart = parts.find((p) => p.type === 'timeZoneName');
|
|
252
|
+
return (_a = tzPart === null || tzPart === void 0 ? void 0 : tzPart.value) !== null && _a !== void 0 ? _a : this._identifier;
|
|
253
|
+
}
|
|
254
|
+
catch (_b) {
|
|
255
|
+
return this._identifier;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// ============================================================================
|
|
259
|
+
// Offset Calculations
|
|
260
|
+
// ============================================================================
|
|
261
|
+
/**
|
|
262
|
+
* Get UTC offset in minutes for a given date
|
|
263
|
+
*/
|
|
264
|
+
getOffsetMinutes(date = new Date()) {
|
|
265
|
+
try {
|
|
266
|
+
// Create formatters for UTC and target timezone
|
|
267
|
+
const utcFormatter = new Intl.DateTimeFormat('en-US', {
|
|
268
|
+
timeZone: 'UTC',
|
|
269
|
+
year: 'numeric',
|
|
270
|
+
month: 'numeric',
|
|
271
|
+
day: 'numeric',
|
|
272
|
+
hour: 'numeric',
|
|
273
|
+
minute: 'numeric',
|
|
274
|
+
hour12: false,
|
|
275
|
+
});
|
|
276
|
+
const tzFormatter = new Intl.DateTimeFormat('en-US', {
|
|
277
|
+
timeZone: this._identifier,
|
|
278
|
+
year: 'numeric',
|
|
279
|
+
month: 'numeric',
|
|
280
|
+
day: 'numeric',
|
|
281
|
+
hour: 'numeric',
|
|
282
|
+
minute: 'numeric',
|
|
283
|
+
hour12: false,
|
|
284
|
+
});
|
|
285
|
+
const utcParts = this._parseIntlParts(utcFormatter.formatToParts(date));
|
|
286
|
+
const tzParts = this._parseIntlParts(tzFormatter.formatToParts(date));
|
|
287
|
+
const utcDate = new Date(Date.UTC(utcParts.year, utcParts.month - 1, utcParts.day, utcParts.hour, utcParts.minute));
|
|
288
|
+
const tzDate = new Date(Date.UTC(tzParts.year, tzParts.month - 1, tzParts.day, tzParts.hour, tzParts.minute));
|
|
289
|
+
return (tzDate.getTime() - utcDate.getTime()) / 60000;
|
|
290
|
+
}
|
|
291
|
+
catch (_a) {
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Parse Intl formatter parts to components
|
|
297
|
+
*/
|
|
298
|
+
_parseIntlParts(parts) {
|
|
299
|
+
const result = { year: 0, month: 0, day: 0, hour: 0, minute: 0 };
|
|
300
|
+
for (const part of parts) {
|
|
301
|
+
switch (part.type) {
|
|
302
|
+
case 'year':
|
|
303
|
+
result.year = parseInt(part.value, 10);
|
|
304
|
+
break;
|
|
305
|
+
case 'month':
|
|
306
|
+
result.month = parseInt(part.value, 10);
|
|
307
|
+
break;
|
|
308
|
+
case 'day':
|
|
309
|
+
result.day = parseInt(part.value, 10);
|
|
310
|
+
break;
|
|
311
|
+
case 'hour':
|
|
312
|
+
result.hour = parseInt(part.value, 10);
|
|
313
|
+
break;
|
|
314
|
+
case 'minute':
|
|
315
|
+
result.minute = parseInt(part.value, 10);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get UTC offset in hours for a given date
|
|
323
|
+
*/
|
|
324
|
+
getOffsetHours(date = new Date()) {
|
|
325
|
+
return this.getOffsetMinutes(date) / 60;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get UTC offset as string (e.g., "+05:30", "-08:00")
|
|
329
|
+
*/
|
|
330
|
+
getOffsetString(date = new Date()) {
|
|
331
|
+
const offsetMinutes = this.getOffsetMinutes(date);
|
|
332
|
+
const sign = offsetMinutes >= 0 ? '+' : '-';
|
|
333
|
+
const absMinutes = Math.abs(offsetMinutes);
|
|
334
|
+
const hours = Math.floor(absMinutes / 60);
|
|
335
|
+
const minutes = absMinutes % 60;
|
|
336
|
+
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Get complete offset information
|
|
340
|
+
*/
|
|
341
|
+
getOffset(date = new Date()) {
|
|
342
|
+
const minutes = this.getOffsetMinutes(date);
|
|
343
|
+
return {
|
|
344
|
+
minutes,
|
|
345
|
+
hours: minutes / 60,
|
|
346
|
+
string: this.getOffsetString(date),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// ============================================================================
|
|
350
|
+
// DST (Daylight Saving Time)
|
|
351
|
+
// ============================================================================
|
|
352
|
+
/**
|
|
353
|
+
* Check if DST is in effect for a given date
|
|
354
|
+
*/
|
|
355
|
+
isDST(date = new Date()) {
|
|
356
|
+
const jan = new Date(date.getFullYear(), 0, 1);
|
|
357
|
+
const jul = new Date(date.getFullYear(), 6, 1);
|
|
358
|
+
const janOffset = this.getOffsetMinutes(jan);
|
|
359
|
+
const julOffset = this.getOffsetMinutes(jul);
|
|
360
|
+
const currentOffset = this.getOffsetMinutes(date);
|
|
361
|
+
// DST is in effect if current offset matches the larger offset
|
|
362
|
+
const standardOffset = Math.min(janOffset, julOffset);
|
|
363
|
+
return currentOffset !== standardOffset;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check if timezone observes DST
|
|
367
|
+
*/
|
|
368
|
+
observesDST() {
|
|
369
|
+
const currentYear = new Date().getFullYear();
|
|
370
|
+
const jan = new Date(currentYear, 0, 15);
|
|
371
|
+
const jul = new Date(currentYear, 6, 15);
|
|
372
|
+
return this.getOffsetMinutes(jan) !== this.getOffsetMinutes(jul);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get the next DST transition
|
|
376
|
+
*/
|
|
377
|
+
getNextDSTTransition(from = new Date()) {
|
|
378
|
+
if (!this.observesDST()) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const currentOffset = this.getOffsetMinutes(from);
|
|
382
|
+
const checkDate = new Date(from);
|
|
383
|
+
// Search forward up to 1 year
|
|
384
|
+
for (let i = 0; i < 366; i++) {
|
|
385
|
+
checkDate.setDate(checkDate.getDate() + 1);
|
|
386
|
+
const newOffset = this.getOffsetMinutes(checkDate);
|
|
387
|
+
if (newOffset !== currentOffset) {
|
|
388
|
+
// Found a transition, binary search for exact time
|
|
389
|
+
const exactDate = this._findExactTransition(new Date(checkDate.getTime() - 24 * 60 * 60 * 1000), checkDate);
|
|
390
|
+
return {
|
|
391
|
+
date: exactDate,
|
|
392
|
+
fromOffset: currentOffset,
|
|
393
|
+
toOffset: newOffset,
|
|
394
|
+
isDSTStart: newOffset > currentOffset,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Binary search to find exact DST transition time
|
|
402
|
+
*/
|
|
403
|
+
_findExactTransition(start, end) {
|
|
404
|
+
const startOffset = this.getOffsetMinutes(start);
|
|
405
|
+
while (end.getTime() - start.getTime() > 60000) {
|
|
406
|
+
// Within 1 minute
|
|
407
|
+
const mid = new Date((start.getTime() + end.getTime()) / 2);
|
|
408
|
+
const midOffset = this.getOffsetMinutes(mid);
|
|
409
|
+
if (midOffset === startOffset) {
|
|
410
|
+
start = mid;
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
end = mid;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return end;
|
|
417
|
+
}
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// Conversion
|
|
420
|
+
// ============================================================================
|
|
421
|
+
/**
|
|
422
|
+
* Convert a date to this timezone (returns formatted string)
|
|
423
|
+
*/
|
|
424
|
+
format(date, formatOptions) {
|
|
425
|
+
const options = Object.assign({ timeZone: this._identifier }, formatOptions);
|
|
426
|
+
return new Intl.DateTimeFormat('en-US', options).format(date);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get date components in this timezone
|
|
430
|
+
*/
|
|
431
|
+
getComponents(date) {
|
|
432
|
+
var _a;
|
|
433
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
434
|
+
timeZone: this._identifier,
|
|
435
|
+
year: 'numeric',
|
|
436
|
+
month: 'numeric',
|
|
437
|
+
day: 'numeric',
|
|
438
|
+
hour: 'numeric',
|
|
439
|
+
minute: 'numeric',
|
|
440
|
+
second: 'numeric',
|
|
441
|
+
weekday: 'short',
|
|
442
|
+
hour12: false,
|
|
443
|
+
});
|
|
444
|
+
const parts = formatter.formatToParts(date);
|
|
445
|
+
const result = {
|
|
446
|
+
year: 0,
|
|
447
|
+
month: 0,
|
|
448
|
+
day: 0,
|
|
449
|
+
hour: 0,
|
|
450
|
+
minute: 0,
|
|
451
|
+
second: 0,
|
|
452
|
+
dayOfWeek: 0,
|
|
453
|
+
};
|
|
454
|
+
const dayMap = {
|
|
455
|
+
Sun: 0,
|
|
456
|
+
Mon: 1,
|
|
457
|
+
Tue: 2,
|
|
458
|
+
Wed: 3,
|
|
459
|
+
Thu: 4,
|
|
460
|
+
Fri: 5,
|
|
461
|
+
Sat: 6,
|
|
462
|
+
};
|
|
463
|
+
for (const part of parts) {
|
|
464
|
+
switch (part.type) {
|
|
465
|
+
case 'year':
|
|
466
|
+
result.year = parseInt(part.value, 10);
|
|
467
|
+
break;
|
|
468
|
+
case 'month':
|
|
469
|
+
result.month = parseInt(part.value, 10);
|
|
470
|
+
break;
|
|
471
|
+
case 'day':
|
|
472
|
+
result.day = parseInt(part.value, 10);
|
|
473
|
+
break;
|
|
474
|
+
case 'hour':
|
|
475
|
+
result.hour = parseInt(part.value, 10);
|
|
476
|
+
break;
|
|
477
|
+
case 'minute':
|
|
478
|
+
result.minute = parseInt(part.value, 10);
|
|
479
|
+
break;
|
|
480
|
+
case 'second':
|
|
481
|
+
result.second = parseInt(part.value, 10);
|
|
482
|
+
break;
|
|
483
|
+
case 'weekday':
|
|
484
|
+
result.dayOfWeek = (_a = dayMap[part.value]) !== null && _a !== void 0 ? _a : 0;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return result;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Convert a date from one timezone to another
|
|
492
|
+
*/
|
|
493
|
+
static convert(date, from, to) {
|
|
494
|
+
const fromTz = new ChronosTimezone(from);
|
|
495
|
+
const toTz = new ChronosTimezone(to);
|
|
496
|
+
const fromOffset = fromTz.getOffsetMinutes(date);
|
|
497
|
+
const toOffset = toTz.getOffsetMinutes(date);
|
|
498
|
+
const diffMinutes = toOffset - fromOffset;
|
|
499
|
+
return new Date(date.getTime() + diffMinutes * 60000);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Convert a UTC date to this timezone
|
|
503
|
+
*/
|
|
504
|
+
fromUTC(date) {
|
|
505
|
+
return ChronosTimezone.convert(date, 'UTC', this._identifier);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Convert a date in this timezone to UTC
|
|
509
|
+
*/
|
|
510
|
+
toUTC(date) {
|
|
511
|
+
return ChronosTimezone.convert(date, this._identifier, 'UTC');
|
|
512
|
+
}
|
|
513
|
+
// ============================================================================
|
|
514
|
+
// Information
|
|
515
|
+
// ============================================================================
|
|
516
|
+
/**
|
|
517
|
+
* Get comprehensive timezone information
|
|
518
|
+
*/
|
|
519
|
+
getInfo(date = new Date()) {
|
|
520
|
+
var _a;
|
|
521
|
+
return {
|
|
522
|
+
identifier: (_a = this._originalOffset) !== null && _a !== void 0 ? _a : this._identifier,
|
|
523
|
+
abbreviation: this.getAbbreviation(date),
|
|
524
|
+
name: this.getFullName(date),
|
|
525
|
+
offset: this.getOffset(date),
|
|
526
|
+
isDST: this.isDST(date),
|
|
527
|
+
observesDST: this.observesDST(),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Check if two timezones are equivalent at a given moment
|
|
532
|
+
*/
|
|
533
|
+
equals(other, date = new Date()) {
|
|
534
|
+
const otherTz = typeof other === 'string' ? new ChronosTimezone(other) : other;
|
|
535
|
+
return this.getOffsetMinutes(date) === otherTz.getOffsetMinutes(date);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Check if this is the same timezone identifier
|
|
539
|
+
*/
|
|
540
|
+
isSame(other) {
|
|
541
|
+
const otherIdentifier = typeof other === 'string' ? other : other.identifier;
|
|
542
|
+
return this.identifier === otherIdentifier;
|
|
543
|
+
}
|
|
544
|
+
// ============================================================================
|
|
545
|
+
// Static Utilities
|
|
546
|
+
// ============================================================================
|
|
547
|
+
/**
|
|
548
|
+
* Get all available timezone identifiers
|
|
549
|
+
* Note: This returns common timezones. Use Intl.supportedValuesOf('timeZone') for all.
|
|
550
|
+
*/
|
|
551
|
+
static getAvailableTimezones() {
|
|
552
|
+
// Try to use the native method if available
|
|
553
|
+
if (typeof Intl !== 'undefined' && 'supportedValuesOf' in Intl) {
|
|
554
|
+
try {
|
|
555
|
+
return Intl.supportedValuesOf('timeZone');
|
|
556
|
+
}
|
|
557
|
+
catch (_a) {
|
|
558
|
+
// Fall back to predefined list
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return Object.values(exports.TIMEZONES);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Check if a timezone identifier is valid
|
|
565
|
+
*/
|
|
566
|
+
static isValid(identifier) {
|
|
567
|
+
try {
|
|
568
|
+
new Intl.DateTimeFormat('en-US', { timeZone: identifier });
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
catch (_a) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Get timezones grouped by region
|
|
577
|
+
*/
|
|
578
|
+
static getTimezonesByRegion() {
|
|
579
|
+
const timezones = ChronosTimezone.getAvailableTimezones();
|
|
580
|
+
const grouped = {};
|
|
581
|
+
for (const tz of timezones) {
|
|
582
|
+
const parts = tz.split('/');
|
|
583
|
+
const region = parts[0];
|
|
584
|
+
if (!grouped[region]) {
|
|
585
|
+
grouped[region] = [];
|
|
586
|
+
}
|
|
587
|
+
grouped[region].push(tz);
|
|
588
|
+
}
|
|
589
|
+
return grouped;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Find timezones that match a given offset
|
|
593
|
+
*/
|
|
594
|
+
static findByOffset(offsetHours, date = new Date()) {
|
|
595
|
+
const targetMinutes = offsetHours * 60;
|
|
596
|
+
const result = [];
|
|
597
|
+
for (const tz of ChronosTimezone.getAvailableTimezones()) {
|
|
598
|
+
const timezone = new ChronosTimezone(tz);
|
|
599
|
+
if (timezone.getOffsetMinutes(date) === targetMinutes) {
|
|
600
|
+
result.push(timezone);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Get current time in a specific timezone
|
|
607
|
+
*/
|
|
608
|
+
static now(identifier) {
|
|
609
|
+
const tz = new ChronosTimezone(identifier);
|
|
610
|
+
return tz.fromUTC(new Date());
|
|
611
|
+
}
|
|
612
|
+
// ============================================================================
|
|
613
|
+
// Serialization
|
|
614
|
+
// ============================================================================
|
|
615
|
+
/**
|
|
616
|
+
* Convert to string
|
|
617
|
+
*/
|
|
618
|
+
toString() {
|
|
619
|
+
var _a;
|
|
620
|
+
return (_a = this._originalOffset) !== null && _a !== void 0 ? _a : this._identifier;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Convert to JSON
|
|
624
|
+
*/
|
|
625
|
+
toJSON() {
|
|
626
|
+
var _a;
|
|
627
|
+
const now = new Date();
|
|
628
|
+
return {
|
|
629
|
+
identifier: (_a = this._originalOffset) !== null && _a !== void 0 ? _a : this._identifier,
|
|
630
|
+
offset: this.getOffsetString(now),
|
|
631
|
+
isDST: this.isDST(now),
|
|
632
|
+
observesDST: this.observesDST(),
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Get primitive value
|
|
637
|
+
*/
|
|
638
|
+
valueOf() {
|
|
639
|
+
var _a;
|
|
640
|
+
return (_a = this._originalOffset) !== null && _a !== void 0 ? _a : this._identifier;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
exports.ChronosTimezone = ChronosTimezone;
|
|
644
|
+
// ============================================================================
|
|
645
|
+
// Common Timezone Aliases
|
|
646
|
+
// ============================================================================
|
|
647
|
+
/**
|
|
648
|
+
* Pre-created timezone instances for common timezones
|
|
649
|
+
*/
|
|
650
|
+
exports.Timezones = {
|
|
651
|
+
UTC: ChronosTimezone.utc(),
|
|
652
|
+
Local: ChronosTimezone.local(),
|
|
653
|
+
// US
|
|
654
|
+
Eastern: ChronosTimezone.create('America/New_York'),
|
|
655
|
+
Central: ChronosTimezone.create('America/Chicago'),
|
|
656
|
+
Mountain: ChronosTimezone.create('America/Denver'),
|
|
657
|
+
Pacific: ChronosTimezone.create('America/Los_Angeles'),
|
|
658
|
+
// Europe
|
|
659
|
+
London: ChronosTimezone.create('Europe/London'),
|
|
660
|
+
Paris: ChronosTimezone.create('Europe/Paris'),
|
|
661
|
+
Berlin: ChronosTimezone.create('Europe/Berlin'),
|
|
662
|
+
// Asia
|
|
663
|
+
Tokyo: ChronosTimezone.create('Asia/Tokyo'),
|
|
664
|
+
Shanghai: ChronosTimezone.create('Asia/Shanghai'),
|
|
665
|
+
Singapore: ChronosTimezone.create('Asia/Singapore'),
|
|
666
|
+
Dubai: ChronosTimezone.create('Asia/Dubai'),
|
|
667
|
+
Mumbai: ChronosTimezone.create('Asia/Kolkata'),
|
|
668
|
+
// Australia/Pacific
|
|
669
|
+
Sydney: ChronosTimezone.create('Australia/Sydney'),
|
|
670
|
+
Auckland: ChronosTimezone.create('Pacific/Auckland'),
|
|
671
|
+
};
|