Weekday - Date - Month type

Volodymyr Yepishev - Jul 29 '22 - - Dev Community

Suppose out of the sudden you are dealing with an object, which besides well-known fields has a number of fields which are named after the day of the week, date and month, and contain some value related to that particular day. I.e. let's say you keep a record how many times you're happy to use Typescript instead of pure Javascript:

The final code is available in the playground.

const howHappyToUseTypescriptHistory = {
    Thu29Jul: Infinity,
    overall: Infinity
}
Enter fullscreen mode Exit fullscreen mode

So far good, type is inferred, but what will happen on Friday? Need to add another record, recalculate the average, inferred type changes. Is there a way to type such construction at all?

Turns out, there is. Typescript v4 provides superpowers to do that.

Let's start with creating type for weekdays:

type DaysOfTheWeek = 'Mon' | 'Tue' | 'Wen' | 'Thu' | 'Fri' | 'Sat' | 'Sun';
Enter fullscreen mode Exit fullscreen mode

Now types for months, in accordance with how many days they have (omitting February):

type ThirtyOneDaysMonths = 'Jan' | 'Mar' | 'May' | 'Jul' | 'Aug' | 'Oct' | 'Dec';
type ThirtyDaysMonths = 'Apr' | 'Jun' | 'Sep' | 'Nov';
Enter fullscreen mode Exit fullscreen mode

All months can have 29-30-31 days top, so that's 3 sets of types using Typescript's template literals:

type OneThroughNine = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type OneThroughTwentyNine = `${0 | 1 | 2}${OneThroughNine}`;
type OneThroughThirty = OneThroughTwentyNine | '30';
type OneThroughThirtyOne = OneThroughThirty | "31";
Enter fullscreen mode Exit fullscreen mode

At this point you've probably guessed that we're simply going to use a generic now to glue all these strings together into our DayDateMonth format:

type DayOfWeekDateMonth<DaysInMonth extends string, Months extends string> = `${DaysOfTheWeek}${DaysInMonth}${Months}`;
type DayDateMonth = DayOfWeekDateMonth<OneThroughTwentyNine, 'Feb'> | DayOfWeekDateMonth<OneThroughThirty, ThirtyDaysMonths> | DayOfWeekDateMonth<OneThroughThirtyOne, ThirtyOneDaysMonths>;
Enter fullscreen mode Exit fullscreen mode

With the type for our strings ready, we can define a generic record to store data for different days:

type DayDateMonthRecord<T> = {
    [key in DayDateMonth]?: T;
};
Enter fullscreen mode Exit fullscreen mode

A mapped type may not declare its own properties or methods, so in order to add average or any other field, we would need to extend DayDateMonthRecord<T>:

interface ListWithAverage<T> extends DayDateMonthRecord<T> {
  average: number;
}
Enter fullscreen mode Exit fullscreen mode

Finally, we can type the initial object:

const howHappyToUseTypescriptHistory: ListWithAverage<number> = {
    Thu29Jul: Infinity,
    overall: Infinity
}
Enter fullscreen mode Exit fullscreen mode

Now we have strict typing with intellisense if needed :)

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .