مسیرها
/
JavaScript
JavaScript
/
تمرین‌ها
/
زمان قرار ملاقات
زمان قرار ملاقات

زمان قرار ملاقات

تمرین یادگیری

مقدمه

جاوااسکریپت یک شیء توکار Date دارد که تاریخ و زمان را ذخیره می‌کند و روش‌هایی برای مدیریت آن‌ها فراهم می‌آورد.

Caution

این شیء بر پایه‌ی کلاس java.util.Date جاوا ساخته شد که در اوایل دهه‌ی ۲۰۱۰ جایگزین شد، اما به دلیل سازگاری با نسخه‌های پیشین، Date جاوااسکریپت همچنان پابرجاست.

چون کار کردن با تاریخ‌ها به‌طور کلی دشوار است و چون مدیریت منطقه‌ی زمانی در آن‌ها یا بسیار ضعیف است یا اصلاً وجود ندارد، کتابخانه‌های زیادی پدید آمده‌اند؛ مانند moment.js، day.js، date-fns و luxon. هیچ‌یک از این‌ها در Exercism در دسترس نیست.

در پروژه‌های خودتان از بسته‌های منسوخ یا بدون نگهداری مانند moment.js استفاده نکنید و به‌جای آن به گزینه‌های مدرن‌تری مانند luxon یا Temporal که هنوز چندان رایج نشده است تکیه کنید. تمرکز این تمرین روی Date است که تا پایان عمر جاوااسکریپت مرتبط باقی می‌ماند.

ساخت

یک شیء Date نمونه‌ای از کلاس Date است. می‌توان آن را بدون فرستادن هیچ آرگومانی به تابع سازنده ساخت. نتیجه یک شیء Date است که تاریخ و زمان کنونی را نمایش می‌دهد:

const now = new Date();
// => Thu Apr 14 2022 11:46:08 GMT+0530 (India Standard Time)
// Shows current day, date and time (in your time zone).

مهر زمانی یونیکس (عدد)

اگر یک عدد فرستاده شود، به‌عنوان یک timestamp تفسیر می‌شود. مهر زمانی یک عدد صحیح است که شمار میلی‌ثانیههای سپری‌شده از ۱ ژانویه ۱۹۷۰ UTC+0 را نشان می‌دهد.

const epoch = new Date(0);
// Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time)

const another = new Date(1749508766627);
// Tue Jun 10 2025 00:39:26 GMT+0200 (Central European Summer Time)

ممکن است انتظار داشته باشید که new Date(0) «زودترین» شیء تاریخ را بسازد، اما جاوااسکریپت تاریخ را به منطقه‌ی زمانی محلی شما تبدیل می‌کند، یعنی فقط کسانی که در منطقه‌ی زمانی GMT / با UTC+0 هستند در واقع مقدار مبدأ یونیکس را به دست می‌آورند.

مهر زمانی ISO 8601 (رشته)

می‌توانید یک مقدار رشته‌ای که نماینده‌ی یک تاریخ است به سازنده‌ی Date بفرستید. تنها قالبی که در همه‌ی پیاده‌سازی‌ها یکسان است، نسخه‌ی ساده‌شده چیزی است که به‌صورت بین‌المللی شناخته و استاندارد شده است و به آن رشته‌های مهر زمانی ISO 8601 می‌گویند.

یک لحظه در زمان به UTC این‌گونه است:

YYYY-MM-DDTHH:mm:ss.mssZ
YYYYMMDDTHHmmss.mssZ

که در آن جای‌گذاری‌های زیر انجام می‌شود:

کلید توضیح پیش‌فرض
YYYY سال تقویمی، با ۴ رقم
MM ماه تقویمی، با ۲ رقم 01
DD روز تقویمی، با ۲ رقم 01
T حرف T به‌صورت عینی، جداکننده‌ی تاریخ از زمان
HH ساعت در قالب ۲۴ساعته، ۲ رقم 00
mm دقیقه‌ها، ۲ رقم 00
ss ثانیه‌ها، ۲ رقم 00
mss میلی‌ثانیه‌ها، ۳ رقم 000
Z حرف Z به‌صورت عینی، یا یک انحراف +/-HH:mm

حرف عینی Z نشان‌دهنده‌ی UTC است (بدون منطقه‌ی زمانی، بدون ساعت تابستانی).

چون برای بیشتر اجزا مقدار پیش‌فرض وجود دارد، حذف بخش‌های انتهایی معتبر است:

YYYY-MM-DD

به‌صورت پیش‌فرض زمانی برابر 00:00:00.000 در نظر گرفته می‌شود.

اگر مهر زمانی به Z ختم نشود و به +HH:mm یا -HH:mm هم ختم نشود (که نشان‌دهنده‌ی انحراف منطقه‌ی زمانی است)، به دلایل تاریخی، این قاعده اعمال می‌شود:

