@kashi-ui/persian 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kashi contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,22 @@
1
- # Temporary Holding Version
1
+ # @kashi-ui/persian
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ابزارهای فارسی [کاشی](https://github.com/Sepantapouya/Kashi): تاریخ شمسی، ارقام فارسی، اعتبارسنجی کد ملی و شماره کارت. بدون وابستگی، برای مرورگر، Node.js و هر فریم‌ورکی.
4
+
5
+ ```bash
6
+ npm i @kashi-ui/persian
7
+ ```
8
+
9
+ ```ts
10
+ import { formatJalali, parseJalali, toISODate, isValidNationalId, getBankByCard, toEnDigits } from "@kashi-ui/persian";
11
+
12
+ formatJalali(new Date(2024, 7, 2), { style: "full" }); // "جمعه ۱۲ مرداد ۱۴۰۳"
13
+ parseJalali("۱۴۰۳/۵/۱۲"); // Date
14
+ toISODate(date); // "2024-08-02" (بدون خطای منطقهٔ زمانی)
15
+ isValidNationalId("۰۰۱۳۵۴۲۴۱۹"); // true
16
+ getBankByCard("610433")?.name; // "بانک ملت"
17
+ toEnDigits("۰۹۱۲"); // "0912"
18
+ ```
19
+
20
+ تبدیل تاریخ برای همهٔ روزهای سال‌های ۱۳۰۰ تا ۱۵۰۰ با تقویم persian کتابخانهٔ ICU تست شده است.
21
+
22
+ مستندات کامل: [kashi.sepantapouya.com](https://kashi.sepantapouya.com/docs/persian/utilities/) · کد: [GitHub](https://github.com/Sepantapouya/Kashi) · لایسنس MIT
package/dist/card.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export interface Bank {
2
+ /** نام فارسی */
3
+ name: string;
4
+ /** شناسهٔ لاتین برای کلاس یا لوگو */
5
+ slug: string;
6
+ }
7
+ /** اعتبارسنجی شماره کارت ۱۶ رقمی (الگوریتم Luhn) */
8
+ export declare function isValidCardNumber(value: string): boolean;
9
+ /** نام بانک از روی شش رقم اول؛ اگر شناخته نشود null */
10
+ export declare function getBankByCard(value: string): Bank | null;
11
+ /** گروه‌بندی چهارتایی: 6037991234567890 → 6037 9912 3456 7890 */
12
+ export declare function formatCardNumber(value: string): string;
package/dist/card.js ADDED
@@ -0,0 +1,72 @@
1
+ import { onlyDigits } from "./digits.js";
2
+ // شش رقم اول کارت (BIN) بانک‌های ایرانی. فهرست ممکن است کامل نباشد؛ نام بانک فقط برای راهنمایی است.
3
+ const BINS = {
4
+ "603799": { name: "بانک ملی", slug: "melli" },
5
+ "589210": { name: "بانک سپه", slug: "sepah" },
6
+ "627648": { name: "بانک توسعه صادرات", slug: "edbi" },
7
+ "627961": { name: "بانک صنعت و معدن", slug: "bim" },
8
+ "603770": { name: "بانک کشاورزی", slug: "keshavarzi" },
9
+ "639217": { name: "بانک کشاورزی", slug: "keshavarzi" },
10
+ "628023": { name: "بانک مسکن", slug: "maskan" },
11
+ "627760": { name: "پست بانک", slug: "postbank" },
12
+ "502908": { name: "بانک توسعه تعاون", slug: "tosee-taavon" },
13
+ "627412": { name: "بانک اقتصاد نوین", slug: "eghtesad-novin" },
14
+ "622106": { name: "بانک پارسیان", slug: "parsian" },
15
+ "639194": { name: "بانک پارسیان", slug: "parsian" },
16
+ "627884": { name: "بانک پارسیان", slug: "parsian" },
17
+ "502229": { name: "بانک پاسارگاد", slug: "pasargad" },
18
+ "639347": { name: "بانک پاسارگاد", slug: "pasargad" },
19
+ "627488": { name: "بانک کارآفرین", slug: "karafarin" },
20
+ "502910": { name: "بانک کارآفرین", slug: "karafarin" },
21
+ "621986": { name: "بانک سامان", slug: "saman" },
22
+ "639346": { name: "بانک سینا", slug: "sina" },
23
+ "639607": { name: "بانک سرمایه", slug: "sarmayeh" },
24
+ "636214": { name: "بانک آینده", slug: "ayandeh" },
25
+ "502806": { name: "بانک شهر", slug: "shahr" },
26
+ "504706": { name: "بانک شهر", slug: "shahr" },
27
+ "502938": { name: "بانک دی", slug: "dey" },
28
+ "603769": { name: "بانک صادرات", slug: "saderat" },
29
+ "610433": { name: "بانک ملت", slug: "mellat" },
30
+ "991975": { name: "بانک ملت", slug: "mellat" },
31
+ "627353": { name: "بانک تجارت", slug: "tejarat" },
32
+ "585983": { name: "بانک تجارت", slug: "tejarat" },
33
+ "589463": { name: "بانک رفاه", slug: "refah" },
34
+ "505785": { name: "بانک ایران زمین", slug: "iran-zamin" },
35
+ "606373": { name: "بانک قرض‌الحسنه مهر ایران", slug: "mehr-iran" },
36
+ "505416": { name: "بانک گردشگری", slug: "gardeshgari" },
37
+ "636795": { name: "بانک مرکزی", slug: "markazi" },
38
+ "585947": { name: "بانک خاورمیانه", slug: "khavarmianeh" },
39
+ "505809": { name: "بانک خاورمیانه", slug: "khavarmianeh" },
40
+ "507677": { name: "موسسه اعتباری نور", slug: "noor" },
41
+ "606256": { name: "موسسه اعتباری ملل", slug: "melal" },
42
+ "628157": { name: "موسسه اعتباری توسعه", slug: "tosee" },
43
+ "504172": { name: "بانک قرض‌الحسنه رسالت", slug: "resalat" },
44
+ };
45
+ /** الگوریتم Luhn */
46
+ function luhn(digits) {
47
+ let sum = 0;
48
+ for (let i = 0; i < digits.length; i++) {
49
+ let d = Number(digits[digits.length - 1 - i]);
50
+ if (i % 2 === 1) {
51
+ d *= 2;
52
+ if (d > 9)
53
+ d -= 9;
54
+ }
55
+ sum += d;
56
+ }
57
+ return sum % 10 === 0;
58
+ }
59
+ /** اعتبارسنجی شماره کارت ۱۶ رقمی (الگوریتم Luhn) */
60
+ export function isValidCardNumber(value) {
61
+ const n = onlyDigits(value);
62
+ return /^\d{16}$/.test(n) && luhn(n);
63
+ }
64
+ /** نام بانک از روی شش رقم اول؛ اگر شناخته نشود null */
65
+ export function getBankByCard(value) {
66
+ const n = onlyDigits(value);
67
+ return n.length >= 6 ? (BINS[n.slice(0, 6)] ?? null) : null;
68
+ }
69
+ /** گروه‌بندی چهارتایی: 6037991234567890 → 6037 9912 3456 7890 */
70
+ export function formatCardNumber(value) {
71
+ return onlyDigits(value).slice(0, 16).replace(/(\d{4})(?=\d)/g, "$1 ");
72
+ }
@@ -0,0 +1,12 @@
1
+ /** ارقام لاتین را فارسی می‌کند: 1403 → ۱۴۰۳ */
2
+ export declare function toFaDigits(value: string | number): string;
3
+ /** ارقام فارسی و عربی را لاتین می‌کند: ۱۴۰۳ → 1403 */
4
+ export declare function toEnDigits(value: string): string;
5
+ /** فقط ارقام (با هر خطی) را نگه می‌دارد و لاتین برمی‌گرداند */
6
+ export declare function onlyDigits(value: string): string;
7
+ export interface FormatNumberOptions {
8
+ /** @default "fa" */
9
+ digits?: "fa" | "latin";
10
+ }
11
+ /** عدد با جداکنندهٔ هزارگان فارسی (٬): 1250000 → ۱٬۲۵۰٬۰۰۰ */
12
+ export declare function formatNumber(value: number | string, { digits }?: FormatNumberOptions): string;
package/dist/digits.js ADDED
@@ -0,0 +1,25 @@
1
+ const FA = "۰۱۲۳۴۵۶۷۸۹";
2
+ const AR = "٠١٢٣٤٥٦٧٨٩";
3
+ /** ارقام لاتین را فارسی می‌کند: 1403 → ۱۴۰۳ */
4
+ export function toFaDigits(value) {
5
+ return String(value).replace(/\d/g, (d) => FA[Number(d)]);
6
+ }
7
+ /** ارقام فارسی و عربی را لاتین می‌کند: ۱۴۰۳ → 1403 */
8
+ export function toEnDigits(value) {
9
+ return value.replace(/[۰-۹٠-٩]/g, (d) => {
10
+ const fa = FA.indexOf(d);
11
+ return String(fa >= 0 ? fa : AR.indexOf(d));
12
+ });
13
+ }
14
+ /** فقط ارقام (با هر خطی) را نگه می‌دارد و لاتین برمی‌گرداند */
15
+ export function onlyDigits(value) {
16
+ return toEnDigits(value).replace(/\D/g, "");
17
+ }
18
+ /** عدد با جداکنندهٔ هزارگان فارسی (٬): 1250000 → ۱٬۲۵۰٬۰۰۰ */
19
+ export function formatNumber(value, { digits = "fa" } = {}) {
20
+ const n = typeof value === "number" ? value : Number(onlyDigits(value));
21
+ if (!Number.isFinite(n))
22
+ return "";
23
+ const out = new Intl.NumberFormat("en-US").format(n);
24
+ return digits === "fa" ? toFaDigits(out).replace(/,/g, "٬") : out;
25
+ }
package/dist/iban.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { Bank } from "./card.js";
2
+ /** ۲۴ رقم شبا بدون IR؛ «IR12 0170…» یا «۱۲۰۱۷۰…» را می‌پذیرد */
3
+ export declare function ibanDigits(value: string): string;
4
+ /** اعتبارسنجی شماره شبا (IBAN ایران): IR + ۲۴ رقم با رقم‌های کنترل ISO 13616 */
5
+ export declare function isValidIban(value: string): boolean;
6
+ /** نام بانک از روی شبا؛ اگر شناخته نشود null */
7
+ export declare function getBankByIban(value: string): Bank | null;
8
+ /** IR + گروه‌های چهارتایی: IR06 0170 0000 … */
9
+ export declare function formatIban(value: string): string;
package/dist/iban.js ADDED
@@ -0,0 +1,62 @@
1
+ import { toEnDigits } from "./digits.js";
2
+ // کد سه‌رقمی بانک در شبا (رقم‌های ۵ تا ۷ پس از IR). فهرست ممکن است کامل نباشد.
3
+ const BANK_CODES = {
4
+ "010": { name: "بانک مرکزی", slug: "markazi" },
5
+ "011": { name: "بانک صنعت و معدن", slug: "bim" },
6
+ "012": { name: "بانک ملت", slug: "mellat" },
7
+ "013": { name: "بانک رفاه", slug: "refah" },
8
+ "014": { name: "بانک مسکن", slug: "maskan" },
9
+ "015": { name: "بانک سپه", slug: "sepah" },
10
+ "016": { name: "بانک کشاورزی", slug: "keshavarzi" },
11
+ "017": { name: "بانک ملی", slug: "melli" },
12
+ "018": { name: "بانک تجارت", slug: "tejarat" },
13
+ "019": { name: "بانک صادرات", slug: "saderat" },
14
+ "020": { name: "بانک توسعه صادرات", slug: "edbi" },
15
+ "021": { name: "پست بانک", slug: "postbank" },
16
+ "022": { name: "بانک توسعه تعاون", slug: "tosee-taavon" },
17
+ "053": { name: "بانک کارآفرین", slug: "karafarin" },
18
+ "054": { name: "بانک پارسیان", slug: "parsian" },
19
+ "055": { name: "بانک اقتصاد نوین", slug: "eghtesad-novin" },
20
+ "056": { name: "بانک سامان", slug: "saman" },
21
+ "057": { name: "بانک پاسارگاد", slug: "pasargad" },
22
+ "058": { name: "بانک سرمایه", slug: "sarmayeh" },
23
+ "059": { name: "بانک سینا", slug: "sina" },
24
+ "060": { name: "بانک قرض‌الحسنه مهر ایران", slug: "mehr-iran" },
25
+ "061": { name: "بانک شهر", slug: "shahr" },
26
+ "062": { name: "بانک آینده", slug: "ayandeh" },
27
+ "064": { name: "بانک گردشگری", slug: "gardeshgari" },
28
+ "066": { name: "بانک دی", slug: "dey" },
29
+ "069": { name: "بانک ایران زمین", slug: "iran-zamin" },
30
+ "070": { name: "بانک قرض‌الحسنه رسالت", slug: "resalat" },
31
+ "078": { name: "بانک خاورمیانه", slug: "khavarmianeh" },
32
+ };
33
+ /** ۲۴ رقم شبا بدون IR؛ «IR12 0170…» یا «۱۲۰۱۷۰…» را می‌پذیرد */
34
+ export function ibanDigits(value) {
35
+ return toEnDigits(value).toUpperCase().replace(/^\s*IR/, "").replace(/\D/g, "").slice(0, 24);
36
+ }
37
+ /** باقیماندهٔ تقسیم یک عدد بلند (به‌صورت رشته) بر ۹۷ */
38
+ function mod97(numeric) {
39
+ let r = 0;
40
+ for (let i = 0; i < numeric.length; i += 7)
41
+ r = Number(String(r) + numeric.slice(i, i + 7)) % 97;
42
+ return r;
43
+ }
44
+ /** اعتبارسنجی شماره شبا (IBAN ایران): IR + ۲۴ رقم با رقم‌های کنترل ISO 13616 */
45
+ export function isValidIban(value) {
46
+ const raw = toEnDigits(value).toUpperCase().replace(/[\s-]/g, "");
47
+ const digits = raw.startsWith("IR") ? raw.slice(2) : raw;
48
+ if (!/^\d{24}$/.test(digits))
49
+ return false;
50
+ // I=18، R=27؛ دو حرف و رقم‌های کنترل به انتهای شماره منتقل می‌شوند
51
+ return mod97(digits.slice(2) + "1827" + digits.slice(0, 2)) === 1;
52
+ }
53
+ /** نام بانک از روی شبا؛ اگر شناخته نشود null */
54
+ export function getBankByIban(value) {
55
+ const d = ibanDigits(value);
56
+ return d.length >= 5 ? (BANK_CODES[d.slice(2, 5)] ?? null) : null;
57
+ }
58
+ /** IR + گروه‌های چهارتایی: IR06 0170 0000 … */
59
+ export function formatIban(value) {
60
+ const d = ibanDigits(value);
61
+ return d ? `IR${d}`.replace(/(.{4})(?=.)/g, "$1 ") : "";
62
+ }
@@ -0,0 +1,13 @@
1
+ export { toFaDigits, toEnDigits, onlyDigits, formatNumber } from "./digits.js";
2
+ export type { FormatNumberOptions } from "./digits.js";
3
+ export { JALALI_MONTHS, WEEKDAYS, WEEKDAYS_SHORT, toJalali, toGregorian, isLeapJalaliYear, jalaliMonthLength, isValidJalaliDate, jalaliWeekday, formatJalali, parseJalali, toISODate, fromISODate, } from "./jalali.js";
4
+ export type { JalaliDate, FormatJalaliOptions } from "./jalali.js";
5
+ export { isValidNationalId } from "./national-id.js";
6
+ export { isValidCardNumber, getBankByCard, formatCardNumber } from "./card.js";
7
+ export type { Bank } from "./card.js";
8
+ export { isValidIban, getBankByIban, formatIban, ibanDigits } from "./iban.js";
9
+ export { normalizeMobile, isValidMobile, getMobileOperator, formatMobile } from "./mobile.js";
10
+ export type { Operator } from "./mobile.js";
11
+ export { numberToWords } from "./words.js";
12
+ export { normalizePersian } from "./text.js";
13
+ export type { NormalizeOptions } from "./text.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { toFaDigits, toEnDigits, onlyDigits, formatNumber } from "./digits.js";
2
+ export { JALALI_MONTHS, WEEKDAYS, WEEKDAYS_SHORT, toJalali, toGregorian, isLeapJalaliYear, jalaliMonthLength, isValidJalaliDate, jalaliWeekday, formatJalali, parseJalali, toISODate, fromISODate, } from "./jalali.js";
3
+ export { isValidNationalId } from "./national-id.js";
4
+ export { isValidCardNumber, getBankByCard, formatCardNumber } from "./card.js";
5
+ export { isValidIban, getBankByIban, formatIban, ibanDigits } from "./iban.js";
6
+ export { normalizeMobile, isValidMobile, getMobileOperator, formatMobile } from "./mobile.js";
7
+ export { numberToWords } from "./words.js";
8
+ export { normalizePersian } from "./text.js";
@@ -0,0 +1,40 @@
1
+ export interface JalaliDate {
2
+ /** سال شمسی */
3
+ jy: number;
4
+ /** ماه ۱ تا ۱۲ */
5
+ jm: number;
6
+ /** روز ۱ تا ۳۱ */
7
+ jd: number;
8
+ }
9
+ export declare const JALALI_MONTHS: readonly ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"];
10
+ /** روزهای هفته از شنبه */
11
+ export declare const WEEKDAYS: readonly ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه"];
12
+ export declare const WEEKDAYS_SHORT: readonly ["ش", "ی", "د", "س", "چ", "پ", "ج"];
13
+ /** آیا سال شمسی کبیسه است؟ */
14
+ export declare function isLeapJalaliYear(jy: number): boolean;
15
+ /** تعداد روزهای یک ماه شمسی */
16
+ export declare function jalaliMonthLength(jy: number, jm: number): number;
17
+ /** آیا این تاریخ شمسی وجود دارد؟ */
18
+ export declare function isValidJalaliDate(jy: number, jm: number, jd: number): boolean;
19
+ /** تاریخ میلادی (Date، به وقت محلی) → شمسی */
20
+ export declare function toJalali(date: Date): JalaliDate;
21
+ /** شمسی → Date میلادی (نیمه‌شب به وقت محلی) */
22
+ export declare function toGregorian(jy: number, jm: number, jd: number): Date;
23
+ /** روز هفته با شروع از شنبه: ۰ = شنبه … ۶ = جمعه */
24
+ export declare function jalaliWeekday(date: Date): number;
25
+ export interface FormatJalaliOptions {
26
+ /** "numeric": ۱۴۰۳/۰۵/۱۲ — "long": ۱۲ مرداد ۱۴۰۳ — "full": جمعه ۱۲ مرداد ۱۴۰۳ @default "numeric" */
27
+ style?: "numeric" | "long" | "full";
28
+ /** @default "fa" */
29
+ digits?: "fa" | "latin";
30
+ }
31
+ export declare function formatJalali(date: Date, { style, digits }?: FormatJalaliOptions): string;
32
+ /**
33
+ * تاریخ به شکل YYYY-MM-DD بر اساس وقت محلی، مناسب ارسال به سرور.
34
+ * از date.toISOString() استفاده نکنید: به UTC تبدیل می‌کند و در ایران (UTC+3:30) نیمه‌شب را روز قبل می‌کند.
35
+ */
36
+ export declare function toISODate(date: Date): string;
37
+ /** «2024-08-02» را به Date محلی (نیمه‌شب) تبدیل می‌کند؛ در صورت نامعتبر بودن null */
38
+ export declare function fromISODate(text: string): Date | null;
39
+ /** «۱۴۰۳/۵/۱۲»، «1403-05-12» یا «۱۴۰۳ ۰۵ ۱۲» را می‌خواند؛ در صورت نامعتبر بودن null */
40
+ export declare function parseJalali(text: string): Date | null;
package/dist/jalali.js ADDED
@@ -0,0 +1,142 @@
1
+ import { toEnDigits, toFaDigits } from "./digits.js";
2
+ export const JALALI_MONTHS = [
3
+ "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور",
4
+ "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند",
5
+ ];
6
+ /** روزهای هفته از شنبه */
7
+ export const WEEKDAYS = ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه"];
8
+ export const WEEKDAYS_SHORT = ["ش", "ی", "د", "س", "چ", "پ", "ج"];
9
+ const BREAKS = [-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178];
10
+ const div = (a, b) => ~~(a / b);
11
+ const mod = (a, b) => a - ~~(a / b) * b;
12
+ function jalCal(jy) {
13
+ const gy = jy + 621;
14
+ let leapJ = -14;
15
+ let jp = BREAKS[0];
16
+ let jump = 0;
17
+ if (jy < jp || jy >= BREAKS[BREAKS.length - 1])
18
+ throw new RangeError(`سال شمسی خارج از محدوده: ${jy}`);
19
+ for (let i = 1; i < BREAKS.length; i++) {
20
+ const jm = BREAKS[i];
21
+ jump = jm - jp;
22
+ if (jy < jm)
23
+ break;
24
+ leapJ += div(jump, 33) * 8 + div(mod(jump, 33), 4);
25
+ jp = jm;
26
+ }
27
+ let n = jy - jp;
28
+ leapJ += div(n, 33) * 8 + div(mod(n, 33) + 3, 4);
29
+ if (mod(jump, 33) === 4 && jump - n === 4)
30
+ leapJ += 1;
31
+ const leapG = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150;
32
+ const march = 20 + leapJ - leapG;
33
+ if (jump - n < 6)
34
+ n = n - jump + div(jump + 4, 33) * 33;
35
+ let leap = mod(mod(n + 1, 33) - 1, 4);
36
+ if (leap === -1)
37
+ leap = 4;
38
+ return { leap, gy, march };
39
+ }
40
+ function g2d(gy, gm, gd) {
41
+ let d = div((gy + div(gm - 8, 6) + 100100) * 1461, 4) + div(153 * mod(gm + 9, 12) + 2, 5) + gd - 34840408;
42
+ d = d - div(div(gy + 100100 + div(gm - 8, 6), 100) * 3, 4) + 752;
43
+ return d;
44
+ }
45
+ function d2g(jdn) {
46
+ let j = 4 * jdn + 139361631;
47
+ j = j + div(div(4 * jdn + 183187720, 146097) * 3, 4) * 4 - 3908;
48
+ const i = div(mod(j, 1461), 4) * 5 + 308;
49
+ const gd = div(mod(i, 153), 5) + 1;
50
+ const gm = mod(div(i, 153), 12) + 1;
51
+ const gy = div(j, 1461) - 100100 + div(8 - gm, 6);
52
+ return { gy, gm, gd };
53
+ }
54
+ function j2d(jy, jm, jd) {
55
+ const r = jalCal(jy);
56
+ return g2d(r.gy, 3, r.march) + (jm - 1) * 31 - div(jm, 7) * (jm - 7) + jd - 1;
57
+ }
58
+ function d2j(jdn) {
59
+ const { gy } = d2g(jdn);
60
+ let jy = gy - 621;
61
+ const r = jalCal(jy);
62
+ let k = jdn - g2d(gy, 3, r.march);
63
+ if (k >= 0) {
64
+ if (k <= 185)
65
+ return { jy, jm: 1 + div(k, 31), jd: mod(k, 31) + 1 };
66
+ k -= 186;
67
+ }
68
+ else {
69
+ jy -= 1;
70
+ k += 179;
71
+ if (r.leap === 1)
72
+ k += 1;
73
+ }
74
+ return { jy, jm: 7 + div(k, 30), jd: mod(k, 30) + 1 };
75
+ }
76
+ /** آیا سال شمسی کبیسه است؟ */
77
+ export function isLeapJalaliYear(jy) {
78
+ return jalCal(jy).leap === 0;
79
+ }
80
+ /** تعداد روزهای یک ماه شمسی */
81
+ export function jalaliMonthLength(jy, jm) {
82
+ if (jm <= 6)
83
+ return 31;
84
+ if (jm <= 11)
85
+ return 30;
86
+ return isLeapJalaliYear(jy) ? 30 : 29;
87
+ }
88
+ /** آیا این تاریخ شمسی وجود دارد؟ */
89
+ export function isValidJalaliDate(jy, jm, jd) {
90
+ return (Number.isInteger(jy) && Number.isInteger(jm) && Number.isInteger(jd) &&
91
+ jy >= -61 && jy <= 3177 && jm >= 1 && jm <= 12 && jd >= 1 && jd <= jalaliMonthLength(jy, jm));
92
+ }
93
+ /** تاریخ میلادی (Date، به وقت محلی) → شمسی */
94
+ export function toJalali(date) {
95
+ return d2j(g2d(date.getFullYear(), date.getMonth() + 1, date.getDate()));
96
+ }
97
+ /** شمسی → Date میلادی (نیمه‌شب به وقت محلی) */
98
+ export function toGregorian(jy, jm, jd) {
99
+ const { gy, gm, gd } = d2g(j2d(jy, jm, jd));
100
+ return new Date(gy, gm - 1, gd);
101
+ }
102
+ /** روز هفته با شروع از شنبه: ۰ = شنبه … ۶ = جمعه */
103
+ export function jalaliWeekday(date) {
104
+ return (date.getDay() + 1) % 7;
105
+ }
106
+ export function formatJalali(date, { style = "numeric", digits = "fa" } = {}) {
107
+ const { jy, jm, jd } = toJalali(date);
108
+ const pad = (n) => String(n).padStart(2, "0");
109
+ let out;
110
+ if (style === "numeric")
111
+ out = `${jy}/${pad(jm)}/${pad(jd)}`;
112
+ else {
113
+ out = `${jd} ${JALALI_MONTHS[jm - 1]} ${jy}`;
114
+ if (style === "full")
115
+ out = `${WEEKDAYS[jalaliWeekday(date)]} ${out}`;
116
+ }
117
+ return digits === "fa" ? toFaDigits(out) : out;
118
+ }
119
+ /**
120
+ * تاریخ به شکل YYYY-MM-DD بر اساس وقت محلی، مناسب ارسال به سرور.
121
+ * از date.toISOString() استفاده نکنید: به UTC تبدیل می‌کند و در ایران (UTC+3:30) نیمه‌شب را روز قبل می‌کند.
122
+ */
123
+ export function toISODate(date) {
124
+ const pad = (n) => String(n).padStart(2, "0");
125
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
126
+ }
127
+ /** «2024-08-02» را به Date محلی (نیمه‌شب) تبدیل می‌کند؛ در صورت نامعتبر بودن null */
128
+ export function fromISODate(text) {
129
+ const m = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
130
+ if (!m)
131
+ return null;
132
+ const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
133
+ return d.getMonth() === Number(m[2]) - 1 ? d : null;
134
+ }
135
+ /** «۱۴۰۳/۵/۱۲»، «1403-05-12» یا «۱۴۰۳ ۰۵ ۱۲» را می‌خواند؛ در صورت نامعتبر بودن null */
136
+ export function parseJalali(text) {
137
+ const m = toEnDigits(text.trim()).match(/^(\d{4})\s*[/\-.\s]\s*(\d{1,2})\s*[/\-.\s]\s*(\d{1,2})$/);
138
+ if (!m)
139
+ return null;
140
+ const [jy, jm, jd] = [Number(m[1]), Number(m[2]), Number(m[3])];
141
+ return isValidJalaliDate(jy, jm, jd) ? toGregorian(jy, jm, jd) : null;
142
+ }
@@ -0,0 +1,15 @@
1
+ export interface Operator {
2
+ name: string;
3
+ slug: "mci" | "irancell" | "rightel";
4
+ }
5
+ /**
6
+ * شماره موبایل را به شکل استاندارد ۰۹xxxxxxxxx درمی‌آورد.
7
+ * +98912…، 0098912…، 98912… و 912… پذیرفته می‌شوند. اگر ساختار درست نباشد، ارقام همان‌طور برمی‌گردند.
8
+ */
9
+ export declare function normalizeMobile(value: string): string;
10
+ /** شماره موبایل ایران: ۱۱ رقم که با ۰۹ شروع می‌شود */
11
+ export declare function isValidMobile(value: string): boolean;
12
+ /** اپراتور از روی پیش‌شماره؛ اگر شناخته نشود null */
13
+ export declare function getMobileOperator(value: string): Operator | null;
14
+ /** ۰۹۱۲ ۳۴۵ ۶۷۸۹ */
15
+ export declare function formatMobile(value: string): string;
package/dist/mobile.js ADDED
@@ -0,0 +1,38 @@
1
+ import { onlyDigits } from "./digits.js";
2
+ const MCI = { name: "همراه اول", slug: "mci" };
3
+ const IRANCELL = { name: "ایرانسل", slug: "irancell" };
4
+ const RIGHTEL = { name: "رایتل", slug: "rightel" };
5
+ // پیش‌شماره‌ها پس از ۰۹؛ فهرست اپراتورهای اصلی، ممکن است کامل نباشد
6
+ const PREFIXES = {
7
+ ...Object.fromEntries(["10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "90", "91", "92", "93", "94"].map((p) => [p, MCI])),
8
+ ...Object.fromEntries(["01", "02", "03", "04", "05", "30", "33", "35", "36", "37", "38", "39", "41"].map((p) => [p, IRANCELL])),
9
+ ...Object.fromEntries(["20", "21", "22"].map((p) => [p, RIGHTEL])),
10
+ };
11
+ /**
12
+ * شماره موبایل را به شکل استاندارد ۰۹xxxxxxxxx درمی‌آورد.
13
+ * +98912…، 0098912…، 98912… و 912… پذیرفته می‌شوند. اگر ساختار درست نباشد، ارقام همان‌طور برمی‌گردند.
14
+ */
15
+ export function normalizeMobile(value) {
16
+ let d = onlyDigits(value);
17
+ if (d.startsWith("0098"))
18
+ d = "0" + d.slice(4);
19
+ else if (d.startsWith("98") && d.length === 12)
20
+ d = "0" + d.slice(2);
21
+ else if (d.startsWith("9") && d.length === 10)
22
+ d = "0" + d;
23
+ return d;
24
+ }
25
+ /** شماره موبایل ایران: ۱۱ رقم که با ۰۹ شروع می‌شود */
26
+ export function isValidMobile(value) {
27
+ return /^09\d{9}$/.test(normalizeMobile(value));
28
+ }
29
+ /** اپراتور از روی پیش‌شماره؛ اگر شناخته نشود null */
30
+ export function getMobileOperator(value) {
31
+ const d = normalizeMobile(value);
32
+ return d.length >= 4 && d.startsWith("09") ? (PREFIXES[d.slice(2, 4)] ?? null) : null;
33
+ }
34
+ /** ۰۹۱۲ ۳۴۵ ۶۷۸۹ */
35
+ export function formatMobile(value) {
36
+ const d = normalizeMobile(value).slice(0, 11);
37
+ return [d.slice(0, 4), d.slice(4, 7), d.slice(7, 11)].filter(Boolean).join(" ");
38
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * اعتبارسنجی کد ملی ایران (۱۰ رقم با رقم کنترل).
3
+ * ارقام فارسی و عربی و فاصله/خط تیره پذیرفته می‌شوند.
4
+ */
5
+ export declare function isValidNationalId(value: string): boolean;
@@ -0,0 +1,19 @@
1
+ import { onlyDigits } from "./digits.js";
2
+ /**
3
+ * اعتبارسنجی کد ملی ایران (۱۰ رقم با رقم کنترل).
4
+ * ارقام فارسی و عربی و فاصله/خط تیره پذیرفته می‌شوند.
5
+ */
6
+ export function isValidNationalId(value) {
7
+ const id = onlyDigits(value);
8
+ if (!/^\d{10}$/.test(id))
9
+ return false;
10
+ // کدهای با ارقام تکراری (مثل ۱۱۱۱۱۱۱۱۱۱) رقم کنترل درست دارند ولی صادر نمی‌شوند
11
+ if (/^(\d)\1{9}$/.test(id))
12
+ return false;
13
+ const check = Number(id[9]);
14
+ let sum = 0;
15
+ for (let i = 0; i < 9; i++)
16
+ sum += Number(id[i]) * (10 - i);
17
+ const r = sum % 11;
18
+ return r < 2 ? check === r : check === 11 - r;
19
+ }
package/dist/text.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface NormalizeOptions {
2
+ /** خط ارقام خروجی؛ undefined یعنی بدون تغییر */
3
+ digits?: "fa" | "latin";
4
+ }
5
+ /**
6
+ * یکدست‌سازی متن فارسی برای ذخیره و جستجو:
7
+ * «ي» و «ى» عربی ← «ی»، «ك» ← «ک»، «ة» ← «ه»، حذف اعراب و کشیده (ـ)،
8
+ * و جمع کردن فاصله‌ها و نیم‌فاصله‌های تکراری.
9
+ */
10
+ export declare function normalizePersian(text: string, { digits }?: NormalizeOptions): string;
package/dist/text.js ADDED
@@ -0,0 +1,23 @@
1
+ import { toEnDigits, toFaDigits } from "./digits.js";
2
+ /**
3
+ * یکدست‌سازی متن فارسی برای ذخیره و جستجو:
4
+ * «ي» و «ى» عربی ← «ی»، «ك» ← «ک»، «ة» ← «ه»، حذف اعراب و کشیده (ـ)،
5
+ * و جمع کردن فاصله‌ها و نیم‌فاصله‌های تکراری.
6
+ */
7
+ export function normalizePersian(text, { digits } = {}) {
8
+ let out = text
9
+ .replace(/[يى]/g, "ی")
10
+ .replace(/ك/g, "ک")
11
+ .replace(/ة/g, "ه")
12
+ .replace(/[ً-ٰٟ]/g, "") // اعراب
13
+ .replace(/ـ/g, "") // کشیده
14
+ .replace(/‌{2,}/g, "‌")
15
+ .replace(/ *‌ */g, "‌")
16
+ .replace(/[ \t]{2,}/g, " ")
17
+ .trim();
18
+ if (digits === "fa")
19
+ out = toFaDigits(toEnDigits(out));
20
+ if (digits === "latin")
21
+ out = toEnDigits(out);
22
+ return out;
23
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * عدد به حروف فارسی: 1250000 → «یک میلیون و دویست و پنجاه هزار».
3
+ * برای اعداد صحیح تا ۹۹۹ تریلیون. «هزار» بدون «یک» خوانده می‌شود (۱۰۰۰ → هزار).
4
+ */
5
+ export declare function numberToWords(value: number | bigint): string;
package/dist/words.js ADDED
@@ -0,0 +1,44 @@
1
+ const ONES = ["", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه"];
2
+ const TEENS = ["ده", "یازده", "دوازده", "سیزده", "چهارده", "پانزده", "شانزده", "هفده", "هجده", "نوزده"];
3
+ const TENS = ["", "", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود"];
4
+ const HUNDREDS = ["", "صد", "دویست", "سیصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد", "نهصد"];
5
+ const SCALES = ["", "هزار", "میلیون", "میلیارد", "تریلیون"];
6
+ function under1000(n) {
7
+ const parts = [];
8
+ const h = Math.floor(n / 100);
9
+ const rest = n % 100;
10
+ if (h)
11
+ parts.push(HUNDREDS[h]);
12
+ if (rest >= 10 && rest < 20)
13
+ parts.push(TEENS[rest - 10]);
14
+ else {
15
+ if (rest >= 20)
16
+ parts.push(TENS[Math.floor(rest / 10)]);
17
+ if (rest % 10)
18
+ parts.push(ONES[rest % 10]);
19
+ }
20
+ return parts.join(" و ");
21
+ }
22
+ /**
23
+ * عدد به حروف فارسی: 1250000 → «یک میلیون و دویست و پنجاه هزار».
24
+ * برای اعداد صحیح تا ۹۹۹ تریلیون. «هزار» بدون «یک» خوانده می‌شود (۱۰۰۰ → هزار).
25
+ */
26
+ export function numberToWords(value) {
27
+ let n = typeof value === "bigint" ? value : BigInt(Math.trunc(value));
28
+ if (n === 0n)
29
+ return "صفر";
30
+ const negative = n < 0n;
31
+ if (negative)
32
+ n = -n;
33
+ if (n >= 10n ** 15n)
34
+ throw new RangeError("عدد بزرگ‌تر از حد پشتیبانی‌شده است");
35
+ const groups = [];
36
+ for (let i = 0; n > 0n; i++, n /= 1000n) {
37
+ const g = Number(n % 1000n);
38
+ if (!g)
39
+ continue;
40
+ const words = i === 1 && g === 1 ? "" : under1000(g);
41
+ groups.unshift([words, SCALES[i]].filter(Boolean).join(" "));
42
+ }
43
+ return (negative ? "منفی " : "") + groups.join(" و ");
44
+ }
package/package.json CHANGED
@@ -1,6 +1,26 @@
1
1
  {
2
2
  "name": "@kashi-ui/persian",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.4.0",
4
+ "description": "ابزارهای فارسی کاشی: تاریخ شمسی، اعتبارسنجی کد ملی و شماره کارت، ارقام فارسی",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": ["dist", "README.md"],
13
+ "sideEffects": false,
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "test": "node --test test/*.test.mjs"
17
+ },
18
+ "devDependencies": { "typescript": "^5.6.0" },
19
+ "publishConfig": { "access": "public" },
20
+ "keywords": ["persian", "farsi", "jalali", "shamsi", "national-id", "iran", "date"],
21
+ "repository": { "type": "git", "url": "git+https://github.com/Sepantapouya/Kashi.git", "directory": "packages/persian" },
22
+ "homepage": "https://kashi.sepantapouya.com",
23
+ "bugs": "https://github.com/Sepantapouya/Kashi/issues",
24
+ "author": "Sepanta Pouya (https://sepantapouya.com)",
25
+ "license": "MIT"
26
+ }