Ralph Spandl
Ralph Spandl

Reputation: 151

Intl.DateTimeFormat causes TypeScript error certain options

I have the following code that works perfectly:

const option = {
    year: "numeric",
    month: "short",
    day: "numeric",
    weekday: 'long',
}

const myDate = new Intl.DateTimeFormat('de', {
    year: "numeric",
    month: "short",
    day: "numeric",
    weekday: 'long',
}).format(date)

const myDateTest2 = new Intl.DateTimeFormat('de', option as any).format(date)

This means I get a date if I pass the object directly into the constructor. It also works if I define the options first and then use any.

But this is throwing a TypeScript error:

const myDateTest = new Intl.DateTimeFormat('de', option).format(date)

as follows:

Argument of type '{ year: string; month: string; day: string; weekday: string; }' is not assignable to parameter of type 'DateTimeFormatOptions'.
  Types of property 'weekday' are incompatible.
    Type 'string' is not assignable to type '"short" | "long" | "narrow"'.ts(2345)

Why is this?

I tried multiple TS configurations, but none of them solve the issue.

Here is what I have now:

"compilerOptions": {
    "allowJs": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "ES2020",
    "sourceMap": true,
    "outDir": "./.tmp/build/",
    "moduleResolution": "node",
    "declaration": true,
    "lib": [
      "ES2020",
      "ES2020.Intl",
      "dom"
    ],
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true
  },

TypeScript version is "typescript": "^4.8.4",

Upvotes: 1

Views: 2508

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42160

Problem: Variable has too broad of a type

What you are experiencing here is a common problem when dealing with functions that require string literal types in their arguments. It's nothing specific to Intl.DateTimeFormat. You can read the docs on Literal Inference for more info.

Let's take a look at what your error is telling you:

Argument of type '{ year: string; month: string; day: string; weekday: string; }' is not assignable to parameter of type 'DateTimeFormatOptions'.

Types of property 'weekday' are incompatible.

Type 'string' is not assignable to type '"short" | "long" | "narrow"'.ts(2345)

The type of your option.weekday is inferred as string. This is not specific enough for the Intl.DateTimeFormat constructor, which only allows three possible string literal values: "short" | "long" | "narrow".

The value of your option variable is fine. But TypeScript only looks at types, not values.

When you write your assignment statement like this:

const option = {
    year: "numeric",
    month: "short",
    day: "numeric",
    weekday: "long",
}

TypeScript infers the type as:

const option: {
    year: string;
    month: string;
    day: string;
    weekday: string;
}

We need to create your variable in a way that will give it a narrower type.

Solution 1: Declare the type

We know from your error that the type which your option variable needs to fit is Intl.DateTimeFormatOptions. We can use that type when you create the option variable.

const option: Intl.DateTimeFormatOptions = {
    year: "numeric",
    month: "short",
    day: "numeric",
    weekday: "long",
}

TypeScript will check each of the properties of your object to make sure that they are assignable to the constraints of the Intl.DateTimeFormatOptions type.

If you provide an invalid value, like this:

const option: Intl.DateTimeFormatOptions = {
    weekday: "numeric",
}

You'll see a red underline on the weekday property. I prefer this approach for that reason -- it is easier to see exactly where the problems are. For weekday: "numeric" your error message would be:

Type '"numeric"' is not assignable to type '"long" | "short" | "narrow" | undefined'

Solution 2: as const

You can change the way that TypeScript infers the type by using an as const statement when you create your variable. This tells TypeScript to treat all properties of your object as their literal types (explained here).

const option = {
    year: "numeric",
    month: "short",
    day: "numeric",
    weekday: "long",
} as const;

TypeScript infers the type as:

const option: {
    readonly year: "numeric";
    readonly month: "short";
    readonly day: "numeric";
    readonly weekday: "long";
}

Each property has a string literal type like "long" rather than just string.

Assuming that your values are valid, these literal types will be assignable to the broader Intl.DateTimeFormatOptions type. For example, "long" is assignable to the union type '"short" | "long" | "narrow"' on the weekday property.

Upvotes: 9

Related Questions