Skip to content

Commit

Permalink
Format last qol for next (#203)
Browse files Browse the repository at this point in the history
* add WeekStartsOn defaults depending on the locale

* init a Language Select comp
  • Loading branch information
jycouet authored Jan 9, 2024
1 parent fa009e0 commit 44f07aa
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 135 deletions.
49 changes: 49 additions & 0 deletions packages/svelte-ux/src/lib/components/LanguageSelect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script lang="ts">
import Button from './Button.svelte';
import Menu from './Menu.svelte';
import MenuItem from './MenuItem.svelte';
import { cls } from '../utils/styles';
import { getSettings } from './settings';
const { locale } = getSettings();
let open = false;
type Language = {
name: string;
code: string;
flag: string;
};
export let languagesDemo: Language[] = [
{ name: 'English', code: 'en', flag: '🇺🇸' },
{ name: 'Français', code: 'fr', flag: '🇫🇷' },
// add more for the demo
];
$: languageSelected = languagesDemo.find((c) => c.code === $locale)!;
</script>

<Button on:click={() => (open = !open)}>
{languageSelected.flag}
<Menu bind:open on:close={() => (open = false)} offset={4} explicitClose resize>
<div class="grid gap-2 p-2 border-b border-surface-content/10">
{#each languagesDemo as language}
<MenuItem
on:click={() => {
languageSelected = language;
locale.set(language.code);
}}
class={cls(
'bg-surface-100 text-surface-content font-semibold border shadow',
languageSelected === language && 'ring-2 ring-surface-content'
)}
>
{language.flag} - {language.name}
</MenuItem>
{/each}
</div>

<div class="p-2 grid grid-cols-[auto,1fr] gap-2 items-center text-xs">
<span class="font-medium">Affect dates & numbers formats</span>
</div>
</Menu>
</Button>
74 changes: 46 additions & 28 deletions packages/svelte-ux/src/lib/utils/date.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import { describe, it, expect } from 'vitest';
import {
PeriodType,
formatDate,
getMonthDaysByWeek,
localToUtcDate,
utcToLocalDate,
DayOfWeek,
formatIntl,
type CustomIntlDateTimeFormatOptions,
type FormatDateOptions,
DateToken,
formatDateWithLocale,
} from './date';
import { getSettings } from '$lib/components';
import { format, formatWithLocale } from '.';
import { formatWithLocale } from '.';
import { createLocaleSettings, defaultLocale } from './locale';
import {
PeriodType,
type FormatDateOptions,
DayOfWeek,
type CustomIntlDateTimeFormatOptions,
DateToken,
} from './date_types';
import { getWeekStartsOnFromIntl } from './dateInternal';

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?)
const dt_2M_2d = new Date(2023, 10, 21);
const dt_2M_1d = new Date(2023, 10, 7);
const dt_1M_1d = new Date(2023, 2, 7);
const dt_first = new Date(2024, 1, 1);

const dt_1M_1d_time_pm = new Date(2023, 2, 7, 14, 2, 3, 4);
const dt_1M_1d_time_am = new Date(2023, 2, 7, 1, 2, 3, 4);
Expand All @@ -30,21 +33,21 @@ const fr = createLocaleSettings({
dates: {
ordinalSuffixes: {
one: 'er',
two: '',
few: '',
other: '',
},
},
},
});

