Date و Intl؛ زمان سرور، نمایش محلی و ترتیب پیام
در این درس ۵ بخش
در فهرست پیامها، ساعت فقط یک برچسب زیبا نیست؛ بخشی از قرارداد داده است. اگر زمان نمایششده را بهجای زمان اصلی ذخیره کنیم یا از ساعت دستگاه کاربر ترتیب قطعی بسازیم، پیامها با تغییر منطقهٔ زمانی جابهجا به نظر میرسند. در دورهٔ قبل، Intl را برای رقم و مبلغ به کار بردیم و دربارهٔ تاریخ نامعتبر هشدار دادیم. اکنون لحظهٔ زمانی، منطقهٔ زمانی، تقویم و ترتیب دریافت را جدا میکنیم. مثالها مستقلاند و با زمان ثابت اجرا میشوند تا نتیجه وابسته به لحظهٔ اجرای شما نباشد. هدف، ساخت تاریخ و ساعت قابل توضیح در یک رابط چندکاربره است.
Date یک لحظه را نگه میدارد، نه منطقهٔ زمانیِ انتخابشده را
Date در مدل جاوااسکریپت، یک مقدار عددی برحسب میلیثانیه نسبت به مبدأ زمانی مشخص دارد؛ این را با متن تقویم شمسی اشتباه نگیرید. نمایش محلی میتواند ساعت و حتی روز دیگری نشان دهد، درحالیکه لحظه عوض نشده است. زمان UTC مرجع مشترک نمایش با Z است؛ offset در یک رشته میگوید ساعت نوشتهشده نسبت به UTC چه جابهجایی دارد. خود شیء Date پس از تجزیه، نام یک منطقهٔ زمانی را بهعنوان هویت همراه زمان نگه نمیدارد. #date-intl مرجع مفید این جداسازی است؛ هنگام خواندن مثال، «مقدار» و «برچسب» را جدا یادداشت کنید.
فایل instant-lab.mjs زیر را اجرا کنید. دو رشته، ساعت ظاهری متفاوتی دارند ولی یک لحظه را توصیف میکنند؛ اختلاف سه ساعت و نیم در نمونه صریح است و از تنظیم دستگاه حدس زده نشده. getTime برای مقایسهٔ عددی مناسب است و toISOString نمایش UTC سازگار تولید میکند. این نتیجه به معنی مناسببودن هر رشتهٔ تاریخ ورودی نیست؛ قالبهای محلی و عبارت بدون offset میتوانند ابهام داشته باشند. برای مرز ارتباط، قالب مورد قبول را دقیق بنویسید و خروجی قابل نمایش را به مرحلهٔ رابط بسپارید.
const utc = new Date('2026-10-29T12:00:00.000Z');
const offset = new Date('2026-10-29T15:30:00.000+03:30');
console.log(utc.getTime() === offset.getTime()); // true
console.log(utc.toISOString()); // 2026-10-29T12:00:00.000Z
console.log(offset.toISOString()); // 2026-10-29T12:00:00.000Zتجزیهٔ موفق، جای اعتبارسنجی تاریخ را نمیگیرد
در قرارداد آموزشی بعد، فقط رشتهٔ UTC با سال چهارتایی و سه رقم میلیثانیه را میپذیریم. این محدودیت، انتخاب همین مثال است و ادعا ندارد همهٔ تاریخهای معتبر ISO باید این شکل را داشته باشند. ابتدا نوع و شکل بررسی میشود، سپس Date ساخته و بازنویسی استاندارد با متن اصلی مقایسه میشود. این مرحله تاریخ ناموجودی را که به روز دیگری عادیسازی شده رد میکند. برای #validation-contracts باید نمونهٔ مجاز و نامجاز را روشن کنید؛ تبدیل همهٔ ورودیهای خراب به «زمان فعلی» فقط خطای داده را در پشت خروجی ظاهراً قابل قبول پنهان میکند.
function parseCanonicalTimestamp(raw) {
if (typeof raw !== 'string' ||
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(raw)) {
throw new TypeError('Expected canonical UTC timestamp');
}
const date = new Date(raw);
if (!Number.isFinite(date.getTime()) || date.toISOString() !== raw) {
throw new RangeError('Invalid calendar timestamp');
}
return date;
}
console.log(parseCanonicalTimestamp('2026-10-29T12:00:00.000Z').getUTCDate()); // 29
for (const raw of ['2026-02-30T12:00:00.000Z', '2026-10-29', 'not-a-date']) {
try { parseCanonicalTimestamp(raw); }
catch { console.log('rejected:', raw); }
}بلوک را مستقل در timestamp-contract.mjs ذخیره کنید. ورودی تاریخِ تنها را عمدی رد میکنیم، چون تاریخ تقویمی مانند روز تولد با لحظهٔ ارسال پیام مسئلهٔ یکسانی نیست. برای روز تولد معمولاً نباید بیدلیل ساعت و منطقه اضافه کنیم. همچنین Date نامعتبر قبل از toISOString باید تشخیص داده شود، چون تولید رشته از آن خطا میدهد. ترتیب بررسیها بخشی از قرارداد است؛ بررسی با روش نمایشی فارسی، کنترل قابل اتکایی برای قالب شبکه نیست. متن اصلی معتبر را در مدل نگه دارید و هنگام ساخت نمای هر پیام آن را به برچسب محلی تبدیل کنید.
Intl نمایش را با منطقه و تقویم صریح میسازد
منطقهٔ زمانی قواعد تبدیل لحظه به ساعت محلی است؛ تقویم روش نامگذاری سال و ماه و روز است. این دو را یکی نگیرید. میتوان یک لحظه را در منطقهٔ تهران با تقویم میلادی یا شمسی نشان داد. در Intl.DateTimeFormat، timeZone را صریح میدهیم تا خروجی به تنظیم پیشفرض سرور یا دستگاه وابسته نباشد. calendar نیز تصمیم تقویم را آشکار میکند. اگر فقط fa-IR بدهید، برخی پیشفرضها مناسب فارسی خواهند بود، اما برای قرارداد محصول بهتر است بدانیم دقیقاً کدام انتخاب عمدی است. formatter را برای فهرست کوچک یکبار بسازید و برای چند پیام به کار ببرید.
const moment = new Date('2026-10-29T12:00:00.000Z');
const fa = new Intl.DateTimeFormat('fa-IR', {
calendar: 'persian', timeZone: 'Asia/Tehran',
dateStyle: 'medium', timeStyle: 'short'
});
const utcClock = new Intl.DateTimeFormat('en-GB', {
timeZone: 'UTC', hour: '2-digit', minute: '2-digit', hourCycle: 'h23'
});
console.log(fa.format(moment)); // متن فارسی را در محیط خود بررسی کنید.
console.log(fa.resolvedOptions().timeZone); // Asia/Tehran
console.log(utcClock.format(moment)); // 12:00
console.log(moment.toISOString()); // مقدار اصلی تغییر نکرده است.این نمونه را در time-label.mjs اجرا کنید. فاصله، نشانهگذاری یا شکل اختصار خروجی محلی ممکن است با دادههای زبان و نسخهٔ محیط فرق کند؛ برای آزمون، تمام جملهٔ فارسی را به یک رشتهٔ ثابت و شکننده تبدیل نکنید. میتوانید گزینههای حلشده یا بخشهای معنایی formatToParts را بررسی کنید. اگر برنامه نام منطقهٔ ناشناخته بگیرد، باید خطا را در مرز تنظیم مدیریت کند. افتادن بیخبر به منطقهٔ پیشفرض، اطلاعات نادرست اما مرتب تولید میکند. در کنار ساعت کوتاه، برای پیامهای دور از امروز یک تاریخ قابل فهم یا جزئیات کامل نیز در نظر بگیرید.
برچسب «دو دقیقه پیش» نسبت به یک زمان مرجع ساخته میشود. Intl.RelativeTimeFormat خودش اختلاف دو تاریخ را حساب نمیکند؛ فقط عدد و واحدی را که میدهید قالببندی میکند. در relative-time.mjs زیر، زمان مرجع ثابت است تا آزمون تکرارپذیر بماند. در رابط واقعی، ممکن است ساعت کاربر جابهجا یا نادرست باشد؛ نتیجهٔ نسبی برای کمک به خواندن است، نه شاهد قطعی تأیید ارسال. فاصلهٔ منفی در مثال به معنی گذشته است. واحد، روش گردکردن و زمان بهروزرسانی برچسب را جزو تصمیم رابط ثبت کنید تا پیام هر ثانیه بیدلیل نپرد.
const now = Date.parse('2026-10-29T12:02:20.000Z');
const sent = Date.parse('2026-10-29T12:00:00.000Z');
const minutes = Math.trunc((sent - now) / 60000);
const relative = new Intl.RelativeTimeFormat('fa-IR', { numeric: 'auto' });
console.log(minutes); // -2
console.log(relative.format(minutes, 'minute')); // عبارت محلیِ دو دقیقه پیشساعت نمایشی، شناسه و ترتیب پیام را از هم جدا کنید
دو پیام میتوانند زمان ثبت یکسان داشته باشند. ساعت دو دستگاه نیز الزاماً هماهنگ نیست و هنگام اصلاح ساعت سیستم میتواند جابهجا شود. پس مرتبسازی برحسب Date.now محلی، تضمین ترتیب درستِ یک گفتوگوی چندکاربره نیست. اگر سرور در قرارداد شمارهٔ ترتیب میدهد، همان را بررسی کنید؛ اگر snapshot مرتب میفرستد، ترتیب اعلامشده را حفظ کنید. شناسهٔ تصادفی نیز بهتنهایی شمارهٔ زمانی نیست. #array-sort-reverse یک ابزار مرتبسازی را توضیح میدهد، ولی پیش از مقایسهگر باید بدانید کدام فیلد واقعاً مرجع ترتیب است. ابزار درست بدون قرارداد داده نتیجهٔ قابل اعتماد نمیسازد.
در پروژهٔ چت، زمان و شناسه را از پیام پذیرفتهشدهٔ سرور میخوانیم؛ نام دقیق فیلد و قواعد آن در قرارداد همان پروژه آمده است. مثالهای این درس پیشنهاد تغییر قالب پروژه نیستند. نمایش محلی یک نمای مشتقشده است و نباید شناسهٔ پیام را عوض کند یا فهرست را با متن فارسی مرتب کند. برای انتقال، #json دادهٔ سازگار را حمل میکند، نه اعتبار هویت فرستنده را. نامی که کاربر انتخاب کرده نیز با زمان سرور معتبر نمیشود. همین تفکیک جلوی برداشتهای نادرست از یک رابط ظاهراً حرفهای را میگیرد.
تمرین با پاسخ و ارجاع
سه پیام با زمان معتبر بسازید که دو پیامشان لحظهٔ یکسان داشته باشند. خروجی نمایشی تهران و UTC تولید کنید، اما آرایهٔ ورودی و ترتیبش را نگه دارید. پاسخ راهنما: formatter را بیرون map بسازید، از هر ردیف یک نمای دارای id و timeLabel برگردانید و فهرست را بهخاطر ساعت فارسی مرتب نکنید. یک تاریخ ناموجود و یک رشتهٔ بدون Z را به تابع سختگیرانهٔ این درس بدهید؛ هر دو باید طبق قرارداد رد شوند. معیار تحویل، جدابودن لحظه از نمایش، رد ورودی خراب و ثابتماندن ترتیب در زمانهای برابر است، نه برابرشدن کامل متن محلی در همهٔ مرورگرها.
برای فرق نمایش فارسی و مقدار مدل، این درس را مرور کنید: https://www.darsavand.com/courses/javascript-project-lab?lesson=eca62509-d014-4312-9ee7-82c031713fb2
برای اعتبار داده و تاریخ ناموجود در مرز ورود، به این درس برگردید: https://www.darsavand.com/courses/javascript-project-lab?lesson=6e91dd87-6881-4db5-85bf-25954577268f
پیش از توسعهٔ رابط چت، در یادداشت خود بنویسید کدام زمان از سرور آمده، کدام متن صرفاً برای نمایش ساخته شده و ترتیب از چه قراردادی پیروی میکند. اگر یکی از این پاسخها نامعلوم است، افزودن قالب تاریخ زیباتر هنوز مسئلهٔ اعتماد به داده را حل نکرده است.
منابع رسمی انگلیسی:
MDN — Date: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
MDN — toISOString: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
MDN — Intl.DateTimeFormat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
MDN — Intl.RelativeTimeFormat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
تألیف و مثالهای مستقل: تحریریه درسآوند. منابع رسمی برای بررسی رفتار زبان و مطالعهٔ بیشتر معرفی شدهاند.
تألیف و مثالهای اختصاصی: تحریریه درسآوند. فایلهای کامل پروژه و راهنمای اجرا و دیباگ:
https://www.darsavand.com/resources/simple-websocket-chat-project
متن درسهای عمومی را بدون ورود بخوانید.