وقتی انحراف منطقه‌ی زمانی وجود ندارد، قالب‌های فقط-تاریخ به‌عنوان زمان UTC تفسیر می‌شوند و قالب‌های تاریخ-و-زمان به‌عنوان زمان محلی. تفسیر به‌عنوان زمان UTC ناشی از یک خطای تاریخی در مشخصات است که با ISO 8601 سازگار نبود، اما به دلیل سازگاری با وب نمی‌توانست تغییر کند. به تجزیه‌گر معیوب، یک مسئله‌ی واقعی وب نگاه کنید.

Caution

قالب‌های دیگری که Date.parse می‌پذیرد ممکن است کار کنند یا نکنند. وقتی در جاوااسکریپت با تاریخ‌ها کار می‌کنید، هنگام تبدیل از یک string به یک Date همیشه از یک مهر زمانی ISO 8601 استفاده کنید.

قالب‌های فقط-تاریخ مجازند، اما همه‌ی قالب‌های ISO 8601 پشتیبانی نمی‌شوند. به صفحه‌ی توضیح نسخه‌ی ساده‌شده در MDN مراجعه کنید.

شیء Date

یک شیء تاریخ موجود را هم می‌توان به‌عنوان آرگومان سازنده به کار برد. این کار یک رونوشت از شیء Date موجود با همان تاریخ و زمان می‌سازد.

const t1 = new Date();
const t2 = new Date(t1);
// Values of t1 and t2 will be the same.

فرستادن مقادیر تک‌تک اجزای تاریخ و زمان

می‌توان با فرستادن سه عدد یک تاریخ ساخت. و با فرستادن ۶ عدد می‌توان تاریخی همراه با زمان ساخت.

const date1 = new Date(95, 11, 17);
// Creates Date for Dec 17 1995 00:00 if your local timezone is equivalent to UTC.

const date2 = new Date(2013, 12, 5, 13, 24, 0);
// Creates Date for Jan 5 2014 13:24 if your local timezone is equivalent to UTC.

مقدار دوم month است که از 0 برای ژانویه شروع می‌شود و تا 11 برای دسامبر ادامه دارد.

Date.parse()

ممکن است جاهایی به تابع تجزیه‌ی تاریخ Date.parse اشاره شده باشد. چون این تابع تنها چند مورد ثابت (مسلّم) دارد و بقیه‌ی پیاده‌سازی آن مشخص نشده است (و بنابراین استاندارد نیست)، بهتر است از آن استفاده نکنید.

دسترسی به اجزای Date

روش‌های گوناگونی روی شیءهای تاریخ وجود دارد که اجزای تاریخ را برمی‌گردانند:

getFullYear(); // Get the year (4 digits)
getMonth(); // Get the month, from 0 to 11.
getDate(); // Get the day of month, from 1 to 31.
getHours(); // Get the hour in a 24 clock, from 0 to 23
getMinutes(); // Get the minutes, from 0 to 59
getSeconds(); // Get the seconds, from 0 to 59
getMilliseconds(); // Get the milliseconds, from 0 and 999
getDay(); // Get the day of week, from 0 (Sunday) to 6 (Saturday).

هر یک از این‌ها یک گونه‌ی set متناظر دارد (مثلاً setFullYear) که مقدار را به‌روزرسانی می‌کند. هر مقدار سرریزشده به جزء بعدی منتقل می‌شود:

const date = new Date('2025-02-28T12:42:00Z');
// => Fri Feb 28 2025 13:42:00 GMT+0100 (Central European Standard Time)

date.setDate(29);
// there was no February 29th in 2025.

date.getDate();
// => 1

date.toString();
// => Sat Mar 01 2025 13:42:00 GMT+0100 (Central European Standard Time)

برای همه‌ی روش‌ها گونه‌های UTC هم وجود دارد که منطقه‌ی زمانی محلی را نادیده می‌گیرند.

تبدیل از تاریخ

شیءهای تاریخ روشی به نام getTime() دارند که مهر زمانی یونیکس را بر حسب میلی‌ثانیه برمی‌گرداند؛ یعنی شمار میلی‌ثانیه‌های سپری‌شده از نیمه‌شب آغاز ۱ ژانویه ۱۹۷۰ به وقت UTC. همچنین روش toISOString() در دسترس است که یک شیء تاریخ را به رشته‌ی مهر زمانی ISO 8601 تبدیل می‌کند.

مقایسه‌ی تاریخ‌ها

عملگرهای بزرگ‌تر از (>) و بزرگ‌تر یا مساوی (>=) و همچنین کوچک‌تر از (<) و کوچک‌تر یا مساوی (<=) را می‌توان مستقیماً بین دو تاریخ یا بین یک تاریخ و یک عدد به کار برد. این کار می‌کند چون جاوااسکریپت می‌کوشد تاریخ را به یک مقدار اولیه تبدیل کند.

