code icon Code

Format Duration

Format duration in milliseconds/seconds to human readable (e.g., '2h 30m', '5 days')

Source Code

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

const [inputPath, durationField = "", unit = "ms", outputField = "duration", outputPath = ""] = process.argv.slice(2);

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

/**
 * Format duration to human readable string
 */
function formatDuration(durationMs, compact = false) {
  if (typeof durationMs !== "number" || isNaN(durationMs)) return "unknown";
  if (durationMs < 0) durationMs = Math.abs(durationMs);

  const seconds = Math.floor(durationMs / 1000);
  const minutes = Math.floor(seconds / 60);
  const hours = Math.floor(minutes / 60);
  const days = Math.floor(hours / 24);
  const weeks = Math.floor(days / 7);
  const months = Math.floor(days / 30);
  const years = Math.floor(days / 365);

  if (compact) {
    // Short format: "2h 30m"
    if (years > 0) return `${years}y ${days % 365}d`;
    if (months > 0) return `${months}mo ${days % 30}d`;
    if (weeks > 0) return `${weeks}w ${days % 7}d`;
    if (days > 0) return `${days}d ${hours % 24}h`;
    if (hours > 0) return `${hours}h ${minutes % 60}m`;
    if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
    if (seconds > 0) return `${seconds}s`;
    return `${durationMs}ms`;
  }

  // Long format: "2 hours 30 minutes"
  if (years > 0) return `${years} year${years !== 1 ? "s" : ""} ${days % 365} day${days % 365 !== 1 ? "s" : ""}`;
  if (months > 0) return `${months} month${months !== 1 ? "s" : ""} ${days % 30} day${days % 30 !== 1 ? "s" : ""}`;
  if (weeks > 0) return `${weeks} week${weeks !== 1 ? "s" : ""} ${days % 7} day${days % 7 !== 1 ? "s" : ""}`;
  if (days > 0) return `${days} day${days !== 1 ? "s" : ""} ${hours % 24} hour${hours % 24 !== 1 ? "s" : ""}`;
  if (hours > 0) return `${hours} hour${hours !== 1 ? "s" : ""} ${minutes % 60} minute${minutes % 60 !== 1 ? "s" : ""}`;
  if (minutes > 0) return `${minutes} minute${minutes !== 1 ? "s" : ""} ${seconds % 60} second${seconds % 60 !== 1 ? "s" : ""}`;
  if (seconds > 0) return `${seconds} second${seconds !== 1 ? "s" : ""}`;
  return `${durationMs} millisecond${durationMs !== 1 ? "s" : ""}`;
}

/**
 * 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;
}

/**
 * Convert to milliseconds
 */
function toMs(value, unit) {
  const num = typeof value === "number" ? value : parseFloat(value);
  if (isNaN(num)) return NaN;
  return unit === "s" ? num * 1000 : num;
}

try {
  let result;

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

    if (Array.isArray(data)) {
      result = data.map(item => {
        const durationValue = getField(item, durationField);
        const durationMs = toMs(durationValue, unit);
        const newItem = { ...item };
        setField(newItem, outputField, formatDuration(durationMs, true));
        setField(newItem, `${outputField}Long`, formatDuration(durationMs, false));
        return newItem;
      });
      console.log(`Formatted ${result.length} durations`);
    } else if (typeof data === "number") {
      const durationMs = toMs(data, unit);
      result = {
        raw: data,
        [outputField]: formatDuration(durationMs, true),
        [`${outputField}Long`]: formatDuration(durationMs, false)
      };
      console.log(`Formatted: ${result[outputField]}`);
    } else {
      const durationValue = getField(data, durationField);
      const durationMs = toMs(durationValue, unit);
      result = { ...data };
      setField(result, outputField, formatDuration(durationMs, true));
      setField(result, `${outputField}Long`, formatDuration(durationMs, false));
      console.log(`Formatted: ${result[outputField]}`);
    }
  } else {
    // Treat input as a duration value
    const durationMs = toMs(inputPath, unit);
    result = {
      raw: inputPath,
      [outputField]: formatDuration(durationMs, true),
      [`${outputField}Long`]: formatDuration(durationMs, false)
    };
    console.log(`Formatted: ${result[outputField]}`);
  }

  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);
}