ohlcv-indicators 3.3.2 → 3.3.4

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.
@@ -1,11 +1,10 @@
1
1
  import { FasterRSI } from 'trading-signals';
2
2
  import { FasterSMA } from 'trading-signals';
3
- import { calcMagnitude } from '../utilities/numberUtilities.js';
4
3
 
5
- const defaultTarget = 'close';
4
+ const defaultTarget = 'close'
5
+ export const rsi = (main, index, size, { target, lag }) => {
6
+ const { verticalOhlcv, instances } = main;
6
7
 
7
- export const rsi = (main, index, size, { scale, target, lag, autoMinMax }) => {
8
- const { verticalOhlcv, instances, lastIndexReplace } = main;
9
8
  const suffix = target === defaultTarget ? '' : `_${target}`;
10
9
  const rsiKey = `rsi_${size}${suffix}`;
11
10
  const rsiSmaKey = `rsi_sma_${size}${suffix}`;
@@ -13,7 +12,7 @@ export const rsi = (main, index, size, { scale, target, lag, autoMinMax }) => {
13
12
  // Initialization on the first index.
14
13
  if (index === 0) {
15
14
 
16
- const { crossPairsList, nullArray } = main;
15
+ const { crossPairsList, len, arrayTypes } = main;
17
16
 
18
17
  if (!verticalOhlcv.hasOwnProperty(target)) {
19
18
  throw new Error(`Target property ${target} not found in verticalOhlcv for rsi.`);
@@ -27,55 +26,52 @@ export const rsi = (main, index, size, { scale, target, lag, autoMinMax }) => {
27
26
  });
28
27
 
29
28
  Object.assign(verticalOhlcv, {
30
- [rsiKey]: [...nullArray],
31
- [rsiSmaKey]: [...nullArray],
29
+ [rsiKey]: new Float64Array(len).fill(NaN),
30
+ [rsiSmaKey]: new Float64Array(len).fill(NaN),
32
31
  });
33
32
 
33
+
34
+
35
+ const keyNames = [rsiKey, rsiSmaKey]
36
+
34
37
  if(lag > 0)
35
38
  {
36
- main.lag([rsiKey, rsiSmaKey], lag)
39
+ main.lag(keyNames, lag)
37
40
  }
38
41
 
39
- if(autoMinMax)
42
+ for(const key of keyNames)
40
43
  {
41
- main.autoMinMaxKeys.push(rsiKey, rsiSmaKey)
44
+ arrayTypes[key] = 'Float64Array'
42
45
  }
43
46
 
44
47
  }
45
48
 
46
49
  const value = verticalOhlcv[target][index];
47
- let currentRsi = null;
48
- let smoothedRsi = null;
50
+ let currentRsi = NaN;
51
+ let smoothedRsi = NaN;
49
52
 
50
53
  // Update the RSI indicator.
51
- instances[rsiKey].update(value, lastIndexReplace);
54
+ instances[rsiKey].update(value);
52
55
 
53
56
  try {
54
57
  currentRsi = instances[rsiKey].getResult();
55
58
  } catch (err) {
56
- currentRsi = null;
59
+ currentRsi = NaN;
57
60
  }
58
61
 
59
- if (currentRsi !== null && typeof currentRsi === 'number' && scale) {
60
- currentRsi = calcMagnitude(currentRsi, scale);
61
- }
62
62
 
63
- // Always push the RSI value, using null as a fallback.
63
+ // Always push the RSI value, using NaN as a fallback.
64
64
  main.pushToMain({ index, key: rsiKey, value: currentRsi });
65
65
 
66
66
  // Update the SMA indicator only if a valid RSI value is available.
67
- if (currentRsi !== null && typeof currentRsi === 'number') {
68
- instances[rsiSmaKey].update(currentRsi, lastIndexReplace);
67
+ if (!Number.isNaN(currentRsi)) {
68
+ instances[rsiSmaKey].update(currentRsi);
69
69
  }
70
70
 
71
71
  try {
72
72
  smoothedRsi = instances[rsiSmaKey].getResult();
73
73
  } catch (err) {
74
- smoothedRsi = null;
75
- }
76
-
77
- if (smoothedRsi !== null && typeof smoothedRsi === 'number' && scale) {
78
- smoothedRsi = calcMagnitude(smoothedRsi, scale);
74
+ smoothedRsi = NaN;
79
75
  }
80
76
 
81
77
  // Always push the smoothed RSI value.
@@ -1,15 +1,14 @@
1
1
  import { FasterEMA } from 'trading-signals';
2
- import { calcMagnitude } from '../utilities/numberUtilities.js';
3
2
 
4
- export const volumeOscillator = (main, index, fast, slow, {scale}) => {
3
+ export const volumeOscillator = (main, index, fast, slow, {lag}) => {
5
4
 
6
- const {verticalOhlcv, instances, lastIndexReplace} = main
5
+ const {verticalOhlcv, instances} = main
7
6
  const value = verticalOhlcv.volume[index]
8
7
  const key = 'volume_oscillator'
9
8
 
10
9
  if (index === 0) {
11
10
 
12
- const {crossPairsList, nullArray} = main
11
+ const {crossPairsList, len, arrayTypes} = main
13
12
  Object.assign(instances, {
14
13
  volume_oscillator: {
15
14
  fastEMA: new FasterEMA(fast),
@@ -17,40 +16,42 @@ export const volumeOscillator = (main, index, fast, slow, {scale}) => {
17
16
  }
18
17
  })
19
18
 
20
- verticalOhlcv[key] = [...nullArray];
21
- crossPairsList.push({ fast: key, slow: 0, isDefault: true });
19
+ verticalOhlcv[key] = new Float64Array(len).fill(NaN)
20
+ crossPairsList.push({ fast: key, slow: 0, isDefault: true })
21
+
22
+ if(lag > 0)
23
+ {
24
+ main.lag([key], lag)
25
+ }
26
+
27
+ arrayTypes[key] = 'Float64Array'
22
28
  }
23
29
 
24
30
  const { fastEMA, slowEMA } = instances[key];
25
31
 
26
- fastEMA.update(value, lastIndexReplace);
27
- slowEMA.update(value, lastIndexReplace);
32
+ fastEMA.update(value);
33
+ slowEMA.update(value);
28
34
 
29
- let fastValue = null;
30
- let slowValue = null;
35
+ let fastValue = NaN;
36
+ let slowValue = NaN;
31
37
 
32
38
  try {
33
39
  fastValue = fastEMA.getResult();
34
40
  } catch (err) {
35
- fastValue = null;
41
+
36
42
  }
37
43
 
38
44
  try {
39
45
  slowValue = slowEMA.getResult();
40
46
  } catch (err) {
41
- slowValue = null;
47
+
42
48
  }
43
49
 
44
- let volumeOscValue = null
50
+ let volumeOscValue = NaN
45
51
 
46
- if(typeof fastValue === 'number' && typeof slowValue === 'number' && slowValue !== 0)
52
+ if(!Number.isNaN(fastValue) && !Number.isNaN(slowValue))
47
53
  {
48
54
  volumeOscValue = 100 * (fastValue - slowValue) / slowValue
49
-
50
- if(scale)
51
- {
52
- volumeOscValue = calcMagnitude(volumeOscValue, scale)
53
- }
54
55
  }
55
56
 
56
57
  main.pushToMain({index, key, value: volumeOscValue})
@@ -1,36 +1,39 @@
1
- export const dateTime = (main, index) => {
1
+ export const dateTime = (main, index, {lag}) => {
2
2
 
3
3
  const {instances, verticalOhlcv} = main
4
4
 
5
5
  if(index === 0)
6
6
  {
7
- const {nullArray, isValidDate} = main
8
- if(!isValidDate) throw Error('dateTime method found and invalid "date" in input ohlcv')
9
-
10
- const index0DateStr = verticalOhlcv.date[0].slice(0, 10)
7
+ const {len, dateType, arrayTypes} = main
8
+ if(!dateType) throw Error('dateTime method found and invalid "date" in input ohlcv')
11
9
 
12
10
  Object.assign(instances, {
13
11
  dateTime: {
14
- prevDateStr: index0DateStr,
12
+ prevDateStr: verticalOhlcv.date[0],
15
13
  sessionDailyIndexCount: 0,
16
14
  sessionIntradayIndexCount: 0,
17
- cachedDayInfo: index0DateStr
15
+ cachedDayInfo: verticalOhlcv.date[0]
18
16
  }
19
17
  })
20
18
 
21
- Object.assign(verticalOhlcv, {
22
- day_of_the_week: [...nullArray],
23
- day_of_the_month: [...nullArray],
24
- week_of_the_month: [...nullArray],
25
- hour: [...nullArray],
26
- month: [...nullArray],
27
- year: [...nullArray],
28
- session_daily_index: [...nullArray],
29
- session_intraday_index: [...nullArray],
30
- })
19
+ const colKeys = ['day_of_the_week', 'day_of_the_month', 'week_of_the_month', 'hour', 'month', 'year', 'session_daily_index', 'session_intraday_index']
20
+
21
+ for(const key of colKeys)
22
+ {
23
+ arrayTypes[key] = 'Int32Array'
24
+ }
25
+
26
+ Object.assign(verticalOhlcv, {...Object.fromEntries(colKeys.map(k => [k, new Int32Array(len).fill(NaN)]))})
27
+
28
+ if(lag > 0)
29
+ {
30
+ main.lag(colKeys, lag)
31
+ }
31
32
  }
32
33
 
34
+
33
35
  const currDate = verticalOhlcv.date[index]
36
+
34
37
  const {
35
38
  day_of_the_week,
36
39
  day_of_the_month,
@@ -41,7 +44,7 @@ export const dateTime = (main, index) => {
41
44
  } = getDateInfo(currDate)
42
45
 
43
46
 
44
- const currDateStr = currDate.slice(0, 10)
47
+ const currDateStr = currDate
45
48
 
46
49
  if(currDateStr !== instances.dateTime.prevDateStr)
47
50
  {
@@ -68,8 +71,7 @@ export const dateTime = (main, index) => {
68
71
 
69
72
 
70
73
 
71
- const getDateInfo = dateString => {
72
- const date = new Date(dateString)
74
+ const getDateInfo = date => {
73
75
  const month = date.getMonth()
74
76
  const year = date.getFullYear()
75
77
  const hour = date.getHours()
@@ -15,12 +15,11 @@ class CrossInstance {
15
15
  Object.assign(this, {
16
16
  interval: 0,
17
17
  prevState: 0.5,
18
- prevFast: null,
19
- prevSlow: null,
20
- prevHigh: null,
21
- prevLow: null,
18
+ prevFast: NaN,
19
+ prevSlow: NaN,
20
+ prevHigh: NaN,
21
+ prevLow: NaN,
22
22
  areHighAndLowUndefined: false,
23
- index: 0,
24
23
  crossIndexes: {
25
24
  up: [],
26
25
  down: []
@@ -28,7 +27,7 @@ class CrossInstance {
28
27
  })
29
28
 
30
29
  }
31
- update({fast, high, slow, low})
30
+ update(index, {fast, high, slow, low})
32
31
  {
33
32
  let currState = this.prevState
34
33
 
@@ -40,7 +39,7 @@ class CrossInstance {
40
39
  }
41
40
  }
42
41
 
43
- if(fast === null || slow === null){
42
+ if(Number.isNaN(fast) || Number.isNaN(slow)){
44
43
  currState = 0.5
45
44
  }
46
45
  else if(eq(fast, slow)) {
@@ -54,7 +53,7 @@ class CrossInstance {
54
53
  }
55
54
  else
56
55
  {
57
- if ([low, slow, this.prevLow, this.prevSlow].every(v => v !== null) && lt(low, slow) && gt(this.prevLow, this.prevSlow))
56
+ if ([low, slow, this.prevLow, this.prevSlow].every(v => !Number.isNaN(v)) && lt(low, slow) && gt(this.prevLow, this.prevSlow))
58
57
  {
59
58
  currState = 0
60
59
  }
@@ -72,7 +71,7 @@ class CrossInstance {
72
71
  }
73
72
  else
74
73
  {
75
- if([high, slow, this.prevHigh, this.prevSlow].every(v => v !== null) && gt(high, slow) && lt(this.prevHigh, this.prevSlow))
74
+ if([high, slow, this.prevHigh, this.prevSlow].every(v => !Number.isNaN(v)) && gt(high, slow) && lt(this.prevHigh, this.prevSlow))
76
75
  {
77
76
  currState = 1
78
77
  }
@@ -111,12 +110,11 @@ class CrossInstance {
111
110
  }
112
111
 
113
112
 
114
- if(this.interval === 1) this.crossIndexes.up.push(this.index)
115
- if(this.interval === -1) this.crossIndexes.down.push(this.index)
113
+ if(this.interval === 1) this.crossIndexes.up.push(index)
114
+ if(this.interval === -1) this.crossIndexes.down.push(index)
116
115
 
117
116
  //save prev state
118
117
  Object.assign(this, {
119
- index: this.index + 1,
120
118
  prevState: currState,
121
119
  prevFast: fast,
122
120
  prevSlow: slow,
@@ -134,7 +132,7 @@ class CrossInstance {
134
132
 
135
133
  export const crossPairs = (main, index) => {
136
134
 
137
- const {nullArray, verticalOhlcv, instances, len} = main
135
+ const {verticalOhlcv, instances, len, arrayTypes} = main
138
136
 
139
137
  if(index === 0)
140
138
  {
@@ -158,7 +156,7 @@ export const crossPairs = (main, index) => {
158
156
  {
159
157
  if(typeof slow === 'number')
160
158
  {
161
- verticalOhlcv[slow] = new Array(len).fill(slow)
159
+ verticalOhlcv[slow] = new Int32Array(len).fill(slow)
162
160
  }
163
161
 
164
162
  if(fast !== 'price' && !verticalOhlcv.hasOwnProperty(fast)) throw Error(`fast "${fast} not found in crossPairs"`)
@@ -167,7 +165,9 @@ export const crossPairs = (main, index) => {
167
165
 
168
166
  instances[crossName] = new CrossInstance()
169
167
 
170
- verticalOhlcv[crossName] = [...nullArray]
168
+ verticalOhlcv[crossName] = new Int32Array(len).fill(NaN)
169
+
170
+ arrayTypes[crossName] = 'Int32Array'
171
171
  }
172
172
 
173
173
  let fastValue
@@ -183,14 +183,14 @@ export const crossPairs = (main, index) => {
183
183
  lowValue = verticalOhlcv.low[index]
184
184
  slowValue = verticalOhlcv[slow][index]
185
185
 
186
- instances[crossName].update({fast: closeValue, slow: slowValue, high: highValue, low: lowValue})
186
+ instances[crossName].update(index, {fast: closeValue, slow: slowValue, high: highValue, low: lowValue})
187
187
 
188
188
  } else
189
189
  {
190
190
  fastValue = verticalOhlcv[fast][index]
191
191
  slowValue = verticalOhlcv[slow][index]
192
192
 
193
- instances[crossName].update({fast: fastValue, slow: slowValue})
193
+ instances[crossName].update(index, {fast: fastValue, slow: slowValue})
194
194
  }
195
195
 
196
196
  main.pushToMain({index, key: crossName, value: instances[crossName].getResult()})
@@ -1,24 +1,26 @@
1
+ import { buildArray } from "../utilities/assignTypes.js";
2
+
1
3
  export const lag = (main, index) => {
2
4
 
3
- const {instances, verticalOhlcv, nullArray} = main
5
+ const {instances, verticalOhlcv, len, arrayTypes, priceBased} = main
4
6
 
5
7
  if(index === 0)
6
8
  {
7
- const {inputParams, priceBased} = main
9
+ const {inputParams} = main
8
10
  const findParams = inputParams.filter(o => o.key === 'lag')
9
11
 
10
12
  if(typeof findParams !== 'object') return
11
13
 
12
14
  const params = findParams.map(o => o.params)
13
15
 
14
- for (const [colKeys, lags] of params)
16
+ for (const [colKeys, lookback] of params)
15
17
  {
16
18
  for (const colKey of colKeys)
17
19
  {
18
20
  if(priceBased.includes(colKey))
19
21
  {
20
- for (let lag = 1; lag <= lags; lag++) {
21
- priceBased.push(`${colKey}_lag_${lag}`)
22
+ for (let step = 1; step <= lookback; step++) {
23
+ priceBased.push(`${colKey}_lag_${step}`)
22
24
  }
23
25
  }
24
26
  }
@@ -31,23 +33,30 @@ export const lag = (main, index) => {
31
33
 
32
34
  const {lagParams} = instances.lag
33
35
 
34
- for (const [colKeys, lags] of lagParams) {
36
+ for (const [colKeys, lookback] of lagParams) {
35
37
 
36
38
  for (const colKey of colKeys) {
37
39
  const currentColumn = verticalOhlcv[colKey];
38
40
 
39
41
  // Initialize lagged arrays only on the first index
40
42
  if (index === 0) {
41
- for (let lag = 1; lag <= lags; lag++) {
42
- const key = `${colKey}_lag_${lag}`;
43
- verticalOhlcv[key] = [...nullArray];
43
+ for (let step = 1; step <= lookback; step++) {
44
+ const key = `${colKey}_lag_${step}`;
45
+
46
+ if(!arrayTypes.hasOwnProperty(colKey))
47
+ {
48
+ throw new Error(`Lag processing invoked by "${colKey}" expected arrayTypes to have a "${colKey}" property, but it wasn’t found.`)
49
+ }
50
+
51
+ verticalOhlcv[key] = buildArray(arrayTypes[colKey], len)
52
+ arrayTypes[key] = arrayTypes[colKey]
44
53
  }
45
54
  }
46
55
 
47
56
  // Populate lagged values
48
- for (let lag = 1; lag <= lags; lag++) {
49
- const key = `${colKey}_lag_${lag}`;
50
- const laggedIndex = index - lag;
57
+ for (let step = 1; step <= lookback; step++) {
58
+ const key = `${colKey}_lag_${step}`;
59
+ const laggedIndex = index - step;
51
60
 
52
61
  const value = (laggedIndex <= 0 || typeof currentColumn[laggedIndex] === 'undefined')
53
62
  ? null
@@ -0,0 +1,112 @@
1
+ /* General Scaler: supports "minmax" and "zscore" types */
2
+
3
+ const normalizeMinMax = (value, min, max, [validMin, validMax]) => {
4
+ const clamped = Math.min(Math.max(value, min), max);
5
+ return ((clamped - min) / (max - min)) * (validMax - validMin) + validMin;
6
+ };
7
+
8
+ const normalizeZScore = (value, mean, std) => {
9
+ return std === 0 ? 0 : (value - mean) / std;
10
+ };
11
+
12
+ export const Scaler = (
13
+ main,
14
+ index,
15
+ size,
16
+ colKeys,
17
+ type,
18
+ group,
19
+ range, // used for minmax: [validMin, validMax]
20
+ lag,
21
+ ) => {
22
+ const { verticalOhlcv, instances, arrayTypes } = main;
23
+ const prefix = `${type}_${size}`;
24
+ let groupKey = '';
25
+
26
+ if (index === 0) {
27
+ const { len, priceBased, groups } = main;
28
+ groupKey = `${prefix}_group_${colKeys.join('_')}`;
29
+
30
+ instances.scaler = {
31
+ groupKeyLen: colKeys.length,
32
+ groupKey,
33
+ windows: {} // stores arrays of past values for each key or group
34
+ };
35
+
36
+ for (const target of colKeys) {
37
+ if (!verticalOhlcv.hasOwnProperty(target)) {
38
+ throw new Error(`Target property "${target}" not found in verticalOhlcv`);
39
+ }
40
+ if (!priceBased.includes(target)) {
41
+ throw new Error(`Column "${target}" is not priceBased and cannot be scaled.`);
42
+ }
43
+
44
+ const key = `${prefix}_${target}`;
45
+ verticalOhlcv[key] = new Float64Array(len).fill(NaN);
46
+ arrayTypes[key] = 'Float64Array'
47
+
48
+ const winKey = group ? groupKey : target;
49
+ instances.scaler.windows[winKey] = [];
50
+
51
+ if (group) {
52
+ if (!main.ScaledGroups[groupKey]) main.ScaledGroups[groupKey] = [];
53
+ main.ScaledGroups[groupKey].push(key);
54
+ }
55
+
56
+ if (lag > 0) {
57
+ if (group) {
58
+ const lags = Array.from({ length: lag }).map((_, i) => `${key}_lag_${i + 1}`);
59
+ main.ScaledGroups[groupKey].push(...lags);
60
+ }
61
+ main.lag([key], lag);
62
+ }
63
+ }
64
+ }
65
+
66
+ const { windows } = instances.scaler;
67
+ groupKey = instances.scaler.groupKey;
68
+
69
+ // update windows with current values
70
+ for (const target of colKeys) {
71
+ const val = verticalOhlcv[target][index];
72
+ const winKey = group ? groupKey : target;
73
+ const win = windows[winKey];
74
+
75
+ win.push(val);
76
+
77
+ if (win.length > (group ? size * instances.scaler.groupKeyLen : size)) {
78
+ win.shift();
79
+ }
80
+
81
+ }
82
+
83
+ const ready = index + 1 >= size;
84
+
85
+ // scale values once enough data
86
+ for (const target of colKeys) {
87
+ const val = verticalOhlcv[target][index];
88
+ const key = `${prefix}_${target}`;
89
+ let scaled = null;
90
+
91
+ if (ready) {
92
+ const winKey = group ? groupKey : target;
93
+ const win = windows[winKey];
94
+
95
+ if (type === 'minmax') {
96
+ const mn = Math.min(...win);
97
+ const mx = Math.max(...win);
98
+ scaled = normalizeMinMax(val, mn, mx, range);
99
+ } else if (type === 'zscore') {
100
+ const mean = win.reduce((sum, x) => sum + x, 0) / win.length;
101
+ const variance = win.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / win.length;
102
+ const std = Math.sqrt(variance);
103
+ scaled = normalizeZScore(val, mean, std);
104
+ } else {
105
+ throw new Error(`Unknown scaler type "${type}"`);
106
+ }
107
+ }
108
+
109
+ main.pushToMain({ index, key, value: scaled });
110
+ }
111
+ };
112
+
@@ -0,0 +1,39 @@
1
+ import { classifyNum } from "./numberUtilities.js";
2
+ import { selectDateFormatter } from "./dateUtilities.js";
3
+
4
+ const numberKeys = ['open', 'high', 'low', 'close']
5
+
6
+ export const buildArray = (arrayType, len) => {
7
+ if(arrayType === 'Array') return new Array(len).fill(null)
8
+ if(arrayType === 'Float64Array') return new Float64Array(len).fill(NaN)
9
+ if(arrayType === 'Int32Array') return new Int32Array(len).fill(NaN)
10
+ }
11
+
12
+ export const assignTypes = firstRow => {
13
+ const inputTypes = {}
14
+ const arrayTypes = {}
15
+
16
+ for (const [colName, cellValue] of Object.entries(firstRow)) {
17
+
18
+ if (colName === 'date') {
19
+ inputTypes[colName] = selectDateFormatter(cellValue, true)
20
+ arrayTypes[colName] = 'Array'
21
+ }
22
+ else if(colName === 'volume')
23
+ {
24
+ inputTypes[colName] = classifyNum(cellValue, true)
25
+ arrayTypes[colName] = 'Int32Array'
26
+ }
27
+ else if(numberKeys.includes(colName))
28
+ {
29
+ inputTypes[colName] = classifyNum(cellValue, true)
30
+ arrayTypes[colName] = 'Float64Array'
31
+ } else {
32
+ const thisType = classifyNum(cellValue, false)
33
+ inputTypes[colName] = thisType
34
+ arrayTypes[colName] = (thisType === 'number') ? 'Float64Array' : 'Array'
35
+ }
36
+ }
37
+
38
+ return { inputTypes, arrayTypes }
39
+ }
@@ -0,0 +1,79 @@
1
+ export const dateFormaters = {
2
+ dateObject: d => d,
3
+ dateMilliseconds: d => new Date(d),
4
+ dateSeconds: d => new Date(d * 1000),
5
+ dateIncompleteDayString: d => new Date(`${d} 00:00:00`),
6
+ dateCompleteDateString: d => new Date(d)
7
+ }
8
+
9
+ export const selectDateFormatter = value => {
10
+
11
+ // Regex for a UTC Time Format where the string ends with "Z".
12
+ const utcRegex = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/i;
13
+
14
+ // Regex for a Timezone Offset Format,
15
+ // which includes a plus or minus sign followed by HH:MM at the end.
16
+ const offsetRegex = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?([+-]\d{2}:\d{2})$/;
17
+
18
+ // Regex for a Local Time Format that contains no timezone indicator.
19
+ const localRegex = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
20
+
21
+ // Regex for a YYYY-MM-DD Format.
22
+ const YYMMDDRegexDash = /^\d{4}-\d{2}-\d{2}$/;
23
+ const YYMMDDRegexSlash = /^\d{4}\/\d{2}\/\d{2}$/;
24
+
25
+ const validFormatNames = [
26
+ 'Date -> object',
27
+ 'Milliseconds Timestamp -> integer',
28
+ 'Seconds Timestamp -> integer',
29
+ 'UTC Time Format (ISO 8601) -> string',
30
+ 'Including a Timezone Offset (ISO 8601) -> string',
31
+ 'Local Time Format (ISO 8601) -> string',
32
+ 'YYYY/MM/DD or YYYY-MM-DD -> string',
33
+ ]
34
+
35
+ const errorMessage = () => {
36
+ throw new Error(`Invalid "${typeof value}" date value passed in selectDateFormatter. Supported formats: \n-${validFormatNames.join("\n- ")}`)
37
+ }
38
+
39
+ if(typeof value === 'object')
40
+ {
41
+ if(value instanceof Date && !isNaN(value.valueOf()))
42
+ {
43
+ return 'dateObject'
44
+ }
45
+ else
46
+ {
47
+ errorMessage()
48
+ }
49
+ }
50
+ else if (typeof value === 'number') {
51
+ // Use a numeric threshold based on the absolute value: typically,
52
+ // if the absolute value is below 1e11, then it's a seconds timestamp.
53
+ // Otherwise, it's a milliseconds timestamp.
54
+ if (Math.abs(value) < 1e11) {
55
+ return 'dateSeconds';
56
+ } else {
57
+ return 'dateMillseconds';
58
+ }
59
+ }
60
+ else if(typeof value === 'string')
61
+ {
62
+ if(utcRegex.test(value) || offsetRegex.test(value) || (localRegex.test(value) && value.length > 10) || YYMMDDRegexSlash.test(value))
63
+ {
64
+ return 'dateCompleteDateString'
65
+ }
66
+ else if(YYMMDDRegexDash.test(value))
67
+ {
68
+ return 'dateIncompleteDayString'
69
+ }
70
+ else
71
+ {
72
+ errorMessage()
73
+ }
74
+ }
75
+ else
76
+ {
77
+ errorMessage()
78
+ }
79
+ }