@aemforms/af-formatters 0.22.26 → 0.22.29

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,34 +1,26 @@
1
1
  /*************************************************************************
2
- * ADOBE CONFIDENTIAL
3
- * ___________________
4
- *
5
- * Copyright 2022 Adobe
6
- * All Rights Reserved.
7
- *
8
- * NOTICE: All information contained herein is, and remains
9
- * the property of Adobe and its suppliers, if any. The intellectual
10
- * and technical concepts contained herein are proprietary to Adobe
11
- * and its suppliers and are protected by all applicable intellectual
12
- * property laws, including trade secret and copyright laws.
13
- * Dissemination of this information or reproduction of this material
14
- * is strictly forbidden unless prior written permission is obtained
15
- * from Adobe.
16
- **************************************************************************/
17
- /**
18
- * Credit: https://git.corp.adobe.com/dc/dfl/blob/master/src/patterns/dates.js
19
- */
20
- // Test Japanese full/half width character support
2
+ * ADOBE CONFIDENTIAL
3
+ * ___________________
4
+ *
5
+ * Copyright 2022 Adobe
6
+ * All Rights Reserved.
7
+ *
8
+ * NOTICE: All information contained herein is, and remains
9
+ * the property of Adobe and its suppliers, if any. The intellectual
10
+ * and technical concepts contained herein are proprietary to Adobe
11
+ * and its suppliers and are protected by all applicable intellectual
12
+ * property laws, including trade secret and copyright laws.
13
+ * Dissemination of this information or reproduction of this material
14
+ * is strictly forbidden unless prior written permission is obtained
15
+ * from Adobe.
16
+
17
+ * Adobe permits you to use and modify this file solely in accordance with
18
+ * the terms of the Adobe license agreement accompanying it.
19
+ *************************************************************************/
20
+
21
+ import { ShorthandStyles, parseDateTimeSkeleton } from './SkeletonParser.js';
21
22
 
22
- import {parseDateTimeSkeleton, ShorthandStyles} from './SkeletonParser.js';
23
-
24
- // get the localized month names resulting from a given pattern
25
23
  const twelveMonths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(m => new Date(2000, m, 1));
