Files
ruseemrooz/src/lib/date.ts
mrfelfel dfb0e857f1 fix
2026-07-18 19:41:33 +03:30

108 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Helpers for Persian (Shamsi) dates and relative time labels used across the site.
const PERSIAN_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
/** Convert any latin digits in a string (or a number) to Persian digits. */
export const toPersianDigits = (value: string | number): string =>
String(value).replace(/\d/g, (digit) => PERSIAN_DIGITS[Number(digit)] ?? digit);
type DatedPost = { publishedAt?: string; slug: string; time: string };
/**
* Resolve the publish timestamp of a post.
*
* Posts created by the TASS agent embed `Date.now()` at the end of their slug,
* so we can recover a real timestamp even for older posts that only stored the
* placeholder string "هم‌اکنون".
*/
export const getPublishedAt = (post: DatedPost): Date | null => {
if (post.publishedAt) {
const date = new Date(post.publishedAt);
return Number.isNaN(date.getTime()) ? null : date;
}
const match = post.slug.match(/(\d{13})$/);
if (match) {
const date = new Date(Number(match[1]));
return Number.isNaN(date.getTime()) ? null : date;
}
return null;
};
/** Derive an ISO timestamp for a post, or null when none can be inferred. */
export const derivePublishedAt = (post: DatedPost): string | undefined => {
const date = getPublishedAt(post);
return date ? date.toISOString() : undefined;
};
/** Exact Shamsi date + time, e.g. "۱۴ تیر ۱۴۰۵، ۱۴:۳۰". */
export const formatShamsiDate = (date: Date): string => {
const formatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
return toPersianDigits(formatter.format(date));
};
/** Short Shamsi date without time, e.g. "۱۴ تیر ۱۴۰۵". */
export const formatShamsiDay = (date: Date): string => {
const formatter = new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
return toPersianDigits(formatter.format(date));
};
/**
* Relative Persian time label: "هم‌اکنون", "۲ دقیقه پیش", "۱۰ دقیقه پیش",
* "۳ ساعت پیش", "۵ روز پیش" — and falls back to an exact Shamsi date for
* anything older than a month.
*/
export const formatRelativeTime = (date: Date): string => {
const now = Date.now();
const diffSeconds = Math.max(0, Math.round((now - date.getTime()) / 1000));
if (diffSeconds < 60) {
return 'هم‌اکنون';
}
const diffMinutes = Math.floor(diffSeconds / 60);
if (diffMinutes < 60) {
return `${toPersianDigits(diffMinutes)} دقیقه پیش`;
}
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) {
return `${toPersianDigits(diffHours)} ساعت پیش`;
}
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 30) {
return `${toPersianDigits(diffDays)} روز پیش`;
}
return formatShamsiDay(date);
};
/** Relative label for a post, falling back to its stored `time` string. */
export const formatPostRelativeTime = (post: DatedPost): string => {
const date = getPublishedAt(post);
return date ? formatRelativeTime(date) : post.time;
};
/** Exact Shamsi label for a post, falling back to its stored `time` string. */
export const formatPostShamsiDate = (post: DatedPost): string => {
const date = getPublishedAt(post);
return date ? formatShamsiDate(date) : post.time;
};