jupyter-ijavascript-utils 1.55.0 → 1.56.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/DOCS.md CHANGED
@@ -74,6 +74,7 @@ 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.56 - #84 (object.renamePropertiesFromList), #82 (date.getWeekday)
77
78
  * 1.55 - #76, #77, #74, #78
78
79
  * 1.54 - additional Date logic, and formatting. #70 #71 #72
79
80
  * 1.53 - additional docs and examples for {@link module:color|color/colour} package.
package/README.md CHANGED
@@ -54,6 +54,7 @@ This is not intended to be the only way to accomplish many of these tasks, and a
54
54
  ![Screenshot of example notebook](docResources/img/mainExampleNotebook.png)
55
55
 
56
56
  # What's New
57
+ * 1.56 - #84 (object.renamePropertiesFromList), #82 (date.getWeekday)
57
58
  * 1.55 - #76, #77, #74, #78
58
59
  * 1.54 - additional Date logic, and formatting. #70 #71 #72
59
60
  * 1.53 - additional docs and examples for color/colour package.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.55.0",
3
+ "version": "1.56.0",
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
@@ -8,6 +8,7 @@
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
10
  * * {@link module:date.toLocalISO|date.toLocalISO} - prints in 8601 format with timezone offset based on a tz entry - like america/chicago
11
+ * * {@link module:date.localISOFormatter|date.localISOFormatter} - prints in 8601 format - slightly improved performance for large scale use
11
12
  * * {@link module:date.getTimezoneOffset|date.getTimezoneOffset(String)} - gets the number of milliseconds offset for a given timezone
12
13
  * * {@link module:date.correctForTimezone|date.correctForTimezone(Date, String)} - meant to correct a date already off from UTC to the correct time
13
14
  * * {@link module:date.epochShift|date.epochShift(Date, String)} - offsets a date from UTC to a given time amount
@@ -180,12 +181,22 @@ module.exports.durationLong = function durationLong(epochDifference) {
180
181
  return `${signStr}${days} days, ${hours} hours, ${minutes} minutes, ${seconds}.${milli} seconds`;
181
182
  };
182
183
 
184
+ /**
185
+ * Function that is passed a date for formatting
186
+ *
187
+ * @callback dateFormatter
188
+ * @param {Date} dateToFormat - the date to format
189
+ */
190
+
183
191
  /**
184
192
  * @typedef {Object} TimezoneEntry
185
193
  * @property {String} tz - the name of the timezone
186
194
  * @property {Function} formatter - formats a date to that local timezone
187
195
  * @property {Number} epoch - the difference in milliseconds from that tz to UTC
188
196
  * @property {String} offset - ISO format for how many hours and minutes offset to UTC '+|-' HH:MMM
197
+ * @property {dateFormatter} toLocalISO - formatter function that formats a date to local ISO
198
+ * @property {dateFormatter} toLocalISOWeekday - formatter function that formats a date to local ISO + weekday
199
+ * @property {dateFormatter} getWeekday - formatter function that determines the day of week for a date
189
200
  */
190
201
 
