Ctrl K
ComunidadGuías…

shared/utils/parseNaturalLanguageDate.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Type-only import is fully erased at compile time, so it does not pull
// chrono-node into the bundle.
import type * as Chrono from "chrono-node";

/**
 * chrono-node is a sizeable dependency, so it is loaded lazily on first use
 * via a dynamic import. The bundler splits it into its own chunk that is only
 * fetched when a date actually needs to be parsed (i.e. when the user types in
 * the mention menu), keeping it out of the main bundle.
 */
let chronoPromise: Promise<typeof Chrono> | undefined;

function loadChrono(): Promise<typeof Chrono> {
  if (!chronoPromise) {
    chronoPromise = import("chrono-node").catch((err) => {
      // Don't cache a rejected import (e.g. a transient chunk-load failure),
      // otherwise every subsequent parse would reuse the failure. Clearing it
      // lets the next call retry.
      chronoPromise = undefined;
      throw err;
    });
  }
  return chronoPromise;
}

export interface ParsedNaturalLanguageDate {
  /** The matched date, at local midnight unless a time was given. */
  date: Date;
  /** Whether the input named a specific time of day. */
  hasTime: boolean;
}

/**
 * Parse a natural language string such as "tomorrow", "next friday",
 * "jan 2", "in 3 days" or "1pm" into a calendar date.
 *
 * The time component is only kept when the input actually named one,
 * otherwise the matched date is normalized to local midnight. chrono-node is
 * loaded asynchronously the first time this is called.
 *
 * @param input the natural language string to parse.
 * @param referenceDate the date relative to which terms like "tomorrow"
 * are resolved, defaults to now.
 * @returns a promise resolving to the matched date and whether it is
 * time-specific, or null when no date could be confidently parsed.
 */
export async function parseNaturalLanguageDate(
  input: string,
  referenceDate: Date = new Date()
): Promise<ParsedNaturalLanguageDate | null> {
  const trimmed = input.trim();
  if (!trimmed) {
    return null;
  }

  const chrono = await loadChrono();
  const results = chrono.parse(trimmed, referenceDate, { forwardDate: true });
  const result = results[0];
  if (!result) {
    return null;
  }

  // Only accept matches that span (roughly) the whole input so that
  // unrelated text typed after "@" does not accidentally resolve to a date.
  if (result.text.trim().length < trimmed.length) {
    return null;
  }

  const date = result.start.date();
  const hasTime = result.start.isCertain("hour");

  return {
    date: hasTime
      ? new Date(
          date.getFullYear(),
          date.getMonth(),
          date.getDate(),
          date.getHours(),
          date.getMinutes()
        )
      : new Date(date.getFullYear(), date.getMonth(), date.getDate()),
    hasTime,
  };
}