Search code examples
typescriptluxon

Luxon: Cannot assign luxon DateTime when it's created from ISO string


When I create a luxon DateTime object from the fromISO method, seems like it cannot be assigned to DateTime properly.

e.g.

let datetime: DateTime = DateTime.fromISO(isoString);

does not compile with the error:

Type 'DateTime<true> | DateTime<false>' is not assignable to type 'DateTime<boolean>'.
  Type 'DateTime<true>' is missing the following properties from type 'DateTime<boolean>': isWeekend, localWeekday, localWeekNumber, localWeekYear, weeksInLocalWeekYear

What is a proper way to construct DateTime from ISO string?


Solution

  • This is a somewhat complicated one. I'll provide some explanation below, but — in short — you can avoid the issue either by:

    • not using an explicit annotation, and instead simply rely on TypeScript's inference when initializing your variable:

      TS Playground

      import { DateTime } from "luxon";
      
      const isoString = "2017-05-15T08:30:00";
      let dt = DateTime.fromISO(isoString);
      //  ^? let dt: DateTime<true> | DateTime<false>
      
    • or you can use the exported type alias DateTimeMaybeValid to annotate the instance:

      TS Playground

      import { DateTime, type DateTimeMaybeValid } from "luxon";
      
      const isoString = "2017-05-15T08:30:00";
      let dt: DateTimeMaybeValid = DateTime.fromISO(isoString);
      //  ^? let dt: DateTime<true> | DateTime<false>
      

    More:

    Luxon has a concept of validity. By default, DateTimes fail silently instead of throwing exceptions, and the validity information is stored on the DateTime instances. Here's an example:

    <script type="module">
    
    import { DateTime } from "https://cdn.jsdelivr.net/npm/[email protected]/build/es6/luxon.js";
    
    for (const input of ["2017-05-15T08:30:00", "tomorrow"]) {
      const dt = DateTime.fromISO(input);
      const { isValid, invalidExplanation, invalidReason } = dt;
      const text = dt.toString();
      console.log({ input, isValid, invalidReason, invalidExplanation, text });
    }
    
    </script>

    However, Luxon can be configured to throw in cases where an invalid DateTime would be produced:

    <script type="module">
    
    import { DateTime, Settings } from "https://cdn.jsdelivr.net/npm/[email protected]/build/es6/luxon.js";
    
    Settings.throwOnInvalid = true;
    
    for (const input of ["2017-05-15T08:30:00", "tomorrow"]) {
      try {
        const dt = DateTime.fromISO(input);
        const text = dt.toString();
        console.log({ input, valid: true, text });
      } catch (cause) {
        console.log({ input, valid: false });
        console.error(cause);
      }
    }
    
    </script>

    In the type system, DateTime is generic in an attempt to encode the validity state so that the compiler can use it to narrow:

    TS Playground

    for (const input of ["2017-05-15T08:30:00", "tomorrow"]) {
      const dt = DateTime.fromISO(input);
      if (dt.isValid) {
        dt.invalidReason
        // ^? (property) DateTime<true>.invalidReason: null
        dt.invalidExplanation
        // ^? (property) DateTime<true>.invalidExplanation: null
      } else {
        dt.invalidReason
        // ^? (property) DateTime<false>.invalidReason: string
        dt.invalidExplanation
        // ^? (property) DateTime<false>.invalidExplanation: string | null
      }
    }
    

    Note: You can inform the TypeScript compiler that you enabled the exception-throwing option (above): 1, 2