code icon Code

Filter by Date Range

Filter items by date range with support for relative dates

Source Code

import fs from "fs";
import path from "path";

const [inputPath, dateField, startArg, endArg, outputPath] =
  process.argv.slice(2);

if (!inputPath || !dateField || !startArg || !endArg || !outputPath) {
  console.error("Usage: inputPath dateField start end outputPath");
  process.exit(1);
}

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

/**
 * Parse relative date string like "7d", "1m", "1y"
 */
function parseRelativeDate(str) {
  if (str === "now") {
    return new Date();
  }

  const match = str.match(/^(\d+)([dmywh])$/);
  if (!match) {
    return null;
  }

  const [, num, unit] = match;
  const amount = parseInt(num);
  const now = new Date();

  switch (unit) {
    case "h":
      return new Date(now.getTime() - amount * 60 * 60 * 1000);
    case "d":
      return new Date(now.getTime() - amount * 24 * 60 * 60 * 1000);
    case "w":
      return new Date(now.getTime() - amount * 7 * 24 * 60 * 60 * 1000);
    case "m":
      const monthsAgo = new Date(now);
      monthsAgo.setMonth(monthsAgo.getMonth() - amount);
      return monthsAgo;
    case "y":
      const yearsAgo = new Date(now);
      yearsAgo.setFullYear(yearsAgo.getFullYear() - amount);
      return yearsAgo;
    default:
      return null;
  }
}

/**
 * Parse date from string (ISO or relative)
 */
function parseDate(str) {
  // Try relative first
  const relative = parseRelativeDate(str);
  if (relative) return relative;

  // Try ISO/standard date
  const date = new Date(str);
  return isNaN(date.getTime()) ? null : date;
}

try {
  console.log(`Reading ${inputPath}...`);
  const raw = fs.readFileSync(inputPath, "utf-8");
  const data = JSON.parse(raw);

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

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

  const startDate = parseDate(startArg);
  const endDate = parseDate(endArg);

  if (!startDate) {
    console.error(`Invalid start date: ${startArg}`);
    process.exit(1);
  }
  if (!endDate) {
    console.error(`Invalid end date: ${endArg}`);
    process.exit(1);
  }

  console.log(
    `Filtering ${items.length} items where ${dateField} between ${startDate.toISOString()} and ${endDate.toISOString()}`
  );

  const filtered = items.filter((item) => {
    const fieldValue = getField(item, dateField);
    if (!fieldValue) return false;

    const itemDate = new Date(fieldValue);
    if (isNaN(itemDate.getTime())) return false;

    return itemDate >= startDate && itemDate <= endDate;
  });

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

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

  console.log(`\nāœ“ Filtered ${items.length} → ${filtered.length} items`);
  console.log(`  Date field: ${dateField}`);
  console.log(`  Range: ${startDate.toISOString().split("T")[0]} to ${endDate.toISOString().split("T")[0]}`);
  console.log(`  Written to: ${outputPath}`);

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      inputCount: items.length,
      outputCount: filtered.length,
      dateRange: {
        start: startDate.toISOString(),
        end: endDate.toISOString(),
      },
    })
  );
} catch (error) {
  console.error("Error:", error.message);
  process.exit(1);
}