@loadsmart/loadsmart-ui 5.16.1 → 5.17.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.
@@ -0,0 +1 @@
1
+ export declare const getOrdinalSuffix: (number: number) => 'st' | 'nd' | 'rd' | 'th';
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadsmart/loadsmart-ui",
3
- "version": "5.16.1",
3
+ "version": "5.17.0",
4
4
  "description": "Miranda UI, a React UI library",
5
5
  "main": "dist",
6
6
  "files": [
@@ -134,5 +134,32 @@ describe('DateFormatHelper', () => {
134
134
  )
135
135
  ).toBe('02/01/2022 11:22:33')
136
136
  })
137
+
138
+ it('formats compound tokens', () => {
139
+ expect(
140
+ DateFormatHelper('ddd, Do, MMM').format(
141
+ DateHelper('2021-05-04T04:00:00Z', {
142
+ normalize: false,
143
+ })
144
+ )
145
+ ).toBe('Tue, 4th, May')
146
+ expect(
147
+ DateFormatHelper('ddd, DDo, MMM').format(
148
+ DateHelper('2021-05-05T04:00:00Z', {
149
+ normalize: false,
150
+ })
151
+ )
152
+ ).toBe('Wed, 05th, May')
153
+ })
154
+
155
+ it('does not formats a compound token when the previous token is invalid', () => {
156
+ expect(
157
+ DateFormatHelper('ddd, YYYYo, MMM').format(
158
+ DateHelper('2021-05-10T04:00:00Z', {
159
+ normalize: false,
160
+ })
161
+ )
162
+ ).toBe('Mon, 2021o, May')
163
+ })
137
164
  })
138
165
  })
@@ -2,11 +2,18 @@ import { identity } from '@loadsmart/utils-function'
2
2
  import { padded } from './Date.helper'
3
3
 
4
4
  import type { CalendarDate } from './Date.helper'
5
+ import { getOrdinalSuffix } from 'utils/toolset/getOrdinalSuffix'
5
6
 
6
7
  export interface DateFormat {
7
8
  format(date: CalendarDate): string
8
9
  }
9
10
 
11
+ const getTokenValue = (date: CalendarDate, token: string) => {
12
+ const value = DEFAULT_FORMATTERS[token].format(date.get())
13
+
14
+ return (ADDITIONAL_FORMATTERS[token] || identity)(value)
15
+ }
16
+
10
17
  /**
11
18
  * This helpers provides a convenient layer on top of `Intl.DateTimeFormat`,
12
19
  * using common tokens (based on `momentjs`) to format dates.
@@ -17,11 +24,16 @@ export default function DateFormatHelper(format: string): DateFormat {
17
24
  return {
18
25
  format(date: CalendarDate) {
19
26
  return tokens
20
- .map((token) => {
27
+ .map((token, index) => {
28
+ const previousIndex = index - 1
29
+ const previousToken = tokens[previousIndex]
30
+
21
31
  if (token in DEFAULT_FORMATTERS) {
22
- const value = DEFAULT_FORMATTERS[token].format(date.get())
32
+ return getTokenValue(date, token)
33
+ }
23
34
 
24
- return (ADDITIONAL_FORMATTERS[token] || identity)(value)
35
+ if (token in COMPOUND_FORMATTERS && COMPOUND_FORMATTERS[token].valid(previousToken)) {
36
+ return COMPOUND_FORMATTERS[token].format(getTokenValue(date, previousToken))
25
37
  }
26
38
 
27
39
  return token
@@ -39,16 +51,18 @@ export default function DateFormatHelper(format: string): DateFormat {
39
51
  *| Month | MM | 01, 02, ..., 11, 12 |
40
52
  *| | MMM | Jan, Feb, ..., Nov, Dec |
41
53
  *| | MMMM | January, February, ..., November,December |
42
- *| Day of Month | DD | 01, 02, ..., 30, 31 |
54
+ *| Day of Month | D | 1, 2, ..., 30, 31 |
55
+ *| Day of Month with leading 0 | DD | 01, 02, ..., 30, 31 |
43
56
  *| Day of week | ddd | Sun, Mon, ... Fri, Sat |
44
57
  *| | dddd | Sunday, Monday, ..., Friday, Saturday |
45
58
  *| Year | YYYY | 1970, 1971, ..., 2029, 2030 |
46
59
  *| Hour | HH | 00, 01, ..., 22, 23 |
47
60
  *| | hh | 00, 01, ..., 11, 12 |
48
- *| Minute | mm | 01, 02, ..., 11, 12 |
61
+ *| Minute | mm | 01, 02, ..., 58, 59 |
49
62
  *| Seconds | ss | 01, 02, ..., 58, 59 |
50
63
  *| Post or ante meridiem | a | am, pm |
51
64
  *| | A | AM, PM |
65
+ *| Ordinal numbers | o | 1st, 2nd, 3rd, ..., 10th |
52
66
  *| Scaped sequence | [] | |
53
67
  *
54
68
  * @param format
@@ -56,7 +70,10 @@ export default function DateFormatHelper(format: string): DateFormat {
56
70
  */
