Files
customerTicket/design-prototypes/chaharsu/receipt-date.js
T
2026-10-05 12:36:58 +03:30

82 lines
5.1 KiB
JavaScript

/* 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();
});
})();