191
202
  /**
@@ -220,6 +231,12 @@ module.exports.getTimezoneEntry = function getTimezoneEntry(timezoneStr) {
220
231
  timeZone: cleanTz
221
232
  });
222
233
 
234
+ const dayOfWeekFormat = new Intl.DateTimeFormat('en-us', {
235
+ weekday: 'short',
236
+ timeZone: cleanTz
237
+ });
238
+ const getWeekday = (date) => dayOfWeekFormat.format(date);
239
+
223
240
  const getOffset = (dateValue) => {
224
241
  const dm = dtFormat.formatToParts(dateValue)
225
242
  .filter(({ type }) => type !== 'literal')
@@ -258,7 +275,10 @@ module.exports.getTimezoneEntry = function getTimezoneEntry(timezoneStr) {
258
275
  const diffMinutes = remainder.value;
259
276
  const offset = `${diffSign}${DateUtils.padTime(diffHours)}:${DateUtils.padTime(diffMinutes)}`;
260
277
 
261
- const result = ({ tz: cleanTz, formatter, epoch: diff, offset });
278
+ const toLocalISO = (date) => `${formatter(date)}${offset}`;
279
+ const toLocalISOWeekday = (date) => `${formatter(date)}${offset} - ${getWeekday(date)}`;
280
+
281
+ const result = ({ tz: cleanTz, formatter, toLocalISO, toLocalISOWeekday, getWeekday, epoch: diff, offset });
262
282
 
263
283
  DateUtils.timezoneOffsetMap.set(cleanTz, result);
264
284
 
@@ -381,27 +401,14 @@ module.exports.correctForOtherTimezone = function correctForTimezones(date, sour
381
401
  *
382
402
  * Use this if you somehow have a date that needs to be shifted by the timezone offset.
383
403
  *
384
- * This is rarely useful in itself, but in combination with the {@link module:date.correctForTimezone|date.correctForTimezone}
385
- * you can use this to correct for "local dates" but are in another timezone than you are in.
386
- *
387
- * (For example, you got a local date for 2:15 PM EST, but your current computer is in CST)
404
+ * For example, if you have a time that is already in GMT, and want the date shifted by a timezone.
388
405
  *
389
- * JavaScript dates only have three ways of importing dates:
390
- *
391
- * * Parse the date assuming local timezone
392
- * * Parse the date using [ISO 8601 formats](https://www.iso.org/iso-8601-date-and-time-format.html)
393
- * * This option DOES provide an option for providing a timezone offset (ex: `-0500`)
394
- *
395
- * Since this is the opposite of {@link module:date.correctForTimezone|date.correctForTimezone}, this can be useful.
396
- *
397
- * But most likely, you'd like to use either:
398
- *
399
- * * {@link module:date.correctForTimezone|date.correctForTimezone} or
400
- * * {@link module:date.correctForTimezones|date.correctForTimezones}.
406
+ * This is used internally for {@link module:date.correctForOtherTimezone|date.correctForOtherTimezone}
407
+ * if local dates are provided - but for a different timezone you yourself are not in.
401
408
  *
402
409
  * ---
403
410
  *
404
- * Epoch shift a date, so the utcDate is no longer correct,
411
+ * Epoch shift changes the internals of a JavaScript date, so the utcDate is no longer correct,
405
412
  * but many other functions behave closer to expected.
406
413
  *
407
414
  * Once you epoch shift the date, then time stored in the date is incorrect (because it always points to GMT)
@@ -410,20 +417,8 @@ module.exports.correctForOtherTimezone = function correctForTimezones(date, sour
410
417
  *
411
418
  * See {@link https://stackoverflow.com/a/15171030|here why this might not be what you want}
412
419
  *
413
- * --
414
- *
415
- * Sometimes though, some libraries use the "getMonth()", "getDate()" of the date, and do not support using timezones.
416
- *
417
- * That is when this shines.
418
- *
419
- * ```
420
- * timeStamp = 1738437341000;
421
- * d = new Date(timeStamp);
422
- * d.toIsoString(); // 2025-02-01T19:15:41.000Z
423
- * `current time is: ${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}` // 'current time is: 2025-2-1'
424
- *
425
- *
426
- * ```
420
+ * * {@link module:date.correctForTimezone|date.correctForTimezone} or
421
+ * * {@link module:date.correctForTimezones|date.correctForTimezones}.
427
422
  *
428
423
  * @param {Date} date - date to shift
429
424
  * @param {String} timezoneStr - the tz database name of the timezone
@@ -456,13 +451,66 @@ module.exports.epochShift = function epochShift(date, timezoneStr) {
456
451
  * utils.date.toLocalISO(d, 'europe/paris'); // '2024-12-27T14:30:00.000+01:00'
457
452
  * ```
458
453
  *
454
+ * Sometimes it is helpful to have the weekday to make sense of things
455
+ *
456
+ * ```
457
+ * utils.date.toLocalISO(d, 'america/Chicago', true); // '2024-12-27T07:30:00.000-06:00 FRI'
458
+ * utils.date.toLocalISO(d, 'europe/paris', true); // '2024-12-27T14:30:00.000+01:00 FRI'
459
+ * ```
460
+ *
459
461
  * @param {Date} date - date to print
460
462
  * @param {String} timezoneStr - the tz database name of the timezone
463
+ * @param {Boolean} [includeWeekday=false] - whether to include the weekday
464
+ * @see {@link module:date.localISOFormatter|date.localISOFormatter} - if you're converting to string frequently
461
465
  * @returns {String} - ISO format with timezone offset
462
466
  */
