Every language
12 langages, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
function relativeTime(date, now = new Date()) {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const UNITS = [
['year', 60 * 60 * 24 * 365],
['month', 60 * 60 * 24 * 30],
['day', 60 * 60 * 24],
['hour', 60 * 60],
['minute', 60],
['second', 1],
];
const delta = (date.getTime() - now.getTime()) / 1000; // seconds, past is negative
for (const [unit, secs] of UNITS) {
if (Math.abs(delta) >= secs || unit === 'second') {
return rtf.format(Math.round(delta / secs), unit);
}
}
}
relativeTime(new Date(Date.now() - 3 * 60 * 60 * 1000)); // '3 hours ago'Intl.RelativeTimeFormat formats a (value, unit) pair — it does not CHOOSE the unit. Walking the table and picking is your job; numeric: 'auto' turns 1 into 'a minute' and 0 into 'now' ('always' is the mechanical '1 minute ago').
TSTypeScript
export function relativeTime(
delta: number, // epoch milliseconds of the instant to describe
now: Date = new Date(),
): string {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const UNITS: ReadonlyArray<[Intl.RelativeTimeFormatUnit, number]> = [
['year', 31_536_000],
['month', 2_592_000],
['day', 86_400],
['hour', 3_600],
['minute', 60],
['second', 1],
];
const secs = (delta - now.getTime()) / 1000;
for (const [unit, size] of UNITS) {
if (Math.abs(secs) >= size || unit === 'second') {
return rtf.format(Math.round(secs / size), unit);
}
}
}Same API as the JS version with the unit table typed as Intl.RelativeTimeFormatUnit and an injectable now for tests. The numeric option is a site-wide contract: 'auto' reads human, 'always' reads mechanical — pick once.
GoGo
import (
"fmt"
"time"
)
// RelativeTime renders t as "3 hours ago" relative to now (same time zone!).
func RelativeTime(t, now time.Time) string {
d := now.Sub(t) // time.Duration — int64 nanoseconds
switch {
case d < time.Minute:
return fmt.Sprintf("%d seconds ago", int(d.Seconds()))
case d < time.Hour:
return fmt.Sprintf("%d minutes ago", int(d.Minutes()))
case d < 24*time.Hour:
return fmt.Sprintf("%d hours ago", int(d.Hours()))
case d < 30*24*time.Hour:
return fmt.Sprintf("%d days ago", int(d.Hours())/24)
case d < 365*24*time.Hour:
return fmt.Sprintf("%d months ago", int(d.Hours())/24/30)
default:
return fmt.Sprintf("%d years ago", int(d.Hours())/24/365)
}
}time.Duration is int64 NANOSECONDS: `d < 86400` compiles — and compares against 86.4 microseconds. Spell every threshold in time.Minute / 24*time.Hour products. No stdlib relative formatter exists; the switch is the idiom.
RsRust
use chrono::{DateTime, Utc};
fn relative_time(t: DateTime<Utc>, now: DateTime<Utc>) -> String {
// TimeDelta (now - t) flattened to whole seconds for the table
let s = (now - t).num_seconds().max(0);
match s {
0..=44 => "a few seconds ago".into(),
45..=89 => "a minute ago".into(),
90..=2_699 => format!("{} minutes ago", (s + 30) / 60),
2_700..=5_399 => "an hour ago".into(),
5_400..=86_399 => format!("{} hours ago", (s + 1_800) / 3_600),
86_400..=2_591_999 => format!("{} days ago", s / 86_400),
2_592_000..=31_535_999 => format!("about {} months ago", s / 2_592_000),
_ => format!("about {} years ago", s / 31_536_000),
}
}chrono::TimeDelta is (days, secs) — num_seconds() flattens it for the table. Months and years need calendar math: these fall back to 'about N' over flat 30-day months; chrono's Months arithmetic is the calendar-exact route.
PHPPHP
function relative_time(int $epochThen, ?int $now = null): string
{
$now ??= time();
$delta = $epochThen - $now; // < 0 means the past
$abs = abs($delta); // threshold on the magnitude…
foreach (
[31536000 => 'year', 2592000 => 'month', 86400 => 'day',
3600 => 'hour', 60 => 'minute', 1 => 'second'] as $size => $unit
) {
if ($abs >= $size) {
$n = intdiv($abs, $size);
$phrase = $n === 1 ? "a $unit" : "$n {$unit}s";
return $delta < 0 ? "$phrase ago" : "in $phrase"; // …sign re-attached here
}
}
}abs() the delta, run the thresholds on the magnitude, re-attach the sign only when phrasing — formatting the signed value directly prints 'in -3 hours'. time() is epoch seconds from one clock; feed both operands from it.
PyPython
from datetime import datetime, timezone
def relative_time(dt: datetime, now: datetime | None = None) -> str:
now = now or datetime.now(timezone.utc)
secs = (now - dt).total_seconds() # aware − aware, same zone
age = abs(secs)
for unit, size in (('year', 31_536_000), ('month', 2_592_000),
('day', 86_400), ('hour', 3_600),
('minute', 60), ('second', 1)):
if age >= size or unit == 'second':
n = int(age // size)
plural = '' if n == 1 else 's'
return f'{n} {unit}{plural} ago' if secs > 0 else f'in {n} {unit}{plural}'The stdlib has NO humanizer — humanize/arrow are the third-party answers. Subtract two tz-aware datetimes in the same zone (datetime.now(timezone.utc)): aware minus naive raises, and mixing local clocks across a DST fold produces negative ages.
C#C#
using System;
static string RelativeTime(DateTimeOffset then, DateTimeOffset now)
{
TimeSpan d = now - then; // positive = the past
bool past = d >= TimeSpan.Zero;
TimeSpan a = d.Duration(); // TimeSpan's abs()
(int n, string unit) = a switch
{
_ when a >= TimeSpan.FromDays(365) => (a.Days / 365, "year"),
_ when a >= TimeSpan.FromDays(30) => (a.Days / 30, "month"),
_ when a >= TimeSpan.FromDays(1) => (a.Days, "day"),
_ when a >= TimeSpan.FromHours(1) => (a.Hours, "hour"),
_ when a >= TimeSpan.FromMinutes(1) => (a.Minutes, "minute"),
_ => (a.Seconds, "second"),
};
string phrase = $"{n} {unit}{(n == 1 ? "" : "s")}";
return past ? $"{phrase} ago" : $"in {phrase}";
}DateTimeOffset over DateTime — the offset travels with the value, so the subtraction can never mix zones. For testability take a TimeProvider (.NET 8+) and read provider.GetUtcNow() instead of calling the wall clock directly.
JvJava
import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.time.ZoneOffset;
static String relativeTime(Instant then, Instant now) {
long secs = Duration.between(then, now).abs().getSeconds();
long n;
String unit;
if (secs < 60) { n = secs; unit = "second"; }
else if (secs < 3600) { n = secs / 60; unit = "minute"; }
else if (secs < 86400) { n = secs / 3600; unit = "hour"; }
else if (secs < 2592000) { n = secs / 86400; unit = "day"; }
else {
// Duration tops out at days — months/years are calendar math
long months = Period.between(
then.atZone(ZoneOffset.UTC).toLocalDate(),
now.atZone(ZoneOffset.UTC).toLocalDate()).toTotalMonths();
if (months < 12) return months + (months == 1 ? " month ago" : " months ago");
long years = months / 12;
return years + (years == 1 ? " year ago" : " years ago");
}
return n + " " + unit + (n == 1 ? " ago" : "s ago");
}Duration.toDays() is where Duration STOPS — there is no toMonths(). Calendar-exact months and years come from Period.between on the LocalDate side; Duration only ever handles the sub-month part.
SwSwift
import Foundation
func relativeTime(_ date: Date, relativeTo now: Date = Date()) -> String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated // '3h ago'; .full → '3 hours ago'
formatter.dateTimeStyle = .named // 'yesterday' when it lands exactly
formatter.locale = Locale(identifier: "en_US")
return formatter.localizedString(for: date, relativeTo: now)
}
print(relativeTime(Date(timeIntervalSinceNow: -3 * 3600))) // '3h ago'Foundation ships the whole feature — RelativeDateTimeFormatter picks the unit AND localizes. unitsStyle: .abbreviated gives '3h ago', .full gives '3 hours ago'; dateTimeStyle .named yields 'yesterday'. Formatters are expensive — reuse one when rendering a list.
KtKotlin
import java.time.Duration
import java.time.Instant
import java.time.Period
import java.time.ZoneOffset
fun relativeTime(then: Instant, now: Instant = Instant.now()): String {
val secs = Duration.between(then, now).abs().seconds
val (n, unit) = when {
secs < 60 -> secs to "second"
secs < 3_600 -> secs / 60 to "minute"
secs < 86_400 -> secs / 3_600 to "hour"
secs < 2_592_000 -> secs / 86_400 to "day"
else -> {
// Duration has no months — that half lives in Period
val months = Period.between(
then.atZone(ZoneOffset.UTC).toLocalDate(),
now.atZone(ZoneOffset.UTC).toLocalDate()
).toTotalMonths()
if (months < 12) return "$months month${if (months == 1L) "" else "s"} ago"
val years = months / 12
return "$years year${if (years == 1L) "" else "s"} ago"
}
}
return "$n $unit${if (n == 1L) "" else "s"} ago"
}Same JVM split as Java — Period vs Duration is the line: Duration below a month, Period.between(...).toTotalMonths() above it. The default-arg now keeps the function pure and coroutine-clean (no clock capture mid-suspension); inject a Clock for virtual time.
RbRuby
def relative_time(t, now = Time.now)
secs = (now - t).to_i.abs # Time#− is Float seconds — to_i it
table = { 'year' => 31_536_000, 'month' => 2_592_000, 'day' => 86_400,
'hour' => 3_600, 'minute' => 60 }
unit, size = table.find { |_, s| secs >= s } || ['second', 1]
n = secs / size
phrase = "#{n} #{unit}#{'s' unless n == 1}"
now < t ? "in #{phrase}" : "#{phrase} ago"
endTime#− returns Float seconds — to_i before dividing or '59.999 seconds ago' leaks out. The stdlib has no formatter at all; ActiveSupport's time_ago_in_words (Rails only) is the drop-in when you are inside Rails.
ZigZig
const std = @import("std");
/// `then` / `now` are epoch SECONDS (std.time.timestamp()).
fn relativeTime(buf: []u8, then: i64, now: i64) ![]u8 {
const delta = now - then;
const past = delta >= 0;
const secs: u64 = @abs(delta);
const Unit = struct { name: []const u8, size: u64 };
const table = [_]Unit{
.{ .name = "year", .size = 365 * 86400 }, // flat 365 — no calendar
.{ .name = "month", .size = 30 * 86400 }, // flat 30 — 'about a month'
.{ .name = "day", .size = 86400 },
.{ .name = "hour", .size = 3600 },
.{ .name = "minute", .size = 60 },
.{ .name = "second", .size = 1 },
};
for (table) |u| {
if (secs >= u.size) {
const n = secs / u.size;
const plural: []const u8 = if (n == 1) "" else "s";
return if (past)
std.fmt.bufPrint(buf, "{d} {s}{s} ago", .{ n, u.name, plural })
else
std.fmt.bufPrint(buf, "in {d} {s}{s}", .{ n, u.name, plural });
}
}
return std.fmt.bufPrint(buf, "now", .{});
}std.time.timestamp() returns epoch SECONDS — JS/Java sources in milliseconds must be divided by 1000 first. std has no civil calendar at all, so months are flat 30 days and years flat 365; a day of drift at boundaries is the accepted cost.