fix
This commit is contained in:
107
src/lib/date.ts
Normal file
107
src/lib/date.ts
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user