code icon Code

Format Relative Time

Format dates as relative time strings (e.g., '5 minutes ago', '2 days ago')

Source Code

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

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

if (!inputPath) {
  console.error("Usage: stdlib.time.ago <inputPath|date> [dateField] [outputField] [outputPath]");
  process.exit(1);
}

/**
 * Format date as relative time string
 */
function formatTimeAgo(date) {
  if (!date) return "unknown";
  const d = new Date(date);
  if (isNaN(d.getTime())) return "invalid date";

  const seconds = Math.floor((Date.now() - d.getTime()) / 1000);

  if (seconds < 0) {
    // Future date
    const absSeconds = Math.abs(seconds);
    if (absSeconds < 60) return "in a few seconds";
    if (absSeconds < 3600) return `in ${Math.floor(absSeconds / 60)}m`;
    if (absSeconds < 86400) return `in ${Math.floor(absSeconds / 3600)}h`;
    if (absSeconds < 604800) return `in ${Math.floor(absSeconds / 86400)}d`;
    return `in ${Math.floor(absSeconds / 604800)}w`;
  }

  if (seconds < 10) return "just now";
  if (seconds < 60) return `${seconds}s ago`;

  const minutes = Math.floor(seconds / 60);
  if (minutes < 60) return `${minutes}m ago`;

  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours}h ago`;

  const days = Math.floor(hours / 24);
  if (days < 7) return `${days}d ago`;

  const weeks = Math.floor(days / 7);
  if (days < 30) return `${weeks}w ago`;

  const months = Math.floor(days / 30);
  if (months < 12) return `${months}mo ago`;

  const years = Math.floor(days / 365);
  return `${years}y ago`;
}

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

/**
 * Set nested field value
 */
function setField(obj, fieldPath, value) {
  const parts = fieldPath.split(".");
  let current = obj;
  for (let i = 0; i < parts.length - 1; i++) {
    if (!current[parts[i]]) current[parts[i]] = {};
    current = current[parts[i]];
  }
  current[parts[parts.length - 1]] = value;
}

try {
  // Check if input is a file or a date string
  let result;

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

    if (Array.isArray(data)) {
      // Process array of items
      result = data.map(item => {
        const dateValue = getField(item, dateField);
        const newItem = { ...item };
        setField(newItem, outputField, formatTimeAgo(dateValue));
        return newItem;
      });
      console.log(`Formatted ${result.length} dates as relative time`);
    } else if (typeof data === "object" && data !== null) {
      // Process single object
      const dateValue = getField(data, dateField);
      result = { ...data };
      setField(result, outputField, formatTimeAgo(dateValue));
      console.log(`Formatted date as: ${result[outputField]}`);
    } else {
      // Process single date value
      result = { date: data, [outputField]: formatTimeAgo(data) };
      console.log(`Formatted: ${result[outputField]}`);
    }
  } else {
    // Treat input as a date string
    const ago = formatTimeAgo(inputPath);
    result = { date: inputPath, [outputField]: ago };
    console.log(`Formatted: ${ago}`);
  }

  if (outputPath) {
    const dir = path.dirname(outputPath);
    if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
    console.log(`Written to: ${outputPath}`);
  }

  console.log(JSON.stringify({ success: true, result: Array.isArray(result) ? { count: result.length } : result }));
} catch (error) {
  console.error("Error:", error.message);
  process.exit(1);
}