Skip to content

feat(formatDate): add locale option to be extensible for absolute format #19157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -1848,6 +1848,15 @@
"contributions": [
"code"
]
},
{
"login": "dkaushik95",
"name": "Dishant Kaushik",
"avatar_url": "https://avatars.githubusercontent.com/u/8481567?v=4",
"profile": "https://github.com/dkaushik95",
"contributions": [
"code"
]
}
],
"commitConvention": "none"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ check out our [Contributing Guide](/.github/CONTRIBUTING.md) and our
<td align="center"><a href="https://github.com/maisonsmd"><img src="https://avatars.githubusercontent.com/u/16435155?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Son H. Mai (Mason)</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=maisonsmd" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/warrenmblood"><img src="https://avatars.githubusercontent.com/u/69060697?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Warren Blood</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=warrenmblood" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/vcherneny"><img src="https://avatars.githubusercontent.com/u/11604315?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vlad Cherneny</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=vcherneny" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/dkaushik95"><img src="https://avatars.githubusercontent.com/u/8481567?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dishant Kaushik</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=dkaushik95" title="Code">💻</a></td>
</tr>
</table>

Expand Down
75 changes: 75 additions & 0 deletions packages/utilities/src/dateTimeFormat/absolute-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const styles = ['full', 'long', 'medium', 'short'];
const date = new Date('2016-04-28T19:12:47Z');
const endDate = new Date('2018-07-01T09:00:02Z');
const sameDayEndDate = new Date('2016-04-28T21:04:30Z');
const timeZones = ['UTC', 'America/New_York', 'Asia/Tokyo'];

describe(locale, () => {
describe('formatTime', () => {
Expand All @@ -23,6 +24,23 @@ describe(locale, () => {
const actualOutput = absolute.formatTime(date, { locale, style });
expect(actualOutput).toBe(expectedOutput);
});

timeZones.forEach((timeZone) => {
const dtf = new Intl.DateTimeFormat(locale, {
timeStyle: style,
timeZone,
});
const expectedOutput = dtf.format(date);

test(`${style} with timeZone ${timeZone} → ${expectedOutput}`, () => {
const actualOutput = absolute.formatTime(date, {
locale,
style,
timeZone,
});
expect(actualOutput).toBe(expectedOutput);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't put this within the timeStyle loop, as (a) it multiplies the number of iterations 4 → 12 and (b) timeStyle and timeZone aren't dependent on each other.

We don't even need to test three different time zones, for timeZone is simply passed through to Intl.DateTimeFormat regardless of its value. Some alternatives, in ascending order of complexity:

  1. Test any two time zones so that, even if one happens to match the host's, the other won't; or
  2. spy on Intl.DateTimeFormat and simply assert that absolute.format*() instantiated it with the given timeZone option; or
  3. dynamically select a time zone other than the host's, and test just that one.

I'd personally go with # 2 as it's more of a true unit test, but seeing how none of the preexisting tests are written this way, it might be less controversial to stick with # 1.

Whichever you choose, please replicate to all tests. 🙏🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I chose the 1st option. I chose two timezones Tokyo and UTC so that it's always different for any host for at least one test. I also addressed the 2nd concern of using describe for timeZones so it should be consistent across all tests. I was trying different approaches and accidentally added both approaches :)

This will how the tests would show up.

At the end there will be 2 test cases for each function (8 iterations in all)

Screenshot 2025-04-21 at 8 58 21 AM

});
});

Expand All @@ -35,6 +53,23 @@ describe(locale, () => {
const actualOutput = absolute.formatDate(date, { locale, style });
expect(actualOutput).toBe(expectedOutput);
});

timeZones.forEach((timeZone) => {
const dtf = new Intl.DateTimeFormat(locale, {
dateStyle: style,
timeZone,
});
const expectedOutput = dtf.format(date);

test(`${style} with timeZone ${timeZone} → ${expectedOutput}`, () => {
const actualOutput = absolute.formatDate(date, {
locale,
style,
timeZone,
});
expect(actualOutput).toBe(expectedOutput);
});
});
});
});

