-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrelative-time.html
More file actions
655 lines (619 loc) · 48.3 KB
/
Copy pathrelative-time.html
File metadata and controls
655 lines (619 loc) · 48.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Free Relative Time Calculator — Time Ago Tool | FreeDevTool</title>
<meta name="description" content="Convert timestamps to human-friendly relative time like "3 days ago" or "in 2 hours". Accepts Unix, ISO 8601, and common date strings. Free, fast, bulk mode included.">
<meta name="robots" content="index, follow">
<meta name="author" content="Anees Ur Rehman">
<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","datePublished":"2026-05-02","dateModified":"2026-05-19","inLanguage":"en-US","isPartOf":{"@type":"WebSite","name":"FreeDevTool","url":"https://freedevtool.org"}}</script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"Person","name":"Anees Ur Rehman","url":"https://freedevtool.org/about","jobTitle":"Full-stack developer","worksFor":{"@type":"Organization","name":"FreeDevTool","url":"https://freedevtool.org"}}</script>
<link rel="canonical" href="https://freedevtool.org/relative-time">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=DM+Sans:wght@300;400;500;600&display=swap" as="style">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=DM+Sans:wght@300;400;500;600&display=swap">
<link rel="preload" href="style.css?v=20260502-cards" as="style">
<link rel="stylesheet" href="style.css?v=20260502-cards">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/favicon.svg">
<meta property="og:image" content="https://freedevtool.org/og-image.svg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="FreeDevTool — 50+ free, fast, privacy-first developer tools">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://freedevtool.org/og-image.svg">
<meta name="twitter:title" content="Relative Time Calculator — '3 Days Ago' Free Tool">
<meta name="twitter:description" content="Convert any timestamp into '3 days ago' / 'in 2 hours' phrases. Auto-detect epoch sec/ms/us, ISO 8601, RFC 2822. Bulk mode, live update.">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-3L0CMH3X36"></script>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','G-3L0CMH3X36');</script>
<meta property="og:title" content="Relative Time Calculator — Free Timestamp Converter">
<meta property="og:description" content="Convert timestamps to human-friendly relative phrases. Unix, ISO 8601, bulk mode, live clock.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://freedevtool.org/relative-time">
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"WebApplication","name":"Relative Time Calculator","url":"https://freedevtool.org/relative-time","description":"Convert timestamps to human-readable relative time with Unix, ISO 8601 support, breakdown, and bulk mode.","applicationCategory":"DeveloperApplication","operatingSystem":"Any","offers":{"@type":"Offer","price":"0","priceCurrency":"USD"}}
</script>
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"What is a relative time calculator?","acceptedAnswer":{"@type":"Answer","text":"A relative time calculator converts absolute timestamps (Unix epoch seconds, milliseconds, ISO 8601 strings, or natural date strings) into human-readable phrases relative to now, like '3 days ago', 'in 2 hours', or 'just now'. It's the same formatting style used in GitHub, Slack, and most social feeds, and is useful for debugging logs, building UI components, and verifying backend timestamps."}},{"@type":"Question","name":"What timestamp formats does this tool accept?","acceptedAnswer":{"@type":"Answer","text":"Auto-detection handles Unix seconds (10 digits), Unix milliseconds (13 digits), Unix microseconds (16 digits), ISO 8601 (2024-01-15T12:00:00Z), RFC 2822 (Mon, 15 Jan 2024 12:00:00 GMT), and many natural date strings parseable by JavaScript's Date. Negative numbers represent pre-1970 timestamps."}},{"@type":"Question","name":"How are units chosen for the relative phrase?","acceptedAnswer":{"@type":"Answer","text":"The tool picks the largest meaningful unit: under 45 seconds shows 'just now', then seconds up to 60, minutes up to 60, hours up to 24, days up to 7, weeks up to 4, months up to 12, then years. Singular/plural is handled correctly ('1 day ago' not '1 days ago'), and future timestamps use 'in X' rather than 'X ago'."}},{"@type":"Question","name":"Does the tool account for my local timezone?","acceptedAnswer":{"@type":"Answer","text":"Yes. The tool displays both the parsed local time (using your browser's IANA timezone) and the equivalent UTC. The relative phrase itself is timezone-agnostic — it's computed as the elapsed duration between the parsed instant and the current instant, which is the same number of seconds no matter where you are."}},{"@type":"Question","name":"How does bulk mode work?","acceptedAnswer":{"@type":"Answer","text":"Paste a list of timestamps (one per line, any mix of formats) into the bulk textarea and each line is parsed independently, producing a parallel column of relative phrases. This is useful for translating log files, CSV exports, or database dumps into human-readable form. Invalid lines are flagged inline."}},{"@type":"Question","name":"How do I display 5 minutes ago in JavaScript?","acceptedAnswer":{"@type":"Answer","text":"Use Intl.RelativeTimeFormat — the standard browser API. Create a new RelativeTimeFormat with locale en and format with a negative number and unit. It localizes automatically for the users language and handles plural/singular correctly. No library needed in modern browsers."}},{"@type":"Question","name":"What is Intl.RelativeTimeFormat?","acceptedAnswer":{"@type":"Answer","text":"A native JavaScript API (since 2018) for formatting relative time in any locale. Replaces the moment.js fromNow pattern and similar library calls. Built into every modern browser. Combine with Intl.DateTimeFormat for full localization without external dependencies."}},{"@type":"Question","name":"How does Twitter display time on tweets?","acceptedAnswer":{"@type":"Answer","text":"Twitter uses an extremely compact format: 30s for 30 seconds, 5m for 5 minutes, 2h for 2 hours, 3d for 3 days. After a week, the format switches to the absolute date. Compact format saves horizontal space in feeds."}}]}
</script>
<style>
.rt-input-row{display:flex;gap:10px;align-items:center;margin-bottom:10px;flex-wrap:wrap}
.rt-input-row input{flex:1;min-width:240px;padding:12px 14px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font-family:var(--mono);font-size:14px}
.rt-input-row input:focus{border-color:var(--accent);outline:none;box-shadow:0 0 0 3px rgba(0,208,132,.08)}
.rt-input-row button{padding:12px 18px;background:var(--bg4);border:1px solid var(--border);border-radius:var(--radius);color:var(--text2);font-family:var(--mono);font-size:13px;cursor:pointer;transition:all .2s}
.rt-input-row button:hover{border-color:var(--accent);color:var(--accent)}
.rt-detected{font-size:11px;color:var(--text3);font-family:var(--mono);margin-bottom:16px}
.rt-detected .fmt{color:var(--accent)}
.rt-hero{padding:24px;background:linear-gradient(180deg,var(--bg4),var(--bg));border:1px solid var(--border);border-radius:var(--radius);text-align:center;margin-bottom:16px}
.rt-hero-phrase{font-size:28px;font-weight:700;color:var(--accent);font-family:var(--mono);margin:0}
.rt-hero-sub{font-size:12px;color:var(--text3);font-family:var(--mono);margin-top:8px}
.rt-hero.err .rt-hero-phrase{color:#ff6b6b}
.rt-grid{display:grid;grid-template-columns:1fr 1fr;gap:2px;background:var(--border);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}
.rt-cell{display:flex;justify-content:space-between;padding:10px 14px;background:var(--bg);font-family:var(--mono);font-size:13px}
.rt-cell .lbl{color:var(--text3);font-size:11px}
.rt-cell .val{color:var(--text);font-weight:500;cursor:pointer}
.rt-cell .val:hover{color:var(--accent)}
.rt-breakdown{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:12px;margin-bottom:16px;font-family:var(--mono);font-size:12px}
.rt-breakdown h4{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--accent);margin:0 0 8px 0}
.rt-bd-row{display:grid;grid-template-columns:80px 1fr;gap:10px;padding:4px 0;color:var(--text2)}
.rt-bd-row .k{color:var(--text3)}
.rt-bulk{margin-top:18px}
.rt-bulk h3{font-size:12px;text-transform:uppercase;letter-spacing:1px;color:var(--accent);font-family:var(--mono);margin:0 0 10px 0;font-weight:600}
.rt-bulk-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.rt-bulk textarea,.rt-bulk-out{width:100%;min-height:180px;padding:10px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font-family:var(--mono);font-size:12px;box-sizing:border-box}
.rt-bulk textarea{resize:vertical}
.rt-bulk textarea:focus{border-color:var(--accent);outline:none;box-shadow:0 0 0 3px rgba(0,208,132,.08)}
.rt-bulk-out{white-space:pre;overflow:auto;color:var(--text2);line-height:1.55}
.rt-bulk-out .err{color:#ff8888}
@media(max-width:600px){.rt-grid,.rt-bulk-grid{grid-template-columns:1fr}.rt-hero-phrase{font-size:22px}}
</style>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://freedevtool.org/"
},
{
"@type": "ListItem",
"position": 2,
"name": "All Tools",
"item": "https://freedevtool.org/all-tools"
},
{
"@type": "ListItem",
"position": 3,
"name": "Relative Time Calculator",
"item": "https://freedevtool.org/relative-time"
}
]
}
</script>
<script src="/ga4-events.js" defer></script>
</head>
<body>
<nav>
<a class="nav-logo" href="/" aria-label="FreeDevTool home"><svg class="logo-mark" width="22" height="22" viewBox="0 0 24 24" aria-hidden="true" fill="none"><rect x="1" y="1" width="22" height="22" rx="6" fill="currentColor" opacity=".12"/><path d="M9.5 8.5L6 12l3.5 3.5M14.5 8.5L18 12l-3.5 3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>FreeDevTool</a>
<div class="nav-links">
<div class="nav-dropdown" id="tools-dropdown">
<a href="all-tools" onclick="event.preventDefault();this.parentElement.classList.toggle('open')" aria-haspopup="true">Tools <svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></a>
<div class="nav-dropdown-menu">
<a href="encoding-tools">
<div class="dd-icon">b64</div>
<div class="dd-info"><div class="dd-name">Encoding & Conversion</div><div class="dd-count">11 tools · Base64, YAML, px→rem</div></div>
</a>
<a href="generation-tools">
<div class="dd-icon">{ }</div>
<div class="dd-info"><div class="dd-name">Generation & Formatting</div><div class="dd-count">16 tools · JSON, SQL, gradients</div></div>
</a>
<a href="security-tools">
<div class="dd-icon">#</div>
<div class="dd-info"><div class="dd-name">Security & Hashing</div><div class="dd-count">3 tools · JWT, MD5, SHA</div></div>
</a>
<a href="text-tools">
<div class="dd-icon">.*</div>
<div class="dd-info"><div class="dd-name">Code & Text Tools</div><div class="dd-count">9 tools · Regex, diff, tokens</div></div>
</a>
<a href="devops-tools">
<div class="dd-icon">JS</div>
<div class="dd-info"><div class="dd-name">Optimization & DevOps</div><div class="dd-count">7 tools · Minifiers, cURL, git</div></div>
</a>
<a href="network-tools">
<div class="dd-icon">IP</div>
<div class="dd-info"><div class="dd-name">Network & Time</div><div class="dd-count">4 tools · IP, DNS, timestamps</div></div>
</a>
<a href="seo-tools">
<div class="dd-icon">SEO</div>
<div class="dd-info"><div class="dd-name">SEO & Meta Tools</div><div class="dd-count">3 tools · OG, meta, slug</div></div>
</a>
<div class="nav-dropdown-divider"></div>
<a class="dd-all" href="all-tools">
<div class="dd-icon">All</div>
<div class="dd-info"><div class="dd-name">Browse all 50 tools</div><div class="dd-count">Searchable catalog & categories</div></div>
</a>
</div>
</div>
<a href="/guides">Guides</a>
<a href="about">About</a>
<a href="privacy">Privacy</a>
</div>
</nav>
<div class="wrapper">
<section class="tool-header">
<a class="tool-back" href="/" aria-label="Back to home">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg>
Back
</a>
<span class="tool-badge">Time</span>
<h1>Relative Time Calculator</h1>
<p>This relative time calculator turns any timestamp into a human-readable phrase like "3 days ago" or "in 2 hours", the same style used in GitHub issues, Slack messages, and most modern UIs. Paste a Unix epoch (seconds, milliseconds, or microseconds), an ISO 8601 string, an RFC 2822 date, or virtually any date format your browser can parse — the tool auto-detects the format, shows the picked unit, breaks the delta down across seconds/minutes/hours/days/weeks/months/years, and displays both local and UTC times. A bulk mode converts whole lists at once, and the live clock keeps sub-minute phrases accurate.</p>
<div class="last-updated">Last updated: May 2026 · Written by <a href="/about">Anees Ur Rehman</a>, full-stack developer</div>
</section>
<div class="tool-card">
<div class="card-dots"><span></span><span></span><span></span></div>
<div class="tool-body">
<div class="rt-input-row">
<input type="text" id="rt-input" placeholder="Unix timestamp, ISO 8601, or any date string" spellcheck="false" autocomplete="off">
<button onclick="insertNow()">Now</button>
<button onclick="copyPhrase()">Copy phrase</button>
</div>
<div id="rt-detected" class="rt-detected"></div>
<div id="rt-hero" class="rt-hero">
<div class="rt-hero-phrase" id="rt-phrase">just now</div>
<div class="rt-hero-sub" id="rt-sub">Type a timestamp to get started</div>
</div>
<div class="rt-grid" id="rt-grid" style="display:none"></div>
<div class="rt-breakdown" id="rt-breakdown" style="display:none">
<h4>Delta breakdown</h4>
<div id="rt-bd-body"></div>
</div>
<div class="rt-bulk">
<h3>Bulk mode — one timestamp per line</h3>
<div class="rt-bulk-grid">
<textarea id="rt-bulk-in" placeholder="1700000000 2024-01-15T12:00:00Z Mon, 15 Jan 2024 12:00:00 GMT" spellcheck="false"></textarea>
<div id="rt-bulk-out" class="rt-bulk-out"></div>
</div>
</div>
</div>
</div>
<div id="toast" style="position:fixed;bottom:30px;left:50%;transform:translateX(-50%);background:var(--accent);color:#000;padding:8px 20px;border-radius:var(--radius);font-size:13px;font-weight:600;opacity:0;transition:opacity .3s;pointer-events:none;z-index:999">Copied!</div>
<p class="aeo-lead" style="font-size:16px;line-height:1.7;color:var(--text);max-width:760px;margin:24px auto 18px;padding:0 4px">
<strong>Relative time</strong> is the human-friendly way of displaying a timestamp: "5 minutes ago", "yesterday", "in 3 days". Different platforms use different conventions — Twitter "5m", GitHub "last Tuesday", Slack "yesterday at 3:14 PM". This <strong>free relative time calculator</strong> converts any timestamp into all major platform formats simultaneously.
</p>
<section id="examples" style="max-width:760px;margin:24px auto 32px">
<h2 style="font-size:18px;margin-bottom:14px">Examples</h2>
<div style="background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px">
<strong style="display:block;color:var(--accent);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Twitter format§§Now: "just now"§§30 sec ago: "30s"§§5 min ago: "5m"§§3 hours: "3h"§§yesterday: "1d"§§last year: "Jun 2025"§§Twitter format is the most compact — 2-3 chars max.</strong>
</div>
<div style="background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px">
<strong style="display:block;color:var(--accent);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">GitHub format§§5 min ago: "5 minutes ago"§§yesterday: "yesterday"§§3 days: "3 days ago"§§last month: "last month"§§last year: "on Jun 6, 2025"§§GitHub uses words, longer but more readable.</strong>
</div>
<div style="background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px">
<strong style="display:block;color:var(--accent);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Discord timestamps§§<t:1717094400:R> renders as "5 minutes ago" in any locale§§<t:1717094400:F> renders as "Thursday, May 30, 2026 12:00 PM"§§Discord auto-localizes timestamps with the right type modifier.</strong>
</div>
</section>
<aside class="founder-note" style="max-width:760px;margin:24px auto 32px;padding:20px 24px;background:rgba(0,208,132,0.05);border-left:3px solid var(--accent);border-radius:6px;font-size:14px;line-height:1.7;color:var(--text2)"><div style="font-family:var(--mono);font-size:11px;color:var(--accent);letter-spacing:1.5px;text-transform:uppercase;margin-bottom:10px;font-weight:600">💡 Why I built this</div><p style="margin:0 0 12px">I built this because every platform displays time differently: Twitter says ‘5m ago’, GitHub says ‘last Tuesday’, Slack says ‘yesterday at 3:14 PM’. When rebuilding a UI that needs to feel familiar, you need to know exactly which format each platform uses for which time range. This tool converts any timestamp to all major social/dev platform formats simultaneously — Twitter, GitHub, Slack, Discord, Linear.</p><p style="margin:0;font-size:13px;color:var(--text3)">— <a href="/about" style="color:var(--accent);text-decoration:none">Anees Ur Rehman</a>, full-stack developer</p></aside>
<section class="article-section">
<h2>Relative time formatting — the "3 days ago" pattern that ships in every modern UI</h2>
<p>Every successful product UI shows times relatively rather than absolutely. GitHub commits, Slack messages, Twitter posts, Stripe transactions, Linear issues — they all tell you "5 minutes ago" or "yesterday" rather than dumping a full ISO timestamp. Relative formatting feels human; absolute formatting feels like log output. This guide covers the threshold buckets that produce natural-sounding phrases, the <code>Intl.RelativeTimeFormat</code> API that ships in every modern browser, the i18n surprises (English uses "yesterday"; Japanese has separate "<ruby>昨日<rp>(</rp><rt>kinou</rt><rp>)</rp></ruby>" for spoken vs written), and the live-update pattern that keeps "1 minute ago" from going stale.</p>
<h3>The threshold buckets — where the boundaries are</h3>
<p>The convention used by Twitter, GitHub, Slack, and the JavaScript Intl spec all converge on roughly these thresholds:</p>
<table class="ref-table">
<thead><tr><th>Delta</th><th>Phrase</th><th>Notes</th></tr></thead>
<tbody>
<tr><td>0–10 s</td><td>"just now"</td><td>Sub-resolution; treats "right now" as a single bucket.</td></tr>
<tr><td>10–60 s</td><td>"42 seconds ago"</td><td>Show seconds for live feeds; some products skip and jump to "less than a minute ago".</td></tr>
<tr><td>1–60 min</td><td>"5 minutes ago"</td><td>The most common bucket for UI activity feeds.</td></tr>
<tr><td>1–24 h</td><td>"3 hours ago"</td><td>Round down (3.7 h shows as "3 hours ago", not "4").</td></tr>
<tr><td>1–7 days</td><td>"yesterday" / "3 days ago"</td><td>"Yesterday" is special-cased.</td></tr>
<tr><td>1–4 weeks</td><td>"2 weeks ago"</td><td>Some UIs prefer "10 days ago" up to ~14 days, then switch.</td></tr>
<tr><td>1–12 months</td><td>"6 months ago"</td><td>Calendar months, not 30-day blocks.</td></tr>
<tr><td>≥ 1 year</td><td>"3 years ago" or absolute date</td><td>GitHub switches to absolute "Mar 2023" past 1 year.</td></tr>
</tbody>
</table>
<h3>Intl.RelativeTimeFormat — the platform API</h3>
<p>The browser ships an i18n-aware relative-time formatter as of 2018 (Chrome 71, Firefox 65, Safari 14). It handles plurals, future vs past, and locale-specific phrasing automatically:</p>
<pre><code>const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
rtf.format(-1, 'day'); // "yesterday"
rtf.format( 0, 'day'); // "today"
rtf.format( 1, 'day'); // "tomorrow"
rtf.format(-3, 'day'); // "3 days ago"
rtf.format( 5, 'minute'); // "in 5 minutes"
// numeric: 'always' forces numbers (for log displays)
new Intl.RelativeTimeFormat('en', { numeric: 'always' })
.format(-1, 'day'); // "1 day ago"
// Different locales handle phrasing differently
new Intl.RelativeTimeFormat('ja', { numeric: 'auto' })
.format(-1, 'day'); // "昨日"
new Intl.RelativeTimeFormat('de', { numeric: 'auto' })
.format(-1, 'day'); // "gestern"</code></pre>
<p>This is the recommended approach in 2026. It eliminates the "I shipped my homegrown relative formatter and it broke for German users" class of bug.</p>
<h3>The live-update pattern — keeping "1 min ago" fresh</h3>
<p>If the user lands on the page and a comment was posted 30 seconds ago, the UI shows "30 seconds ago". One minute later it should say "1 minute ago", then "2 minutes ago", and so on. The standard implementation:</p>
<pre><code>function startRelativeTimeUpdates() {
const elements = document.querySelectorAll('time[datetime]');
function updateAll() {
const now = Date.now();
elements.forEach(el => {
const t = new Date(el.dateTime).getTime();
el.textContent = formatRelative(t - now);
});
}
updateAll();
setInterval(updateAll, 30_000); // refresh every 30s
}</code></pre>
<p>For a feed with hundreds of timestamps, batching updates like this beats a per-element timer. Stop the interval when the tab is hidden (use the Page Visibility API) to save battery.</p>
<h3>Why "n minutes ago" vs "in n minutes" matters more than it seems</h3>
<p>Future and past tense in relative time should never collide. A naive implementation might show:</p>
<ul>
<li><code>30 seconds ago</code> — the comment was posted just now (correct).</li>
<li><code>30 seconds ago</code> — your access token <em>expired</em> 30 seconds ago (also correct, but the phrase is identical).</li>
<li><code>in 30 seconds</code> — your access token expires in 30 seconds (different).</li>
</ul>
<p>Always preserve the sign of the delta. "Created 5 min ago" and "Expires in 5 min" are different states; collapsing them to the same phrase has caused real bugs in dashboard expiry warnings.</p>
<h3>Time-zone surprises — the "yesterday" trap</h3>
<p>"Yesterday" is a calendar concept, not a duration. A timestamp 18 hours ago might be "yesterday" or "today" depending on the user's local time:</p>
<ul>
<li>It is now 09:00 local. A timestamp 18 hours ago happened at 15:00 local <em>yesterday</em>. "Yesterday" is correct.</li>
<li>It is now 23:00 local. A timestamp 18 hours ago happened at 05:00 local <em>today</em>. "Today" / "18 hours ago" is correct.</li>
</ul>
<p>Use the local-date concept (zero-out the time portion of both timestamps and compare days) when you want to special-case "today", "yesterday", "last week".</p>
<h3>Locale differences that bite</h3>
<table class="ref-table">
<thead><tr><th>Locale</th><th>"3 days ago"</th><th>"in 3 days"</th></tr></thead>
<tbody>
<tr><td>English (en)</td><td>3 days ago</td><td>in 3 days</td></tr>
<tr><td>German (de)</td><td>vor 3 Tagen</td><td>in 3 Tagen</td></tr>
<tr><td>Spanish (es)</td><td>hace 3 días</td><td>dentro de 3 días</td></tr>
<tr><td>French (fr)</td><td>il y a 3 jours</td><td>dans 3 jours</td></tr>
<tr><td>Japanese (ja)</td><td>3 日前</td><td>3 日後</td></tr>
<tr><td>Arabic (ar)</td><td>قبل 3 أيام</td><td>خلال 3 أيام</td></tr>
<tr><td>Russian (ru)</td><td>3 дня назад</td><td>через 3 дня</td></tr>
</tbody>
</table>
<p>Languages with grammatical gender (Russian, Arabic) or different plural rules (Russian: 1 / 2-4 / 5+ all use different forms) make hand-rolling impossible. Always use Intl.RelativeTimeFormat for any product going beyond English.</p>
<h3>Library landscape — when to reach for what</h3>
<table class="ref-table">
<thead><tr><th>Tool</th><th>Bundle size</th><th>Best for</th></tr></thead>
<tbody>
<tr><td>Intl.RelativeTimeFormat</td><td>0 (built-in)</td><td>Everything new in 2026.</td></tr>
<tr><td><code>date-fns</code> formatDistanceToNow</td><td>~14 KB minified+gzipped</td><td>Apps already using date-fns; tree-shakable.</td></tr>
<tr><td><code>day.js</code> + relativeTime plugin</td><td>~3 KB</td><td>Lightweight Moment.js replacement.</td></tr>
<tr><td><code>luxon</code> toRelative()</td><td>~70 KB</td><td>Heavy-duty calendars and TZ work.</td></tr>
<tr><td>Moment.js fromNow()</td><td>~232 KB</td><td>Legacy code only — Moment is in maintenance mode.</td></tr>
<tr><td>Vercel <code>@formatjs/intl-relativetimeformat</code></td><td>~6 KB + locale files</td><td>Polyfill for older browsers (still useful for India/Africa Android fleets).</td></tr>
</tbody>
</table>
<h3>Common relative-time mistakes</h3>
<ul>
<li><strong>Forgetting to update.</strong> A static "5 minutes ago" rendered server-side becomes hilariously wrong an hour later. Always re-render or invalidate.</li>
<li><strong>Using "ago" for future timestamps.</strong> The expired/expires distinction must respect sign.</li>
<li><strong>Hand-rolling pluralization.</strong> "1 minutes ago" looks broken; "1 minute ago" requires plural-aware code. Intl handles this.</li>
<li><strong>Skipping the absolute timestamp on hover.</strong> "Yesterday" is friendly; for debugging or audit trails users need the actual timestamp. Render the absolute time in a <code>title</code> or <code><time datetime></code> attribute so it's accessible.</li>
<li><strong>Treating every event as relative.</strong> A meeting "in 3 hours" is helpful; a contract that "expires in 11 months" is not — show the actual date for far-future and far-past events.</li>
<li><strong>Forgetting screen readers.</strong> Wrap relative times in <code><time datetime="2026-05-02T14:30:00Z">3 hours ago</time></code> so assistive tech can announce the exact value if needed.</li>
<li><strong>Server / client clock drift.</strong> If your server thinks a comment was 0.1 s ago and the client thinks 30 s have passed, the relative phrase jumps. Use server-rendered relative time on initial load, then re-render on the client.</li>
</ul>
<aside class="article-aside">
<strong>Authoritative references:</strong>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat" rel="noopener" target="_blank">MDN — Intl.RelativeTimeFormat reference</a></li>
<li><a href="https://tc39.es/ecma402/#relativetimeformat-objects" rel="noopener" target="_blank">ECMA-402 — RelativeTimeFormat specification</a></li>
<li><a href="https://date-fns.org/docs/formatDistanceToNow" rel="noopener" target="_blank">date-fns — formatDistanceToNow documentation</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time" rel="noopener" target="_blank">MDN — <time> element (machine-readable timestamps)</a></li>
<li><a href="https://cldr.unicode.org/" rel="noopener" target="_blank">Unicode CLDR — locale data behind Intl APIs</a></li>
</ul>
</aside>
</section>
<section class="faq-section">
<h2>Frequently Asked Questions</h2>
<div class="faq-item"><div class="faq-q" onclick="toggleFaq(this)">What is a relative time calculator?<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></div><div class="faq-a">A <strong>relative time calculator</strong> converts an absolute timestamp — Unix epoch, ISO 8601, RFC 2822, or a natural date string — into a human-readable phrase relative to <em>right now</em>: "3 days ago", "in 2 hours", "last week", "just now". It's the same formatting vocabulary you see in GitHub issues, Slack, Twitter, and most modern UIs. Developers use it to sanity-check log timestamps, design notification copy, debug TTL/expiry fields, or translate large lists of backend timestamps into something humans can skim.</div></div>
<div class="faq-item"><div class="faq-q" onclick="toggleFaq(this)">What timestamp formats does this tool accept?<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></div><div class="faq-a">Auto-detection handles the common formats: <strong>Unix seconds</strong> (10-digit integer), <strong>Unix milliseconds</strong> (13-digit — JavaScript's native epoch), <strong>Unix microseconds</strong> (16-digit, sometimes emitted by Python and Postgres), <strong>ISO 8601</strong> (<code>2024-01-15T12:00:00Z</code> and offset variants), <strong>RFC 2822</strong> (<code>Mon, 15 Jan 2024 12:00:00 GMT</code>), and any natural string JavaScript's <code>Date</code> constructor accepts. Negative integers represent pre-1970 epochs. The detected format is shown just above the result.</div></div>
<div class="faq-item"><div class="faq-q" onclick="toggleFaq(this)">How are units chosen for the relative phrase?<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></div><div class="faq-a">The tool picks the largest meaningful unit given the magnitude: under 45 seconds renders as <strong>"just now"</strong>; up to 60 seconds as <strong>seconds</strong>; up to 60 minutes as <strong>minutes</strong>; up to 24 hours as <strong>hours</strong>; up to 6 days as <strong>days</strong>; up to 4 weeks as <strong>weeks</strong>; up to 12 months as <strong>months</strong>; then <strong>years</strong>. Singular/plural is handled correctly ("1 day ago", not "1 days ago"). Future timestamps are prefixed with "in " instead of suffixed with " ago". These thresholds match the Intl.RelativeTimeFormat style used by major libraries.</div></div>
<div class="faq-item"><div class="faq-q" onclick="toggleFaq(this)">Does the tool account for my local timezone?<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></div><div class="faq-a">Yes, in two ways. First, the <strong>absolute time</strong> display shows the parsed instant rendered in your browser's IANA timezone, alongside the equivalent UTC — so you can quickly see that a Unix timestamp stored in a database maps to, say, 14:30 local and 19:30 UTC. Second, the <strong>relative phrase</strong> is timezone-independent — it's the elapsed duration between two instants in real time, which is the same number of seconds no matter where you or the server are located.</div></div>
<div class="faq-item"><div class="faq-q" onclick="toggleFaq(this)">How does bulk mode work?<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></div><div class="faq-a">Paste a list of timestamps — one per line, any mix of formats — into the left textarea and the right column fills with the equivalent relative phrases. Useful for translating log exports, CSV columns, or database dumps into something you can skim in a PR review. Invalid lines show an <code>invalid</code> marker rather than aborting the whole batch, so you can copy-paste messy data without pre-cleaning it.</div></div>
<div class="faq-item"><div class="faq-q" onclick="toggleFaq(this)">Why does the phrase update every second?<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></div><div class="faq-a">"Now" is a moving target — a timestamp that's 42 seconds ago becomes 43 seconds ago one second later. The tool recomputes the relative phrase every second via a <code>setInterval</code> so sub-minute phrases stay accurate in real time. Once the delta exceeds a minute, the visible phrase only changes when the unit threshold is crossed (e.g., from "59 minutes" to "1 hour"), but the internal calculation keeps ticking.</div></div>
<div class="faq-item"><div class="faq-q" onclick="toggleFaq(this)">What's the difference between Unix seconds and milliseconds?<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></div><div class="faq-a">Both count the time since the Unix epoch (1970-01-01 00:00:00 UTC) — the difference is the scale. <strong>Seconds</strong> (10 digits as of now, e.g., <code>1710000000</code>) are standard in Unix, Go, and most databases. <strong>Milliseconds</strong> (13 digits, e.g., <code>1710000000000</code>) are native to JavaScript (<code>Date.now()</code>) and Java. <strong>Microseconds</strong> (16 digits) occasionally appear in Postgres and Python <code>time.time_ns()</code>. This tool detects which one you pasted based on the digit count and parses accordingly.</div></div>
</section>
<section class="related-section">
<h2>Related Tools</h2>
<div class="related-grid">
<a class="related-card" href="unix-timestamp-converter"><div class="related-icon">⏱</div><div class="related-card-info"><div class="related-card-name">UNIX Timestamp Converter</div><div class="related-card-desc">Convert between epoch and date</div></div></a>
<a class="related-card" href="timestamp-diff"><div class="related-icon">Δt</div><div class="related-card-info"><div class="related-card-name">Timestamp Diff</div><div class="related-card-desc">Compute duration between dates</div></div></a>
<a class="related-card" href="jwt-decoder"><div class="related-icon">JWT</div><div class="related-card-info"><div class="related-card-name">JWT Decoder</div><div class="related-card-desc">Inspect iat / exp claims</div></div></a>
</div>
</section>
<section class="all-tools-section" aria-label="Browse all FreeDevTool developer tools">
<h2>Browse all 50 free developer tools</h2>
<p class="atc-sub">All tools run in your browser, no signup required, nothing sent to a server.</p>
<div class="all-tools-grid">
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">b64</div>
<div class="atc-cat-title"><h3>Encoding & Conversion</h3><span class="atc-cat-count">11 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/base64-encoder">Base64 Encoder / Decoder</a></li>
<li><a href="/base64-image">Image to Base64</a></li>
<li><a href="/byte-converter">Byte Converter (KB / MB / GB)</a></li>
<li><a href="/case-converter">Case Converter</a></li>
<li><a href="/hex-to-rgb">Hex to RGB / HSL</a></li>
<li><a href="/html-entity">HTML Entity Encoder</a></li>
<li><a href="/json-to-csv">JSON to CSV Converter</a></li>
<li><a href="/px-to-rem">PX to REM Converter</a></li>
<li><a href="/string-escape">String Escape / Unescape</a></li>
<li><a href="/url-encoder">URL Encoder / Decoder</a></li>
<li><a href="/yaml-to-json">YAML to JSON Converter</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">{ }</div>
<div class="atc-cat-title"><h3>Formatting & Generators</h3><span class="atc-cat-count">13 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/color-name">Color Name from Hex</a></li>
<li><a href="/color-picker">Color Palette Picker</a></li>
<li><a href="/css-box-shadow">CSS Box Shadow</a></li>
<li><a href="/css-gradient">CSS Gradient Generator</a></li>
<li><a href="/json-formatter">JSON Formatter / Validator</a></li>
<li><a href="/lorem-ipsum">Lorem Ipsum Generator</a></li>
<li><a href="/markdown-preview">Markdown Preview</a></li>
<li><a href="/password-generator">Password Generator</a></li>
<li><a href="/qr-generator">QR Code Generator</a></li>
<li><a href="/sql-formatter">SQL Formatter</a></li>
<li><a href="/uuid-generator">UUID Generator</a></li>
<li><a href="/word-to-markdown">Word to Markdown</a></li>
<li><a href="/xml-formatter">XML Formatter</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">JS</div>
<div class="atc-cat-title"><h3>Minifiers & DevOps</h3><span class="atc-cat-count">6 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/chmod-calculator">chmod Calculator</a></li>
<li><a href="/cron-parser">Cron Expression Parser</a></li>
<li><a href="/css-minifier">CSS Minifier</a></li>
<li><a href="/html-minifier">HTML Minifier</a></li>
<li><a href="/js-minifier">JavaScript Minifier</a></li>
<li><a href="/http-status">HTTP Status Codes</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">#</div>
<div class="atc-cat-title"><h3>Security & Hashing</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/hash-generator">Hash Generator (MD5, SHA)</a></li>
<li><a href="/jwt-decoder">JWT Decoder</a></li>
<li><a href="/jwt-generator">JWT Generator</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">.*</div>
<div class="atc-cat-title"><h3>Code & Text</h3><span class="atc-cat-count">8 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/ai-token-counter">AI Token Counter</a></li>
<li><a href="/char-counter">Character & Word Counter</a></li>
<li><a href="/git-cheatsheet">Git Commands Cheatsheet</a></li>
<li><a href="/number-base">Number Base Converter</a></li>
<li><a href="/regex-explainer">Regex Explainer</a></li>
<li><a href="/regex-tester">Regex Tester</a></li>
<li><a href="/text-diff">Text Diff Checker</a></li>
<li><a href="/wcag-contrast">WCAG Contrast Checker</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">IP</div>
<div class="atc-cat-title"><h3>Network & APIs</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/dns-lookup">DNS Lookup</a></li>
<li><a href="/http-request-builder">HTTP Request Builder</a></li>
<li><a href="/ip-lookup">IP Address Lookup</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">⏱</div>
<div class="atc-cat-title"><h3>Time & Dates</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/relative-time">Relative Time Calculator</a></li>
<li><a href="/timestamp-diff">Timestamp Diff</a></li>
<li><a href="/unix-timestamp-converter">Unix Timestamp Converter</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">SEO</div>
<div class="atc-cat-title"><h3>SEO & Meta</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/meta-tag-generator">Meta Tag Generator</a></li>
<li><a href="/og-preview">Open Graph Preview</a></li>
<li><a href="/slug-generator">URL Slug Generator</a></li>
</ul>
</div>
</div>
</section>
</div>
<footer>
<div>© 2026 FreeDevTool. Built for developers, by developers.</div>
<div class="footer-links"><a href="/all-tools">All Tools</a><a href="/about">About</a><a href="/privacy">Privacy Policy</a><a href="/terms">Terms of Use</a></div>
</footer>
<script>
function parseTimestamp(raw){
const s=String(raw).trim();
if(!s)return{ok:false,reason:'empty'};
if(/^-?\d+(\.\d+)?$/.test(s)){
const n=Number(s);
const abs=Math.abs(n);
let ms,fmt;
if(abs>=1e15){ms=n/1000;fmt='Unix microseconds';}
else if(abs>=1e12){ms=n;fmt='Unix milliseconds';}
else if(abs>=1e9||abs===0){ms=n*1000;fmt='Unix seconds';}
else{ms=n*1000;fmt='Unix seconds';}
const d=new Date(ms);
if(isNaN(d.getTime()))return{ok:false,reason:'out of range'};
return{ok:true,date:d,fmt:fmt};
}
const d=new Date(s);
if(isNaN(d.getTime()))return{ok:false,reason:'unrecognized format'};
let fmt='Date string';
if(/^\d{4}-\d{2}-\d{2}T/.test(s))fmt='ISO 8601';
else if(/^[A-Z][a-z]{2},/.test(s))fmt='RFC 2822';
return{ok:true,date:d,fmt:fmt};
}
function pluralize(n,unit){return Math.abs(n)===1?unit:unit+'s';}
function relativePhrase(date,now){
const diff=date.getTime()-now.getTime();
const abs=Math.abs(diff);
const sec=Math.round(abs/1000);
const future=diff>0;
if(sec<45)return 'just now';
let n,unit;
if(sec<60){n=sec;unit='second';}
else if(sec<3600){n=Math.round(sec/60);unit='minute';}
else if(sec<86400){n=Math.round(sec/3600);unit='hour';}
else if(sec<86400*7){n=Math.round(sec/86400);unit='day';}
else if(sec<86400*30){n=Math.round(sec/(86400*7));unit='week';}
else if(sec<86400*365){n=Math.round(sec/(86400*30));unit='month';}
else{n=Math.round(sec/(86400*365));unit='year';}
const phrase=n+' '+pluralize(n,unit);
return future?('in '+phrase):(phrase+' ago');
}
function breakdown(date,now){
const diff=date.getTime()-now.getTime();
const abs=Math.abs(diff);
const s=abs/1000;
return{
milliseconds:Math.round(abs),
seconds:s,
minutes:s/60,
hours:s/3600,
days:s/86400,
weeks:s/(86400*7),
months:s/(86400*30.4375),
years:s/(86400*365.25),
sign:diff>=0?'future':'past'
};
}
function fmtNum(n){
if(Math.abs(n)>=100)return n.toLocaleString('en-US',{maximumFractionDigits:2});
if(Math.abs(n)>=10)return n.toLocaleString('en-US',{maximumFractionDigits:3});
return n.toLocaleString('en-US',{maximumFractionDigits:4});
}
let currentDate=null;
function renderOne(){
const raw=document.getElementById('rt-input').value;
const detected=document.getElementById('rt-detected');
const hero=document.getElementById('rt-hero');
const phrase=document.getElementById('rt-phrase');
const sub=document.getElementById('rt-sub');
const grid=document.getElementById('rt-grid');
const bd=document.getElementById('rt-breakdown');
const bdBody=document.getElementById('rt-bd-body');
if(!raw.trim()){
detected.innerHTML='';
hero.classList.remove('err');
phrase.textContent='just now';
sub.textContent='Type a timestamp to get started';
grid.style.display='none';
bd.style.display='none';
currentDate=null;
return;
}
const p=parseTimestamp(raw);
if(!p.ok){
detected.innerHTML='<span class="fmt">Could not parse:</span> '+p.reason;
hero.classList.add('err');
phrase.textContent='invalid';
sub.textContent='Check the format and try again';
grid.style.display='none';
bd.style.display='none';
currentDate=null;
return;
}
currentDate=p.date;
hero.classList.remove('err');
const now=new Date();
detected.innerHTML='Detected format: <span class="fmt">'+p.fmt+'</span>';
phrase.textContent=relativePhrase(p.date,now);
sub.textContent=p.date.toString();
grid.style.display='';
grid.innerHTML=
'<div class="rt-cell"><span class="lbl">Local time</span><span class="val" onclick="copyText(\''+escapeJs(p.date.toLocaleString())+'\')" title="Click to copy">'+escapeHtml(p.date.toLocaleString())+'</span></div>'+
'<div class="rt-cell"><span class="lbl">UTC</span><span class="val" onclick="copyText(\''+escapeJs(p.date.toUTCString())+'\')" title="Click to copy">'+escapeHtml(p.date.toUTCString())+'</span></div>'+
'<div class="rt-cell"><span class="lbl">ISO 8601</span><span class="val" onclick="copyText(\''+escapeJs(p.date.toISOString())+'\')" title="Click to copy">'+escapeHtml(p.date.toISOString())+'</span></div>'+
'<div class="rt-cell"><span class="lbl">Unix seconds</span><span class="val" onclick="copyText(\''+Math.floor(p.date.getTime()/1000)+'\')" title="Click to copy">'+Math.floor(p.date.getTime()/1000)+'</span></div>';
const b=breakdown(p.date,now);
bd.style.display='';
bdBody.innerHTML=
'<div class="rt-bd-row"><span class="k">Seconds</span><span>'+fmtNum(b.seconds)+'</span></div>'+
'<div class="rt-bd-row"><span class="k">Minutes</span><span>'+fmtNum(b.minutes)+'</span></div>'+
'<div class="rt-bd-row"><span class="k">Hours</span><span>'+fmtNum(b.hours)+'</span></div>'+
'<div class="rt-bd-row"><span class="k">Days</span><span>'+fmtNum(b.days)+'</span></div>'+
'<div class="rt-bd-row"><span class="k">Weeks</span><span>'+fmtNum(b.weeks)+'</span></div>'+
'<div class="rt-bd-row"><span class="k">Months</span><span>'+fmtNum(b.months)+'</span></div>'+
'<div class="rt-bd-row"><span class="k">Years</span><span>'+fmtNum(b.years)+'</span></div>'+
'<div class="rt-bd-row"><span class="k">Direction</span><span>'+b.sign+'</span></div>';
}
function renderBulk(){
const raw=document.getElementById('rt-bulk-in').value;
const out=document.getElementById('rt-bulk-out');
if(!raw.trim()){out.innerHTML='';return;}
const now=new Date();
const lines=raw.split(/\r?\n/);
const result=lines.map(line=>{
if(!line.trim())return'';
const p=parseTimestamp(line);
if(!p.ok)return'<span class="err">'+escapeHtml(line)+' — invalid</span>';
return escapeHtml(line)+' → '+escapeHtml(relativePhrase(p.date,now));
});
out.innerHTML=result.join('\n');
}
function insertNow(){
document.getElementById('rt-input').value=Math.floor(Date.now()/1000);
renderOne();
}
function copyPhrase(){
const v=document.getElementById('rt-phrase').textContent;
if(!v||v==='invalid')return;
navigator.clipboard.writeText(v);
showToast();
}
function copyText(v){
navigator.clipboard.writeText(v);
showToast();
}
function showToast(){
const t=document.getElementById('toast');t.style.opacity='1';setTimeout(()=>t.style.opacity='0',1500);
}
function escapeHtml(s){return String(s).replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[c]));}
function escapeJs(s){return String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'");}
document.getElementById('rt-input').addEventListener('input',renderOne);
document.getElementById('rt-bulk-in').addEventListener('input',renderBulk);
setInterval(()=>{
if(currentDate){
const now=new Date();
document.getElementById('rt-phrase').textContent=relativePhrase(currentDate,now);
}
renderBulk();
},1000);
function toggleFaq(el){el.parentElement.classList.toggle('open');}
</script>
</body>
</html>