UNPKG

angular-material-jalali-datepicker-adapter

Version:
610 lines (601 loc) 20.7 kB
import * as i0 from '@angular/core'; import { Injectable, inject } from '@angular/core'; import { DateAdapter, MAT_DATE_LOCALE, MAT_DATE_FORMATS } from '@angular/material/core'; class JalaliDateService { _locale = 'fa-IR'; _calendar = 'persian'; _monthNames = [ 'فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند', ]; _dayNames = { long: [ 'شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه', ], short: ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج'], narrow: ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج'], }; breaks = [ -61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178, ]; // Cache formatter to avoid recreating _formatter; get monthNames() { return [...this._monthNames]; } get dayNames() { return this._dayNames; } getFormatter() { return (this._formatter ||= new Intl.DateTimeFormat(this._locale, { calendar: this._calendar, numberingSystem: 'latn', })); } getYear(date) { return parseInt(this.getFormatter() .formatToParts(date) .find((part) => part.type === 'year')?.value || '0'); } getMonth(date) { return (parseInt(this.getFormatter() .formatToParts(date) .find((part) => part.type === 'month')?.value || '1') - 1); } getDate(date) { return parseInt(this.getFormatter() .formatToParts(date) .find((part) => part.type === 'day')?.value || '1'); } addDays(date, days) { const result = new Date(date); result.setDate(result.getDate() + days); return result; } addMonths(date, months) { const jalali = this.toJalali(date); let newMonth = jalali.month + months; let newYear = jalali.year; while (newMonth > 12) { newMonth -= 12; newYear++; } while (newMonth < 1) { newMonth += 12; newYear--; } // Handle day overflow for different month lengths const maxDaysInMonth = this.getDaysInMonth(newYear, newMonth); const newDay = Math.min(jalali.day, maxDaysInMonth); const gregorian = this.toGregorian(newYear, newMonth, newDay); return new Date(gregorian.year, gregorian.month - 1, gregorian.date); } addYears(date, years) { return this.addMonths(date, years * 12); } parse(value, parseFormat = 'yyyy/MM/dd') { if (!value || value === '') return null; if (value instanceof Date) { return this.isValid(value) ? value : null; } if (typeof value === 'string') { return this.parseString(value.trim(), parseFormat); } if (typeof value === 'number') { const date = new Date(value); return this.isValid(date) ? date : null; } return null; } parseString(dateStr, format) { // Convert Persian digits const normalizedStr = dateStr.replace(/[۰-۹]/g, (d) => '۰۱۲۳۴۵۶۷۸۹'.indexOf(d).toString()); // Simple yyyy/MM/dd parsing (most common case) if (format === 'yyyy/MM/dd') { const match = normalizedStr.match(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/); if (match) { const [, year, month, day] = match.map(Number); return this.createJalaliDate(year, month, day); } } return null; } createJalaliDate(year, month, day) { try { const gregorian = this.toGregorian(year, month, day); const date = new Date(gregorian.year, gregorian.month - 1, gregorian.date); return this.isValid(date) ? date : null; } catch { return null; } } format(date, displayFormat) { if (!this.isValid(date)) return ''; const year = this.getYear(date); const month = this.getMonth(date) + 1; const day = this.getDate(date); const monthLong = this.monthNames[month - 1]; const monthShort = monthLong.slice(0, 3); // Handle common format patterns return displayFormat .replace(/MMMM/g, monthLong) .replace(/MMM/g, monthShort) .replace(/yyyy/g, year.toString()) .replace(/MM/g, month.toString().padStart(2, '0')) .replace(/dd/g, day.toString().padStart(2, '0')) .replace(/M/g, month.toString()) .replace(/d/g, day.toString()); } isValid(date) { return date !== null && date instanceof Date && !isNaN(date.getTime()); } getDaysInMonth(year, month) { if (month < 6) return 31; if (month < 11) return 30; return this.isLeapYear(year) ? 30 : 29; } // Convert Gregorian to Jalali toJalali(date) { const year = this.getYear(date); const month = this.getMonth(date) + 1; // Convert to 1-based const day = this.getDate(date); return { year, month, day }; } toGregorian(year, month, date) { const julian = this.jalaliToJulian(year, month, date); return this.julianToGregorian(julian); } isLeapYear(year) { try { const leap = this.calculateLeap(year); return leap === 0; } catch { return false; } } // Core conversion methods (simplified) jalaliToJulian(year, month, date) { const r = this.calculateJalali(year, false); return (this.gregorianToJulian(r.gregorianYear, 3, r.march) + (month - 1) * 31 - this.div(month, 7) * (month - 7) + date - 1); } julianToGregorian(julian) { let j = 4 * julian + 139361631; j = j + this.div(this.div(4 * julian + 183187720, 146097) * 3, 4) * 4 - 3908; const i = this.div(this.mod(j, 1461), 4) * 5 + 308; const date = this.div(this.mod(i, 153), 5) + 1; const month = this.mod(this.div(i, 153), 12) + 1; const year = this.div(j, 1461) - 100100 + this.div(8 - month, 6); return { year, month, date }; } gregorianToJulian(year, month, date) { const julian = this.div((year + this.div(month - 8, 6) + 100100) * 1461, 4) + this.div(153 * this.mod(month + 9, 12) + 2, 5) + date - 34840408; return (julian - this.div(this.div(year + 100100 + this.div(month - 8, 6), 100) * 3, 4) + 752); } calculateJalali(year, calculateLeap = true) { const bl = this.breaks.length; const gregorianYear = year + 621; let leapJ = -14; let jp = this.breaks[0]; if (year < jp || year >= this.breaks[bl - 1]) { throw new Error(`Invalid Jalali year ${year}`); } let jump = 0; for (let i = 1; i < bl; i++) { const jm = this.breaks[i]; jump = jm - jp; if (year < jm) break; leapJ = leapJ + this.div(jump, 33) * 8 + this.div(this.mod(jump, 33), 4); jp = jm; } let n = year - jp; leapJ = leapJ + this.div(n, 33) * 8 + this.div(this.mod(n, 33) + 3, 4); if (this.mod(jump, 33) === 4 && jump - n === 4) { leapJ += 1; } const leapG = this.div(gregorianYear, 4) - this.div((this.div(gregorianYear, 100) + 1) * 3, 4) - 150; const march = 20 + leapJ - leapG; return { gregorianYear, march, leap: calculateLeap ? this.calculateLeap(year, { jp, jump }) : -1, }; } calculateLeap(year, calculated) { const bl = this.breaks.length; let jp = calculated ? calculated.jp : this.breaks[0]; let jump = calculated ? calculated.jump : 0; if (!calculated) { if (year < jp || year >= this.breaks[bl - 1]) { throw new Error(`Invalid Jalali year ${year}`); } for (let i = 1; i < bl; i++) { const jm = this.breaks[i]; jump = jm - jp; if (year < jm) break; jp = jm; } } let n = year - jp; if (jump - n < 6) { n = n - jump + this.div(jump + 4, 33) * 33; } let leap = this.mod(this.mod(n + 1, 33) - 1, 4); if (leap === -1) { leap = 4; } return leap; } div(a, b) { return ~~(a / b); } mod(a, b) { return a - ~~(a / b) * b; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: JalaliDateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: JalaliDateService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: JalaliDateService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); const JALALI_DATE_FORMATS = { parse: { dateInput: 'yyyy/MM/dd', }, display: { dateInput: 'yyyy/MM/dd', monthYearLabel: 'yyyy MMMM', dateA11yLabel: 'yyyy/MM/dd', monthYearA11yLabel: 'yyyy MMMM', }, }; class MaterialJalaliDateAdapter extends DateAdapter { dateService = inject(JalaliDateService); getYear(date) { return this.dateService.getYear(date); } getMonth(date) { return this.dateService.getMonth(date); } getDate(date) { return this.dateService.getDate(date); } getDayOfWeek(date) { // Convert JS day (0=Sun, 6=Sat) to Persian (0=Sat, 6=Fri) return (date.getDay() + 1) % 7; } getMonthNames(_style) { return this.dateService.monthNames; } getDateNames() { // Cache the array since it's static return this._dateNames ||= Array.from({ length: 31 }, (_, i) => String(i + 1)); } _dateNames; getDayOfWeekNames(style) { return [...this.dateService.dayNames[style]]; } getYearName(date) { return this.getYear(date).toString(); } getFirstDayOfWeek() { return 0; // Saturday is first day in Persian calendar } getNumDaysInMonth(date) { const year = this.getYear(date); const month = this.getMonth(date); // Convert to 1-based return this.dateService.getDaysInMonth(year, month); } clone(date) { return new Date(date.getTime()); } createDate(year, month, date) { // Validate input parameters if (!this.isValidJalaliInput(year, month + 1, date)) { return this.invalid(); } try { const gregorian = this.dateService.toGregorian(year, month + 1, date); const result = new Date(gregorian.year, gregorian.month - 1, gregorian.date); return this.isValid(result) ? result : this.invalid(); } catch { return this.invalid(); } } today() { return new Date(); } parse(value, parseFormat) { return this.dateService.parse(value, parseFormat); } format(date, displayFormat) { return this.dateService.format(date, displayFormat); } addCalendarYears(date, years) { if (!this.isValid(date)) return this.invalid(); try { return this.dateService.addYears(date, years); } catch { return this.invalid(); } } addCalendarMonths(date, months) { if (!this.isValid(date)) return this.invalid(); try { return this.dateService.addMonths(date, months); } catch { return this.invalid(); } } addCalendarDays(date, days) { if (!this.isValid(date)) return this.invalid(); try { return this.dateService.addDays(date, days); } catch { const result = this.clone(date); result.setDate(result.getDate() + days); return result; } } toIso8601(date) { return date.toISOString(); } isDateInstance(obj) { return obj instanceof Date; } isValid(date) { return this.dateService.isValid(date); } invalid() { return new Date(NaN); } deserialize(value) { if (value == null || value === '') { return null; } if (typeof value === 'string') { const trimmed = value.trim(); return trimmed ? this.parse(trimmed, JALALI_DATE_FORMATS.parse.dateInput) : null; } if (typeof value === 'number' && !isNaN(value)) { const date = new Date(value); return this.isValid(date) ? date : null; } if (this.isDateInstance(value)) { return this.isValid(value) ? this.clone(value) : null; } return null; } // Helper method for input validation isValidJalaliInput(year, month, day) { return (Number.isInteger(year) && year > 0 && year <= 3178 && Number.isInteger(month) && month >= 1 && month <= 12 && Number.isInteger(day) && day >= 1 && day <= 31); } } class MaterialJalaliStringDateAdapter extends DateAdapter { invalid() { return 'INVALID_DATE'; } dateService = inject(JalaliDateService); getDateParts(date) { if (!date || typeof date !== 'string') { throw new Error('Invalid date string'); } const parts = date.split('/'); if (parts.length !== 3) { throw new Error('Invalid date format'); } const year = parseInt(parts[0], 10); const month = parseInt(parts[1], 10) - 1; // Convert to 0-based const day = parseInt(parts[2], 10); if (isNaN(year) || isNaN(month) || isNaN(day)) { throw new Error('Invalid date components'); } return { year, month, day }; } getYear(date) { return this.getDateParts(date).year; } getMonth(date) { return this.getDateParts(date).month; } getDate(date) { return this.getDateParts(date).day; } getDayOfWeek(date) { const dateObj = this.dateService.parse(date, JALALI_DATE_FORMATS.parse.dateInput); if (!dateObj) return 0; // Convert JS day (0=Sun, 6=Sat) to Persian (0=Sat, 6=Fri) return (dateObj.getDay() + 1) % 7; } getMonthNames(_style) { return this.dateService.monthNames; } getDateNames() { // Cache the array since it's static return this._dateNames ||= Array.from({ length: 31 }, (_, i) => String(i + 1)); } _dateNames; getDayOfWeekNames(style) { return [...this.dateService.dayNames[style]]; } getYearName(date) { return this.getYear(date).toString(); } getFirstDayOfWeek() { return 0; // Saturday is first day } getNumDaysInMonth(date) { const year = this.getYear(date); const month = this.getMonth(date) + 1; // Convert to 1-based return this.dateService.getDaysInMonth(year, month); } clone(date) { return date; } createDate(year, month, date) { // Ensure valid ranges const validYear = Math.max(1, year); const validMonth = Math.max(0, Math.min(11, month)); const maxDays = this.dateService.getDaysInMonth(validYear, validMonth); const validDate = Math.max(1, Math.min(maxDays, date)); return `${validYear}/${(validMonth + 1).toString().padStart(2, '0')}/${validDate.toString().padStart(2, '0')}`; } today() { const today = new Date(); return this.dateService.format(today, JALALI_DATE_FORMATS.parse.dateInput); } parse(value, parseFormat) { if (!value) return null; if (typeof value === 'string') { // If it's already in the correct format, validate and return as-is if (/^\d{4}\/\d{1,2}\/\d{1,2}$/.test(value)) { const dateObj = this.dateService.parse(value, parseFormat); return dateObj ? value : null; } } if (typeof value === 'number') { const dateObj = new Date(value); if (this.dateService.isValid(dateObj)) { return this.dateService.format(dateObj, JALALI_DATE_FORMATS.parse.dateInput); } } if (value instanceof Date && this.dateService.isValid(value)) { return this.dateService.format(value, JALALI_DATE_FORMATS.parse.dateInput); } return null; } format(date, displayFormat) { const dateObj = this.dateService.parse(date, JALALI_DATE_FORMATS.parse.dateInput); if (!dateObj) return ''; return this.dateService.format(dateObj, displayFormat); } addCalendarYears(date, years) { const pYear = this.getYear(date); const pMonth = this.getMonth(date); const pDay = this.getDate(date); return this.createDate(pYear + years, pMonth, pDay); } addCalendarMonths(date, months) { const pYear = this.getYear(date); const pMonth = this.getMonth(date); const pDay = this.getDate(date); const totalMonths = pMonth + months; const newYear = pYear + Math.floor(totalMonths / 12); let newMonth = totalMonths % 12; // Handle negative months if (newMonth < 0) { newMonth = 12 + newMonth; } // Handle month overflow by adjusting day if needed const maxDay = this.dateService.getDaysInMonth(newYear, newMonth); const adjustedDay = Math.min(pDay, maxDay); return this.createDate(newYear, newMonth, adjustedDay); } addCalendarDays(date, days) { const dateObject = this.dateService.parse(date, JALALI_DATE_FORMATS.parse.dateInput); if (!dateObject) return date; dateObject.setDate(dateObject.getDate() + days); return this.dateService.format(dateObject, JALALI_DATE_FORMATS.parse.dateInput); } toIso8601(date) { const dateObject = this.dateService.parse(date, JALALI_DATE_FORMATS.parse.dateInput); if (!dateObject) return ''; return dateObject.toISOString(); } isDateInstance(obj) { return typeof obj === 'string' && /^\d{4}\/\d{1,2}\/\d{1,2}$/.test(obj); } isValid(date) { if (!date) return false; const dateObj = this.dateService.parse(date, JALALI_DATE_FORMATS.parse.dateInput); return dateObj !== null && this.dateService.isValid(dateObj); } deserialize(value) { if (!value) return null; if (typeof value === 'string') { return this.parse(value, JALALI_DATE_FORMATS.parse.dateInput); } if (typeof value === 'number') { return this.dateService.format(new Date(value), JALALI_DATE_FORMATS.parse.dateInput); } if (value instanceof Date) { return this.dateService.format(value, JALALI_DATE_FORMATS.parse.dateInput); } return null; } } function provideJalaiDateAdapter(type = 'date') { return [ { provide: DateAdapter, useClass: type === 'date' ? MaterialJalaliDateAdapter : MaterialJalaliStringDateAdapter, }, { provide: MAT_DATE_LOCALE, useValue: 'fa-IR' }, { provide: MAT_DATE_FORMATS, useValue: JALALI_DATE_FORMATS }, ]; } /* * Public API Surface of angular-material-jalali-datepicker-adapter */ /** * Generated bundle index. Do not edit. */ export { JALALI_DATE_FORMATS, JalaliDateService, MaterialJalaliDateAdapter, MaterialJalaliStringDateAdapter, provideJalaiDateAdapter }; //# sourceMappingURL=angular-material-jalali-datepicker-adapter.mjs.map