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