Skip to content

Commit ad4c34f

Browse files
committed
feat(formatDate): Support second argument as explicit format string accepting both Unicode and strftime formats (converting Unicode to strftime) while still supporting period type string/enum.
1 parent 7c986f8 commit ad4c34f

3 files changed

Lines changed: 72 additions & 12 deletions

File tree

.changeset/social-poems-start.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@layerstack/utils': patch
3+
---
4+
5+
feat(formatDate): Support second argument as explicit `format` string accepting both `Unicode` and `strftime` formats (converting `Unicode` to `strftime`) while still supporting period type string/enum.

packages/utils/src/lib/date.test.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,38 @@ describe('formatDate()', () => {
8686
it('should allow formatting with PeriodTypeCode', () => {
8787
expect(formatDate(testDate, 'day')).equal('11/21/2023');
8888
});
89+
90+
describe('strftime format', () => {
91+
test.each([
92+
[new Date('2023-03-07T04:00:00.000Z'), '%Y-%m-%d', '2023-03-07'],
93+
// [new Date('2023-03-07T04:00:00.000Z'), '%m/%d/%Y', '3/7/2023'], // Not suported
94+
[new Date('2023-03-07T04:00:00.000Z'), '%m/%d/%Y', '03/07/2023'],
95+
[new Date('2023-03-07T04:00:00.000Z'), '%m/%d/%y', '03/07/23'],
96+
[new Date('2023-03-07T04:00:00.000Z'), '%A, %B %d, %Y', 'Tuesday, March 07, 2023'],
97+
[new Date('1900-01-01T15:25:59.000Z'), '%H:%M:%S', '11:25:59'],
98+
[new Date('1900-01-01T18:30:00.000Z'), '%I:%M %p', '02:30 PM'],
99+
[new Date('2023-03-07T18:30:45.000Z'), '%Y-%m-%d %H:%M:%S', '2023-03-07 14:30:45'],
100+
[new Date('2023-03-07T21:30:45.000Z'), '%Y-%m-%d %H:%M:%S %Z', '2023-03-07 17:30:45 -0400'],
101+
])('formatDate(%s, %s) => %s', (date, format, expected) => {
102+
expect(formatDate(date, format)).toEqual(expected);
103+
});
104+
});
105+
106+
describe('Unicode format', () => {
107+
test.each([
108+
[new Date('2023-03-07T04:00:00.000Z'), 'yyyy-MM-dd', '2023-03-07'],
109+
// [new Date('2023-03-07T04:00:00.000Z'), 'M/d/yyyy', '3/7/2023'], // Not suported
110+
[new Date('2023-03-07T04:00:00.000Z'), 'MM/dd/yyyy', '03/07/2023'],
111+
[new Date('2023-03-07T04:00:00.000Z'), 'M/d/yy', '03/07/23'],
112+
[new Date('2023-03-07T04:00:00.000Z'), 'EEEE, MMMM dd, yyyy', 'Tuesday, March 07, 2023'],
113+
[new Date('1900-01-01T15:25:59.000Z'), 'HH:mm:ss', '11:25:59'],
114+
[new Date('1900-01-01T18:30:00.000Z'), 'hh:mm a', '02:30 PM'],
115+
[new Date('2023-03-07T18:30:45.000Z'), 'yyyy-MM-dd HH:mm:ss', '2023-03-07 14:30:45'],
116+
[new Date('2023-03-07T21:30:45.000Z'), 'yyyy-MM-dd HH:mm:ss z', '2023-03-07 17:30:45 -0400'],
117+
])('formatDate(%s, %s) => %s', (date, format, expected) => {
118+
expect(formatDate(date, format)).toEqual(expected);
119+
});
120+
});
89121
});
90122

