Skip to content

Commit 5378325

Browse files
jycouettechniq
authored andcommitted
Format last qol for next (#203)
* add WeekStartsOn defaults depending on the locale * init a Language Select comp
1 parent f22e64a commit 5378325

File tree

10 files changed

+211
-135
lines changed

10 files changed

+211
-135
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script lang="ts">
2+
import Button from './Button.svelte';
3+
import Menu from './Menu.svelte';
4+
import MenuItem from './MenuItem.svelte';
5+
import { cls } from '../utils/styles';
6+
import { getSettings } from './settings';
7+
const { locale } = getSettings();
8+
9+
let open = false;
10+
11+
type Language = {
12+
name: string;
13+
code: string;
14+
flag: string;
15+
};
16+
17+
export let languagesDemo: Language[] = [
18+
{ name: 'English', code: 'en', flag: '🇺🇸' },
19+
{ name: 'Français', code: 'fr', flag: '🇫🇷' },
20+
// add more for the demo
21+
];
22+
$: languageSelected = languagesDemo.find((c) => c.code === $locale)!;
23+
</script>
24+
25+
<Button on:click={() => (open = !open)}>
26+
{languageSelected.flag}
27+
<Menu bind:open on:close={() => (open = false)} offset={4} explicitClose resize>
28+
<div class="grid gap-2 p-2 border-b border-surface-content/10">
29+
{#each languagesDemo as language}
30+
<MenuItem
31+
on:click={() => {
32+
languageSelected = language;
33+
locale.set(language.code);
34+
}}
35+
class={cls(
36+
'bg-surface-100 text-surface-content font-semibold border shadow',
37+
languageSelected === language && 'ring-2 ring-surface-content'
38+
)}
39+
>
40+
{language.flag} - {language.name}
41+
</MenuItem>
42+
{/each}
43+
</div>
44+
45+
<div class="p-2 grid grid-cols-[auto,1fr] gap-2 items-center text-xs">
46+
<span class="font-medium">Affect dates & numbers formats</span>
47+
</div>
48+
</Menu>
49+
</Button>

packages/svelte-ux/src/lib/utils/date.test.ts

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import { describe, it, expect } from 'vitest';
22
import {
3-
PeriodType,
43
formatDate,
54
getMonthDaysByWeek,
65
localToUtcDate,
76
utcToLocalDate,
8-
DayOfWeek,
97
formatIntl,
10-
type CustomIntlDateTimeFormatOptions,
11-
type FormatDateOptions,
12-
DateToken,
138
formatDateWithLocale,
149
} from './date';
15-
import { getSettings } from '$lib/components';
16-
import { format, formatWithLocale } from '.';
10+
import { formatWithLocale } from '.';
1711
import { createLocaleSettings, defaultLocale } from './locale';
12+
import {
13+
PeriodType,
14+
type FormatDateOptions,
15+
DayOfWeek,
16+
type CustomIntlDateTimeFormatOptions,
17+
DateToken,
18+
} from './date_types';
19+
import { getWeekStartsOnFromIntl } from './dateInternal';
1820

1921
const DATE = '2023-11-21'; // "good" default date as the day (21) is bigger than 12 (number of months). And november is a good month1 (because why not?)
2022
const dt_2M_2d = new Date(2023, 10, 21);
2123
const dt_2M_1d = new Date(2023, 10, 7);
2224
const dt_1M_1d = new Date(2023, 2, 7);
25+
const dt_first = new Date(2024, 1, 1);
2326

2427
const dt_1M_1d_time_pm = new Date(2023, 2, 7, 14, 2, 3, 4);
2528
const dt_1M_1d_time_am = new Date(2023, 2, 7, 1, 2, 3, 4);
@@ -30,21 +33,21 @@ const fr = createLocaleSettings({
3033
dates: {
3134
ordinalSuffixes: {
3235
one: 'er',
33-
two: '',
34-
few: '',
35-
other: '',
3636
},
3737
},
3838
},
3939
});
4040

4141
describe('formatDate()', () => {
4242
it('should return empty string for null or undefined date', () => {
43+
// @ts-expect-error
4344
expect(formatDate(null)).equal('');
45+
// @ts-expect-error
4446
expect(formatDate(undefined)).equal('');
4547
});
4648

4749
it('should return empty string for invalid date', () => {
50+
// @ts-expect-error
4851
expect(formatDate('invalid date')).equal('');
4952
});
5053

@@ -125,7 +128,7 @@ describe('formatDate()', () => {
125128
}
126129
});
127130

128-
describe('should format date for PeriodType.WeekSun / Mon', () => {
131+
describe('should format date for PeriodType.WeekSun / Mon no mather the locale', () => {
129132
const combi = [
130133
[PeriodType.WeekSun, 'short', defaultLocale, '11/19 - 11/25'],
131134
[PeriodType.WeekSun, 'short', fr, '19/11 - 25/11'],
@@ -138,32 +141,28 @@ describe('formatDate()', () => {
138141
for (const c of combi) {
139142
const [periodType, variant, locales, expected] = c;
140143
it(c.toString(), () => {
141-
expect(formatDateWithLocale(locales, DATE, periodType, { variant, locales })).equal(
142-
expected
143-
);
144+
expect(formatDateWithLocale(locales, DATE, periodType, { variant })).equal(expected);
144145
});
145146
}
146147
});
147148

148-
describe('should format date for PeriodType.Week', () => {
149+
describe('should format date for PeriodType.Week with the good weekstarton locale', () => {
149150
const combi = [
150-
[PeriodType.Week, 'short', defaultLocale, DayOfWeek.Sunday, '11/19 - 11/25'],
151-
[PeriodType.Week, 'short', fr, DayOfWeek.Sunday, '19/11 - 25/11'],
152-
[PeriodType.Week, 'long', defaultLocale, DayOfWeek.Sunday, '11/19/2023 - 11/25/2023'],
153-
[PeriodType.Week, 'long', fr, DayOfWeek.Sunday, '19/11/2023 - 25/11/2023'],
154-
155-
[PeriodType.Week, 'short', defaultLocale, DayOfWeek.Monday, '11/20 - 11/26'],
156-
[PeriodType.Week, 'short', fr, DayOfWeek.Monday, '20/11 - 26/11'],
157-
[PeriodType.Week, 'long', defaultLocale, DayOfWeek.Monday, '11/20/2023 - 11/26/2023'],
158-
[PeriodType.Week, 'long', fr, DayOfWeek.Monday, '20/11/2023 - 26/11/2023'],
151+
[PeriodType.Week, 'short', defaultLocale, '11/19 - 11/25'],
152+
[PeriodType.Week, 'short', fr, '20/11 - 26/11'],
153+
[PeriodType.Week, 'long', defaultLocale, '11/19/2023 - 11/25/2023'],
154+
[PeriodType.Week, 'long', fr, '20/11/2023 - 26/11/2023'],
155+
156+
[PeriodType.Week, 'short', defaultLocale, '11/19 - 11/25'],
157+
[PeriodType.Week, 'short', fr, '20/11 - 26/11'],
158+
[PeriodType.Week, 'long', defaultLocale, '11/19/2023 - 11/25/2023'],
159+
[PeriodType.Week, 'long', fr, '20/11/2023 - 26/11/2023'],
159160
] as const;
160161

161162
for (const c of combi) {
162-
const [periodType, variant, locales, weekStartsOn, expected] = c;
163+
const [periodType, variant, locales, expected] = c;
163164
it(c.toString(), () => {
164-
expect(formatDateWithLocale(locales, DATE, periodType, { variant, weekStartsOn })).equal(
165-
expected
166-
);
165+
expect(formatDateWithLocale(locales, DATE, periodType, { variant })).equal(expected);
167166
});
168167
}
169168
});
@@ -286,6 +285,7 @@ describe('formatDate()', () => {
286285
for (const c of combi) {
287286
const [variant, locales] = c;
288287
it(c.toString(), () => {
288+
// @ts-expect-error
289289
expect(formatDateWithLocale(locales, DATE, undefined, { variant })).equal(expected);
290290
});
291291
}
@@ -316,6 +316,7 @@ describe('formatIntl() tokens', () => {
316316
[dt_2M_2d, { dateStyle: 'medium', withOrdinal: true }, ['Nov 21st, 2023', '21 nov. 2023']],
317317
[dt_2M_2d, { dateStyle: 'short' }, ['11/21/23', '21/11/2023']],
318318
[dt_1M_1d, { dateStyle: 'short' }, ['3/7/23', '07/03/2023']],
319+
[dt_first, DateToken.DayOfMonth_withOrdinal, ['1st', '1er']],
319320

320321
// time
321322
[dt_1M_1d_time_pm, [DateToken.Hour_numeric, DateToken.Minute_numeric], ['2:02 PM', '14:02']],
@@ -500,3 +501,20 @@ describe('getMonthDaysByWeek()', () => {
500501
`);
501502
});
502503
});
504+
505+
describe('getWeekStartsOnFromIntl() tokens', () => {
506+
it('by default, sunday', () => {
507+
const val = getWeekStartsOnFromIntl();
508+
expect(val).toBe(DayOfWeek.Sunday);
509+
});
510+
511+
it('For en it should be synday', () => {
512+
const val = getWeekStartsOnFromIntl('en');
513+
expect(val).toBe(DayOfWeek.Sunday);
514+
});
515+
516+
it('For fr it should be monday', () => {
517+
const val = getWeekStartsOnFromIntl('fr');
518+
expect(val).toBe(DayOfWeek.Monday);
519+
});
520+
});

packages/svelte-ux/src/lib/utils/date.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@ import {
3232

3333
import { hasKeyOf } from '../types/typeGuards';
3434
import { chunk } from './array';
35-
import type { DateRange } from './dateRange';
36-
import { PeriodType, DayOfWeek, DateToken } from './date_types';
35+
import {
36+
PeriodType,
37+
DayOfWeek,
38+
DateToken,
39+
type SelectedDate,
40+
type CustomIntlDateTimeFormatOptions,
41+
type FormatDateOptions,
42+
type DateFormatVariantPreset,
43+
} from './date_types';
3744
import { defaultLocale, type LocaleSettings } from './locale';
3845

3946
export * from './date_types';
@@ -676,6 +683,7 @@ export function formatDateWithLocale(
676683
}
677684

678685
const weekStartsOn = options.weekStartsOn ?? settings.formats.dates.weekStartsOn;
686+
679687
const { day, dayTime, timeOnly, week, month, monthsYear, year } = settings.formats.dates.presets;
680688

681689
if (periodType === PeriodType.Week) {
@@ -723,80 +731,80 @@ export function formatDateWithLocale(
723731

724732
switch (periodType) {
725733
case PeriodType.Custom:
726-
return formatIntl(settings, date, options.custom);
734+
return formatIntl(settings, date, options.custom!);
727735

728736
case PeriodType.Day:
729-
return formatIntl(settings, date, rv(day));
737+
return formatIntl(settings, date, rv(day!)!);
730738

731739
case PeriodType.DayTime:
732-
return formatIntl(settings, date, rv(dayTime));
740+
return formatIntl(settings, date, rv(dayTime!)!);
733741

734742
case PeriodType.TimeOnly:
735-
return formatIntl(settings, date, rv(timeOnly));
743+
return formatIntl(settings, date, rv(timeOnly!)!);
736744

737745
case PeriodType.WeekSun:
738-
return range(settings, date, 0, rv(week));
746+
return range(settings, date, 0, rv(week!)!);
739747
case PeriodType.WeekMon:
740-
return range(settings, date, 1, rv(week));
748+
return range(settings, date, 1, rv(week!)!);
741749
case PeriodType.WeekTue:
742-
return range(settings, date, 2, rv(week));
750+
return range(settings, date, 2, rv(week!)!);
743751
case PeriodType.WeekWed:
744-
return range(settings, date, 3, rv(week));
752+
return range(settings, date, 3, rv(week!)!);
745753
case PeriodType.WeekThu:
746-
return range(settings, date, 4, rv(week));
754+
return range(settings, date, 4, rv(week!)!);
747755
case PeriodType.WeekFri:
748-
return range(settings, date, 5, rv(week));
756+
return range(settings, date, 5, rv(week!)!);
749757
case PeriodType.WeekSat:
750-
return range(settings, date, 6, rv(week));
758+
return range(settings, date, 6, rv(week!)!);
751759

752760
case PeriodType.Month:
753-
return formatIntl(settings, date, rv(month));
761+
return formatIntl(settings, date, rv(month!)!);
754762

755763
case PeriodType.MonthYear:
756-
return formatIntl(settings, date, rv(monthsYear));
764+
return formatIntl(settings, date, rv(monthsYear!)!);
757765

758766
case PeriodType.Quarter:
759767
return [
760-
formatIntl(settings, startOfQuarter(date), rv(month)),
761-
formatIntl(settings, endOfQuarter(date), rv(monthsYear)),
768+
formatIntl(settings, startOfQuarter(date), rv(month!)!),
769+
formatIntl(settings, endOfQuarter(date), rv(monthsYear!)!),
762770
].join(' - ');
763771

764772
case PeriodType.CalendarYear:
765-
return formatIntl(settings, date, rv(year));
773+
return formatIntl(settings, date, rv(year!)!);
766774

767775
case PeriodType.FiscalYearOctober:
768776
const fDate = new Date(getFiscalYear(date), 0, 1);
769-
return formatIntl(settings, fDate, rv(year));
777+
return formatIntl(settings, fDate, rv(year!)!);
770778

771779
case PeriodType.BiWeek1Sun:
772-
return range(settings, date, 0, rv(week), 1);
780+
return range(settings, date, 0, rv(week!)!, 1);
773781
case PeriodType.BiWeek1Mon:
774-
return range(settings, date, 1, rv(week), 1);
782+
return range(settings, date, 1, rv(week!)!, 1);
775783
case PeriodType.BiWeek1Tue:
776-
return range(settings, date, 2, rv(week), 1);
784+
return range(settings, date, 2, rv(week!)!, 1);
777785
case PeriodType.BiWeek1Wed:
778-
return range(settings, date, 3, rv(week), 1);
786+
return range(settings, date, 3, rv(week!)!, 1);
779787
case PeriodType.BiWeek1Thu:
780-
return range(settings, date, 4, rv(week), 1);
788+
return range(settings, date, 4, rv(week!)!, 1);
781789
case PeriodType.BiWeek1Fri:
782-
return range(settings, date, 5, rv(week), 1);
790+
return range(settings, date, 5, rv(week!)!, 1);
783791
case PeriodType.BiWeek1Sat:
784-
return range(settings, date, 6, rv(week), 1);
792+
return range(settings, date, 6, rv(week!)!, 1);
785793

786794
case PeriodType.BiWeek2Sun:
787-
return range(settings, date, 0, rv(week), 2);
795+
return range(settings, date, 0, rv(week!)!, 2);
788796
case PeriodType.BiWeek2Mon:
789-
return range(settings, date, 1, rv(week), 2);
797+
return range(settings, date, 1, rv(week!)!, 2);
790798
case PeriodType.BiWeek2Tue:
791-
return range(settings, date, 2, rv(week), 2);
799+
return range(settings, date, 2, rv(week!)!, 2);
792800
case PeriodType.BiWeek2Wed:
793-
return range(settings, date, 3, rv(week), 2);
801+
return range(settings, date, 3, rv(week!)!, 2);
794802
case PeriodType.BiWeek2Thu:
795-
return range(settings, date, 4, rv(week), 2);
803+
return range(settings, date, 4, rv(week!)!, 2);
796804
case PeriodType.BiWeek2Fri:
797-
return range(settings, date, 5, rv(week), 2);
805+
return range(settings, date, 5, rv(week!)!, 2);
798806
case PeriodType.BiWeek2Sat:
799-
return range(settings, date, 6, rv(week), 2);
807+
return range(settings, date, 6, rv(week!)!, 2);
800808

801809
default:
802810
return formatISO(date);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { DayOfWeek } from './date_types';
2+
3+
export function getWeekStartsOnFromIntl(locales?: string): DayOfWeek {
4+
if (!locales) {
5+
return DayOfWeek.Sunday;
6+
}
7+
8+
const info = new Intl.Locale(locales);
9+
// @ts-ignore
10+
return (info.weekInfo.firstDay ?? 0) % 7; // (in Intl, sunday is 7 not 0, so we need to mod 7)
11+
}

packages/svelte-ux/src/lib/utils/dateRange.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { startOfDay, isLeapYear, isAfter, isBefore, subYears } from 'date-fns';
22

3-
import { getDateFuncsByPeriodType, PeriodType } from './date';
3+
import { getDateFuncsByPeriodType } from './date';
4+
import { PeriodType } from './date_types';
45

56
export type DateRange = {
67
from: Date | null;

packages/svelte-ux/src/lib/utils/date_types.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { DateRange } from './dateRange';
2+
13
export type SelectedDate = Date | Date[] | DateRange | null;
24

35
export type Period = {
@@ -110,15 +112,15 @@ export enum DateToken {
110112
}
111113

112114
export type OrdinalSuffixes = {
113-
one: string;
114-
two: string;
115-
few: string;
116-
other: string;
115+
one?: string;
116+
two?: string;
117+
few?: string;
118+
other?: string;
117119
zero?: string;
118120
many?: string;
119121
};
120122
export type DateFormatVariant = 'short' | 'default' | 'long';
121-
type DateFormatVariantPreset = {
123+
export type DateFormatVariantPreset = {
122124
short?: CustomIntlDateTimeFormatOptions;
123125
default?: CustomIntlDateTimeFormatOptions;
124126
long?: CustomIntlDateTimeFormatOptions;

0 commit comments

Comments
 (0)