jupyter-ijavascript-utils 1.51.0 → 1.52.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/DOCS.md CHANGED
@@ -74,6 +74,8 @@ Give it a try here:
74
74
  [![Binder:what can I do with this](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/paulroth3d/jupyter-ijavascript-utils/main?labpath=example.ipynb)
75
75
 
76
76
  ## What's New
77
+ * 1.52 - print a date to ISO in local time with {@link module:date.toLocalISO|date.toLocalISO}
78
+ * 1.51 - added in {@link module:date|date} - and addressed issue #69, #68, #67, #66
77
79
  * 1.50 - added in {@link module:color|color/colour} - and addressed issue #65
78
80
  * 1.49 - Additional documentation for issues #18, #63, #62, #61 (like table.generateObjectCollection)
79
81
  * 1.48 - Correct table rendering html if filter was used ({@link https://github.com/paulroth3d/jupyter-ijavascript-utils/issues/64|#64})
package/Dockerfile CHANGED
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.51.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.52.1
package/README.md CHANGED
@@ -53,7 +53,9 @@ This is not intended to be the only way to accomplish many of these tasks, and a
53
53
 
54
54
  ![Screenshot of example notebook](docResources/img/mainExampleNotebook.png)
55
55
 
56
- # What's New
56
+ # What's New
57
+ * 1.52 - print a date to ISO in local time with date.toLocalISO
58
+ * 1.51 - added in date - and addressed issues #69, #68, #67, #66
57
59
  * 1.50 - added in color/colour - and addressed issue #65
58
60
  * 1.49 - Additional documentation for issues #18, #63, #62, #61 (like table.generateObjectCollection)
59
61
  * 1.48 - Correct table rendering html if filter was used (#46)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.51.0",
3
+ "version": "1.52.1",
4
4
  "description": "Utilities for working with iJavaScript - a Jupyter Kernel",
5
5
  "homepage": "https://jupyter-ijavascript-utils.onrender.com/",
6
6
  "license": "MIT",
package/src/date.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * * Parse
8
8
  * * {@link module:date.parse|date.parse(String)} - parse a date and throw an exception if it is not a valid date
9
9
  * * TimeZones
10
+ * * {@link module:date.toLocalISO|date.toLocalISO} - prints in 8601 format with timezone offset based on a tz entry - like america/chicago
10
11
  * * {@link module:date.getTimezoneOffset|date.getTimezoneOffset(String)} - gets the number of milliseconds offset for a given timezone
11
12
  * * {@link module:date.correctForTimezone|date.correctForTimezone(Date, String)} - meant to correct a date already off from UTC to the correct time
12
13
  * * {@link module:date.epochShift|date.epochShift(Date, String)} - offsets a date from UTC to a given time amount
@@ -15,6 +16,10 @@
15
16
  * * {@link module:date.add|date.add(Date, {days, hours, minutes, seconds)} - shift a date by a given amount
16
17
  * * {@link module:date.endOfDay|date.endOfDay(Date)} - finds the end of day UTC for a given date
17
18
  * * {@link module:date.startOfDay|date.startOfDay(Date)} - finds the end of day UTC for a given date
19
+ * * Print
20
+ * * {@link module:date.durationLong|date.durationLong(epoch)} - displays duration in legible form:
21
+ * `D days, H hours, M minutes, S.MMM seconds`
22
+ * * {@link module:date.durationISO|date.durationISO(epoch)} - displays duration in condensed forme:
18
23
  *
19
24
  * --------
20
25
  *
@@ -35,6 +40,8 @@
35
40
  module.exports = {};
36
41
  const DateUtils = module.exports;
37
42
 
43
+ module.exports.divideRemainder = (val, denominator) => ({ value: Math.floor(val / denominator), remainder: val % denominator });
44
+
38
45
  /**
39
46
  * Collection of time durations in milliseconds
40
47
  */
@@ -99,55 +106,151 @@ module.exports.parse = (dateStr) => {
99
106
  return result;
100
107
  };
101
108
 
102
- module.exports.timeZoneOffsets = new Map();
109
+ /**
110
+ * Prints the duration in ISO format: `D:HH:MM:SS.MMM`
111
+ *
112
+ * ```
113
+ * start = new Date(Date.ISO(2024, 12, 26, 12, 0, 0));
114
+ * end = new Date(Date.ISO(2024, 12, 26, 13, 0, 0));
115
+ *
116
+ * duration = end.getTime() - start.getTime();
117
+ *
118
+ * utils.date.durationLong(duration); // '0 days, 1 hours, 0 minutes, 0.00 seconds'
119
+ *
120
+ * @param {Number} epochDifference - difference in milliseconds between two dates
121
+ * @returns {String} `D days, H hours, M minutes,S.MMMM seconds`
122
+ * @see {@link module:date.DateRange.duration|DateRange.duration}
123
+ */
124
+ module.exports.durationISO = function durationISO(epochDifference) {
125
+ const signStr = epochDifference < 0 ? '-' : '';
126
+ let result = DateUtils.divideRemainder(Math.abs(epochDifference), DateUtils.TIME.DAY);
127
+ const days = String(result.value);
128
+ result = DateUtils.divideRemainder(result.remainder, DateUtils.TIME.HOUR);
129
+ const hours = String(result.value).padStart(2, '0');
130
+ result = DateUtils.divideRemainder(result.remainder, DateUtils.TIME.MINUTE);
131
+ const minutes = String(result.value).padStart(2, '0');
132
+ result = DateUtils.divideRemainder(result.remainder, DateUtils.TIME.SECOND);
133
+ const seconds = String(result.value).padStart(2, '0');
134
+ const milli = String(result.remainder).padStart(3, '0');
135
+ return `${signStr}${days}:${hours}:${minutes}:${seconds}.${milli}`;
136
+ };
103
137
 
104
138
  /**
105
- * Determines the number of milliseconds difference between
106
- * a given timezone and UTC.
139
+ * Prints the duration in long format: `D days, H hours, M minutes, S.MMM seconds`
107
140
  *
108
- * (Note: these values are cached, and optimized for repeated use on the same value)
141
+ * ```
142
+ * start = new Date(Date.ISO(2024, 12, 26, 12, 0, 0));
143
+ * end = new Date(Date.ISO(2024, 12, 27, 13, 0, 0));
109
144
  *
110
- * See {@link https://en.wikipedia.org/wiki/List_of_tz_database_time_zones|the list of TZ database time zones}
111
- * for the full list of options.
145
+ * duration = end.getTime() - start.getTime();
112
146
  *
113
- * @param {String} timeZoneStr - a timezone string like "America/Toronto"
114
- * @returns {Number} - the number of milliseconds between UTC and that timezone
147
+ * utils.date.durationLong(duration); // '1 days, 1 hours, 0 minutes, 0.00 seconds'
148
+ *
149
+ * @param {Number} epochDifference - difference in milliseconds between two dates
150
+ * @returns {String} `D days, H hours, M minutes,S.MMMM seconds`
151
+ * @see {@link module:date.DateRange.duration|DateRange.duration}
115
152
  */
116
- module.exports.getTimezoneOffset = function getTimezoneOffset(timeZoneStr) {
117
- if (DateUtils.timeZoneOffsets.has(timeZoneStr)) {
118
- return DateUtils.timeZoneOffsets.get(timeZoneStr);
153
+ module.exports.durationLong = function durationLong(epochDifference) {
154
+ const signStr = epochDifference < 0 ? '-' : '';
155
+ let result = DateUtils.divideRemainder(Math.abs(epochDifference), DateUtils.TIME.DAY);
156
+ const days = result.value;
157
+ result = DateUtils.divideRemainder(result.remainder, DateUtils.TIME.HOUR);
158
+ const hours = result.value;
159
+ result = DateUtils.divideRemainder(result.remainder, DateUtils.TIME.MINUTE);
160
+ const minutes = result.value;
161
+ result = DateUtils.divideRemainder(result.remainder, DateUtils.TIME.SECOND);
162
+ const seconds = result.value;
163
+ const milli = result.remainder;
164
+ return `${signStr}${days} days, ${hours} hours, ${minutes} minutes, ${seconds}.${milli} seconds`;
165
+ };
166
+
167
+ /**
168
+ * @typedef {Object} TimeZoneEntry
169
+ * @property {String} tz - the name of the timezone
170
+ * @property {Function} formatter - formats a date to that local timezone
171
+ * @property {Number} epoch - the difference in milliseconds from that tz to UTC
172
+ * @property {String} offset - ISO format for how many hours and minutes offset to UTC '+|-' HH:MMM
173
+ */
174
+
175
+ /**
176
+ * Collection of TimeZoneEntries by the tz string
177
+ * @private
178
+ * @type {Map<String,TimeZoneEntry>}
179
+ */
180
+ module.exports.timeZoneOffsetMap = new Map();
181
+
182
+ /**
183
+ * Fetches or creates a TimeZoneEntry
184
+ * @private
185
+ * @param {String} timeZoneStr - tz database entry for the timezone
186
+ * @returns {TimeZoneEntry}
187
+ */
188
+ module.exports.getTimezoneEntry = function getTimezoneEntry(timeZoneStr) {
189
+ const cleanTz = String(timeZoneStr).toLowerCase();
190
+ if (DateUtils.timeZoneOffsetMap.has(cleanTz)) {
191
+ return DateUtils.timeZoneOffsetMap.get(cleanTz);
119
192
  }
120
193
  const d = new Date();
121
194
 
122
- const format = new Intl.DateTimeFormat('en-us', {
123
- year: 'numeric',
124
- month: 'numeric',
125
- day: 'numeric',
126
- hour: 'numeric',
127
- minute: 'numeric',
128
- second: 'numeric',
129
- hour12: false,
130
- fractionalSecondDigits: 3,
131
- timeZone: timeZoneStr
132
- });
133
- const dm = format.formatToParts(d)
134
- .filter(({ type }) => type !== 'literal')
135
- .reduce((result, { type, value }) => {
136
- // eslint-disable-next-line no-param-reassign
137
- result[type] = value;
138
- return result;
139
- }, {});
140
- // const impactedDate = new Date(d.toLocaleString('en-US', { timeZone: timeZoneStr }));
141
- const dateStr = `${dm.year}-${DateUtils.padTime(dm.month)}-${DateUtils.padTime(dm.day)}T${
142
- DateUtils.padTime(dm.hour)}:${DateUtils.padTime(dm.minute)}:${DateUtils.padTime(dm.second)}.${
143
- DateUtils.padTime(dm.fractionalSecond, 3)}`;
144
- const impactedDate = new Date(dateStr);
195
+ const formatter = (dateValue) => {
196
+ const dtFormat = new Intl.DateTimeFormat('en-us', {
197
+ year: 'numeric',
198
+ month: 'numeric',
199
+ day: 'numeric',
200
+ hour: 'numeric',
201
+ minute: 'numeric',
202
+ second: 'numeric',
203
+ hour12: false,
204
+ fractionalSecondDigits: 3,
205
+ timeZone: cleanTz
206
+ });
207
+ const dm = dtFormat.formatToParts(dateValue)
208
+ .filter(({ type }) => type !== 'literal')
209
+ .reduce((result, { type, value }) => {
210
+ // eslint-disable-next-line no-param-reassign
211
+ result[type] = value;
212
+ return result;
213
+ }, {});
214
+ // const impactedDate = new Date(d.toLocaleString('en-US', { timeZone: timeZoneStr }));
215
+ const dateStr = `${dm.year}-${DateUtils.padTime(dm.month)}-${DateUtils.padTime(dm.day)}T${
216
+ DateUtils.padTime(dm.hour)}:${DateUtils.padTime(dm.minute)}:${DateUtils.padTime(dm.second)}.${
217
+ DateUtils.padTime(dm.fractionalSecond, 3)}`;
218
+ return dateStr;
219
+ };
220
+
221
+ const impactedDateStr = formatter(d);
222
+ const impactedDate = new Date(impactedDateStr);
145
223
 
146
224
  const diff = d.getTime() - impactedDate.getTime();
147
225
 
148
- DateUtils.timeZoneOffsets.set(timeZoneStr, diff);
226
+ const diffSign = diff > 0 ? '-' : '+';
227
+ let remainder = DateUtils.divideRemainder(Math.abs(diff), DateUtils.TIME.HOUR);
228
+ const diffHours = remainder.value;
229
+ remainder = DateUtils.divideRemainder(remainder.remainder, DateUtils.TIME.MINUTE);
230
+ const diffMinutes = remainder.value;
231
+ const offset = `${diffSign}${DateUtils.padTime(diffHours)}:${DateUtils.padTime(diffMinutes)}`;
149
232
 
150
- return diff;
233
+ const result = ({ tz: cleanTz, formatter, epoch: diff, offset });
234
+
235
+ DateUtils.timeZoneOffsetMap.set(cleanTz, result);
236
+
237
+ return result;
238
+ };
239
+
240
+ /**
241
+ * Determines the number of milliseconds difference between
242
+ * a given timezone and UTC.
243
+ *
244
+ * (Note: these values are cached, and optimized for repeated use on the same value)
245
+ *
246
+ * See {@link https://en.wikipedia.org/wiki/List_of_tz_database_time_zones|the list of TZ database time zones}
247
+ * for the full list of options.
248
+ *
249
+ * @param {String} timeZoneStr - a timezone string like "America/Toronto"
250
+ * @returns {TimeZoneEntry} - the number of milliseconds between UTC and that timezone
251
+ */
252
+ module.exports.getTimezoneOffset = function getTimezoneOffset(timeZoneStr) {
253
+ return DateUtils.getTimezoneEntry(timeZoneStr).epoch;
151
254
  };
152
255
 
153
256
  /**
@@ -181,8 +284,8 @@ module.exports.getTimezoneOffset = function getTimezoneOffset(timeZoneStr) {
181
284
  * @returns {Date} - copy of the date corrected
182
285
  */
183
286
  module.exports.correctForTimezone = function correctForTimezone(date, timeZoneStr) {
184
- const offsetMilli = DateUtils.getTimezoneOffset(timeZoneStr);
185
- return new Date(date.getTime() + offsetMilli);
287
+ const { epoch } = DateUtils.getTimezoneEntry(timeZoneStr);
288
+ return new Date(date.getTime() + epoch);
186
289
  };
187
290
 
188
291
  /**
@@ -194,10 +297,33 @@ module.exports.correctForTimezone = function correctForTimezone(date, timeZoneSt
194
297
  * @param {Date} date - date to shift
195
298
  * @param {String} timeZoneStr - the tz database name of the timezone
196
299
  * @returns {Date}
300
+ * @see {@link module:date.toLocalISO|date.toLocalISO} - consider as an alternative.
301
+ * This prints the correct time, without updating the date object.
197
302
  */
198
303
  module.exports.epochShift = function epochShift(date, timeZoneStr) {
199
- const offsetMilli = DateUtils.getTimezoneOffset(timeZoneStr);
200
- return new Date(date.getTime() - offsetMilli);
304
+ const { epoch } = DateUtils.getTimezoneEntry(timeZoneStr);
305
+ return new Date(date.getTime() - epoch);
306
+ };
307
+
308
+ /**
309
+ * Prints a date in 8601 format to a timezone (with +H:MM offset)
310
+ *
311
+ * Consider this as an alternative to epochShifting.
312
+ *
313
+ * ```
314
+ * d = Date.parse('2024-12-27 13:30:00');
315
+ *
316
+ * utils.date.toLocalISO(d, 'america/Chicago'); // '2024-12-27T07:30:00.000-06:00'
317
+ * utils.date.toLocalISO(d, 'europe/paris'); // '2024-12-27T14:30:00.000+01:00'
318
+ * ```
319
+ *
320
+ * @param {Date} date - date to print
321
+ * @param {String} timeZoneStr - the tz database name of the timezone
322
+ * @returns {String} - ISO format with timezone offset
323
+ */
324
+ module.exports.toLocalISO = function toLocalISO(date, timeZoneStr) {
325
+ const { formatter, offset } = DateUtils.getTimezoneEntry(timeZoneStr);
326
+ return `${formatter(date)}${offset}`;
201
327
  };
202
328
 
203
329
  /**
@@ -336,10 +462,10 @@ class DateRange {
336
462
  * @param {DateRange} targetDateRange - dateRange to compare
337
463
  * @returns {Boolean}
338
464
  * @example
339
- * overlapA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
340
- * overlapB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
341
- * overlapC = new Date(Date.UTC(2024, 12, 26, 14, 0, 0));
342
- * overlapD = new Date(Date.UTC(2024, 12, 26, 15, 0, 0));
465
+ * overlapA = new Date(Date.UTC(2024, 11, 26, 12, 0, 0));
466
+ * overlapB = new Date(Date.UTC(2024, 11, 26, 13, 0, 0));
467
+ * overlapC = new Date(Date.UTC(2024, 11, 26, 14, 0, 0));
468
+ * overlapD = new Date(Date.UTC(2024, 11, 26, 15, 0, 0));
343
469
  *
344
470
  * rangeBefore = new utils.DateRange(overlapA, overlapB);
345
471
  * rangeAfter = new utils.DateRange(overlapC, overlapD);
@@ -365,10 +491,10 @@ class DateRange {
365
491
  * @returns {Boolean} - if the value is within the range (true) or not (false)
366
492
  *
367
493
  * @example
368
- * withinA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
369
- * withinB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
370
- * withinC = new Date(Date.UTC(2024, 12, 26, 14, 0, 0));
371
- * withinD = new Date(Date.UTC(2024, 12, 26, 15, 0, 0));
494
+ * withinA = new Date(Date.UTC(2024, 11, 26, 12, 0, 0));
495
+ * withinB = new Date(Date.UTC(2024, 11, 26, 13, 0, 0));
496
+ * withinC = new Date(Date.UTC(2024, 11, 26, 14, 0, 0));
497
+ * withinD = new Date(Date.UTC(2024, 11, 26, 15, 0, 0));
372
498
  *
373
499
  * range = new utils.DateRange(withinB, withinD);
374
500
  * range.contains(withinA); // false - it was before the range
@@ -387,8 +513,8 @@ class DateRange {
387
513
  * Determines the millisecond duration between the end and start time.
388
514
  *
389
515
  * ```
390
- * durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
391
- * durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
516
+ * durationA = new Date(Date.UTC(2024, 11, 26, 12, 0, 0));
517
+ * durationB = new Date(Date.UTC(2024, 11, 26, 13, 0, 0));
392
518
  * range = new utils.DateRange(durationA, durationB);
393
519
  *
394
520
  * range.durationString(); // 1 hour in milliseconds; 1000 * 60 * 60;
@@ -404,8 +530,8 @@ class DateRange {
404
530
  * Determines the duration in a clear and understandable string;
405
531
  *
406
532
  * ```
407
- * durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
408
- * durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
533
+ * durationA = new Date(Date.UTC(2024, 11, 26, 12, 0, 0));
534
+ * durationB = new Date(Date.UTC(2024, 11, 26, 13, 0, 0));
409
535
  * range = new utils.DateRange(durationA, durationB);
410
536
  *
411
537
  * range.durationString(); // '0 days, 1 hours, 0 minutes, 0.0 seconds';
@@ -415,25 +541,15 @@ class DateRange {
415
541
  */
416
542
  durationString() {
417
543
  const dur = this.duration();
418
- const divideRemainder = (val, denominator) => ({ value: Math.floor(val / denominator), remainder: val % denominator });
419
- let result = divideRemainder(dur, DateUtils.TIME.DAY);
420
- const days = result.value;
421
- result = divideRemainder(result.remainder, DateUtils.TIME.HOUR);
422
- const hours = result.value;
423
- result = divideRemainder(result.remainder, DateUtils.TIME.MINUTE);
424
- const minutes = result.value;
425
- result = divideRemainder(result.remainder, DateUtils.TIME.SECOND);
426
- const seconds = result.value;
427
- const milli = result.remainder;
428
- return `${days} days, ${hours} hours, ${minutes} minutes, ${seconds}.${milli} seconds`;
544
+ return DateUtils.durationLong(dur);
429
545
  }
430
546
 
431
547
  /**
432
548
  * Determines the duration in days:hours:minutes:seconds.milliseconds
433
549
  *
434
550
  * ```
435
- * durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
436
- * durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
551
+ * durationA = new Date(Date.UTC(2024, 11, 26, 12, 0, 0));
552
+ * durationB = new Date(Date.UTC(2024, 11, 26, 13, 0, 0));
437
553
  * range = new utils.DateRange(durationA, durationB);
438
554
  *
439
555
  * range.durationString(); // '0:01:00:00.0000';
@@ -443,17 +559,7 @@ class DateRange {
443
559
  */
444
560
  durationISO() {
445
561
  const dur = this.duration();
446
- const divideRemainder = (val, denominator) => ({ value: Math.floor(val / denominator), remainder: val % denominator });
447
- let result = divideRemainder(dur, DateUtils.TIME.DAY);
448
- const days = String(result.value);
449
- result = divideRemainder(result.remainder, DateUtils.TIME.HOUR);
450
- const hours = String(result.value).padStart(2, '0');
451
- result = divideRemainder(result.remainder, DateUtils.TIME.MINUTE);
452
- const minutes = String(result.value).padStart(2, '0');
453
- result = divideRemainder(result.remainder, DateUtils.TIME.SECOND);
454
- const seconds = String(result.value).padStart(2, '0');
455
- const milli = String(result.remainder).padStart(4, '0');
456
- return `${days}:${hours}:${minutes}:${seconds}.${milli}`;
562
+ return DateUtils.durationISO(dur);
457
563
  }
458
564
 
459
565
  /**
@@ -469,8 +575,8 @@ class DateRange {
469
575
  * Converts the daterange to a string value
470
576
  *
471
577
  * ```
472
- * durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
473
- * durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
578
+ * durationA = new Date(Date.UTC(2024, 11, 26, 12, 0, 0));
579
+ * durationB = new Date(Date.UTC(2024, 11, 26, 13, 0, 0));
474
580
  * range = new utils.DateRange(durationA, durationB);
475
581
  *
476
582
  * range.toString(); // '2025-01-26T12:00:00.000Z to 2025-01-26T13:00:00.000Z';
@@ -486,8 +592,8 @@ class DateRange {
486
592
  * Converts the daterange to a local string value
487
593
  *
488
594
  * ```
489
- * durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
490
- * durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
595
+ * durationA = new Date(Date.UTC(2024, 11, 26, 12, 0, 0));
596
+ * durationB = new Date(Date.UTC(2024, 11, 26, 13, 0, 0));
491
597
  * range = new utils.DateRange(durationA, durationB);
492
598
  *
493
599
  * range.toLocaleString(); // '1/26/2025, 12:00:00 PM to 1/26/2025, 1:00:00 PM'