UNPKG

persidate

Version:

persidate is a lightweight package for converting and managing Shamsi (Jalali) and Gregorian dates in JavaScript/TypeScript.

511 lines (463 loc) 14.8 kB
import { parseArabic } from "./parse-arabic"; declare global { interface Date { addDaysToDate(days: number): Date; } } // Extending Date prototype const DT: any = Date.prototype; /** * Locale setting for Persian (Iran) format. * @constant {string} locale - Locale code for Persian (Iran). */ const locale = "fa-IR"; /** * Converts date to ISO format in local time. * @param {Date} date - The input date. * @returns {string} Local ISO date string. */ export const convertToStandardDateTime = (date: Date): string => { const tzoffset = date?.getTimezoneOffset() * 60000; // Offset in milliseconds const localISOTime = new Date(date.getTime() - tzoffset) ?.toISOString() ?.slice(0, -1); return localISOTime; }; /** * Converts date to ISO format with a timezone offset adjustment. * @param {Date | string | number} date - The input date. * @returns {string} Adjusted ISO date string. */ export const convertToISODateTime = (date: Date | string | number): string => { const formattedDate = typeof date === "string" || typeof date === "number" ? new Date(date) : date; const tzoffset = (formattedDate.getTimezoneOffset() - 60) * 60000; const localISOTime = new Date(formattedDate.getTime() - tzoffset) .toISOString() .slice(0, -1); return localISOTime; }; /** * Converts Jalali date string to Gregorian Date object. * @param {string} jalaliDate - Jalali date string in the format YYYY/MM/DD or YYYY-MM-DD. * @returns {Date} Gregorian Date object. */ export const convertToGregorianDate = (jalaliDate: string): Date => { const [year, month, day] = jalaliDate.split(/[/-]/).map(Number); const [gYear, gMonth, gDay] = toGregorian(year, month, day); return new Date(gYear, gMonth - 1, gDay); // JS months are 0-indexed }; /** * Converts Jalali date string to Gregorian date string in the format YYYY/MM/DD. * @param {string} jalaliDate - Jalali date string in the format YYYY/MM/DD or YYYY-MM-DD. * @returns {string} Gregorian date string in the format YYYY/MM/DD. */ export const convertToGregorianDateString = (jalaliDate: string): string => { const [year, month, day] = jalaliDate.split(/[/-]/).map(Number); const [gYear, gMonth, gDay] = toGregorian(year, month, day); return `${gYear}/${gMonth.toString().padStart(2, "0")}/${gDay .toString() .padStart(2, "0")}`; }; /** * Converts a Gregorian date to a Jalali date in a specified format. * * @param {Date | string | number} date - The Gregorian date input. * @param { "day" | "weekday" | "month" | "dayMonth" | "dayMonthYear" | "weekdayDayMonth" | "weekdayDayMonthYear" } [format] - * Optional format for the output: * - If omitted, returns "YYYY-MM-DD" * - "day": Returns day (e.g. "26") * - "weekday": Returns weekday (e.g. "جمعه") * - "month": Returns month (e.g. "مهر") * - "dayMonth": Returns day and month (e.g. "26 مهر") * - "dayMonthYear": Returns full date (e.g. "26 مهر 1403") * - "weekdayDayMonth": Returns weekday + day + month (e.g. "جمعه 26 مهر") * - "weekdayDayMonthYear": Full format (e.g. "جمعه 26 مهر 1403") * * @returns {string} Jalali date in desired format or "YYYY-MM-DD" by default. * * @example * convertToJalaliDate("2024-10-18"); // → "1403-07-27" * convertToJalaliDate("2024-10-18", "dayMonthYear"); // → "27 مهر 1403" * convertToJalaliDate(new Date(), "weekday"); // → "دوشنبه" */ export const convertToJalaliDate = ( date: Date | string | number, format?: | "day" | "weekday" | "month" | "year" | "dayMonth" | "dayMonthYear" | "weekdayDayMonth" | "weekdayDayMonthYear" ): string => { const parsedDate = typeof date === "number" || typeof date === "string" ? new Date(date) : date; const parts: any = parseArabic(parsedDate.toLocaleDateString("fa-IR")).split( /[-\/]/ ); const [year, month, day] = parts; const weekday = jalaliWeekdayNamesGregorianFormat[parsedDate.getDay()]; const persianMonth = jalaliMonthNames[month - 1]; // Default fallback: YYYY-MM-DD if (!format) { return `${year}-${month}-${day}`; } switch (format) { case "day": return `${day}`; case "weekday": return `${weekday}`; case "month": return `${persianMonth}`; case "year": return `${year}`; case "dayMonth": return `${day} ${persianMonth}`; case "dayMonthYear": return `${day} ${persianMonth} ${year}`; case "weekdayDayMonth": return `${weekday} ${day} ${persianMonth}`; case "weekdayDayMonthYear": return `${weekday} ${day} ${persianMonth} ${year}`; default: return `${year}-${month}-${day}`; } }; /** * Converts a given timestamp or Date object to a Gregorian date string in the format YYYY-MM-DD. * @param {number | Date} date - Unix timestamp or Date object. * @returns {string} Gregorian date string in the format YYYY-MM-DD. */ export const formatToGregorianDate = (date: number | Date): string => { const formattedDate = typeof date === "number" ? new Date(date) : date; const year = formattedDate.getFullYear(); const month = formattedDate.getMonth() + 1; const day = formattedDate.getDate(); return `${year}-${month.toString()}-${day.toString()}`; }; /** * Converts Jalali date and time to Gregorian date string with time. * @param {number | Date} date - Unix timestamp or Date object. * @returns {string} Gregorian date string with time in the format YYYY-MM-DDTHH:mm. */ export const formatToGregorianDateTime = ( date: number | Date, time: string ): string => { const formattedDate = formatToGregorianDate(date); return `${formattedDate}T${time}`; }; /** * Converts a Gregorian date to a formatted Jalali date with padded month and day. * @param {Date | string | number} date - Gregorian date object. * @returns {string} Jalali date in the format YYYY-MM-DD with zero-padded month and day. */ export const formatToJalaliDatePadded = ( date: Date | string | number ): string => { const formattedDate = typeof date === "string" || typeof date === "number" ? new Date(date) : date; const parts = parseArabic(formattedDate.toLocaleDateString(locale)).split( /[/-]/ ); let month = parts[1]?.length < 2 ? `0${parts[1]}` : parts[1]; let day = parts[2]?.length < 2 ? `0${parts[2]}` : parts[2]; return `${parts[0]}-${month}-${day}`; }; type ISupportedDateFormats = | "jYYYY-jM-jD" | "jYYYY-jMM-jDD" | "jMMMM" | "jD" | "jDDD" | "jDDD-jMM-jYY" | "YYYY-MM-DD" | "YYYY/MM/DD" | "YYYY/MM/DD HH:mm" | "HH:mm" | "YYYY/MM/DDTHH:mm:ss"; /** * Formats a date string according to the specified format. * @param {string | Date} date - The input date string or Date object. * @param {SupportedDateFormats} format - The desired output format. * @returns {string | null} Formatted date string or null if invalid. */ export const formatToLocalizedDate = ( date: string | Date, format: ISupportedDateFormats ): string | null => { if (!date) return null; if (date instanceof Date) { date = date.toISOString(); } if (format === "jYYYY-jM-jD") { return convertToJalaliDate(date); } if (format === "jYYYY-jMM-jDD") { return formatToJalaliDatePadded(date); } if (format === "jMMMM") { return convertToJalaliDate(date, "month"); } if (format === "jD") { return convertToJalaliDate(date, "day"); } if (format === "jDDD") { return convertToJalaliDate(date, "dayMonth"); } if (format === "jDDD-jMM-jYY") { return convertToJalaliDate(date, "dayMonthYear"); } if (typeof date === "string") { if (date.includes("/")) date = date.replace(/\//g, "-"); } const spited: any = convertToISODateTime(date)?.split("T"); const clockSpited = spited[1]?.split(":"); if (format === "YYYY-MM-DD") return spited[0]; if (format === "YYYY/MM/DD") return spited[0]?.replace(/-/g, "/"); if (format === "YYYY/MM/DD HH:mm") return ( spited[0]?.replace(/-/g, "/") + " " + clockSpited[0] + ":" + clockSpited[1] ); if (format === "HH:mm") return clockSpited[0] + ":" + clockSpited[1]; if (format === "YYYY/MM/DDTHH:mm:ss") return ( spited[0]?.replace(/-/g, "/") + "T" + clockSpited[0] + ":" + clockSpited[1] + ":" + clockSpited[2]?.split(".")[0] ); return null; }; /** * Gets today's date in ISO format (YYYY-MM-DD). * @returns {string} Today's date in "YYYY-MM-DD" format. */ export const getToday = (): string => { const date = new Date().toISOString(); const splitDate = date.split("T")[0]; return splitDate; }; /** * Extracts time from a date string or Date object in the format HH:mm. * @param {string | number | Date} date - Date as string (YYYY-MM-DDTHH:mm:ss) or Date object. * @returns {string} Time in the format HH:mm. */ export const getTimeFromDate = ( date: string | number | Date = new Date() ): string => { const dateObj = typeof date === "string" || typeof date === "number" ? new Date(date) : date; if (!isNaN(dateObj.getTime())) { // Check if date is valid const hours = String(dateObj.getHours()).padStart(2, "0"); const minutes = String(dateObj.getMinutes()).padStart(2, "0"); const seconds = String(dateObj.getSeconds()).padStart(2, "0"); return `${hours}:${minutes}:${seconds}`; } return ""; }; /** * Calculates the number of days between now and a given date. * @param {string | Date | number} inputDate - A Date object or a string parsable by `new Date() or timeStamp`. * @returns {number} Number of days until the date (can be negative if in the past). */ export const getDaysFromNow = (inputDate: string | Date): number => { const targetDate = typeof inputDate === "string" || typeof inputDate === "number" ? new Date(inputDate) : inputDate; if (isNaN(targetDate.getTime())) { throw new Error("Invalid date format. Use ISO format or Date object."); } const now = new Date(); const msDiff = targetDate.getTime() - now.getTime(); const daysDiff = Math.ceil(msDiff / (1000 * 60 * 60 * 24)); return daysDiff; }; /** * Get Jalali (Persian) date string to a Unix timestamp in milliseconds. * @param {string} jalaliDate - Jalali date in the format "YYYY-MM-DD" * @returns {number} Unix timestamp (milliseconds since 1970-01-01) */ export const getJalaliTimeStamp = (jalaliDate: string): number => { const gregorianDate = convertToGregorianDate(jalaliDate); // "2025-03-20" const timestamp = new Date(gregorianDate).getTime(); //timestamp in milliseconds return timestamp; }; /** * Returns a string indicating how long ago the given date was, * in Persian language format. * * @param {Date | string | number} date - The input date, either a Date object or a string parsable by `new Date()`. * @param {string} [suffix='پیش'] - Optional string to append, like 'پیش', 'قبل', or custom. Defaults to 'پیش'. * @returns {string} A Persian relative time string such as "۵ دقیقه پیش" */ export const getTimeAgo = ( date: Date | string | number, suffix: string = "پیش" ): string => { const d = typeof date === "string" || typeof date === "number" ? new Date(date) : date; const now = new Date(); const diffMs = now.getTime() - d.getTime(); const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHr = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHr / 24); const diffMonth = Math.floor(diffDay / 30); const diffYear = Math.floor(diffDay / 365); if (diffSec < 60) return `لحظاتی ${suffix}`; if (diffMin < 60) return `${diffMin} دقیقه ${suffix}`; if (diffHr < 24) return `${diffHr} ساعت ${suffix}`; if (diffDay < 30) return `${diffDay} روز ${suffix}`; if (diffMonth < 12) return `${diffMonth} ماه ${suffix}`; return `${diffYear} سال ${suffix}`; }; /** * Adds days to the Date object. * @param {number} days - Number of days to add. * @returns {Date} Updated date with added days. */ DT.addDaysToDate = function (days: number): Date { const date = new Date(this.valueOf()); date.setDate(date.getDate() + days); return date; }; /** * Adds a specific number of days to the given date. * @param {Date} date - Input date. * @param {number} daysCount - Number of days to add. * @returns {Date} Updated date. */ export const addDaysToDate = (date: Date, daysCount: number): Date => { return date.addDaysToDate(daysCount); }; /** * Compares two date strings and checks if the first date is before the second. * @param {string} first - First date string. * @param {string} second - Second date string. * @returns {boolean} True if first date is before second date. */ export const isBeforeDate = (first: string, second: string): boolean => { const firstTime = new Date(first).getTime(); const secondTime = new Date(second).getTime(); return firstTime < secondTime; }; /** * Check if the given Jalali year is a leap year. * @param {number} jalaliYear - Jalali year. * @returns {boolean} True if the Jalali year is a leap year. */ export const isLeapYearJalali = (jalaliYear: number): boolean => { const [gy] = toGregorian(jalaliYear, 1, 1); return (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0; }; // Define arrays for Jalali months and days export const jalaliMonthNames: string[] = [ "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", ]; export const jalaliWeekdayNames: string[] = [ "شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنج‌شنبه", "جمعه", ]; const jalaliWeekdayNamesGregorianFormat: string[] = [ "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنج‌شنبه", "جمعه", "شنبه", ]; /** * Converts Jalali date to Gregorian date. * @param {number} jy - Jalali year. * @param {number} jm - Jalali month. * @param {number} jd - Jalali day. * @returns {[number, number, number]} Gregorian year, month, and day. */ const toGregorian = ( jy: number, jm: number, jd: number ): [number, number, number] => { let gy = jy <= 979 ? 621 : 1600; jy -= jy <= 979 ? 0 : 979; let days = 365 * jy + Math.floor(jy / 33) * 8 + Math.floor(((jy % 33) + 3) / 4) + 78 + jd + (jm < 7 ? (jm - 1) * 31 : (jm - 7) * 30 + 186); gy += 400 * Math.floor(days / 146097); days %= 146097; if (days > 36524) { gy += 100 * Math.floor(--days / 36524); days %= 36524; if (days >= 365) days++; } gy += 4 * Math.floor(days / 1461); days %= 1461; gy += Math.floor((days - 1) / 365); if (days > 365) days = (days - 1) % 365; let gd = days + 1; const sal_a = [ 0, 31, (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0 ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, ]; let gm = 0; for (gm = 0; gm < 13; gm++) { if (gd <= sal_a[gm]) break; gd -= sal_a[gm]; } return [gy, gm, gd]; };