57
71
  export function tokenizer(format: string): string[] {
58
72
  function getType(char?: string): string {
59
- if (char != undefined && ['M', 'd', 'D', 'Y', 'H', 'h', 'm', 's', 'A', 'a'].includes(char)) {
73
+ if (
74
+ char != undefined &&
75
+ ['M', 'd', 'D', 'Y', 'H', 'h', 'm', 's', 'A', 'a', 'o'].includes(char)
76
+ ) {
60
77
  return 'token'
61
78
  }
62
79
 
@@ -110,6 +127,9 @@ const DEFAULT_FORMATTERS: Record<string, Intl.DateTimeFormat> = {
110
127
  MMMM: new Intl.DateTimeFormat('en-US', {
111
128
  month: 'long',
112
129
  }),
130
+ D: new Intl.DateTimeFormat('en-US', {
131
+ day: 'numeric',
132
+ }),
113
133
  DD: new Intl.DateTimeFormat('en-US', {
114
134
  day: '2-digit',
115
135
  }),
@@ -154,10 +174,22 @@ const DEFAULT_FORMATTERS: Record<string, Intl.DateTimeFormat> = {
154
174
  * Padding, for example, is applied in some cases due to [this](https://bugs.chromium.org/p/chromium/issues/detail?id=527926) bug.
155
175
  */
156
176
  const ADDITIONAL_FORMATTERS: Record<string, (value: string) => string> = {
157
- hh: (value: string) => (value ? padded(value.split(' ')[0], 2) : value),
177
+ hh: (value: string) => (value ? padded(value.split(/\s/)[0], 2) : value),
158
178
  HH: (value: string) => (value ? padded(value, 2) : value),
159
179
  mm: (value: string) => (value ? padded(value, 2) : value),
160
180
  ss: (value: string) => (value ? padded(value, 2) : value),
161
- a: (value: string) => (value ? (value.split(' ')[1] || '').toLowerCase() : value),
162
- A: (value: string) => (value ? (value.split(' ')[1] || '').toUpperCase() : value),
181
+ a: (value: string) => (value ? (value.split(/\s/)[1] || '').toLowerCase() : value),
182
+ A: (value: string) => (value ? (value.split(/\s/)[1] || '').toUpperCase() : value),
183
+ }
184
+
185
+ type CompoundFormatter = {
186
+ valid: (token: string) => boolean
187
+ format: (value: string) => string
188
+ }
189
+
190
+ const COMPOUND_FORMATTERS: Record<string, CompoundFormatter> = {
191
+ o: {
192
+ valid: (token) => new Set(['D', 'DD']).has(token),
193
+ format: (value: string) => getOrdinalSuffix(parseInt(value, 10)),
194
+ },
163
195
  }
@@ -0,0 +1,27 @@
1
+ import { getOrdinalSuffix } from './getOrdinalSuffix'
2
+
3
+ it('should return "st" for numbers ending with 1', () => {
4
+ expect(getOrdinalSuffix(1)).toBe('st')
5
+ expect(getOrdinalSuffix(101)).toBe('st')
6
+ expect(getOrdinalSuffix(1001)).toBe('st')
7
+ })
8
+
9
+ it('should return "nd" for numbers ending with 2', () => {
10
+ expect(getOrdinalSuffix(2)).toBe('nd')
11
+ expect(getOrdinalSuffix(102)).toBe('nd')
12
+ expect(getOrdinalSuffix(1002)).toBe('nd')
13
+ })
14
+
15
+ it('should return "rd" for numbers ending with 3', () => {
16
+ expect(getOrdinalSuffix(3)).toBe('rd')
17
+ expect(getOrdinalSuffix(103)).toBe('rd')
18
+ expect(getOrdinalSuffix(1003)).toBe('rd')
19
+ })
20
+
21
+ it('should return "th" for 0 and numbers higher than 3 but not ending with 1, 2 or 3', () => {
22
+ expect(getOrdinalSuffix(0)).toBe('th')
23
+ expect(getOrdinalSuffix(4)).toBe('th')
24
+ expect(getOrdinalSuffix(10)).toBe('th')
25
+ expect(getOrdinalSuffix(100)).toBe('th')
26
+ expect(getOrdinalSuffix(1000)).toBe('th')
27
+ })
@@ -0,0 +1,15 @@
1
+ export const getOrdinalSuffix = (number: number): 'st' | 'nd' | 'rd' | 'th' => {
2
+ if (number % 10 === 1 && number !== 11) {
3
+ return 'st'
4
+ }
5
+
6
+ if (number % 10 === 2 && number !== 12) {
7
+ return 'nd'
8
+ }
9
+
10
+ if (number % 10 === 3 && number !== 13) {
11
+ return 'rd'
12
+ }
13
+
14
+ return 'th'
15
+ }