فرمت تاریخ در جاوااسکریپت یکی از چالشهای رایج در توسعه اپلیکیشنهای وب است؛ چالشی که اگر بهدرستی مدیریت نشود، میتواند روی پایداری، دقت و تجربه کاربری تأثیر منفی بگذارد. در این مقاله، مدیریت و فرمتبندی تاریخ در جاوااسکریپت را با بررسی API داخلی Date و کتابخانههای تخصصی مقایسه میکنیم و با استفاده از مثالهای عملی و معیارهای عملکردی، یاد میگیریم چگونه بهترین تصمیم را برای پیادهسازی بگیریم.
در ادامه بررسی میکنیم چه زمانی استفاده از متدهای داخلی جاوااسکریپت انتخاب مناسبی است و چه زمانی بهتر است سراغ کتابخانههای خارجی برویم. همچنین با نحوه مدیریت صحیح localization و منطقه زمانی (Time Zone) آشنا میشویم و یاد میگیریم چگونه از خطاهای رایج مرتبط با تاریخ در پروژههایمان جلوگیری کنیم.
قبل از اینکه یک تاریخ را برای نمایش فرمت کنیم، لازم است فرمت ورودی آن را بهدرستی بشناسیم.
در ادامه، سه فرمت رایج تاریخ را مشاهده میکنیم که همگی یک تاریخ و زمان یکسان را نمایش میدهند:
new Date() ) "2025-02-18T14:30:00.000Z"1732561800000Tue, 18 Feb 2025 14:30:00 +0000هرکدام از این فرمتها بسته به سناریو کاربرد خاص خودشان را دارند. فرمت ISO 8601 به دلیل استاندارد بودن و قابلیت parse آسان با new Date()، رایجترین گزینه در APIها و دیتابیسها محسوب میشود.
از آنجایی که Unix timestamps صرفاً اعداد خام هستند، برای محاسبات و مقایسههای زمانی گزینه بسیار مناسبی به شمار میآیند. فرمت RFC 2822 نیز بیشتر در سیستمهای قدیمیتر یا ایمیلها دیده میشود. فارغ از اینکه با کدام فرمت شروع میکنیم، آبجکت Date در جاوااسکریپت ابزار اصلی ما برای تفسیر و کار با این مقادیر است.
آبجکت Date روش داخلی جاوااسکریپت برای کار با تاریخ و زمان است. چند نکتهی مهم وجود دارد که باید دربارهی آن بدانیم:
// Creating a new Date object
const now = new Date(); // Current date and time
const isoDate = new Date('2025-02-18T14:30:00.000Z'); // From date string
const withComponents = new Date(2025, 1, 18); // Year, month (0-indexed!), day
const timeStampDate = new Date(1732561800000)
آبجکت Date تاریخها را بهصورت تعداد میلیثانیه از پنجشنبه ۱ ژانویه ۱۹۷۰ (Unix Epoch) ذخیره میکند، اما متدهایی در اختیار ما قرار میدهد که میتوانیم با استفاده از آنها این مقادیر را به فرمتهای قابل خواندن برای انسان تبدیل کنیم.
متدهای داخلی این آبجکت به ما اجازه میدهند بخشهای مختلف تاریخ، مانند سال، ماه، روز یا زمان را استخراج کنیم. برای مثال:
const date = new Date('2025-02-18T14:30:15Z');
// Getting components
date.getFullYear(); // 2025
date.getMonth(); // 1 (February, zero-indexed!!!!)
date.getDate(); // 18
date.getHours(); // 14
date.getMinutes(); // 30
date.getSeconds(); // 15
date.getDay(); // 2 (Tuesday, with 0 being Sunday)
date.getTime(); // Milliseconds since epoch
// Setting components
date.setFullYear(2026);
date.setMonth(5); // June (because zero-indexed!!!)
Dateهمه سناریوها نیاز به استفاده از یک کتابخانه کامل ندارند. در بسیاری از مواقع، متدهای داخلی جاوااسکریپت برای فرمتبندی تاریخ کاملاً کافی هستند:
const date = new Date('2025-02-18T14:30:00Z');
// Basic string conversion
date.toString();
// "Tue Feb 18 2025 14:30:00 GMT+0000 (Coordinated Universal Time)"
// Date portion only
date.toDateString();
// "Tue Feb 18 2025"
// Time portion only
date.toTimeString();
// "14:30:00 GMT+0000 (Coordinated Universal Time)"
// UTC version (reliable across timezones)
date.toUTCString();
// "Tue, 18 Feb 2025 14:30:00 GMT"
// ISO 8601 format
date.toISOString();
// "2025-02-18T14:30:00.000Z"
این متدهای native یک راه سریع و بدون وابستگی اضافی برای فرمتبندی تاریخ در اختیار ما قرار میدهند. آنها برای سناریوهای سادهای مثل نمایش مقادیر UTC یا جدا کردن بخش تاریخ و زمان، گزینهای ایدهآل محسوب میشوند.
فرمتبندی مبتنی بر لوکال با toLocaleDateString()
const date = new Date('2025-02-18');
// Basic usage (uses browser's locale)
date.toLocaleDateString();
// In US: "2/18/2025"
// In UK: "18/02/2025"
// In Germany: "18.2.2025"
// With explicit locale
date.toLocaleDateString('fr-FR');
// "18/02/2025"
// With options
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
date.toLocaleDateString('de-DE', options);
// "Dienstag, 18. Februar 2025"
بدون تعیین locale و option:
date.toLocaleDateString(); // 2/18/2025
در برخی مواقع، نیاز داریم یک راهکار سفارشی برای فرمتبندی تاریخ پیادهسازی کنیم. این رویکرد به ما کنترل کامل روی خروجی میدهد و اجازه میدهد فرمت تاریخ را دقیقاً متناسب با نیازهای پروژه بهینهسازی کنیم:
function formatDate(date, format) {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
// Replace tokens with actual values
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
}
const date = new Date('2025-02-18T14:30:45Z');
console.log(formatDate(date, 'YYYY-MM-DD')); // "2025-02-18"
console.log(formatDate(date, 'DD/MM/YYYY HH:mm:ss')); // "18/02/2025 14:30:45"
با اینکه روشهای بالا در بسیاری از موارد کارآمد هستند، اما با در نظر گرفتن موارد زیر، پیچیدگی بهسرعت افزایش پیدا میکند:
برای هر سناریویی فراتر از فرمتهای ساده، زمان آن رسیده است که سراغ ابزارها و کتابخانههای حرفهایتر برویم.
متدهای داخلی جاوااسکریپت در بسیاری از سناریوهای ساده کاربردی هستند، اما در موارد پیچیدهتر میتوانند محدود کننده باشند؛ بهویژه زمانی که با Localization پیشرفته، فرمتهای سفارشی یا مدیریت Time Zone سروکار داریم. در چنین شرایطی، استفاده از کتابخانههای تخصصی، فرایند فرمت تاریخ در جاوااسکریپت را بهشکل قابلتوجهی سادهتر، قابلاعتمادتر و خواناتر میکند. در ادامه، چند کتابخانه محبوب و رایج برای فرمتبندی تاریخ را بررسی میکنیم.
date-fns یکی از بهترین انتخابها برای اپلیکیشنهای مدرن محسوب میشود؛ بهخصوص زمانی که خوانایی کد، عملکرد و کنترل دقیق روی فرمت تاریخ در جاوااسکریپت برایمان اهمیت دارد.
با استفاده از date-fns میتوان بهسادگی کارهای رایجی مانند parse کردن رشتههای ISO به آبجکت Date، فرمت کردن تاریخ به رشتههای خوانا و انجام محاسبات زمانی (مثل اضافه کردن روز یا محاسبه اختلاف بین دو تاریخ) را انجام داد. طراحی تابعمحور این کتابخانه باعث میشود کدها تمیز، قابل پیشبینی و بهراحتی قابل ترکیب باشند؛ ویژگیای که در پروژههای بزرگ اهمیت زیادی دارد.
import { format, parseISO, addDays, differenceInDays } from 'date-fns';
// Parsing
const date = parseISO('2025-02-18T14:30:00Z');
// Formatting
format(date, 'yyyy-MM-dd'); // "2025-02-18"
format(date, 'MMMM do, yyyy'); // "February 18th, 2025"
format(date, 'h:mm a'); // "2:30 PM"
format(date, 'EEEE, MMMM do, yyyy h:mm a'); // "Tuesday, February 18th, 2025 2:30 PM"
// Operations
const nextWeek = addDays(date, 7);
const daysBetween = differenceInDays(nextWeek, date); // 7
کتابخانه date-fns از طریق import جداگانه localeها، پشتیبانی قدرتمندی از Localization ارائه میدهد. این رویکرد ماژولار کمک میکند حجم باندل نهایی حداقل بماند، زیرا فقط localeهایی را وارد پروژه میکنیم که واقعاً به آنها نیاز داریم.
در نتیجه، پیادهسازی فرمت تاریخ در جاوااسکریپت برای زبانها و مناطق مختلف، بدون افزایش غیرضروری حجم پروژه امکانپذیر میشود.
import { format, formatDistance, formatRelative, isDate } from 'date-fns';
import { es, de, fr, ja, zhCN } from 'date-fns/locale';
const date = new Date('2025-02-18T14:30:00Z');
// Basic locale formatting
const localeExamples = {
english: format(date, 'MMMM d, yyyy', { locale: enUS }),
spanish: format(date, 'MMMM d, yyyy', { locale: es }),
german: format(date, 'MMMM d, yyyy', { locale: de }),
french: format(date, 'MMMM d, yyyy', { locale: fr }),
japanese: format(date, 'MMMM d, yyyy', { locale: ja }),
chinese: format(date, 'MMMM d, yyyy', { locale: zhCN })
};
console.log(localeExamples);
Output:
{
english: "February 18, 2025",
spanish: "febrero 18, 2025",
german: "Februar 18, 2025",
french: "février 18, 2025",
japanese: "2月 18, 2025",
chinese: "二月 18, 2025"
}
اگر به سفارشیسازی بیشتری نیاز داشته باشیم یا با سناریوهای خاص مواجه شویم، مراجعه به مستندات رسمی date-fns انتخاب مناسبی است؛ چرا که مثالها و تکنیکهای تکمیلی کاربردیای در آن ارائه شده است.
کتابخانه date-fns-tz قابلیتهای date-fns را با پشتیبانی قدرتمند از Time Zone تکمیل میکند. این ابزار به ما اجازه میدهد تاریخها را بین مناطق زمانی مختلف تبدیل کرده و آنها را بهدرستی فرمت کنیم. استفاده از این کتابخانه، پیادهسازی دقیقتر فرمت تاریخ در جاوااسکریپت را در اپلیکیشنهایی با کاربران بینالمللی ممکن میسازد.
import {
format,
utcToZonedTime,
zonedTimeToUtc,
getTimezoneOffset
} from 'date-fns-tz';
const date = new Date('2025-02-18T14:30:00Z');
// Basic timezone conversion
const timezoneExamples = {
newYork: utcToZonedTime(date, 'America/New_York'),
tokyo: utcToZonedTime(date, 'Asia/Tokyo'),
london: utcToZonedTime(date, 'Europe/London'),
sydney: utcToZonedTime(date, 'Australia/Sydney')
};
// console.log(timezoneExamples)
//{
// newYork: Tue Feb 18 2025 09:30:00 GMT-0500 (Eastern Standard Time),
// tokyo: Tue Feb 18 2025 23:30:00 GMT+0900 (Japan Standard Time),
// london: Tue Feb 18 2025 14:30:00 GMT+0000 (Greenwich Mean Time),
// sydney: Wed Feb 19 2025 01:30:00 GMT+1100 (Australian Eastern Daylight Time)
//}
کتابخانه Day.js بهعنوان یک جایگزین مدرن و مینیمال برای Moment.js، محبوبیت زیادی پیدا کرده است. این کتابخانه با هدف رفع محدودیتها و مشکلات Moment طراحی شده، در حالی که API نسبتاً مشابهی ارائه میدهد؛ به همین دلیل، گزینهای مناسب برای پروژههایی است که قصد دارند بدون تغییرات گسترده در کدها، از Moment.js به یک راهکار مدرنتر انتقال پیدا کنند.
در مثال زیر میبینیم چگونه میتوان با استفاده از پلاگینهای مختلف، فرمت تاریخ در جاوااسکریپت، مدیریت Time Zone، فرمتهای سفارشی و localeها را پیادهسازی کرد. همچنین امکان ایجاد تاریخ از ورودیهای مختلف، تبدیل آن به رشتههای خوانا، تغییر منطقه زمانی و انجام محاسبات زمانی (مثل اضافه یا کم کردن زمان) وجود دارد؛ آن هم بدون تغییر دادن مقدار تاریخ اصلی:
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import localeData from 'dayjs/plugin/localeData';
import customParseFormat from 'dayjs/plugin/customParseFormat';
// Extend with plugins
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(localeData);
dayjs.extend(customParseFormat);
// Creating Day.js objects
const today = dayjs();
const specificDate = dayjs('2025-02-18');
const fromFormat = dayjs('18/02/2025', 'DD/MM/YYYY');
// Formatting
specificDate.format('YYYY-MM-DD'); // "2025-02-18"
specificDate.format('dddd, MMMM D, YYYY'); // "Tuesday, February 18, 2025"
// Timezone handling
specificDate.tz('America/New_York').format('YYYY-MM-DD HH:mm:ss Z');
// "2025-02-18 09:30:00 -05:00"
// Manipulation (immutable - returns new instances)
const nextWeek = specificDate.add(1, 'week');
const lastMonth = specificDate.subtract(1, 'month');
معماری پلاگینمحور Day.js بسیار هوشمندانه طراحی شده است؛ زیرا فقط هزینه حجمی قابلیتهایی را میپردازیم که در پروژه از آنها استفاده میکنیم، بدون آنکه انعطافپذیری در فرمت تاریخ در جاوااسکریپت کاهش پیدا کند.
// Only import the plugins you need
import relativeTime from 'dayjs/plugin/relativeTime';
import calendar from 'dayjs/plugin/calendar';
dayjs.extend(relativeTime);
dayjs.extend(calendar);
// Now you can use these features
dayjs('2025-02-18').fromNow(); // "in X years" (depends on current date)
dayjs('2025-02-18').calendar(); // "02/18/2025" or "Tuesday" based on how far in future
کتابخانه Moment.js زمانی انتخاب اول توسعهدهندگان برای مدیریت و فرمت تاریخ در جاوااسکریپت بود، اما امروز بهعنوان یک گزینه قدیمی شناخته میشود. تیم Moment.js بهصورت رسمی اعلام کرده که این کتابخانه در حالت نگهداری (Maintenance Mode) قرار دارد و استفاده از جایگزینهای مدرنتر را توصیه میکند.
با این حال، هنوز پروژههای زیادی از Moment.js استفاده میکنند و آشنایی با رویکرد آن میتواند مفید باشد.
در مثال زیر، گردشکارهای رایجی مانند ساخت تاریخ از رشتهها یا فرمتهای سفارشی، فرمت کردن خروجی برای نمایش، تغییر تاریخ با اضافه یا کم کردن زمان و تبدیل آن به مناطق زمانی مختلف را مشاهده میکنیم. این مثالها نشان میدهند که فرمت تاریخ در جاوااسکریپت پیش از ظهور کتابخانههای مدرنتر، چگونه در پروژههای واقعی پیادهسازی میشد.
import moment from 'moment';
import 'moment-timezone';
// Creating moments
const now = moment(); // Current date/time
const fromString = moment('2025-02-18T14:30:00Z');
const fromFormat = moment('18/02/2025', 'DD/MM/YYYY');
// Formatting
fromString.format('YYYY-MM-DD'); // "2025-02-18"
fromString.format('dddd, MMMM Do YYYY'); // "Tuesday, February 18th 2025"
fromString.format('h:mm a'); // "2:30 pm"
// Operations (modifies the original moment)
fromString.add(7, 'days');
fromString.subtract(2, 'months');
// Timezone handling
const tokyoTime = fromString.clone().tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm:ss');
const nyTime = fromString.clone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss');
با وجود محبوبیت تاریخی Moment.js، این کتابخانه محدودیتهایی دارد که در پروژههای مدرن، بهویژه در زمینه فرمت تاریخ در جاوااسکریپت، میتواند مشکلساز شود:
پروپوزال Temporal در استاندارد ECMAScript با هدف جایگزینی API مشکلدار Date ارائه شده است. این API جدید، جامعتر، immutable و کاملاً آگاه از Time Zone طراحی شده و بسیاری از مشکلات رایج در فرمت تاریخ در جاوااسکریپت را بهصورت ریشهای حل میکند.
اگرچه Temporal هنوز بهطور رسمی وارد استاندارد نشده است، اما بهعنوان آینده مدیریت تاریخ در جاوااسکریپت، ارزش بررسی و توجه جدی را دارد.
در قطعه کد زیر، رویکرد مدرن Temporal را مشاهده میکنیم: ساخت تاریخهای immutable و آگاه از Time Zone و انجام محاسبات زمانی بهشکلی ایمن و قابل پیشبینی.
// This syntax is not yet available in browsers without polyfills
// Creating a date (Temporal.PlainDate is timezone-independent)
const date = Temporal.PlainDate.from({ year: 2025, month: 2, day: 18 });
// Creating a specific time in a timezone
const nyDateTime = Temporal.ZonedDateTime.from({
timeZone: 'America/New_York',
year: 2025, month: 2, day: 18, hour: 9, minute: 30
});
// Formatting
date.toString(); // "2025-02-18"
nyDateTime.toString(); // "2025-02-18T09:30:00-05:00[America/New_York]"
// Duration and arithmetic (returns new instances)
const futureDate = date.add({ days: 7 });
const duration = date.until(futureDate);
در حال حاضر میتوانیم Temporal را با استفاده از npmjs.com/package/@js-temporal/polyfill تجربه و بررسی کنیم.
با توجه به تنوع ابزارها، انتخاب گزینه مناسب برای مدیریت و فرمت تاریخ در جاوااسکریپت میتواند چالشبرانگیز باشد. برای تصمیمگیری آگاهانهتر، در ادامه گزینههای مختلف را از جنبههای کلیدی مقایسه میکنیم.
Date: بدون هزینه اضافی، چون بخشی از خود جاوااسکریپت استdate-fns: حدود ۱۳ کیلوبایت (minified و gzipped)Date و Moment.js: مقادیر تاریخ را بهصورت mutable مدیریت میکنندdate-fns و Day.js: کاملاً immutable هستند و همیشه آبجکت جدید برمیگرداننداین ویژگی باعث میشود کدهای مرتبط با فرمت تاریخ در جاوااسکریپت قابل پیشبینیتر و امنتر باشند.
date-fns: عملکرد بسیار عالی؛ فقط توابع موردنیاز وارد باندل میشوندDate: نیازی به tree-shaking ندارداز نظر پشتیبانی از Time Zone
Date: امکانات محدودdate-fns: با کتابخانهی مکمل date-fns-tz پشتیبانی قدرتمند ارائه میدهدDate: پشتیبانی قابلقبولdate-fns و Moment.js: پشتیبانی بسیار قویdate-fns: بهترین تجربه توسعهDate: امکانات پایهایاز نظر وضعیت توسعه نیز، date-fns و Day.js بهصورت فعال در حال توسعه هستند، در حالی که Moment.js فقط در حالت نگهداری قرار دارد.
Date کافی نیستند.در اپلیکیشنهایی که بهشدت با تاریخ و زمان سروکار دارند، انتخاب ابزار مناسب برای فرمت تاریخ در جاوااسکریپت میتواند تأثیر مستقیمی بر عملکرد داشته باشد.
در ادامه، یک مقایسه ساده شده از عملکرد ارائه شده است:
// Test with 100,000 operations
const COUNT = 100000;
// Native JS
console.time('Native');
for (let i = 0; i < COUNT; i++) {
new Date().toISOString();
}
console.timeEnd('Native'); // Typically fastest
// date-fns
console.time('date-fns');
for (let i = 0; i < COUNT; i++) {
format(new Date(), 'yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'');
}
console.timeEnd('date-fns'); // Close second
// Day.js
console.time('Day.js');
for (let i = 0; i < COUNT; i++) {
dayjs().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
}
console.timeEnd('Day.js'); // Usually faster than Moment
// Moment.js
console.time('Moment');
for (let i = 0; i < COUNT; i++) {
moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
}
console.timeEnd('Moment'); // Usually slowest
برای کاهش باگهای متداول هنگام کار با فرمت تاریخ در جاوااسکریپت، به نکات زیر توجه داشته باشیم:
Date بهصورت zero-indexed هستند (ژانویه = ۰)Date را مستقیماً با == یا === مقایسه نکنیم و از .getTime() استفاده نماییم//Incorrect: Direct comparison
const date1 = new Date('2025-02-18');
const date2 = new Date('2025-02-18');
if (date1 === date2) { / This will never execute / }// Correct version: Compare timestamps
if (date1.getTime() === date2.getTime()) { /*This works / }
فرمت تاریخ در جاوااسکریپت لزوماً نباید پیچیده یا دردسرساز باشد. نکته کلیدی، انتخاب ابزار مناسب بر اساس نیاز واقعی پروژه است:
Date: مناسب سناریوهای سادهاگر بین گزینهها مردد بودیم، بهتر است از سادهترین راه شروع کنیم و فقط در صورت نیاز به سراغ راهحلهای پیشرفتهتر برویم. این تصمیم هوشمندانه در نهایت به نفع عملکرد و حجم باندل اپلیکیشن خواهد بود.