Advanced

وقتی مقایسه‌ای بین دو تاریخ یا بین یک تاریخ و یک عدد انجام می‌شود، جاوااسکریپت [Symbol.toPrimitive]("number") را فراخوانی می‌کند که درون خودش date.valueOf() را صدا می‌زند. مورد دوم همان فراخوانی date.getTime() است.

اگر نمی‌خواهید به این رفتار تکیه کنید، اول مقدار را با getTime() به یک عدد تبدیل کنید.

تاریخ‌ها را نمی‌توان با برابری (== و ===) مقایسه کرد، اما نتیجه‌ی .getTime() را می‌توان.

دستورالعمل‌ها

در این تمرین روی چند تابع کار می‌کنید تا نوبت‌ها را مدیریت کنید. سیستم همه‌ی اطلاعات را در رشته‌هایی با قالب ISO 8601 ذخیره می‌کند، اما مردم تقویم را این‌طور به کار نمی‌برند. برای تبدیل میان این قالب‌های مختلف، توابع گوناگونی لازم است.

1. یک نوبت ایجاد کنید

یک نوبت برای n روز بعد از الان و در زمان کنونی ایجاد کنید. این تابع n روز را می‌گیرد و زمان نوبت را برای n روز بعد از الان برمی‌گرداند.

createAppointment(4, now);
// Given now is Sun Oct 05 2022 23:28:43 GMT+0600 (Bangladesh Standard Time)
// => Sun Oct 09 2022 23:28:43 GMT+0600 (Bangladesh Standard Time)

اگر پارامتر دوم یعنی now استفاده نشود، باید از زمان کنونی استفاده شود.

2. تبدیل یک تاریخ به برچسب زمان

ابزارهای گوناگون فقط با قالب ISO 8601 که استاندارد بین‌المللی است کار می‌کنند. تابع getAppointmentTimestamp را بنویسید تا یک تاریخ بگیرد و رشته‌ای در همان قالب برگرداند.

const appointment = new Date(Date.UTC(2010, 6, 16, 12, 42, 0, 0));

getAppointmentTimestamp(appointment);
// => '2010-07-16T12:42:00.000Z'

3. جزئیات یک نوبت را دریافت کنید

خواندن برچسب‌های زمان سخت است؛ تابعی که جزئیات نوبت را بگیرد می‌تواند در این کار کمک کند. تابع getAppointmentDetails یک برچسب زمان در قالب ISO 8601 می‌گیرد و سال، ماه، تاریخ، ساعت و دقیقه را برمی‌گرداند.

getAppointmentDetails('2022-04-24T08:15:00.000');
// => { year: 2022, month: 3, date: 24, hour: 8, minute: 15 }

4. به‌روزرسانی یک نوبت با گزینه‌های داده‌شده

این تابع، آرگومان اول را به‌عنوان زمان نوبت و آرگومان دوم را به‌عنوان شیئی از چند گزینه دریافت می‌کند. باید نوبت را بر اساس گزینه‌های موجود در آن شیء به‌روزرسانی کنید و تاریخ نوبت جدید را برگردانید. شیء گزینه‌ها می‌تواند چندین گزینه داشته باشد.

updateAppointment('2022-02-09T09:20:00.000', { month: 6 });
// => { year: 2022, month: 6, date: 9, hour: 10, minute: 20 }

5. دریافت زمان موجود بین دو نوبت

این تابع، دو نوبت (برچسب زمان) را به‌عنوان آرگومان دریافت می‌کند. باید اختلاف آن دو زمان را بر حسب ثانیه برگردانید.

چون نیم ثانیه تقریباً بی‌معناست، عدد را پیش از برگرداندن گرد کنید.

timeBetween('2022-12-12T09:20:00.000', '2022-12-18T08:30:00.000');
// => 515400

6. بررسی اینکه آیا نوبت اکنون معتبر است یا نه

در پایان، وقتی نوبتی ثبت می‌شود، سیستم باید بررسی کند که معتبر است یا نه. به بیان دیگر، نوبت باید در آینده باشد، نه در گذشته.

تابع isValid را بنویسید که دو آرگومان می‌گیرد: یک برچسب زمان نوبت (رشته) و زمان کنونی به‌صورت برچسب زمان (رشته)؛ و اگر نوبت با توجه به زمان کنونی در آینده باشد، true را برمی‌گرداند.

isValid('2022-02-11T23:00:00.000', '2022-02-08T23:00:00.000');
// => true
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
JavaScript Exercism

آماده‌اید زمان قرار ملاقات را شروع کنید؟

در Exercism ثبت‌نام کنید تا JavaScript را همراه با 37 مفهوم159 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.