91123
describe('formatDateWithLocale()', () => {
@@ -1304,7 +1336,7 @@ describe('parseDate()', () => {
13041336
['03/07/2023', '%m/%d/%Y', new Date('2023-03-07T04:00:00.000Z')],
13051337
['03/07/23', '%m/%d/%y', new Date('2023-03-07T04:00:00.000Z')],
13061338
['3/7/23', '%m/%d/%y', new Date('2023-03-07T04:00:00.000Z')],
1307-
['Monday, March 7, 2023', '%A, %B %d, %Y', new Date('2023-03-07T04:00:00.000Z')],
1339+
['Tuesday, March 7, 2023', '%A, %B %d, %Y', new Date('2023-03-07T04:00:00.000Z')],
13081340
['11:25:59', '%H:%M:%S', new Date('1900-01-01T15:25:59.000Z')],
13091341
['2:30 PM', '%I:%M %p', new Date('1900-01-01T18:30:00.000Z')],
13101342
['2023-03-07 14:30:45', '%Y-%m-%d %H:%M:%S', new Date('2023-03-07T18:30:45.000Z')],
@@ -1320,7 +1352,7 @@ describe('parseDate()', () => {
13201352
['3/7/2023', 'M/d/yyyy', new Date('2023-03-07T04:00:00.000Z')],
13211353
['03/07/2023', 'MM/dd/yyyy', new Date('2023-03-07T04:00:00.000Z')],
13221354
['3/7/23', 'M/d/yy', new Date('2023-03-07T04:00:00.000Z')],
1323-
['Monday, December 25, 2023', 'EEEE, MMMM dd, yyyy', new Date('2023-12-25T04:00:00.000Z')],
1355+
['Tuesday, December 25, 2023', 'EEEE, MMMM dd, yyyy', new Date('2023-12-25T04:00:00.000Z')],
13241356
['11:25:59', 'HH:mm:ss', new Date('1900-01-01T15:25:59.000Z')],
13251357
['2:30 PM', 'hh:mm a', new Date('1900-01-01T18:30:00.000Z')],
13261358
['2023-03-07 14:30:45', 'yyyy-MM-dd HH:mm:ss', new Date('2023-03-07T18:30:45.000Z')],

packages/utils/src/lib/date.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
timeFriday,
1818
timeSaturday,
1919
} from 'd3-time';
20-
import { timeParse } from 'd3-time-format';
20+
import { timeFormat, timeParse } from 'd3-time-format';
2121
import { min, max } from 'd3-array';
2222

2323
import { hasKeyOf } from './typeGuards.js';
@@ -621,10 +621,33 @@ function range(
621621

622622
export function formatDate(
623623
date: Date | string | null | undefined,
624-
periodType: PeriodType | PeriodTypeCode,
624+
periodOrFormat: PeriodType | PeriodTypeCode | string,
625625
options: FormatDateOptions = {}
626626
): string {
627-
return formatDateWithLocale(defaultLocale, date, periodType, options);
627+
if (typeof periodOrFormat === 'string' && !getPeriodTypeByCode(periodOrFormat as any)) {
628+
if (!date) {
629+
return '';
630+
} else if (typeof date === 'string') {
631+
// If periodOrFormat is string, treat as unicode/strftime format
632+
date = parseDate(date);
633+
}
634+
635+
let strftimeFormat = periodOrFormat;
636+
if (!periodOrFormat.includes('%')) {
637+
// Unicode format, convert to strftime format
638+
strftimeFormat = convertUnicodeToStrftime(periodOrFormat);
639+
// console.log({ periodOrFormat, strftimeFormat });
640+
}
641+
642+
return timeFormat(strftimeFormat)(date);
643+
}
644+
645+
return formatDateWithLocale(
646+
defaultLocale,
647+
date,
648+
periodOrFormat as PeriodType | PeriodTypeCode,
649+
options
650+
);
628651
}
629652

630653
export function updatePeriodTypeWithWeekStartsOn(
@@ -885,16 +908,16 @@ export function isStringDateWithTimezone(value: string) {
885908
* @returns A Date object
886909
*/
887910
export function parseDate(dateStr: string, format?: string) {
911+
// If format is provided, use it to parse the date string
888912
if (format) {
889-
if (format.includes('%')) {
890-
// strftime format
891-
return timeParse(format)(dateStr) ?? new Date('Invalid Date');
892-
} else {
913+
let strftimeFormat = format;
914+
if (!format.includes('%')) {
893915
// Unicode format, convert to strftime format
894-
const strftimeFormat = convertUnicodeToStrftime(format);
895-
console.log({ format, strftimeFormat });
896-
return timeParse(strftimeFormat)(dateStr) ?? new Date('Invalid Date');
916+
strftimeFormat = convertUnicodeToStrftime(format);
917+
// console.log({ format, strftimeFormat });
897918
}
919+
920+
return timeParse(strftimeFormat)(dateStr) ?? new Date('Invalid Date');
898921
}
899922

900923
if (!isStringDate(dateStr)) return new Date('Invalid Date');

0 commit comments

Comments
 (0)