code icon Code

Parse Natural Dates

Parse natural language dates ("next Tuesday", "in 2 weeks")

Source Code

import fs from "fs";
import path from "path";
import * as chrono from "chrono-node";

const [inputPath, textField, outputField, outputPath, referenceDate = ""] = process.argv.slice(2);

if (!inputPath || !textField || !outputField || !outputPath) {
  console.error("Usage: inputPath textField outputField outputPath [referenceDate]");
  process.exit(1);
}

/**
 * Get nested field value using dot notation
 */
function getField(obj, fieldPath) {
  const parts = fieldPath.split(".");
  let val = obj;
  for (const part of parts) {
    if (val == null) return undefined;
    val = val[part];
  }
  return val;
}

/**
 * Set nested field value using dot notation
 */
function setField(obj, fieldPath, value) {
  const parts = fieldPath.split(".");
  let current = obj;

  for (let i = 0; i < parts.length - 1; i++) {
    const part = parts[i];
    if (!(part in current)) {
      current[part] = {};
    }
    current = current[part];
  }

  current[parts[parts.length - 1]] = value;
}

try {
  console.log(`Parsing natural language dates...`);
  console.log(`  Input: ${inputPath}`);
  console.log(`  Text field: ${textField}`);
  console.log(`  Output field: ${outputField}`);

  const raw = fs.readFileSync(inputPath, "utf-8");
  const data = JSON.parse(raw);

  const items = Array.isArray(data) ? data : data.items || data.results || [];

  if (!Array.isArray(items)) {
    console.error("Input must be a JSON array or object with array property");
    process.exit(1);
  }

  const refDate = referenceDate ? new Date(referenceDate) : new Date();
  console.log(`  Reference date: ${refDate.toISOString()}`);

  let parsed = 0;
  let failed = 0;

  const results = items.map((item) => {
    const newItem = { ...item };
    const text = getField(item, textField);

    if (text) {
      const parsedResults = chrono.parse(String(text), refDate);

      if (parsedResults.length > 0) {
        // Use the first parsed date
        const parsedDate = parsedResults[0].start.date();
        setField(newItem, outputField, parsedDate.toISOString());
        parsed++;
      } else {
        setField(newItem, outputField, null);
        failed++;
      }
    } else {
      setField(newItem, outputField, null);
      failed++;
    }

    return newItem;
  });

  // Ensure output directory exists
  const dir = path.dirname(outputPath);
  if (dir && dir !== ".") {
    fs.mkdirSync(dir, { recursive: true });
  }

  fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));

  console.log(`\nāœ“ Parsed dates`);
  console.log(`  Total items: ${items.length}`);
  console.log(`  Parsed: ${parsed}`);
  console.log(`  Failed: ${failed}`);
  console.log(`  Written to: ${outputPath}`);

  console.log(
    JSON.stringify({
      success: true,
      inputPath,
      outputPath,
      total: items.length,
      parsed,
      failed,
    })
  );
} catch (error) {
  console.error("Error:", error.message);
  process.exit(1);
}