describe('formatDate()', () => {
it('should return empty string for null or undefined date', () => {
// @ts-expect-error
expect(formatDate(null)).equal('');
// @ts-expect-error
expect(formatDate(undefined)).equal('');
});

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

Expand Down Expand Up @@ -125,7 +128,7 @@ describe('formatDate()', () => {
}
});

describe('should format date for PeriodType.WeekSun / Mon', () => {
describe('should format date for PeriodType.WeekSun / Mon no mather the locale', () => {
const combi = [
[PeriodType.WeekSun, 'short', defaultLocale, '11/19 - 11/25'],
[PeriodType.WeekSun, 'short', fr, '19/11 - 25/11'],
Expand All @@ -138,32 +141,28 @@ describe('formatDate()', () => {
for (const c of combi) {
const [periodType, variant, locales, expected] = c;
it(c.toString(), () => {
expect(formatDateWithLocale(locales, DATE, periodType, { variant, locales })).equal(
expected
);
expect(formatDateWithLocale(locales, DATE, periodType, { variant })).equal(expected);
});
}
});

describe('should format date for PeriodType.Week', () => {
describe('should format date for PeriodType.Week with the good weekstarton locale', () => {
const combi = [
[PeriodType.Week, 'short', defaultLocale, DayOfWeek.Sunday, '11/19 - 11/25'],
[PeriodType.Week, 'short', fr, DayOfWeek.Sunday, '19/11 - 25/11'],
[PeriodType.Week, 'long', defaultLocale, DayOfWeek.Sunday, '11/19/2023 - 11/25/2023'],
[PeriodType.Week, 'long', fr, DayOfWeek.Sunday, '19/11/2023 - 25/11/2023'],

[PeriodType.Week, 'short', defaultLocale, DayOfWeek.Monday, '11/20 - 11/26'],
[PeriodType.Week, 'short', fr, DayOfWeek.Monday, '20/11 - 26/11'],
[PeriodType.Week, 'long', defaultLocale, DayOfWeek.Monday, '11/20/2023 - 11/26/2023'],
[PeriodType.Week, 'long', fr, DayOfWeek.Monday, '20/11/2023 - 26/11/2023'],
[PeriodType.Week, 'short', defaultLocale, '11/19 - 11/25'],
[PeriodType.Week, 'short', fr, '20/11 - 26/11'],
[PeriodType.Week, 'long', defaultLocale, '11/19/2023 - 11/25/2023'],
[PeriodType.Week, 'long', fr, '20/11/2023 - 26/11/2023'],

[PeriodType.Week, 'short', defaultLocale, '11/19 - 11/25'],
[PeriodType.Week, 'short', fr, '20/11 - 26/11'],
[PeriodType.Week, 'long', defaultLocale, '11/19/2023 - 11/25/2023'],
[PeriodType.Week, 'long', fr, '20/11/2023 - 26/11/2023'],
] as const;

for (const c of combi) {
const [periodType, variant, locales, weekStartsOn, expected] = c;
const [periodType, variant, locales, expected] = c;
it(c.toString(), () => {
expect(formatDateWithLocale(locales, DATE, periodType, { variant, weekStartsOn })).equal(
expected
);
expect(formatDateWithLocale(locales, DATE, periodType, { variant })).equal(expected);
});
}
});
Expand Down Expand Up @@ -286,6 +285,7 @@ describe('formatDate()', () => {
for (const c of combi) {
const [variant, locales] = c;
it(c.toString(), () => {
// @ts-expect-error
expect(formatDateWithLocale(locales, DATE, undefined, { variant })).equal(expected);
});
}
Expand Down Expand Up @@ -316,6 +316,7 @@ describe('formatIntl() tokens', () => {
[dt_2M_2d, { dateStyle: 'medium', withOrdinal: true }, ['Nov 21st, 2023', '21 nov. 2023']],
[dt_2M_2d, { dateStyle: 'short' }, ['11/21/23', '21/11/2023']],
[dt_1M_1d, { dateStyle: 'short' }, ['3/7/23', '07/03/2023']],
[dt_first, DateToken.DayOfMonth_withOrdinal, ['1st', '1er']],

// time
[dt_1M_1d_time_pm, [DateToken.Hour_numeric, DateToken.Minute_numeric], ['2:02 PM', '14:02']],
Expand Down Expand Up @@ -500,3 +501,20 @@ describe('getMonthDaysByWeek()', () => {
`);
});
});

describe('getWeekStartsOnFromIntl() tokens', () => {
it('by default, sunday', () => {
const val = getWeekStartsOnFromIntl();
expect(val).toBe(DayOfWeek.Sunday);
});

it('For en it should be synday', () => {
const val = getWeekStartsOnFromIntl('en');
expect(val).toBe(DayOfWeek.Sunday);
});

it('For fr it should be monday', () => {
const val = getWeekStartsOnFromIntl('fr');
expect(val).toBe(DayOfWeek.Monday);
});
});
74 changes: 41 additions & 33 deletions packages/svelte-ux/src/lib/utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ import {

import { hasKeyOf } from '../types/typeGuards';
import { chunk } from './array';
import type { DateRange } from './dateRange';
import { PeriodType, DayOfWeek, DateToken } from './date_types';
import {
PeriodType,
DayOfWeek,
DateToken,
type SelectedDate,
type CustomIntlDateTimeFormatOptions,
type FormatDateOptions,
type DateFormatVariantPreset,
} from './date_types';
import { defaultLocale, type LocaleSettings } from './locale';

export * from './date_types';
Expand Down Expand Up @@ -676,6 +683,7 @@ export function formatDateWithLocale(
}

const weekStartsOn = options.weekStartsOn ?? settings.formats.dates.weekStartsOn;

const { day, dayTime, timeOnly, week, month, monthsYear, year } = settings.formats.dates.presets;

if (periodType === PeriodType.Week) {
Expand Down Expand Up @@ -723,80 +731,80 @@ export function formatDateWithLocale(

switch (periodType) {
case PeriodType.Custom:
return formatIntl(settings, date, options.custom);
return formatIntl(settings, date, options.custom!);

case PeriodType.Day:
return formatIntl(settings, date, rv(day));
return formatIntl(settings, date, rv(day!)!);

case PeriodType.DayTime:
return formatIntl(settings, date, rv(dayTime));
return formatIntl(settings, date, rv(dayTime!)!);

case PeriodType.TimeOnly:
return formatIntl(settings, date, rv(timeOnly));
return formatIntl(settings, date, rv(timeOnly!)!);

case PeriodType.WeekSun:
return range(settings, date, 0, rv(week));
return range(settings, date, 0, rv(week!)!);
case PeriodType.WeekMon:
return range(settings, date, 1, rv(week));
return range(settings, date, 1, rv(week!)!);
case PeriodType.WeekTue:
return range(settings, date, 2, rv(week));
return range(settings, date, 2, rv(week!)!);
case PeriodType.WeekWed:
return range(settings, date, 3, rv(week));
return range(settings, date, 3, rv(week!)!);
case PeriodType.WeekThu:
return range(settings, date, 4, rv(week));
return range(settings, date, 4, rv(week!)!);
case PeriodType.WeekFri:
return range(settings, date, 5, rv(week));
return range(settings, date, 5, rv(week!)!);
case PeriodType.WeekSat:
return range(settings, date, 6, rv(week));
return range(settings, date, 6, rv(week!)!);

case PeriodType.Month:
return formatIntl(settings, date, rv(month));
return formatIntl(settings, date, rv(month!)!);

case PeriodType.MonthYear:
return formatIntl(settings, date, rv(monthsYear));
return formatIntl(settings, date, rv(monthsYear!)!);

case PeriodType.Quarter:
return [
formatIntl(settings, startOfQuarter(date), rv(month)),
formatIntl(settings, endOfQuarter(date), rv(monthsYear)),
formatIntl(settings, startOfQuarter(date), rv(month!)!),
formatIntl(settings, endOfQuarter(date), rv(monthsYear!)!),
].join(' - ');

case PeriodType.CalendarYear:
return formatIntl(settings, date, rv(year));
return formatIntl(settings, date, rv(year!)!);

case PeriodType.FiscalYearOctober:
const fDate = new Date(getFiscalYear(date), 0, 1);
return formatIntl(settings, fDate, rv(year));
return formatIntl(settings, fDate, rv(year!)!);

case PeriodType.BiWeek1Sun:
return range(settings, date, 0, rv(week), 1);
return range(settings, date, 0, rv(week!)!, 1);
case PeriodType.BiWeek1Mon:
return range(settings, date, 1, rv(week), 1);
return range(settings, date, 1, rv(week!)!, 1);
case PeriodType.BiWeek1Tue:
return range(settings, date, 2, rv(week), 1);
return range(settings, date, 2, rv(week!)!, 1);
case PeriodType.BiWeek1Wed:
return range(settings, date, 3, rv(week), 1);
return range(settings, date, 3, rv(week!)!, 1);
case PeriodType.BiWeek1Thu:
return range(settings, date, 4, rv(week), 1);
return range(settings, date, 4, rv(week!)!, 1);
case PeriodType.BiWeek1Fri:
return range(settings, date, 5, rv(week), 1);
return range(settings, date, 5, rv(week!)!, 1);
case PeriodType.BiWeek1Sat:
return range(settings, date, 6, rv(week), 1);
return range(settings, date, 6, rv(week!)!, 1);

case PeriodType.BiWeek2Sun:
return range(settings, date, 0, rv(week), 2);
return range(settings, date, 0, rv(week!)!, 2);
case PeriodType.BiWeek2Mon:
return range(settings, date, 1, rv(week), 2);
return range(settings, date, 1, rv(week!)!, 2);
case PeriodType.BiWeek2Tue:
return range(settings, date, 2, rv(week), 2);
return range(settings, date, 2, rv(week!)!, 2);
case PeriodType.BiWeek2Wed:
return range(settings, date, 3, rv(week), 2);
return range(settings, date, 3, rv(week!)!, 2);
case PeriodType.BiWeek2Thu:
return range(settings, date, 4, rv(week), 2);
return range(settings, date, 4, rv(week!)!, 2);
case PeriodType.BiWeek2Fri:
return range(settings, date, 5, rv(week), 2);
return range(settings, date, 5, rv(week!)!, 2);
case PeriodType.BiWeek2Sat:
return range(settings, date, 6, rv(week), 2);
return range(settings, date, 6, rv(week!)!, 2);

default:
return formatISO(date);
Expand Down
11 changes: 11 additions & 0 deletions packages/svelte-ux/src/lib/utils/dateInternal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DayOfWeek } from './date_types';

export function getWeekStartsOnFromIntl(locales?: string): DayOfWeek {
if (!locales) {
return DayOfWeek.Sunday;
}

const info = new Intl.Locale(locales);
// @ts-ignore
return (info.weekInfo.firstDay ?? 0) % 7; // (in Intl, sunday is 7 not 0, so we need to mod 7)
}
3 changes: 2 additions & 1 deletion packages/svelte-ux/src/lib/utils/dateRange.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { startOfDay, isLeapYear, isAfter, isBefore, subYears } from 'date-fns';

import { getDateFuncsByPeriodType, PeriodType } from './date';
import { getDateFuncsByPeriodType } from './date';
import { PeriodType } from './date_types';

export type DateRange = {
from: Date | null;
Expand Down
12 changes: 7 additions & 5 deletions packages/svelte-ux/src/lib/utils/date_types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { DateRange } from './dateRange';

export type SelectedDate = Date | Date[] | DateRange | null;

export type Period = {
Expand Down Expand Up @@ -110,15 +112,15 @@ export enum DateToken {
}

export type OrdinalSuffixes = {
one: string;
two: string;
few: string;
other: string;
one?: string;
two?: string;
few?: string;
other?: string;
zero?: string;
many?: string;
};
export type DateFormatVariant = 'short' | 'default' | 'long';
type DateFormatVariantPreset = {
export type DateFormatVariantPreset = {
short?: CustomIntlDateTimeFormatOptions;
default?: CustomIntlDateTimeFormatOptions;
long?: CustomIntlDateTimeFormatOptions;
Expand Down
Loading

0 comments on commit 44f07aa

Please sign in to comment.