This commit is contained in:
mrfelfel
2026-07-18 19:41:33 +03:30
parent 87ba0d253d
commit dfb0e857f1
158 changed files with 20278 additions and 0 deletions

107
src/lib/date.ts Normal file
View File

@@ -0,0 +1,107 @@
// 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;
};