26
-
27
- /**
28
- * returns the name of all the months for a given locale and given Date Format Settings
29
- * @param locale {string}
30
- * @param options {string} instance of Intl.DateTimeFormatOptions
31
- */
32
24
  function monthNames(locale, options) {
33
25
  return twelveMonths.map(month => {
34
26
  const parts = new Intl.DateTimeFormat(locale, options).formatToParts(month);
@@ -36,32 +28,17 @@ function monthNames(locale, options) {
36
28
  return m && m.value;
37
29
  });
38
30
  }
39
-
40
- /**
41
- * return an array of digits used by a given locale
42
- * @param locale {string}
43
- */
44
31
  function digitChars(locale) {
45
32
  return new Intl.NumberFormat(locale, {style:'decimal', useGrouping:false})
46
33
  .format(9876543210)
47
34
  .split('')
48
35
  .reverse();
49
36
  }
50
-
51
- /**
52
- * returns the calendar name used in a given locale
53
- * @param locale {string}
54
- */
55
37
  function calendarName(locale) {
56
38
  const parts = new Intl.DateTimeFormat(locale, {era:'short'}).formatToParts(new Date());
57
39
  const era = parts.find(p => p.type === 'era')?.value;
58
40
  return era === 'هـ' ? 'islamic' : 'gregory';
59
41
  }
60
-
61
- /**
62
- * returns the representation of the time of day for a given language
63
- * @param language {string}
64
- */
65
42
  function getDayPeriod(language) {
66
43
  const morning = new Date(2000, 1, 1, 1, 1, 1);
67
44
  const afternoon = new Date(2000, 1, 1, 16, 1, 1);
@@ -74,13 +51,7 @@ function getDayPeriod(language) {
74
51
  fn: (period, obj) => obj.hour += (period === pm.value) ? 12 : 0
75
52
  };
76
53
  }
77
-
78
- /**
79
- * get the offset in MS, given a date and timezone
80
- * @param dateObj {Date}
81
- * @param timeZone {string}
82
- */
83
- export function offsetMS(dateObj, timeZone) {
54
+ function offsetMS(dateObj, timeZone) {
84
55
  let tzOffset;
85
56
  try {
86
57
  tzOffset = new Intl.DateTimeFormat('en-US', {timeZone, timeZoneName: 'longOffset'}).format(dateObj);
@@ -89,13 +60,12 @@ export function offsetMS(dateObj, timeZone) {
89
60
  }
90
61
  const offset = /GMT([+\-−])?(\d{1,2}):?(\d{0,2})?/.exec(tzOffset);
91
62
  if (!offset) return 0;
92
- const [sign, hours, minutes] = offset.slice(1)
93
- const nHours = isNaN(parseInt(hours)) ? 0 : parseInt(hours)
94
- const nMinutes = isNaN(parseInt(minutes)) ? 0 : parseInt(minutes)
63
+ const [sign, hours, minutes] = offset.slice(1);
64
+ const nHours = isNaN(parseInt(hours)) ? 0 : parseInt(hours);
65
+ const nMinutes = isNaN(parseInt(minutes)) ? 0 : parseInt(minutes);
95
66
  const result = ((nHours * 60) + nMinutes) * 60 * 1000;
96
67
  return sign === '-' ? - result : result;
97
68
  }
98
-
99
69
  function getTimezoneOffsetFrom(otherTimezone) {
100
70
  var date = new Date();
101
71
  function objFromStr(str) {
@@ -110,65 +80,29 @@ function getTimezoneOffsetFrom(otherTimezone) {
110
80
  var other = objFromStr(str);
111
81
  str = date.toLocaleString('en-US', { day: 'numeric', hour: 'numeric', minute: 'numeric', hourCycle: 'h23' });
112
82
  var myLocale = objFromStr(str);
113
- var otherOffset = (other.day * 24 * 60) + (other.hour * 60) + (other.minute); // utc date + otherTimezoneDifference
114
- var myLocaleOffset = (myLocale.day * 24 * 60) + (myLocale.hour * 60) + (myLocale.minute); // utc date + myTimeZoneDifference
115
- // (utc date + otherZoneDifference) - (utc date + myZoneDifference) - (-1 * myTimeZoneDifference)
83
+ var otherOffset = (other.day * 24 * 60) + (other.hour * 60) + (other.minute);
84
+ var myLocaleOffset = (myLocale.day * 24 * 60) + (myLocale.hour * 60) + (myLocale.minute);
116
85
  return otherOffset - myLocaleOffset - date.getTimezoneOffset();
117
86
  }
118
-
119
- export function offsetMSFallback(dateObj, timezone) {
120
- //const defaultOffset = dateObj.getTimezoneOffset();
121
- const timezoneOffset = getTimezoneOffsetFrom(timezone)
87
+ function offsetMSFallback(dateObj, timezone) {
88
+ const timezoneOffset = getTimezoneOffsetFrom(timezone);
122
89
  return timezoneOffset * 60 * 1000;
123
90
  }
124
-
125
- /**
126
- * adjust from the default JavaScript timezone to the default timezone
127
- * @param dateObj {Date}
128
- * @param timeZone {string}
129
- */
130
- export function adjustTimeZone(dateObj, timeZone) {
91
+ function adjustTimeZone(dateObj, timeZone) {
131
92
  if (dateObj === null) return null;
132
- // const defaultOffset = new Intl.DateTimeFormat('en-US', { timeZoneName: 'longOffset'}).format(dateObj);
133
93
  let baseDate = dateObj.getTime() - dateObj.getTimezoneOffset() * 60 * 1000;
134
94
  const offset = offsetMS(dateObj, timeZone);
135
- const fallBackOffset = offsetMSFallback(dateObj, timeZone);
95
+ offsetMSFallback(dateObj, timeZone);
136
96
  baseDate += - offset;
137
-
138
-
139
- // get the offset for the default JS environment
140
- // return days since the epoch
141
97
  return new Date(baseDate);
142
98
  }
143
-
144
- /**
145
- * Our script object model treats dates as numbers where the integer portion is days since the epoch,
146
- * the fractional portion is the number hours in the day
147
- * @param dateObj {Date}
148
- * @returns {number}
149
- */
150
- export function datetimeToNumber(dateObj) {
99
+ function datetimeToNumber(dateObj) {
151
100
  if (dateObj === null) return 0;
152
- // return days since the epoch
153
101
  return dateObj.getTime() / ( 1000 * 60 * 60 * 24 );
154
102
  }
155
-
156
- /**
157
- * Our script object model treats dates as numbers where the integer portion is days since the epoch,
158
- * the fractional portion is the number hours in the day
159
- * @param num
160
- * @returns {Date}
161
- */
162
- export function numberToDatetime(num) {
103
+ function numberToDatetime(num) {
163
104
  return new Date(Math.round(num * 1000 * 60 * 60 * 24));
164
105
  }
165
-
166
- /**
167
- * in some cases, DateTimeFormat doesn't respect the 'numeric' vs. '2-digit' setting
168
- * for time values. The function corrects that
169
- * @param formattedParts instance of Intl.DateTimeFormatPart[]
170
- * @param parsed
171
- */
172
106
  function fixDigits(formattedParts, parsed) {
173
107
  ['hour', 'minute', 'second'].forEach(type => {
174
108
  const defn = formattedParts.find(f => f.type === type);
@@ -178,55 +112,29 @@ function fixDigits(formattedParts, parsed) {
178
112
  if (fmt === 'numeric' && defn.value.length === 2 && defn.value.charAt(0) === '0') defn.value = defn.value.slice(1);
179
113
  });
180
114
  }
181
-
182
115
  function fixYear(formattedParts, parsed) {
183
- // two digit years are handled differently in DateTimeFormat. 00 becomes 1900
184
- // providing a two digit year 0010 gets formatted to 10 and when parsed becomes 1910
185
- // Hence we need to pad the year with 0 as required by the skeleton and mentioned in
186
- // unicode. https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-year
187
116
  const defn = formattedParts.find(f => f.type === 'year');
188
117
  if (!defn) return;
189
- // eslint-disable-next-line no-unused-vars
190
118
  const chars = parsed.find(pair => pair[0] === 'year')[2];
191
119
  while(defn.value.length < chars) {
192
120
  defn.value = `0${defn.value}`;
193
121
  }
194
122
  }
195
-
196
- /**
197
- *
198
- * @param dateValue {Date}
199
- * @param language {string}
200
- * @param skeleton {string}
201
- * @param timeZone {string}
202
- * @returns {T}
203
- */
204
123
  function formatDateToParts(dateValue, language, skeleton, timeZone) {
205
- // DateTimeFormat renames some of the options in its formatted output
206
- //@ts-ignore
207
124
  const mappings = key => ({
208
125
  hour12: 'dayPeriod',
209
126
  fractionalSecondDigits: 'fractionalSecond',
210
127
  })[key] || key;
211
-
212
- // produces an array of name/value pairs of skeleton parts
213
128
  const allParameters = parseDateTimeSkeleton(skeleton, language);
214
129
  allParameters.push(['timeZone', timeZone]);
215
-
216
130
  const parsed = allParameters.filter(p => !p[0].startsWith('x-'));
217
131
  const nonStandard = allParameters.filter(p => p[0].startsWith('x-'));
218
- // reduce to a set of options that can be used to format
219
132
  const options = Object.fromEntries(parsed);
220
133
  delete options.literal;
221
-
222
134
  const df = new Intl.DateTimeFormat(language, options);
223
- // formattedParts will have all the pieces we need for our date -- but not in the correct order
224
135
  const formattedParts = df.formatToParts(dateValue);
225
-
226
136
  fixDigits(formattedParts, allParameters);
227
137
  fixYear(formattedParts, parsed);
228
- // iterate through the original parsed components and use its ordering and literals,
229
- // and add the formatted pieces
230
138
  return parsed.reduce((result, cur) => {
231
139
  if (cur[0] === 'literal') result.push(cur);
232
140
  else {
@@ -236,37 +144,25 @@ function formatDateToParts(dateValue, language, skeleton, timeZone) {
236
144
  const category = tz[0];
237
145
  if (category === 'Z') {
238
146
  if (tz.length < 4) {
239
- // handle 'Z', 'ZZ', 'ZZZ' Time Zone: ISO8601 basic hms? / RFC 822
240
147
  v.value = v.value.replace(/(GMT|:)/g, '');
241
148
  if (v.value === '') v.value = '+0000';
242
149
  } else if (tz.length === 5) {
243
- // 'ZZZZZ' Time Zone: ISO8601 extended hms?
244
150
  if (v.value === 'GMT') v.value = 'Z';
245
151
  else v.value = v.value.replace(/GMT/, '');
246
152
  }
247
153
  }
248
154
  if (category === 'X' || category === 'x') {
249
155
  if (tz.length === 1) {
250
- // 'X' ISO8601 basic hm?, with Z for 0
251
- // -08, +0530, Z
252
- // 'x' ISO8601 basic hm?, without Z for 0
253
156
  v.value = v.value.replace(/(GMT|:(00)?)/g, '');
254
157
  }
255
158
  if (tz.length === 2) {
256
- // 'XX' ISO8601 basic hm, with Z
257
- // -0800, Z
258
- // 'xx' ISO8601 basic hm, without Z
259
159
  v.value = v.value.replace(/(GMT|:)/g, '');
260
160
  }
261
161
  if (tz.length === 3) {
262
- // 'XXX' ISO8601 extended hm, with Z
263
- // -08:00, Z
264
- // 'xxx' ISO8601 extended hm, without Z
265
162
  v.value = v.value.replace(/GMT/g, '');
266
163
  }
267
164
  if (category === 'X' && v.value === '') v.value = 'Z';
268
165
  } else if (tz === 'O') {
269
- // eliminate 'GMT', leading and trailing zeros
270
166
  v.value = v.value.replace(/GMT/g, '').replace(/0(\d+):/, '$1:').replace(/:00/, '');
271
167
  if (v.value === '') v.value = '+0';
272
168
  }
@@ -276,41 +172,28 @@ function formatDateToParts(dateValue, language, skeleton, timeZone) {
276
172
  return result;
277
173
  }, []);
278
174
  }
279
-
280
- /**
281
- *
282
- * @param dateValue {Date}
283
- * @param language {string}
284
- * @param skeleton {string}
285
- * @param timeZone {string}
286
- */
287
- export function formatDate(dateValue, language, skeleton, timeZone) {
175
+ function formatDate(dateValue, language, skeleton, timeZone) {
176
+ if (skeleton.startsWith('date|')) {
177
+ skeleton = skeleton.split('|')[1];
178
+ }
288
179
  if (ShorthandStyles.find(type => skeleton.includes(type))) {
289
180
  const options = {timeZone};
290
- // the skeleton could have two keywords -- one for date, one for time
291
181
  const parts = skeleton.split(/\s/).filter(s => s.length);
292
182
  if (ShorthandStyles.indexOf(parts[0]) > -1) {
293
- options.dateStyle = parts[0]
183
+ options.dateStyle = parts[0];
294
184
  }
295
185
  if (parts.length > 1 && ShorthandStyles.indexOf(parts[1]) > -1) {
296
- options.timeStyle = parts[1]
186
+ options.timeStyle = parts[1];
297
187
  }
298
188
  return new Intl.DateTimeFormat(language, options).format(dateValue);
299
189
  }
300
190
  const parts = formatDateToParts(dateValue, language, skeleton, timeZone);
301
191
  return parts.map(p => p[1]).join('');
302
192
  }
303
-
304
- /**
305
- *
306
- * @param dateString {string}
307
- * @param language {string}
308
- * @param skeleton {string}
309
- * @param timeZone {string}
310
- */
311
- export function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
312
- // start by getting all the localized parts of a date/time picture:
313
- // digits, calendar name
193
+ function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
194
+ if (skeleton.startsWith('date|')) {
195
+ skeleton = skeleton.split('|')[1];
196
+ }
314
197
  const lookups = [];
315
198
  const regexParts = [];
316
199
  const calendar = calendarName(language);
@@ -321,13 +204,11 @@ export function parseDate(dateString, language, skeleton, timeZone, bUseUTC = fa
321
204
  let hourCycle = 'h12';
322
205
  let _bUseUTC = bUseUTC;
323
206
  let _setFullYear = false;
324
- // functions to process the results of the regex match
325
207
  const isSeparator = str => str.length === 1 && ':-/.'.includes(str);
326
208
  const monthNumber = str => getNumber(str) - 1;
327
209
  const getNumber = str => str.split('').reduce((total, digit) => (total * 10) + digits.indexOf(digit), 0);
328
210
  const yearNumber = templateDigits => str => {
329
211
  let year = getNumber(str);
330
- //todo: align with AF
331
212
  year = year < 100 && templateDigits === 2 ? year + 2000 : year;
332
213
  if (calendar === 'islamic') year = Math.ceil(year * 0.97 + 622);
333
214
  if (templateDigits > 2 && year < 100) {
@@ -336,67 +217,51 @@ export function parseDate(dateString, language, skeleton, timeZone, bUseUTC = fa
336
217
  return year;
337
218
  };
338
219
  const monthLookup = list => month => list.indexOf(month);
339
-
340
220
  const parsed = parseDateTimeSkeleton(skeleton, language);
341
221
  const months = monthNames(language, Object.fromEntries(parsed));
342
- // build up a regex expression that identifies each option in the skeleton
343
- // We build two parallel structures:
344
- // 1. the regex expression that will extract parts of the date/time
345
- // 2. a lookup array that will convert the matched results into date/time values
346
222
  parsed.forEach(([option, value, len]) => {
347
- // use a generic regex pattern for all single-character separator literals.
348
- // Then we'll be forgiving when it comes to separators: / vs - vs : etc
349
223
  if (option === 'literal') {
350
224
  if (isSeparator(value)) regexParts.push(`[^${digits[0]}-${digits[9]}]`);
351
225
  else regexParts.push(value);
352
-
353
226
  } else if (option === 'month' && ['numeric', '2-digit'].includes(value)) {
354
227
  regexParts.push(twoDigit);
355
228
  lookups.push(['month', monthNumber]);
356
-
357
229
  } else if (option === 'month' && ['formatted', 'long', 'short', 'narrow'].includes(value)) {
358
230
  regexParts.push(`(${months.join('|')})`);
359
231
  lookups.push(['month', monthLookup(months)]);
360
-
361
232
  } else if (['day', 'minute', 'second'].includes(option)) {
362
233
  if (option === 'minute' || option === 'second') {
363
- _bUseUTC = false
234
+ _bUseUTC = false;
364
235
  }
365
236
  regexParts.push(twoDigit);
366
237
  lookups.push([option, getNumber]);
367
-
368
238
  } else if (option === 'fractionalSecondDigits') {
369
- _bUseUTC = false
239
+ _bUseUTC = false;
370
240
  regexParts.push(threeDigit);
371
241
  lookups.push([option, (v, obj) => obj.fractionalSecondDigits + getNumber(v)]);
372
-
373
242
  } else if (option === 'hour') {
374
- _bUseUTC = false
243
+ _bUseUTC = false;
375
244
  regexParts.push(twoDigit);
376
245
  lookups.push([option, (v, obj) => obj.hour + getNumber(v)]);
377
246
  } else if (option === 'year') {
378
247
  regexParts.push('numeric' === value ? fourDigit : twoDigit);
379
248
  lookups.push(['year', yearNumber(len)]);
380
249
  } else if (option === 'dayPeriod') {
381
- _bUseUTC = false
250
+ _bUseUTC = false;
382
251
  const dayPeriod = getDayPeriod(language);
383
252
  if (dayPeriod) {
384
253
  regexParts.push(dayPeriod.regex);
385
254
  lookups.push(['hour', dayPeriod.fn]);
386
255
  }
387
- // Any other part that we don't need, we'll just add a non-greedy consumption
388
256
  } else if (option === 'hourCycle') {
389
- _bUseUTC = false
257
+ _bUseUTC = false;
390
258
  hourCycle = value;
391
259
  } else if (option === 'x-timeZoneName') {
392
- _bUseUTC = false
393
- // we handle only the GMT offset picture
260
+ _bUseUTC = false;
394
261
  regexParts.push('(?:GMT|UTC|Z)?([+\\-−0-9]{0,3}:?[0-9]{0,2})');
395
262
  lookups.push([option, (v, obj) => {
396
263
  _bUseUTC = true;
397
- // v could be undefined if we're on GMT time
398
264
  if (!v) return;
399
- // replace the unicode minus, then extract hours [and minutes]
400
265
  const timeParts = v.replace(/−/, '-').match(/([+\-\d]{2,3}):?(\d{0,2})/);
401
266
  const hours = timeParts[1] * 1;
402
267
  obj.hour -= hours;
@@ -404,17 +269,14 @@ export function parseDate(dateString, language, skeleton, timeZone, bUseUTC = fa
404
269
  obj.minute -= (hours < 0) ? - mins : mins;
405
270
  }]);
406
271
  } else if (option !== 'timeZoneName') {
407
- _bUseUTC = false
272
+ _bUseUTC = false;
408
273
  regexParts.push('.+?');
409
274
  }
410
-
411
275
  return regexParts;
412
276
  }, []);
413
277
  const regex = new RegExp(regexParts.join(''));
414
278
  const match = dateString.match(regex);
415
279
  if (match === null) return dateString;
416
-
417
- // now loop through all the matched pieces and build up an object we'll use to create a Date object
418
280
  const dateObj = {year: 1972, month: 0, day: 1, hour: 0, minute: 0, second: 0, fractionalSecondDigits: 0};
419
281
  match.slice(1).forEach((m, index) => {
420
282
  const [element, func] = lookups[index];
@@ -451,7 +313,8 @@ export function parseDate(dateString, language, skeleton, timeZone, bUseUTC = fa
451
313
  }
452
314
  return timeZone == null ? jsDate : adjustTimeZone(jsDate, timeZone);
453
315
  }
454
-
455
- export function parseDefaultDate(dateString, language, bUseUTC) {
316
+ function parseDefaultDate(dateString, language, bUseUTC) {
456
317
  return parseDate(dateString, language, 'short', null, false);
457
318
  }
319
+
320
+ export { adjustTimeZone, datetimeToNumber, formatDate, numberToDatetime, offsetMS, offsetMSFallback, parseDate, parseDefaultDate };