463
- module.exports.toLocalISO = function toLocalISO(date, timezoneStr) {
464
- const { formatter, offset } = DateUtils.getTimezoneEntry(timezoneStr);
465
- return `${formatter(date)}${offset}`;
467
+ module.exports.toLocalISO = function toLocalISO(date, timezoneStr, includeWeekday = false) {
468
+ if (includeWeekday) {
469
+ return DateUtils.getTimezoneEntry(timezoneStr).toLocalISOWeekday(date);
470
+ }
471
+ return DateUtils.getTimezoneEntry(timezoneStr).toLocalISO(date);
472
+ };
473
+
474
+ /**
475
+ * If repeatedly asking for a local time, use this method instead.
476
+ *
477
+ * ```
478
+ * myDate = new Date('2025-01-15T06:00:00.000Z');
479
+ * centralFormatter = utils.date.localISOFormatter('us/central');
480
+ * centralFormatter(myDate); // '2025-01-15T00:00:00.000Z'
481
+ * ```
482
+ *
483
+ * as opposed to
484
+ *
485
+ * ```
486
+ * myDate = new Date('2025-01-15T06:00:00.000Z');
487
+ * utils.date.toLocalISO(myDate, 'us/central'); // '2025-01-15T00:00:00.000Z'
488
+ * ```
489
+ *
490
+ * @param {String} timezoneStr
491
+ * @returns {dateFormatter} - (date) => {String} 'yyyy-mm-ddThh:mm:ss.MMM[+-]TZOFFSET'
492
+ * @see {@link module:date.toLocalISO|date.toLocalISO}
493
+ */
494
+ module.exports.localISOFormatter = function localISOFormatter(timezoneStr, includeWeekday = false) {
495
+ if (includeWeekday) {
496
+ return DateUtils.getTimezoneEntry(timezoneStr).toLocalISOWeekday;
497
+ }
498
+ return DateUtils.getTimezoneEntry(timezoneStr).toLocalISO;
499
+ };
500
+
501
+ /**
502
+ * Determines the weekday of a date
503
+ * @param {Date} date - date to print
504
+ * @param {String} timezoneStr - the tz database name of the timezone
505
+ * @returns {String} - Currently returns `en-us` formatted day of week of a date
506
+ * @see {@link module:date.toLocalISO|date.toLocalISO}
507
+ * @example
508
+ * date = new Date('2025-01-15T06:00:00.000Z');
509
+ * utils.date.getWeekday(date, 'us/pacific'); // Tue
510
+ * utils.date.getWeekday(date, 'us/eastern'); // Wed
511
+ */
512
+ module.exports.getWeekday = function weekdayFormatter(date, timezoneStr) {
513
+ return DateUtils.getTimezoneEntry(timezoneStr).getWeekday(date);
466
514
  };
467
515
 
468
516
  module.exports.toIsoStringNoTimezone = function toIsoStringNoTimezone(date) {
@@ -771,12 +819,19 @@ class DateRange {
771
819
  */
772
820
  endDate;
773
821
 
822
+ /**
823
+ * Data attached to the DateTime
824
+ * @type {any}
825
+ */
826
+ data;
827
+
774
828
  /**
775
829
  * @param {Date|String} startDate - the starting date
776
830
  * @param {Date|String} endDate - the ending date
831
+ * @param {any} [data] - any data to store
777
832
  */
778
- constructor(startDate, endDate) {
779
- this.reinitialize(startDate, endDate);
833
+ constructor(startDate, endDate, data = null) {
834
+ this.reinitialize(startDate, endDate, data);
780
835
  }
781
836
 
782
837
  /**
@@ -794,6 +849,31 @@ class DateRange {
794
849
  * // {start: 2025-03-01TT00:00:00, end: 2025-04-01TT00:00:00}]
795
850
  * ```
796
851
  *
852
+ * Often though, we want to remember something about the DateRange,
853
+ * like which dates that it collected.
854
+ *
855
+ * ```
856
+ * arrayGenerator = function() { return [] };
857
+ * rangeList = utils.DateRange.fromList(dates, arrayGenerator);
858
+ * // [{start: 2025-01-01T00:00:00, end: 2025-02-01TT00:00:00},
859
+ * // {start: 2025-02-01TT00:00:00, end: 2025-03-01TT00:00:00},
860
+ * // {start: 2025-03-01TT00:00:00, end: 2025-04-01TT00:00:00}]
861
+ *
862
+ * dates.forEach((date) => rangeList
863
+ * .find(rl => rl.contains(date))
864
+ * .data.push(date)
865
+ * );
866
+ *
867
+ * rangeList
868
+ * .map(rl => `${rl.toString()}: has ${rl.data.length}`)
869
+ * .join('\n');
870
+ *
871
+ * // 2025-01-01T00:00:00.000Z to 2025-02-01T00:00:00.000Z: has 2
872
+ * // 2025-02-01T00:00:00.000Z to 2025-03-01T00:00:00.000Z: has 1
873
+ * // 2025-03-01T00:00:00.000Z to 2025-04-01T00:00:00.000Z: has 1
874
+ *
875
+ * ```
876
+ *
797
877
  * (Note: you can also use {@link module:date.arrange|date.arrange} or
798
878
  * {@link module:date.generateDateSequence|date.generateDateSequence}
799
879
  * to come up with the list of those dates)
@@ -802,18 +882,27 @@ class DateRange {
802
882
  * the simplest is to remove the dates from the resulting list.)
803
883
  *
804
884
  * @param {Date[]} dateList - list of dates
885
+ * @param {Function} [dataCreationFn] - optional generator for data to be stored in each DataRange in the sequence
805
886
  * @returns {DateRange[]} - list of dateList.length-1 dateRanges,
806
887
  * where the end of the firstRange is the start of the next.
807
888
  * @see {@link module:date.arrange|date.arrange} - to create dates by adding a value multiple times
808
889
  * @see {@link module:date.generateDateSequence|date.generateDateSequence} - to create dates between a start and an end date
809
890
  */
810
- static fromList(dateSequence) {
891
+ static fromList(dateSequence, dataCreationFn) {
811
892
  if (dateSequence.length < 2) return [];
812
893
 
813
894
  const results = new Array(dateSequence.length - 2);
814
- for (let i = 0; i < dateSequence.length - 1; i += 1) {
815
- results[i] = new DateRange(dateSequence[i], dateSequence[i + 1]);
895
+
896
+ if (dataCreationFn) {
897
+ for (let i = 0; i < dateSequence.length - 1; i += 1) {
898
+ results[i] = new DateRange(dateSequence[i], dateSequence[i + 1], dataCreationFn());
899
+ }
900
+ } else {
901
+ for (let i = 0; i < dateSequence.length - 1; i += 1) {
902
+ results[i] = new DateRange(dateSequence[i], dateSequence[i + 1]);
903
+ }
816
904
  }
905
+
817
906
  return results;
818
907
  }
819
908
 
@@ -824,8 +913,9 @@ class DateRange {
824
913
  *
825
914
  * @param {Date|String} startDate - the starting date
826
915
  * @param {Date|String} endDate - the ending date
916
+ * @param {any} [data] - any data to store
827
917
  */
828
- reinitialize(startDate, endDate) {
918
+ reinitialize(startDate, endDate, data = null) {
829
919
  const cleanStart = startDate instanceof Date
830
920
  ? startDate
831
921
  : new Date(Date.parse(startDate));
@@ -840,6 +930,8 @@ class DateRange {
840
930
  this.startDate = cleanStart;
841
931
  this.endDate = cleanEnd;
842
932
  }
933
+
934
+ this.data = data;
843
935
  }
844
936
 
845
937
  /**
package/src/object.js CHANGED
@@ -413,9 +413,32 @@ module.exports.cleanPropertyName = function cleanPropertyName(property) {
413
413
  return cleanProperty;
414
414
  };
415
415
 
416
- const renameObjectProperties = function renameObjectProperties(object, originalKeys, targetKeys) {
416
+ /**
417
+ * Renames properties on an object with a list of original keys and new keys.
418
+ *
419
+ * For example:
420
+ *
421
+ * ```
422
+ * myData = [{ _time: '...', 'series001': 1, 'series002': 2 }];
423
+ *
424
+ * originalKeys = utils.object.keys(myData);
425
+ * // ['series001', 'series002'];
426
+ *
427
+ * myMap = new Map([['series001': 'Alpha'], ['series002', 'Bravo']]);
428
+ * newKeys = utils.format.replaceStrings(originalKeys, myMap);
429
+ * // ['Alpha', 'Bravo'];
430
+ *
431
+ * utils.object.renamePropertiesFromList(myData, originalKeys, newKeys);
432
+ * // [{ _time: '...', 'Alpha': 1, 'Bravo': 2 }];
433
+ *
434
+ * @param {Object[]} objects - objects to reassign - likely from a CSV
435
+ * @param {String[]} originalKeys - list of keys to change FROM
436
+ * @param {String[]} updatedKeys - list of keys to change TO
437
+ * @returns {Object[]}
438
+ */
439
+ module.exports.renamePropertiesFromList = function renamePropertiesFromList(object, originalKeys, targetKeys) {
417
440
  const result = { ...object };
418
- originalKeys.forEach((originalKey, index) => {
441
+ Array.from(originalKeys).forEach((originalKey, index) => {
419
442
  const targetKey = targetKeys[index];
420
443
  if (targetKey !== originalKey) {
421
444
  result[targetKey] = result[originalKey];
@@ -440,10 +463,10 @@ module.exports.renameProperties = function renameProperties(objects, propertyTra
440
463
 
441
464
  if (Array.isArray(objects)) {
442
465
  return objects.map(
443
- (object) => renameObjectProperties(object, originalKeys, targetKeys)
466
+ (object) => ObjectUtils.renamePropertiesFromList(object, originalKeys, targetKeys)
444
467
  );
445
468
  }
446
- return renameObjectProperties(objects, originalKeys, targetKeys);
469
+ return ObjectUtils.renamePropertiesFromList(objects, originalKeys, targetKeys);
447
470
  };
448
471
 
449
472
  const collapseSpecificObject = function collapseSpecificObject(sourceObj, targetObj, depth) {