dce-reactkit 4.2.4-beta-timestamp.2 → 4.2.4-beta.heat-seek.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.
@@ -7,7 +7,7 @@
7
7
  * @param opts.month Month (1-12)
8
8
  * @param opts.day Day of the month (1-31)
9
9
  * @param opts.hour Hour (0-23)
10
- * @param opts.minute Minute (0-59))
10
+ * @param opts.minute Minute (0-59)
11
11
  * @returns Timestamp in milliseconds since epoch
12
12
  */
13
13
  declare const getTimestampFromTimeInfoInET: (opts: {
package/dist/index.d.ts CHANGED
@@ -1574,7 +1574,7 @@ declare const cloneDeep: <T>(obj: T) => T;
1574
1574
  * @param opts.month Month (1-12)
1575
1575
  * @param opts.day Day of the month (1-31)
1576
1576
  * @param opts.hour Hour (0-23)
1577
- * @param opts.minute Minute (0-59))
1577
+ * @param opts.minute Minute (0-59)
1578
1578
  * @returns Timestamp in milliseconds since epoch
1579
1579
  */
1580
1580
  declare const getTimestampFromTimeInfoInET: (opts: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "4.2.4-beta-timestamp.2",
3
+ "version": "4.2.4-beta.heat-seek.1",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -6,6 +6,78 @@ import ReactKitErrorCode from '../types/ReactKitErrorCode';
6
6
  import getTimeInfoInET from './getTimeInfoInET';
7
7
  import padZerosLeft from './padZerosLeft';
8
8
 
9
+ /*----------------------------------------*/
10
+ /* --------------- Helpers -------------- */
11
+ /*----------------------------------------*/
12
+
13
+ /**
14
+ * Check if a timestamp is valid
15
+ * @param opts object containing all arguments
16
+ * @param opts.timestamp Timestamp in milliseconds since epoch
17
+ * @param opts.expectedYear Expected year
18
+ * @param opts.expectedMonth Expected month
19
+ * @param opts.expectedDay Expected day
20
+ * @param opts.expectedHour Expected hour
21
+ * @param opts.expectedMinute Expected minute
22
+ * @returns 1 if the timestamp needs to be increased, -1 if it needs to be decreased, 0 if it is valid
23
+ */
24
+ const checkTimestamp = (
25
+ opts: {
26
+ timestamp: number,
27
+ expectedYear: number,
28
+ expectedMonth: number,
29
+ expectedDay: number,
30
+ expectedHour: number,
31
+ expectedMinute: number,
32
+ },
33
+ ): number => {
34
+ const {
35
+ timestamp,
36
+ expectedYear,
37
+ expectedMonth,
38
+ expectedDay,
39
+ expectedHour,
40
+ expectedMinute,
41
+ } = opts;
42
+
43
+ const timeInfoInET = getTimeInfoInET(timestamp);
44
+ if (timeInfoInET.year < expectedYear) {
45
+ return 1;
46
+ }
47
+ if (timeInfoInET.year > expectedYear) {
48
+ return -1;
49
+ }
50
+ if (timeInfoInET.month < expectedMonth) {
51
+ return 1;
52
+ }
53
+ if (timeInfoInET.month > expectedMonth) {
54
+ return -1;
55
+ }
56
+ if (timeInfoInET.day < expectedDay) {
57
+ return 1;
58
+ }
59
+ if (timeInfoInET.day > expectedDay) {
60
+ return -1;
61
+ }
62
+ if (timeInfoInET.hour < expectedHour) {
63
+ return 1;
64
+ }
65
+ if (timeInfoInET.hour > expectedHour) {
66
+ return -1;
67
+ }
68
+ if (timeInfoInET.minute < expectedMinute) {
69
+ return 1;
70
+ }
71
+ if (timeInfoInET.minute > expectedMinute) {
72
+ return -1;
73
+ }
74
+ return 0;
75
+ };
76
+
77
+ /*----------------------------------------*/
78
+ /* ---------------- Main ---------------- */
79
+ /*----------------------------------------*/
80
+
9
81
  /**
10
82
  * Get a timestamp (ms since epoch) from time info (year, month, day, hour, minute, etc.) in Eastern Time (ET)
11
83
  * @author Gardenia Liu
@@ -15,7 +87,7 @@ import padZerosLeft from './padZerosLeft';
15
87
  * @param opts.month Month (1-12)
16
88
  * @param opts.day Day of the month (1-31)
17
89
  * @param opts.hour Hour (0-23)
18
- * @param opts.minute Minute (0-59))
90
+ * @param opts.minute Minute (0-59)
19
91
  * @returns Timestamp in milliseconds since epoch
20
92
  */
21
93
  const getTimestampFromTimeInfoInET = (
@@ -36,87 +108,69 @@ const getTimestampFromTimeInfoInET = (
36
108
  minute,
37
109
  } = opts;
38
110
 
111
+ // Determine if the date is in DST in Eastern Time
112
+ const tempDate = new Date(year, month - 1, day);
113
+ const janOffset = new Date(year, 0, 1).getTimezoneOffset();
114
+ const isDST = tempDate.getTimezoneOffset() < janOffset;
115
+ const etOffset = isDST ? '-04:00' : '-05:00';
116
+
39
117
  // Format with leading zeroes
40
118
  const mm = padZerosLeft(month, 2);
41
119
  const dd = padZerosLeft(day, 2);
42
120
  const hh = padZerosLeft(hour, 2);
43
121
  const min = padZerosLeft(minute, 2);
44
122
 
45
- // Use Intl.DateTimeFormat to get the ET offset and ET components
46
- const dateForET = new Date(Date.UTC(year, month - 1, day, hour, minute, 0));
47
- const options: Intl.DateTimeFormatOptions = {
48
- timeZone: 'America/New_York',
49
- year: 'numeric',
50
- month: '2-digit',
51
- day: '2-digit',
52
- hour: '2-digit',
53
- minute: '2-digit',
54
- second: '2-digit',
55
- hour12: false,
56
- timeZoneName: 'shortOffset',
57
- };
58
- const parts: Intl.DateTimeFormatPart[] = new Intl.DateTimeFormat('en-US', options).formatToParts(dateForET);
59
- const etYear: string | undefined = parts.find((p: Intl.DateTimeFormatPart) => {
60
- return p.type === 'year';
61
- })?.value;
62
- const etMonth: string | undefined = parts.find((p: Intl.DateTimeFormatPart) => {
63
- return p.type === 'month';
64
- })?.value;
65
- const etDay: string | undefined = parts.find((p: Intl.DateTimeFormatPart) => {
66
- return p.type === 'day';
67
- })?.value;
68
- const etHour: string | undefined = parts.find((p: Intl.DateTimeFormatPart) => {
69
- return p.type === 'hour';
70
- })?.value;
71
- const etMinute: string | undefined = parts.find((p: Intl.DateTimeFormatPart) => {
72
- return p.type === 'minute';
73
- })?.value;
74
- const etSecond: string | undefined = parts.find((p: Intl.DateTimeFormatPart) => {
75
- return p.type === 'second';
76
- })?.value;
77
- const tzName: string | undefined = parts.find((p: Intl.DateTimeFormatPart) => {
78
- return p.type === 'timeZoneName';
79
- })?.value;
80
-
81
- // Validate we got everything we need
82
- if (!etYear || !etMonth || !etDay || !etHour || !etMinute || !etSecond || !tzName) {
83
- throw new ErrorWithCode(
84
- 'Failed to parse date parts from Intl.DateTimeFormat',
85
- ReactKitErrorCode.ETTimestampInvalid,
86
- );
123
+ // Build ET ISO string and convert to UTC timestamp
124
+ const etISOString = `${year}-${mm}-${dd}T${hh}:${min}:00${etOffset}`;
125
+ let timestamp = (new Date(etISOString)).getTime();
126
+
127
+ // Heat seek to get the right timestamp
128
+ const maxOffset = 24 * 60; // 24 hours in minutes
129
+ let currentOffset = 0; // minutes
130
+ const offsetIncrement = 15; // minutes
131
+ const offsetDirection = checkTimestamp({
132
+ timestamp,
133
+ expectedYear: year,
134
+ expectedMonth: month,
135
+ expectedDay: day,
136
+ expectedHour: hour,
137
+ expectedMinute: minute,
138
+ });
139
+ if (offsetDirection === 0) {
140
+ // Valid! Return the timestamp
141
+ return timestamp;
87
142
  }
88
143
 
89
- // Extract numeric offset from "GMT-4" or "UTC-5"
90
- const offsetNum = Number(tzName.replace(/^(GMT|UTC)/, ''));
91
- const sign = offsetNum >= 0 ? '+' : '-';
92
- const offset = `${sign}${padZerosLeft(Math.abs(offsetNum), 2)}:00`;
144
+ // Heat seek
145
+ while (Math.abs(currentOffset) < maxOffset) {
146
+ // Update offset
147
+ currentOffset += (offsetDirection * offsetIncrement);
93
148
 
94
- // Build ISO string with offset
95
- const etDateString = `${etYear}-${etMonth}-${etDay}T${etHour}:${etMinute}:${etSecond}${offset}`;
149
+ // Update timestamp
150
+ const offsetMs = currentOffset * 60 * 1000;
151
+ timestamp += offsetMs;
96
152
 
97
- // Parse into ET timestamp
98
- const etDate = new Date(etDateString);
99
- const timestamp = etDate.getTime();
153
+ // Check timestamp again
154
+ const newDirection = checkTimestamp({
155
+ timestamp,
156
+ expectedYear: year,
157
+ expectedMonth: month,
158
+ expectedDay: day,
159
+ expectedHour: hour,
160
+ expectedMinute: minute,
161
+ });
162
+ if (newDirection === 0) {
163
+ // Valid! Return the timestamp
164
+ return timestamp;
165
+ }
100
166
 
101
- // Verify that the timestamp is correct
102
- const timeInfoInET = getTimeInfoInET(timestamp);
103
- if (
104
- timeInfoInET.year !== year
105
- || timeInfoInET.month !== month
106
- || timeInfoInET.day !== day
107
- || timeInfoInET.hour !== hour
108
- || timeInfoInET.minute !== minute
109
- ) {
110
- throw new ErrorWithCode(
111
- `Timestamp mismatch: expected ${year}-${mm}-${dd} ${hh}:${min}, `
112
- + `got ${timeInfoInET.year}-${padZerosLeft(timeInfoInET.month, 2)}-${padZerosLeft(timeInfoInET.day, 2)} `
113
- + `${padZerosLeft(timeInfoInET.hour, 2)}:${padZerosLeft(timeInfoInET.minute, 2)}`,
114
- ReactKitErrorCode.ETTimestampInvalid,
115
- );
167
+ // Invalid. Keep looping.
116
168
  }
117
169
 
118
- // Valid! Return the timestamp
119
- return timestamp;
170
+ throw new ErrorWithCode(
171
+ `Timestamp mismatch: expected ${year}-${mm}-${dd} ${hh}:${min}, seeked to offset ${currentOffset} minutes but could not find a valid timestamp.`,
172
+ ReactKitErrorCode.ETTimestampInvalid,
173
+ );
120
174
  };
121
175
 
122
176
  export default getTimestampFromTimeInfoInET;