mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
feat: implement relative date formatting, real-time clock display
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface DateDisplayProps {
|
||||
date?: Date;
|
||||
className?: string;
|
||||
@@ -13,15 +15,30 @@ export default function DateDisplay({
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
const [now, setNow] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setNow(new Date());
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const hours = now.getHours();
|
||||
const minutes = now.getMinutes();
|
||||
const seconds = now.getSeconds();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`text-right flex flex-col gap-2 min-w-[140px] ${className}`}
|
||||
>
|
||||
<div className={`text-right flex flex-col gap-2 min-w-35 ${className}`}>
|
||||
<span className="text-[10px] uppercase tracking-[0.4em] text-accent font-bold">
|
||||
Date
|
||||
</span>
|
||||
<span className="text-sm font-serif text-secondary-content italic whitespace-nowrap">
|
||||
{formattedDate}
|
||||
{formattedDate} <br />
|
||||
<span className="text-secondary-content/50 font-sans not-italic">
|
||||
{hours}:{minutes}:{seconds}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { formatRelativeDate } from "./dateFormat";
|
||||
|
||||
describe("formatRelativeDate", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-04-14T15:30:00Z"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("should format a same-day timestamp as Today, <time>", () => {
|
||||
const result = formatRelativeDate("2026-04-14T10:15:00Z");
|
||||
|
||||
expect(result).toBe("Today, 3:45 PM");
|
||||
});
|
||||
|
||||
it("should format a previous-day timestamp as Yesterday, <time>", () => {
|
||||
const result = formatRelativeDate("2026-04-13T09:10:00Z");
|
||||
|
||||
expect(result).toBe("Yesterday, 2:40 PM");
|
||||
});
|
||||
|
||||
it("should format dates within the last week as relative day + time", () => {
|
||||
const result = formatRelativeDate("2026-04-12T08:00:00Z");
|
||||
|
||||
expect(result).toBe("2 days ago, 1:30 PM");
|
||||
});
|
||||
|
||||
it("should format dates older than a week as a full date+time", () => {
|
||||
const result = formatRelativeDate("2026-04-01T13:15:00Z");
|
||||
|
||||
expect(result).toBe("Apr 1, 2026, 6:45 PM");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
const timeFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat(undefined, {
|
||||
numeric: "auto",
|
||||
});
|
||||
|
||||
function startOfDay(d: Date) {
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||||
}
|
||||
|
||||
export function formatRelativeDate(input: Date | string | number) {
|
||||
const date = new Date(input);
|
||||
const now = new Date();
|
||||
|
||||
const dayMs = 24 * 60 * 60 * 1000;
|
||||
const diffDays = Math.round(
|
||||
(startOfDay(date).getTime() - startOfDay(now).getTime()) / dayMs,
|
||||
);
|
||||
|
||||
const time = timeFormatter.format(date);
|
||||
|
||||
if (diffDays === 0) return `Today, ${time}`;
|
||||
if (diffDays === -1) return `Yesterday, ${time}`;
|
||||
if (diffDays > -7) return `${rtf.format(diffDays, "day")}, ${time}`;
|
||||
|
||||
return dateTimeFormatter.format(date);
|
||||
}
|
||||
Reference in New Issue
Block a user