TorbatYar/frontend/lib/jalali.ts
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 15:26:43 +03:30

120 lines
3.5 KiB
TypeScript

/**
* Jalali (Shamsi) date helpers for Design System.
* Form/API values stay Gregorian ISO `YYYY-MM-DD`; UI is always Jalali.
*/
import { toJalaali, toGregorian, jalaaliMonthLength as jMonthLength } from "jalaali-js";
export const WEEKDAYS = ["ش", "ی", "د", "س", "چ", "پ", "ج"] as const;
export const MONTHS = [
"فروردین",
"اردیبهشت",
"خرداد",
"تیر",
"مرداد",
"شهریور",
"مهر",
"آبان",
"آذر",
"دی",
"بهمن",
"اسفند",
] as const;
export type JalaliParts = { year: number; month: number; day: number };
function pad(n: number) {
return String(n).padStart(2, "0");
}
function parseIso(iso: string): { gy: number; gm: number; gd: number } | null {
const m = iso.trim().match(/^(\d{4})-(\d{2})-(\d{2})/);
if (!m) return null;
return { gy: Number(m[1]), gm: Number(m[2]), gd: Number(m[3]) };
}
/** Today as Gregorian ISO date for API defaults. */
export function todayIso(): string {
const now = new Date();
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
}
export function isoToJalaliParts(iso?: string | null): JalaliParts {
const parsed = iso ? parseIso(iso) : null;
const d = parsed
? new Date(parsed.gy, parsed.gm - 1, parsed.gd)
: new Date();
const j = toJalaali(d.getFullYear(), d.getMonth() + 1, d.getDate());
return { year: j.jy, month: j.jm, day: j.jd };
}
export function jalaliPartsToIso(parts: JalaliParts): string {
const g = toGregorian(parts.year, parts.month, parts.day);
return `${g.gy}-${pad(g.gm)}-${pad(g.gd)}`;
}
/** Format Gregorian ISO as Jalali `YYYY/MM/DD`. */
export function formatJalali(value?: string | Date | null): string {
if (value === null || value === undefined || value === "") return "—";
let gy: number;
let gm: number;
let gd: number;
if (value instanceof Date) {
gy = value.getFullYear();
gm = value.getMonth() + 1;
gd = value.getDate();
} else {
const parsed = parseIso(String(value));
if (parsed) {
gy = parsed.gy;
gm = parsed.gm;
gd = parsed.gd;
} else {
const d = new Date(String(value));
if (Number.isNaN(d.getTime())) return String(value);
gy = d.getFullYear();
gm = d.getMonth() + 1;
gd = d.getDate();
}
}
const j = toJalaali(gy, gm, gd);
return `${j.jy}/${pad(j.jm)}/${pad(j.jd)}`;
}
/** Jalali date + local time for audit timestamps. */
export function formatJalaliDateTime(value?: string | Date | null): string {
if (value === null || value === undefined || value === "") return "—";
const d = value instanceof Date ? value : new Date(String(value));
if (Number.isNaN(d.getTime())) return String(value);
return `${formatJalali(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
export function jalaliMonthLength(year: number, month: number): number {
return jMonthLength(year, month);
}
/** Weekday index Sat=0 … Fri=6 for Jalali calendar grid. */
export function jalaliWeekdaySat0(year: number, month: number, day: number): number {
const g = toGregorian(year, month, day);
const jsDay = new Date(g.gy, g.gm - 1, g.gd).getDay(); // 0=Sun
return (jsDay + 1) % 7;
}
export function shiftJalaliMonth(
year: number,
month: number,
delta: number
): { year: number; month: number } {
let m = month + delta;
let y = year;
while (m < 1) {
m += 12;
y -= 1;
}
while (m > 12) {
m -= 12;
y += 1;
}
return { year: y, month: m };
}