Expand All @@ -54,6 +89,26 @@ describe(locale, () => {
expect(actualOutput).toBe(expectedOutput);
});

describe('with timeZone', () => {
timeZones.forEach((timeZone) => {
const dtf = new Intl.DateTimeFormat(locale, {
timeStyle,
dateStyle: timeStyle,
timeZone,
});
const expectedOutput = dtf.format(date);

test(`${timeStyle} in ${timeZone} → ${expectedOutput}`, () => {
const actualOutput = absolute.format(date, {
locale,
style: timeStyle,
timeZone,
});
expect(actualOutput).toBe(expectedOutput);
});
});
});

describe('manual', () => {
styles.forEach((dateStyle) => {
const dtf = new Intl.DateTimeFormat(locale, { timeStyle, dateStyle });
Expand Down Expand Up @@ -99,6 +154,26 @@ describe(locale, () => {
expect(actualOutput).toBe(expectedOutput);
});

describe('with timeZone', () => {
timeZones.forEach((timeZone) => {
const dtf = new Intl.DateTimeFormat(locale, {
timeStyle: dateStyle,
dateStyle,
timeZone,
});
const expectedOutput = dtf.formatRange(date, endDate);

test(`${dateStyle} in ${timeZone} → ${expectedOutput}`, () => {
const actualOutput = absolute.formatRange(date, endDate, {
locale,
style: dateStyle,
timeZone,
});
expect(actualOutput).toBe(expectedOutput);
});
});
});

describe('manual', () => {
[...styles, null].forEach((timeStyle) => {
const dtf = new Intl.DateTimeFormat(locale, {
Expand Down
8 changes: 8 additions & 0 deletions packages/utilities/src/dateTimeFormat/absolute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ export function formatTime(
options?: Partial<{
locale: string;
style: Intl.DateTimeFormatOptions['timeStyle'];
timeZone: Intl.DateTimeFormatOptions['timeZone'];
}>
): string {
const dtf = new Intl.DateTimeFormat(options?.locale, {
timeStyle: options?.style ?? 'short',
timeZone: options?.timeZone,
});

return dtf.format(date);
Expand All @@ -24,10 +26,12 @@ export function formatDate(
options?: Partial<{
locale: string;
style: Intl.DateTimeFormatOptions['dateStyle'];
timeZone: Intl.DateTimeFormatOptions['timeZone'];
}>
): string {
const dtf = new Intl.DateTimeFormat(options?.locale, {
dateStyle: options?.style ?? 'medium',
timeZone: options?.timeZone,
});

return dtf.format(date);
Expand All @@ -40,6 +44,7 @@ export function format(
style: Intl.DateTimeFormatOptions['timeStyle'] | 'tooltip';
timeStyle: Intl.DateTimeFormatOptions['timeStyle'];
dateStyle: Intl.DateTimeFormatOptions['dateStyle'];
timeZone: Intl.DateTimeFormatOptions['timeZone'];
}>
) {
const timeStyle =
Expand All @@ -55,6 +60,7 @@ export function format(
const dtf = new Intl.DateTimeFormat(options?.locale, {
timeStyle,
dateStyle,
timeZone: options?.timeZone,
});

return dtf.format(date);
Expand All @@ -68,6 +74,7 @@ export function formatRange(
style: Intl.DateTimeFormatOptions['timeStyle'];
timeStyle: Intl.DateTimeFormatOptions['timeStyle'] | null;
dateStyle: Intl.DateTimeFormatOptions['dateStyle'] | null;
timeZone: Intl.DateTimeFormatOptions['timeZone'];
}>
) {
const timeStyle =
Expand All @@ -83,6 +90,7 @@ export function formatRange(
const dtf = new Intl.DateTimeFormat(options?.locale, {
timeStyle,
dateStyle,
timeZone: options?.timeZone,
});

return dtf.formatRange(startDate, endDate);
Expand Down
Loading