/* Local Persian date selector. No libraries, CDN, network or production writes. Use Intl's Persian calendar to determine leap Esfand; don't approximate with year % 4. Display is Jalali; submission is Gregorian YYYY-MM-DD, date-only (no timezone timestamp). */ (() => { 'use strict'; const day=document.querySelector('#receipt-day'); if(!day)return; const month=document.querySelector('#receipt-month'); const year=document.querySelector('#receipt-year'); const output=document.querySelector('#receipt-date-value'); const status=document.querySelector('#receipt-date-status'); const todayButton=document.querySelector('#receipt-date-today'); const monthNames=['فروردین','اردیبهشت','خرداد','تیر','مرداد','شهریور','مهر','آبان','آذر','دی','بهمن','اسفند']; const digits=value=>String(value).replace(/\d/g,digit=>'۰۱۲۳۴۵۶۷۸۹'[digit]); const formatter=new Intl.DateTimeFormat('en-US',{calendar:'persian',numberingSystem:'latn',timeZone:'Asia/Tehran',year:'numeric',month:'numeric',day:'numeric'}); function parts(date){ const values=Object.fromEntries(formatter.formatToParts(date).map(part=>[part.type,part.value])); return {year:Number(values.year),month:Number(values.month),day:Number(values.day)}; } function newYear(selectedYear){ // The native Persian calendar provides the exact first day; bounded March search. for(let marchDay=18;marchDay<=24;marchDay++){ const date=new Date(Date.UTC(selectedYear+621,2,marchDay,12)); const value=parts(date); if(value.year===selectedYear && value.month===1 && value.day===1)return date.getTime(); } throw new Error('Persian new year unavailable'); } const yearDays=new Map(); function monthLength(selectedYear,selectedMonth){ if(selectedMonth<=6)return 31; if(selectedMonth<=11)return 30; if(!yearDays.has(selectedYear))yearDays.set(selectedYear,(newYear(selectedYear+1)-newYear(selectedYear))/86400000); return yearDays.get(selectedYear)===366?30:29; } function addOption(select,value,label){ const option=document.createElement('option');option.value=String(value);option.textContent=label;select.append(option); } function updateDays(){ const previous=day.value; const length=year.value && month.value?monthLength(Number(year.value),Number(month.value)):31; day.replaceChildren();addOption(day,'','روز'); for(let value=1;value<=length;value++)addOption(day,value,digits(value)); const invalid=previous && Number(previous)>length; day.value=invalid?'':previous; if(invalid){status.textContent='این ماه '+digits(length)+' روز دارد؛ روز پرداخت را دوباره انتخاب کنید.';output.value='';} else updateValue(); } function updateValue(){ if(!day.value || !month.value || !year.value){day.setCustomValidity('');output.value='';status.textContent='روز، ماه و سال پرداخت را کامل انتخاب کنید.';return;} const selectedYear=Number(year.value),selectedMonth=Number(month.value),selectedDay=Number(day.value); if(!Number.isInteger(selectedYear) || selectedMonth<1 || selectedMonth>12 || selectedDay<1 || selectedDay>monthLength(selectedYear,selectedMonth)){output.value='';day.setCustomValidity('تاریخ معتبر انتخاب کنید.');return;} const offset=selectedMonth<=6?(selectedMonth-1)*31:186+(selectedMonth-7)*30; // Anchor at UTC noon, add whole calendar days, serialize UTC date: no local midnight drift. const gregorian=new Date(newYear(selectedYear)+(offset+selectedDay-1)*86400000); const check=parts(gregorian); if(check.year!==selectedYear || check.month!==selectedMonth || check.day!==selectedDay){ output.value='';day.setCustomValidity('تاریخ معتبر انتخاب کنید.');status.textContent='تاریخ معتبر انتخاب کنید.';return; } day.setCustomValidity('');output.value=gregorian.toISOString().slice(0,10); status.textContent=digits(day.value)+' '+monthNames[Number(month.value)-1]+' '+digits(year.value); } if(formatter.resolvedOptions().calendar!=='persian'){ status.textContent='این مرورگر تقویم شمسی را پشتیبانی نمی‌کند. از نسخهٔ جدید مرورگر استفاده کنید.'; [day,month,year,todayButton].forEach(control=>control.disabled=true);return; } const today=parts(new Date()); month.replaceChildren();year.replaceChildren(); monthNames.forEach((name,index)=>addOption(month,index+1,name)); for(let value=today.year+1;value>=today.year-10;value--)addOption(year,value,digits(value)); year.value=String(today.year);month.value=String(today.month);updateDays(); document.querySelector('#receipt-form').addEventListener('receipt-date:validate',updateValue); day.addEventListener('change',updateValue); month.addEventListener('change',updateDays);year.addEventListener('change',updateDays); todayButton.addEventListener('click',()=>{ const current=parts(new Date()); if(![...year.options].some(option=>option.value===String(current.year)))addOption(year,current.year,digits(current.year)); year.value=String(current.year);month.value=String(current.month);day.value='';updateDays(); day.value=String(current.day);updateValue();day.focus(); }); })();