সরাসরি পর্বে যান
পর্ব ২০ · ৫৯-এর মধ্যে · মডিউল ৩

তারিখ, সময় আর Intl — একই মুহূর্ত, বাংলায় সঠিক ফরম্যাট

JavaScript Dates, Time Zones & the Intl API — Instants, UTC, DST, Bangla Formatting, Lakh/Crore Currency & Temporal

Taught in Bangla, this lesson explains dates and times in JavaScript: a Date as an instant, parsing pitfalls, UTC versus local getters and zero-based months, date arithmetic and comparisons, time zones and DST from Dhaka to New York, Intl.DateTimeFormat and relative time in Bangla, currency in lakh and crore, plural rules, and the upcoming Temporal API.

এই পর্বে যা শিখবেন

  • একটি Date আসলে কী রাখে — “মুহূর্ত” আর “দেয়ালঘড়ির সময়”-এর পার্থক্য
  • Date তৈরির নিরাপদ উপায়, string পড়ার তিন নিয়ম আর ০ থেকে মাসের ফাঁদ
  • দিন যোগ, দুই তারিখের ব্যবধান, মাসের শেষের গড়িয়ে যাওয়া আর DST-এর এক দিনের ভুল
  • Intl দিয়ে বাংলায় তারিখ, “কত দিন আগে”, টাকা, তালিকা আর বহুবচন
  • Temporal, library আর ডেটাবেসে তারিখ রাখার পেশাদার নিয়ম — এবং AI-এর লেখা তারিখের কোড যাচাই

১ · একটি Date মানে একটি মুহূর্ত — ঘড়ির লেখা নয়

একটি দৃশ্য দিয়ে শুরু করি। রাফি একটি অনলাইন দোকানের অ্যাপ বানাচ্ছেন। নিউ ইয়র্কে থাকা এক প্রবাসী গ্রাহক মায়ের জন্য ঢাকায় উপহার পাঠালেন। গ্রাহকের ফোনে অর্ডারের সময় দেখাচ্ছে “সকাল ৬:৩০”, আর ঢাকার দোকানের ড্যাশবোর্ডে দেখাচ্ছে “বিকাল ৪:৩০”। রাফি ভয় পেলেন — দুটি সময় কি দুটি অর্ডার? না। অর্ডার একটিই, মুহূর্তও একটিই। শুধু দুই শহরের দেয়ালঘড়ি সেই মুহূর্তটিকে আলাদা সংখ্যায় পড়ছে।

JavaScript-এর Date ঠিক এই “মুহূর্ত”-টিকেই রাখে। ভেতরে কোনো বছর, মাস বা ঘণ্টা আলাদা করে জমা থাকে না। থাকে মাত্র একটি number — epoch অর্থাৎ ১৯৭০ সালের ১ জানুয়ারি রাত ১২টা (UTC) থেকে কত মিলিসেকেন্ড পেরিয়েছে। এই সংখ্যাকে বলে timestamp. রাফির অর্ডারের timestamp 1773570600000. ঢাকা, নিউ ইয়র্ক, লন্ডন — সব জায়গায় এই সংখ্যা হুবহু এক।

মূল ধারণা

একটি Date = একটি মুহূর্ত (সময়রেখার একটি বিন্দু)। “১৫ মার্চ, বিকাল ৪:৩০” হলো সেই মুহূর্তের একটি রূপ — কোন time zone আর কোন ভাষায় দেখাচ্ছেন, তার ওপর নির্ভর করে। হিসাব করুন মুহূর্তে, দেখানোর সময় রূপ বেছে নিন।

একটি সংখ্যা, অনেক রূপ

timestamp → time zone → locale → পাঠকের চোখে লেখা

  1. মুহূর্ত1773570600000epoch থেকে মিলিসেকেন্ড
  2. Time zoneAsia/Dhakaকোন শহরের দেয়ালঘড়ি
  3. Localebn-BDকোন ভাষা, কোন অঙ্ক
  4. লেখা৪:৩০ বিকালমানুষের জন্য
ছবি ২০.১ — হিসাব আর তুলনা হয় বাঁ দিকের সংখ্যায়; ডানের লেখা শুধু দেখানোর জন্য।

BPL ফাইনালের শেষ বলটি একবারই করা হয়। ঢাকার দর্শকের ঘড়িতে তখন রাত ৯:৪০, লন্ডনে থাকা ভাইয়ের ঘড়িতে বিকাল ৩:৪০। বলটি দুবার হয়নি — একটি ঘটনা, দুটি দেয়ালঘড়ি। Date হলো সেই বলের মুহূর্ত; time zone হলো কার ঘড়ি দিয়ে দেখছেন।

UTC (Coordinated Universal Time) হলো পৃথিবীর “নিরপেক্ষ” ঘড়ি — কোনো দেশের নিজের নয়, সব হিসাবের ভিত্তি। ঢাকার সময় UTC-র চেয়ে ৬ ঘণ্টা এগিয়ে, তাই লেখা হয় UTC offset +06:00. নিউ ইয়র্ক শীতে -05:00, গ্রীষ্মে -04:00 — কারণ সেখানে DST (daylight saving time) আছে। অংশ ৫-এ এই বদলের বিপদ দেখব।

JavaScript-এর Date-এর জন্ম ১৯৯৫ সালে, Netscape-এ। Brendan Eich-কে বলা হয়েছিল ভাষাটি “Java-র মতো দেখতে” হবে, আর হাতে সময় ছিল খুব কম। তাই তারিখের জন্য Java 1.0-এর java.util.Date-এর নকশা প্রায় হুবহু নেওয়া হয় — ০ থেকে মাস গোনার নিয়মসহ। Java পরের বছরগুলোতে সেই নকশা বাতিল করে নতুন API আনে, কিন্তু ওয়েবের পুরোনো সাইট ভাঙা যায় না বলে JavaScript আজও সেই উত্তরাধিকার বইছে। পুরো গল্প ভাবনার প্রশ্ন ১-এ।

২ · Date তৈরি — string পড়ার তিন নিয়ম আর চিরকালের ফাঁদ

Date বানানোর কয়েকটি পথ আছে। প্রতিটির আচরণ আলাদা, আর সবচেয়ে বেশি বাগ জন্মায় এখানেই।

লেখাকোন সময় ধরেমন্তব্য
new Date("2026-03-15T10:30:00Z")UTC (Z = শূন্য offset)সবচেয়ে নিরাপদ — সব মেশিনে একই মুহূর্ত
new Date("2026-03-15T16:30:00+06:00")দেওয়া offset অনুযায়ীওপরেরটির সাথে হুবহু একই মুহূর্ত
new Date("2026-03-15")UTC মধ্যরাতশুধু তারিখ থাকলে UTC — ES5-এর ঐতিহাসিক সিদ্ধান্ত
new Date("2026-03-15T10:00")স্থানীয় সময়offset নেই → যে মেশিনে চলছে তার জোন
new Date(2026, 2, 15)স্থানীয় সময়মাস ০ থেকে — 2 মানে মার্চ!
Date.UTC(2026, 2, 15, 4, 30)UTCDate নয়, timestamp (number) ফেরত দেয়; new Date(...)-এ মুড়ে নিন
new Date(1773570600000)timestamp থেকেসংখ্যা মানেই মুহূর্ত, কোনো অস্পষ্টতা নেই

এখানে লেখার রূপটি ISO 8601 মানদণ্ডের — বছর-মাস-দিন, তারপর T, তারপর ঘণ্টা:মিনিট:সেকেন্ড, শেষে offset. ECMAScript শুধু এই রূপটির নিয়ম নিশ্চিত করে বলে। "15/03/2026" বা "March 15, 2026"-এর মতো রূপ কীভাবে পড়া হবে, তা engine-এর ইচ্ছা। নিচের ধাপে দেখুন একই দেখতে তিনটি string কীভাবে তিনটি আলাদা মুহূর্ত হয়ে যায় — ঢাকা আর নিউ ইয়র্কের দুটি কম্পিউটারে।

new Date("2026-03-15")শুধু তারিখ → UTC মধ্যরাত
ঢাকার কম্পিউটারে:      2026-03-15T00:00:00.000Z
নিউ ইয়র্কের কম্পিউটারে: 2026-03-15T00:00:00.000Z   (একই মুহূর্ত)
কিন্তু নিউ ইয়র্কের ঘড়িতে দেখালে: ১৪ মার্চ, রাত ৮:০০
new Date("2026-03-15T10:00")offset নেই → স্থানীয় সময়
ঢাকার কম্পিউটারে:      2026-03-15T04:00:00.000Z
নিউ ইয়র্কের কম্পিউটারে: 2026-03-15T14:00:00.000Z
একই কোড, ১০ ঘণ্টা দূরের দুটি মুহূর্ত!
new Date("2026-03-15T10:00Z")offset আছে → সব জায়গায় এক
ঢাকার কম্পিউটারে:      2026-03-15T10:00:00.000Z
নিউ ইয়র্কের কম্পিউটারে: 2026-03-15T10:00:00.000Z
"2026-03-15T16:00+06:00" লিখলেও ঠিক এই মুহূর্ত
ISO-র বাইরের রূপengine-এর ইচ্ছা
> new Date("15/03/2026").getTime()
NaN            // Invalid Date (V8)
> new Date("03/15/2026").getDate()
15             // V8 মাস/দিন ধরে, স্থানীয় সময়ে
> new Date("2026-3-15")   // এক অঙ্কের মাস → V8-এ স্থানীয়!
  1. শুধু তারিখ = UTC. "2026-03-15" পড়া হয় UTC মধ্যরাত হিসেবে, সব মেশিনে একই মুহূর্ত। বিপদ আসে দেখানোর সময়: নিউ ইয়র্ক তখন UTC-র ৪ ঘণ্টা পেছনে, তাই getDate() বা স্থানীয় ফরম্যাট দেখায় ১৪ মার্চ। জন্মদিন এক দিন আগে দেখানোর বিখ্যাত বাগ এটিই।
  2. Offset ছাড়া তারিখ-সময় = স্থানীয়। T10:00 থাকলেই নিয়ম উল্টে যায় — এখন স্থানীয় সময়। ঢাকার মেশিনে মুহূর্তটি 04:00Z, নিউ ইয়র্কের মেশিনে 14:00Z. সার্ভার আর ব্রাউজার আলাদা জোনে থাকলে একই ডেটা দুই জায়গায় দুই মুহূর্ত হয়ে যায়।
  3. Offset লিখুন — অস্পষ্টতা শেষ। Z বা +06:00 থাকলে মুহূর্তটি স্থির, কোন মেশিনে চলছে তাতে কিছু যায় আসে না। API, ডেটাবেস আর JSON-এ সবসময় এই রূপ ব্যবহার করুন।
  4. ISO-র বাইরে = অনুমান। দিন/মাস/বছর রূপ V8 পড়তে পারে না (NaN), আবার মাস/দিন/বছর পড়ে আমেরিকান ধরনে। এমনকি "2026-3-15" (মাস এক অঙ্কে) V8-এ স্থানীয় সময় হয়ে যায়, অথচ "2026-03-15" UTC! ব্যবহারকারীর লেখা তারিখ নিজে ভেঙে (বছর, মাস, দিন আলাদা করে) পড়ুন।

Invalid Date চুপচাপ ছড়ায়: ভুল string দিলে error আসে না — আসে একটি “Invalid Date” object, যার getTime() হলো NaN. এর ওপর হিসাব করলে NaN ছড়াতে থাকে (পর্ব ১০)। শুধু toISOString() জোরে আপত্তি করে: RangeError: Invalid time value. তাই বাইরে থেকে আসা তারিখ পড়ার পর Number.isNaN(d.getTime()) দিয়ে যাচাই করুন।

৩ · ভেতরের অংশ পড়া — getUTC বনাম get, ০ থেকে মাস আর বদলে যাওয়া object

একটি Date থেকে বছর, মাস, দিন বের করার দুই সেট method আছে। getUTCFullYear(), getUTCMonth(), getUTCDate(), getUTCHours() — UTC ঘড়িতে পড়ে, তাই সব মেশিনে একই ফল। getFullYear(), getMonth(), getHours() — যে মেশিনে কোড চলছে তার জোনে পড়ে। সার্ভার সাধারণত UTC-তে চলে, ব্যবহারকারীর ফোন ঢাকায় — তাই একই কোড দুই জায়গায় দুই উত্তর দিতে পারে।

  • মাস ০ থেকে ১১: getUTCMonth() মার্চের জন্য দেয় 2. দেখানোর আগে + 1 ভুললে সব তারিখ এক মাস পিছিয়ে যায়।
  • দিন ১ থেকে ৩১: getUTCDate() — এটি কিন্তু ১ থেকেই। একই API-তে দুই নিয়ম!
  • বার ০ থেকে ৬: getUTCDay() — ০ মানে রবিবার, ৬ মানে শনিবার। নাম লাগলে নিজে array বানাবেন না, Intl ব্যবহার করুন (অংশ ৬)।
  • getYear() নয়: পুরোনো এই method ২০২৬-এর জন্য দেয় 126 (বছর − ১৯০০)। এটি Java 1.0-এর উত্তরাধিকার; সবসময় getFullYear/getUTCFullYear.
  • getTime() বা valueOf(): ভেতরের timestamp. +date লিখলেও একই সংখ্যা আসে (unary plus, পর্ব ০৮)।

Date বদলে যায় — mutation-এর ফাঁদ

পর্ব ০৬-এ দেখেছেন string আর number immutable. Date তেমন নয় — এটি একটি object, আর setDate, setMonth, setHours-এর মতো method ওই object-টিকেই বদলে দেয় (mutation). পর্ব ১৪-এ দেখেছেন, object ভ্যারিয়েবলে থাকে reference হিসেবে। ফলে একটি ফাংশনে order.placedAt.setDate(...) লিখলে অর্ডারের আসল সময়ও বদলে যায় — আর সেই একই Date যে আরও দশ জায়গায় ব্যবহার হচ্ছে, সবখানে।

// ভুল: আসল তারিখ বদলে যায়
function addDaysBad(date, n) { date.setUTCDate(date.getUTCDate() + n); return date; }

// ঠিক: নতুন Date বানিয়ে ফেরত দিন
const DAY = 24 * 60 * 60 * 1000;
const addDays = (date, n) => new Date(date.getTime() + n * DAY);
const copy = new Date(order);   // কপি বানানোর সহজ উপায়

প্রথম ফাংশনটি যে Date পায়, সেটিকেই বদলায় — ডাকার পরে আসল অর্ডারের তারিখও বদলে গেছে। দ্বিতীয়টি মিলিসেকেন্ডে যোগ করে নতুন Date বানায়; আসলটি অক্ষত থাকে। new Date(otherDate) একটি স্বাধীন কপি দেয়। ছোট সতর্কতা: মিলিসেকেন্ডে দিন যোগ করা মানে ঠিক ২৪ ঘণ্টা যোগ — UTC বা ঢাকার মতো DST-হীন জোনে এটিই “এক দিন”, কিন্তু DST-ওয়ালা জোনের দেয়ালঘড়িতে এক ঘণ্টা সরে যেতে পারে (অংশ ৪)।

মাস ০ থেকে শুরু আর setUTCMonth-এর আরেকটি চমক — অস্তিত্বহীন তারিখ “গড়িয়ে” পরের মাসে চলে যায় (overflow). নিচের debugger-এ ধাপে ধাপে দেখুন:

1const d = new Date(Date.UTC(2026, 0, 31));2const m = d.getUTCMonth();3d.setUTCMonth(m + 1);4const last = new Date(Date.UTC(2026, 2, 0)).getUTCDate();5const safe = new Date(Date.UTC(2026, 1, Math.min(31, last)));6console.log(safe.toISOString().slice(0, 10));
ভ্যারিয়েবলের মান
d
m
last
safe
  1. ৩১ জানুয়ারি — মাস 0. Date.UTC-এ মাসের জায়গায় 0 মানে জানুয়ারি। কেউ “১ = জানুয়ারি” ভেবে 1 লিখলে পাবেন ফেব্রুয়ারি — কোনো error ছাড়াই।
  2. মাস পড়লে 0. getUTCMonth() জানুয়ারির জন্য দেয় 0. মানুষকে দেখাতে + 1 লাগবে; হিসাবের জন্য এভাবেই রাখুন।
  3. “৩১ ফেব্রুয়ারি” গড়িয়ে ৩ মার্চ। setUTCMonth(1) শুধু মাস বদলায়, দিন ৩১ রেখে দেয়। ২০২৬-এর ফেব্রুয়ারিতে ২৮ দিন, তাই বাড়তি ৩ দিন গড়িয়ে যায় মার্চে: 2026-03-03. লিপ ইয়ার ২০২৪-এ একই কাজ দেয় ২ মার্চ। মাসিক বিল বা কিস্তির তারিখে এটি নীরব বাগ।
  4. দিন 0 = আগের মাসের শেষ দিন। একই গড়িয়ে যাওয়ার নিয়ম উল্টো দিকে কাজে লাগে: মার্চের “০ তারিখ” মানে ফেব্রুয়ারির শেষ দিন। তাই Date.UTC(2026, 2, 0) থেকে পাওয়া যায় 28 — কোনো মাসে কত দিন, জানার পুরোনো কৌশল।
  5. নিরাপদ “এক মাস পরে”। দিনটিকে মাসের শেষ দিনে আটকে দিলাম: Math.min(31, 28) = 28. ব্যাংক, মোবাইল প্ল্যান আর বেশিরভাগ ব্যবসা “৩১ জানুয়ারির এক মাস পরে = ২৮ ফেব্রুয়ারি” ধরে। নিয়মটি ব্যবসার — লিখে রাখুন।
  6. ফল। 2026-02-28. খেয়াল করুন, পুরো হিসাবে শুধু UTC method ব্যবহার করেছি, তাই যেকোনো মেশিনে একই উত্তর। Temporal-এ PlainDate.add({ months: 1 }) এই নিয়ম নিজেই মানে (অংশ ৮)।

৪ · তারিখের হিসাব — দিন যোগ, ব্যবধান আর তুলনা

যেহেতু Date ভেতরে একটি সংখ্যা, হিসাবও সংখ্যায়: দুই Date বিয়োগ করলে পাবেন মিলিসেকেন্ডের ব্যবধান (b - a নিজে থেকেই getTime() ডাকে)। তুলনাও চলে: a < b ঠিকঠাক কাজ করে। কিন্তু a === b দেখে দুটি object একই কি না, মুহূর্ত একই কি না নয় — একই সময়ের দুটি আলাদা Date-এর জন্য === দেয় false. মুহূর্ত মেলাতে a.getTime() === b.getTime().

“কত দিন বাকি?” — DST-এর এক দিনের ভুল

দুটি তারিখের মধ্যে কত দিন, এটি খুব সাধারণ প্রশ্ন — পরীক্ষার কত দিন বাকি, ফেরতের সময়সীমা পেরিয়েছে কি না। সহজ সূত্র মনে হয়: (b - a) / 86400000. ঢাকায় এটি ঠিক চলে, কারণ এখানে প্রতিটি দিন ঠিক ২৪ ঘণ্টা। কিন্তু DST-ওয়ালা জোনে বছরে একদিন ২৩ ঘণ্টার, একদিন ২৫ ঘণ্টার। দেখুন নিউ ইয়র্কে কী ঘটে:

দুটি দুপুর, নিউ ইয়র্কে৮ মার্চ ২০২৬ রাতে ঘড়ি এক ঘণ্টা এগোয়
a = ৭ মার্চ দুপুর ১২টা (EST, -05:00) = 2026-03-07T17:00:00Z
b = ৯ মার্চ দুপুর ১২টা (EDT, -04:00) = 2026-03-09T16:00:00Z
মিলিসেকেন্ডে ব্যবধানদুই দিন নয়, ৪৭ ঘণ্টা
(b - a) / 3600000  → 47
(b - a) / 86400000 → 1.9583333333333333
Math.floor দিলেএক দিন হারিয়ে গেল
Math.floor(1.958…) → 1   // “১ দিন বাকি” — ভুল!
Math.round(1.958…) → 2   // এখানে বাঁচল, কিন্তু কাকতালীয়
ক্যালেন্ডারের তারিখ ধরে UTC-তেপ্রতিটি দিন ঠিক ২৪ ঘণ্টা
utcDay("2026-03-07") = Date.parse("2026-03-07T00:00:00Z")
utcDay("2026-03-09") = Date.parse("2026-03-09T00:00:00Z")
(পার্থক্য) / 86400000 → 2   // সব জোনে, সব মেশিনে
  1. দুটি দুপুর। নিউ ইয়র্কে ৭ আর ৯ মার্চ দুপুর ১২টা। মাঝখানে ৮ মার্চ রাত ২টায় ঘড়ি সরাসরি ৩টায় চলে যায় (DST শুরু), offset -05:00 থেকে -04:00. তাই দ্বিতীয় দুপুরটি UTC-তে এক ঘণ্টা আগে পড়ে।
  2. মাঝে ৪৭ ঘণ্টা। দুই মুহূর্তের প্রকৃত ব্যবধান ৪৭ ঘণ্টা — পদার্থবিজ্ঞানের দিক থেকে এটিই সত্য। কিন্তু মানুষ জানতে চেয়েছিল “ক্যালেন্ডারে কত দিন”, আর ঘণ্টা ভাগ করে সেই প্রশ্নের উত্তর মেলে না।
  3. এক দিনের ভুল। Math.floor দিলে উত্তর ১ — “আপনার ফেরতের সময়সীমা শেষ” ভুল করে দেখানোর মতো বাগ। Math.round এখানে বাঁচিয়ে দেয়, কিন্তু সেটি নিয়ম নয়, ভাগ্য।
  4. সমাধান: ক্যালেন্ডারের তারিখকে UTC মধ্যরাতে বসান। প্রশ্নটি যদি “কোন দুই তারিখ”, তাহলে সময় ফেলে দিয়ে দুটি তারিখকেই UTC মধ্যরাত বানান। UTC-তে DST নেই, তাই ভাগফল সবসময় পূর্ণসংখ্যা। Java-র LocalDate আর Temporal-এর PlainDate ঠিক এই ধারণাটিই আলাদা টাইপ হিসেবে দেয়।

এবার পুরো ভিত্তিটি নিজে চালিয়ে দেখুন। প্রতিটি লাইনের ফল আগে মনে মনে অনুমান করুন। লক্ষ করুন — কোথাও “এখনকার” সময় নেই, সব তারিখ নির্দিষ্ট আর offset-সহ, তাই আপনার কম্পিউটার যে জোনেই থাকুক, ফল একই আসবে:

const order = new Date("2026-03-15T10:30:00Z");      // Z মানে UTC
console.log(order.getTime());                         // 1970 থেকে মিলিসেকেন্ড
console.log(order.getUTCFullYear(), order.getUTCMonth(), order.getUTCDate(), order.getUTCDay());

console.log(new Date("2026-03-15").getUTCHours());    // শুধু তারিখ → UTC
console.log(new Date("2026-03-15T10:00").getHours()); // offset নেই → স্থানীয়
console.log(new Date("15/03/2026").getTime());        // অচেনা রূপ

const DAY = 24 * 60 * 60 * 1000;
const eta = new Date(order.getTime() + 3 * DAY);      // নতুন object
console.log(eta.toISOString(), order < eta);

const d = new Date(Date.UTC(2026, 0, 31));            // মাস 0 = জানুয়ারি
d.setUTCMonth(d.getUTCMonth() + 1);                   // d নিজেই বদলায়
console.log(d.toISOString().slice(0, 10));

const utcDay = (iso) => Date.parse(iso + "T00:00:00Z");
const daysBetween = (a, b) => (utcDay(b) - utcDay(a)) / DAY;
console.log(daysBetween("2026-03-07", "2026-03-09"), daysBetween("2026-02-01", "2026-03-01"));
console.log(JSON.stringify({ placedAt: order }));

প্রথম লাইনে timestamp 1773570600000. তারপর 2026 2 15 0 — মাস 2 মানে মার্চ, আর বার 0 মানে রবিবার। শুধু-তারিখের string-এর UTC ঘণ্টা 0; offset-ছাড়া string-এর স্থানীয় ঘণ্টা 10 — দুটিই সব মেশিনে একই দেখায়, কিন্তু আসল মুহূর্ত দ্বিতীয়টির ক্ষেত্রে মেশিনভেদে আলাদা (ধাপের ডায়াগ্রাম মনে করুন)। দিন/মাস/বছর রূপ দেয় NaN. তিন দিন পরের ETA 2026-03-18T10:30:00.000Z, আর তুলনা true. ৩১ জানুয়ারির “এক মাস পরে” 2026-03-03. DST পেরিয়েও ব্যবধান ঠিক 2, আর ফেব্রুয়ারি ২০২৬-এ 28 দিন। শেষ লাইনে JSON.stringify Date-কে নিজে থেকেই ISO UTC string বানায় — {"placedAt":"2026-03-15T10:30:00.000Z"}; উল্টো পথে JSON.parse কিন্তু string-ই ফেরত দেয়, Date নয় — বিস্তারিত পর্ব ১৯-এ।

বয়সের হিসাব — দিন গুনবেন না, তারিখ মেলান

“জন্মতারিখ থেকে বয়স” লিখতে AI প্রায়ই লেখে (today - birth) / (365.25 * DAY). জন্মদিনের আশেপাশে এটি এক বছর ভুল দেয়, কারণ বছর ৩৬৫.২৫ দিনের নয় — কখনো ৩৬৫, কখনো ৩৬৬। সঠিক যুক্তি মানুষের মতো: বছরের পার্থক্য নিন, আর এ বছরের জন্মদিন এখনো না এলে এক কমান। “আজ” কোন তারিখ, সেটি আবার জোনের ওপর নির্ভর করে — UTC-তে ১৮ অক্টোবর রাত ৮টা মানে ঢাকায় ১৯ অক্টোবর।

function age(birth, today) {            // দুটিই "YYYY-MM-DD", ক্যালেন্ডারের তারিখ
  const [by, bm, bd] = birth.split("-").map(Number);
  const [y, m, d] = today.split("-").map(Number);
  return y - by - (m < bm || (m === bm && d < bd) ? 1 : 0);
}
age("2000-10-25", "2026-10-19");   // 25 — জন্মদিন আসতে আরও ৬ দিন
age("2000-03-15", "2026-03-15");   // 26 — আজই জন্মদিন

ফাংশনটি কোনো Date বা “এখন” ব্যবহার করে না — দুটি তারিখ argument হিসেবে নেয়। তাই এটি একটি pure function: একই ইনপুটে সবসময় একই উত্তর, টেস্ট লেখা সহজ। “আজ” বের করার কাজটি ডাকার জায়গায়, একবার: new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Dhaka" }).format(now) দেয় "2026-10-19"-এর মতো YYYY-MM-DD রূপ (en-CA locale এই রূপটিই ব্যবহার করে)। ২৯ ফেব্রুয়ারির জন্মদিন কোন দিন “পূর্ণ” হবে, তা আইনের প্রশ্ন — দেশ অনুযায়ী আলাদা হতে পারে।

৫ · Time zone — ঢাকা, নিউ ইয়র্ক আর DST-এর খেলা

Time zone মানে শুধু “+৬ ঘণ্টা” নয়। একটি জোন হলো একটি অঞ্চলের নিয়মের ইতিহাস — কোন বছরে offset কত ছিল, কবে DST শুরু-শেষ হয়। এই নিয়মগুলো জমা থাকে IANA time zone database-এ, যার নাম Area/City ধরনের: Asia/Dhaka, America/New_York, Europe/London. JavaScript engine, অপারেটিং সিস্টেম আর ডেটাবেস — সবাই এই একই database ব্যবহার করে।

  • Asia/Dhaka: বর্তমানে UTC+6, কোনো DST নেই। তবে ইতিহাস আছে — ২০০৯ সালে বাংলাদেশ কয়েক মাসের জন্য DST চালু করে ঘড়ি এক ঘণ্টা এগিয়েছিল। database সেটিও মনে রাখে, তাই ২০০৯ সালের একটি পুরোনো মুহূর্ত দেখালে সঠিক offset আসে।
  • America/New_York: শীতে EST (UTC−5), মার্চের দ্বিতীয় রবিবার থেকে নভেম্বরের প্রথম রবিবার পর্যন্ত EDT (UTC−4).
  • “+06:00” ≠ “Asia/Dhaka”: offset একটি মুহূর্তের তথ্য, জোন একটি নিয়মের সেট। ভবিষ্যতের মিটিং সংরক্ষণে জোনের নাম রাখুন — দেশটি নিয়ম বদলালে offset বদলাবে, নাম নয়।
মুহূর্তএকটি সংখ্যা — জোনহীন
const order = new Date("2026-03-15T10:30:00Z");
order.getTime() → 1773570600000
timeZone: "UTC"offset +00:00
রবিবার ১৫ মার্চ এ ১০:৩০ সকালবেলায়
getTime() এখনো 1773570600000
timeZone: "Asia/Dhaka"offset +06:00
রবিবার ১৫ মার্চ এ ৪:৩০ বিকাল
getTime() এখনো 1773570600000
timeZone: "America/New_York"offset −04:00 (EDT)
রবিবার ১৫ মার্চ এ ৬:৩০ সকালবেলায়
getTime() এখনো 1773570600000
দুই সপ্তাহ আগে, একই UTC সময়২০২৬-০৩-০১T10:30Z
ঢাকা:      ৪:৩০ বিকাল        (এখনো +06:00)
নিউ ইয়র্ক: ৫:৩০ সকালবেলায়   (তখন EST, −05:00)
  1. একটি সংখ্যা। অর্ডারের মুহূর্ত একটি timestamp. এর কোনো জোন নেই — ঠিক যেমন BPL-এর শেষ বলের কোনো “দেশি সময়” নেই।
  2. UTC-র ঘড়িতে। Intl.DateTimeFormat-এ timeZone: "UTC" দিলে সকাল ১০:৩০. সংখ্যাটি বদলায়নি — বদলেছে শুধু দেখানো লেখা।
  3. ঢাকার ঘড়িতে। ৬ ঘণ্টা যোগ হয়ে বিকাল ৪:৩০. দোকানের ড্যাশবোর্ড এটিই দেখাবে। খেয়াল করুন — UTC-তে সন্ধ্যা ৬টা (18:00) বা তার পরের যেকোনো মুহূর্ত ঢাকায় পরের তারিখে পড়ে; “আজকের অর্ডার” গোনার সময় এটি মনে রাখুন।
  4. নিউ ইয়র্কের ঘড়িতে। ৮ মার্চ থেকে সেখানে EDT, তাই ৪ ঘণ্টা বাদ দিয়ে সকাল ৬:৩০. প্রবাসী গ্রাহক এটিই দেখেন। একই getTime(), তিনটি সত্যি লেখা।
  5. DST-এর প্রভাব। দুই সপ্তাহ আগে, ১ মার্চের একই UTC সময়ে নিউ ইয়র্কে তখনো EST — ঘড়িতে ৫:৩০. ঢাকার সাথে পার্থক্য কখনো ১০, কখনো ১১ ঘণ্টা। তাই “নিউ ইয়র্ক = ঢাকা − ১০ ঘণ্টা” কোডে হার্ডকোড করলে বছরের কয়েক মাস ভুল হবে। হিসাব জোনের নামে ছেড়ে দিন।

JavaScript-এর Date নিজে শুধু দুটি জোন চেনে: UTC আর “এই মেশিনের স্থানীয় জোন”। getHours()-কে বলতে পারবেন না “নিউ ইয়র্কের ঘণ্টা দাও”। অন্য যেকোনো জোনের জন্য পথ হলো Intl.DateTimeFormat-এর timeZone option — অথবা ভবিষ্যতে Temporal-এর ZonedDateTime. ব্রাউজারের জোন জানতে: Intl.DateTimeFormat().resolvedOptions().timeZone, যা ঢাকার কম্পিউটারে দেয় "Asia/Dhaka".

৬ · Intl.DateTimeFormat — বাংলায় তারিখ আর “কত দিন আগে”

Intl হলো JavaScript-এর আন্তর্জাতিকীকরণের (internationalization) built-in object. এর ভেতরের formatter-গুলো সংখ্যা, তারিখ, তালিকা আর আপেক্ষিক সময়কে যেকোনো ভাষার নিয়মে লেখে। ভাষার নিয়মগুলো আসে CLDR থেকে — Unicode Consortium-এর বিশাল ডেটা ভান্ডার, যেখানে বাংলার মাসের নাম, বারের নাম, “আগে”, “আগামীকাল” সবই লেখা আছে। নিজে মাসের নামের array বানানোর দরকার নেই।

Intl.DateTimeFormat দুটি জিনিস নেয়: locale (যেমন "bn-BD") আর option-এর object. সবচেয়ে জরুরি option হলো timeZone — না দিলে ব্যবহারকারীর মেশিনের জোন নেওয়া হয়। একই মুহূর্ত 2026-03-15T10:30:00Z, timeZone: "Asia/Dhaka" দিয়ে (Edge আর Node 20-এ যাচাই করা):

optionফল
{ dateStyle: "full" }রবিবার, ১৫ মার্চ, ২০২৬
{ dateStyle: "long" }১৫ মার্চ, ২০২৬
{ dateStyle: "short" }১৫/৩/২৬
{ timeStyle: "short" }৪:৩০ PM
{ timeStyle: "full" }৪:৩০:০০ PM বাংলাদেশ মানক সময়
{ hour: "numeric", minute: "2-digit", dayPeriod: "long" }৪:৩০ বিকাল
{ hour: "numeric", minute: "2-digit", hourCycle: "h23" }১৬:৩০
{ weekday: "short" }রবি
{ dateStyle: "medium", numberingSystem: "latn" }15 মার্চ, 2026
  • dateStyle/timeStyle বনাম আলাদা অংশ: dateStyle আর timeStyle তৈরি নকশা দেয়। নিজে বাছতে চাইলে weekday, day, month, year, hour, minute আলাদা দিন। দুই ধরন মেশানো যায় না — { timeStyle: "short", timeZoneName: "short" } দিলে TypeError (Node 20-এর বার্তা: Can't set option timeZoneName when timeStyle is used; Edge-এর বার্তা আলাদা)।
  • বাংলা বনাম ইংরেজি অঙ্ক: bn-BD-এর ডিফল্ট numbering system হলো beng (০১২৩…)। ইংরেজি অঙ্ক চাইলে numberingSystem: "latn" অথবা locale-এর ভেতরেই "bn-BD-u-nu-latn". উল্টোটাও চলে: en-US-এ numberingSystem: "beng".
  • AM/PM বনাম বাংলা দিনের ভাগ: CLDR-এর বাংলা নকশায় সময়ের সাথে “AM/PM” আসে। dayPeriod: "long" দিলে আসে “ভোরবেলায়”, “সকালবেলায়”, “দুপুরবেলায়”, “বিকাল”, “সন্ধ্যাবেলায়”, “রাত্রিবেলায়”। ২৪ ঘণ্টার ঘড়ি চাইলে hourCycle: "h23".
  • বারবার একই formatter: formatter বানানো তুলনামূলক দামি, তাই একবার বানিয়ে ভ্যারিয়েবলে রেখে হাজার সারিতে .format() ডাকুন। date.toLocaleDateString("bn-BD", {...}) একই কাজের ছোট রূপ, কিন্তু প্রতিবার নতুন করে বানায়।

formatToParts — টুকরো টুকরো হাতে পাওয়া

কখনো পুরো লেখা নয়, অংশগুলো আলাদা চাই — যেমন একটি ক্যালেন্ডার কার্ডে দিনটি বড় করে, মাসটি ছোট করে দেখাতে। formatToParts একটি array দেয়: [{type:"day", value:"১৫"}, {type:"literal", value:" "}, {type:"month", value:"মার্চ"}, …]. নিজে string কেটে অংশ বের করার চেয়ে এটি অনেক নিরাপদ, কারণ ভিন্ন locale-এ অংশের ক্রম ভিন্ন হয়। এটি দিয়ে অন্য জোনের ঘণ্টা-মিনিটও সংখ্যা হিসেবে বের করা যায় — en-US locale আর hourCycle: "h23" দিয়ে নিউ ইয়র্কের hour: "06".

Intl.RelativeTimeFormat — “৩ দিন আগে”

সামাজিক মাধ্যম, নোটিফিকেশন, অর্ডারের ইতিহাস — সব জায়গায় “৫ মিনিট আগে”, “গতকাল”। Intl.RelativeTimeFormat শুধু লেখার কাজটি করে: একটি সংখ্যা আর একক ("day", "week", "month"…) দিলে বাক্য বানায়। ব্যবধান হিসাব করার দায়িত্ব আপনার।

  • rtf.format(-3, "day") → “৩ দিন আগে”; rtf.format(2, "week") → “২ সপ্তাহে”।
  • { numeric: "auto" } দিলে: -1 দিন → “গতকাল”, 1 → “আগামীকাল”, 0 → “আজ”, -2 → “গত পরশু”; ছাড়া দিলে “১ দিন আগে”।
  • CLDR-এর সব শব্দ সবসময় সবার পছন্দের হয় না — যেমন format(-1, "year") দেয় “১ বছর পূর্বে”। ব্যবহারকারীর সামনে যাওয়ার আগে একবার চোখ বুলিয়ে নিন।

Engine-ভেদে শব্দ সামান্য আলাদা: ICU আর CLDR-এর সংস্করণ অনুযায়ী বাংলা বানান বদলায়। আমাদের পরীক্ষায় Edge দেখিয়েছে “জানুয়ারি”, “ফেব্রুয়ারি”, “২ ঘণ্টা আগে”, আর Node 20 দেখিয়েছে “জানুয়ারী”, “ফেব্রুয়ারী”, “২ ঘন্টা আগে”। তাই টেস্টে ফরম্যাট করা বাংলা লেখা হুবহু মেলানোর বদলে মূল মান (timestamp, ISO string) মেলান, অথবা formatToParts-এর কাঠামো যাচাই করুন।

এবার অর্ডারের পুরো গল্প Intl দিয়ে — দুই শহরে সময়, তিন দিন পরের ডেলিভারি, “কত দিন আগে” আর টাকার অঙ্ক:

const order = new Date("2026-03-15T10:30:00Z");
const when = (timeZone) => new Intl.DateTimeFormat("bn-BD", { timeZone,
  weekday: "long", day: "numeric", month: "long",
  hour: "numeric", minute: "2-digit", dayPeriod: "long" }).format(order);
console.log("ঢাকা:", when("Asia/Dhaka"));
console.log("নিউ ইয়র্ক:", when("America/New_York"));

const eta = new Date(order.getTime() + 3 * 86400000);
const full = new Intl.DateTimeFormat("bn-BD", { timeZone: "Asia/Dhaka", dateStyle: "full" });
console.log("ডেলিভারি:", full.format(eta));

const now = new Date("2026-03-18T12:00:00Z");          // নির্দিষ্ট "এখন"
const days = Math.round((order - now) / 86400000);
const rtf = new Intl.RelativeTimeFormat("bn", { numeric: "auto" });
console.log("অর্ডার হয়েছে", rtf.format(days, "day"), "|", rtf.format(-1, "day"));

const tk = new Intl.NumberFormat("bn-BD", { style: "currency", currency: "BDT" });
const big = new Intl.NumberFormat("bn-BD", { notation: "compact", compactDisplay: "long" });
console.log(tk.format(1234567.5), "|", big.format(25000000));
console.log(new Intl.ListFormat("bn").format(["চাল", "ডাল", "তেল"]));

একই order ঢাকায় “রবিবার ১৫ মার্চ এ ৪:৩০ বিকাল”, নিউ ইয়র্কে “রবিবার ১৫ মার্চ এ ৬:৩০ সকালবেলায়”। মাঝের “এ” CLDR-এর বাংলা নকশার জোড়া-শব্দ; অপছন্দ হলে তারিখ আর সময় দুটি আলাদা formatter-এ বানিয়ে নিজের পছন্দমতো জুড়ুন। ডেলিভারি “বুধবার, ১৮ মার্চ, ২০২৬”। “এখন” ইচ্ছা করে নির্দিষ্ট রাখা হয়েছে — তাই ফল সবসময় “৩ দিন আগে | গতকাল”। আসল অ্যাপে now বাইরে থেকে argument হিসেবে আসবে। টাকা লাখ-কোটির কমায় ১২,৩৪,৫৬৭.৫০৳, আড়াই কোটি হয় “২.৫ কোটি”, আর তালিকা “চাল, ডাল এবং তেল” — CLDR বাংলায় “ও”-র বদলে “এবং” ব্যবহার করে। এই সব লেখা আমরা Edge আর Node 20 দুই জায়গায় মিলিয়ে দেখেছি।

৭ · Intl-এর বাকি পরিবার — টাকা, একক, বহুবচন, তালিকা আর সাজানো

পর্ব ১০-এ Intl.NumberFormat-এর সাথে পরিচয় হয়েছে। এবার একটু গভীরে — একটি মুদি দোকানের রসিদ ভাবুন, যেখানে টাকা, ওজন, ছাড় আর মোট সব বাংলায়।

কাজlocale ও optionফল
লাখ-কোটির কমা"bn-BD"১২,৩৪,৫৬,৭৮৯
একই, ইংরেজি অঙ্কে"en-IN"12,34,56,789
আন্তর্জাতিক কমা"en-US"123,456,789
টাকা{ style: "currency", currency: "BDT" }২,৫০,০০০.০০৳
টাকা, মুদ্রার নামসহcurrencyDisplay: "name"১,২৫০.০০ বাংলাদেশী টাকা
সংক্ষেপে বড় সংখ্যা{ notation: "compact" }১৫ লা, ২.৫ কো
পুরো শব্দেcompactDisplay: "long"১.৩ লাখ, ১২০ কোটি
শতাংশ{ style: "percent", maximumFractionDigits: 1 } → 0.075৭.৫%
একক{ style: "unit", unit: "kilogram" }৫ কেজি
একক, পুরো নামেunit: "liter", unitDisplay: "long"২ লিটার

লক্ষ করুন: en-BD দিলে কমা আন্তর্জাতিক ধরনে (BDT 1,234,567.50) আসে — লাখ-কোটি চাইলে bn-BD বা en-IN. { style: "percent" }-এর ডিফল্ট দশমিক ০, তাই 0.155 হয় “১৬%”. আর মনে রাখুন পর্ব ১০-এর নিয়ম: হিসাব integer পয়সায়, NumberFormat শুধু শেষে দেখানোর জন্য।

Intl.PluralRules

“1 item” না “2 items”? PluralRules বলে দেয় একটি সংখ্যা কোন শ্রেণির: ইংরেজিতে select(1) → "one", select(2) → "other". ক্রমবাচকে (type: "ordinal") 2 → "two" (2nd), 3 → "few" (3rd). বাংলায় শ্রেণি দুটি — bn-এ ০ আর ১ দুটিই "one", বাকি "other".

“১টি পণ্য” · “৫টি পণ্য”

Intl.ListFormat

ListFormat তালিকাকে ভাষার নিয়মে জোড়ে: ["চাল","ডাল","তেল"] → “চাল, ডাল এবং তেল”। type: "disjunction" দিলে “বিকাশ বা নগদ”। join(", ")-এর চেয়ে ভালো, কারণ শেষের “এবং/and”-এর নিয়ম ভাষাভেদে আলাদা।

ইংরেজিতে: rice, lentils, and oil

Intl.Collator

পর্ব ০৯-এ দেখেছেন, Collator ভাষার নিয়মে সাজায়। ["Zara","amit","Bina"].toSorted() দেয় Bina,Zara,amit (code unit-এর ক্রমে বড় হাতের অক্ষর আগে), আর toSorted(new Intl.Collator("en").compare) দেয় amit,Bina,Zara.

বাংলা নাম: new Intl.Collator("bn")

একটি বাস্তব উদাহরণ — পরীক্ষার রুটিন। তারিখগুলো ISO-তে রাখা, দেখানোর সময় বাংলায় বারসহ:

const routine = [["বাংলা", "2026-11-01T04:00:00Z"], ["গণিত", "2026-11-03T04:00:00Z"]];
const fmt = new Intl.DateTimeFormat("bn-BD", { timeZone: "Asia/Dhaka",
  weekday: "long", day: "numeric", month: "long", year: "numeric" });
for (const [subject, at] of routine) console.log(subject, "—", fmt.format(new Date(at)));
// প্রত্যাশিত আউটপুট:
// বাংলা — রবিবার, ১ নভেম্বর, ২০২৬
// গণিত — মঙ্গলবার, ৩ নভেম্বর, ২০২৬

প্রতিটি পরীক্ষার সময় ঢাকার সকাল ১০টা (UTC 04:00), ISO string হিসেবে রাখা। বারের নাম কোথাও হাতে লেখা নেই — formatter নিজেই হিসাব করে “রবিবার”, “মঙ্গলবার” বসিয়েছে, তাই তারিখ বদলালে বারের ভুল হওয়ার সুযোগ নেই। একই কোড "en-US" দিলে ইংরেজি রুটিন বানাবে।

C/C++/Python/Java জানলে

Java-র পুরোনো java.util.Date আর JavaScript-এর Date প্রায় যমজ — দুটিতেই ০ থেকে মাস আর বদলানো যায় এমন object. Java ২০১৪ সালে (Java 8) immutable java.time এনেছে: Instant, LocalDate, ZonedDateTime — Temporal প্রায় এই ধারণাই অনুসরণ করে (Java-র তারিখ ও সময়)। Python-এর datetime-এ মাস ১ থেকে, আর “naive” বনাম “aware” (জোনসহ) datetime-এর পার্থক্য আমাদের “স্থানীয় বনাম offset-সহ” ফাঁদের মতোই (Python-এর standard library). C-র time_t সাধারণত সেকেন্ডে গোনে, JavaScript গোনে মিলিসেকেন্ডে — দুই সিস্টেমের মধ্যে timestamp আদান-প্রদানে ১০০০ দিয়ে গুণ-ভাগ ভুলবেন না।

পরীক্ষা ও ইন্টারভিউতে
  • new Date(2026, 1, 1) কোন মাস? — ফেব্রুয়ারি; Date-এ মাস ০ থেকে ১১, কিন্তু দিন ১ থেকে।
  • Date ভেতরে কী রাখে? — একটি number: ১৯৭০-০১-০১T00:00:00Z থেকে মিলিসেকেন্ড; জোন রাখে না।
  • new Date("2026-03-15") আর new Date("2026-03-15T00:00") কি একই? — না; প্রথমটি UTC মধ্যরাত, দ্বিতীয়টি স্থানীয় মধ্যরাত।
  • দুটি Date একই মুহূর্ত কি না কীভাবে দেখবেন? — a.getTime() === b.getTime(); === object-এর পরিচয় তুলনা করে।
  • ডেটাবেসে তারিখ কোন রূপে রাখবেন? — UTC মুহূর্ত (ISO 8601 string বা timestamp); দেখানোর সময় ব্যবহারকারীর জোনে রূপান্তর।

৮ · Temporal, library আর তারিখ সংরক্ষণের পেশাদার নিয়ম

Date-এর সব সমস্যা — ০ থেকে মাস, mutation, শুধু দুটি জোন, “তারিখ” আর “মুহূর্ত” এক টাইপে — সমাধানের জন্য TC39-এ ২০১৭ সাল থেকে কাজ চলছে নতুন API Temporal-এর ওপর। এটি দীর্ঘদিন Stage 3-এ ছিল, আর ২০২৫ সাল থেকে ব্রাউজারে আসতে শুরু করেছে — Firefox প্রথম চালু করে, আর আমাদের পরীক্ষায় Edge-এর বর্তমান সংস্করণেও typeof Temporal হলো "object". কিন্তু Node 20-এ নেই, সব ব্রাউজারেও এখনো নেই। তাই কোর্সের চালানো কোডে আমরা Temporal ব্যবহার করছি না; নিচেরটি শুধু ঝলক (Edge-এ যাচাই করা প্রত্যাশিত আউটপুট)। ব্যবহারের আগে MDN-এ সমর্থনের তালিকা দেখে নিন।

// Temporal — সব object immutable, মাস 1 থেকে, তারিখ আর মুহূর্ত আলাদা টাইপ
Temporal.PlainDate.from("2026-01-31").add({ months: 1 }).toString();
// "2026-02-28"   ← গড়িয়ে মার্চে যায় না

const z = Temporal.Instant.from("2026-03-15T10:30:00Z").toZonedDateTimeISO("Asia/Dhaka");
z.toString();                                     // "2026-03-15T16:30:00+06:00[Asia/Dhaka]"
z.withTimeZone("America/New_York").toString();    // "2026-03-15T06:30:00-04:00[America/New_York]"
Temporal.PlainDate.from("2026-03-07").until("2026-03-09").days;   // 2

PlainDate শুধু ক্যালেন্ডারের তারিখ — কোনো সময় বা জোন নেই, তাই জন্মদিন বা ছুটির দিনের জন্য একদম ঠিক টাইপ। Instant একটি মুহূর্ত (আজকের Date-এর মতো, তবে nanosecond পর্যন্ত)। ZonedDateTime মুহূর্ত + জোনের নাম — DST-এর নিয়ম মেনে হিসাব করে। [Asia/Dhaka] বন্ধনী-রূপটি ২০২৪ সালের IETF মানদণ্ড RFC 9557-এর অংশ, যা ISO 8601-এর পরে জোনের নাম যোগ করার নিয়ম দেয়।

আজকের বাস্তব পছন্দ — library

  • date-fns: ছোট ছোট pure function (addDays, differenceInCalendarDays), সাধারণ Date-এর ওপর কাজ করে, শুধু যা দরকার তা import করা যায়। জোনের জন্য আলাদা সহায়ক package আছে।
  • Day.js: খুব হালকা, পুরোনো Moment.js-এর মতো chain করা API; জোন আর আপেক্ষিক সময় plugin হিসেবে।
  • Luxon: immutable, Intl-এর ওপর তৈরি, জোন সামলানোয় শক্তিশালী — ধারণায় Temporal-এর কাছাকাছি।
  • Moment.js: এক সময়ের সবচেয়ে জনপ্রিয়, এখন রক্ষণাবেক্ষণ মোডে; নির্মাতারাই নতুন প্রজেক্টে অন্যটি বেছে নিতে বলেন।

Library বসানোর নিয়ম পর্ব ৪৬-এ (npm). ছোট প্রজেক্টে এই পর্বের Date + Intl-ই যথেষ্ট; জোন-ভারী ক্যালেন্ডার বা বুকিং অ্যাপে library বা Temporal.

তারিখ সংরক্ষণের সোনালি নিয়ম

  1. মুহূর্ত রাখুন UTC-তে: অর্ডার, লগইন, পেমেন্ট — "2026-03-15T10:30:00.000Z" (ISO string) বা timestamp. JSON-এ toISOString()-এর রূপ সবচেয়ে প্রচলিত।
  2. ক্যালেন্ডারের তারিখ রাখুন তারিখ হিসেবে: জন্মদিন, ছুটির দিন — "2000-10-25". একে মধ্যরাতের মুহূর্ত বানালে জোন বদলালেই তারিখ সরে যায়।
  3. ভবিষ্যতের স্থানীয় ঘটনায় জোনের নাম: “প্রতি রবিবার ঢাকার সকাল ১০টায় ক্লাস” — স্থানীয় সময় + "Asia/Dhaka"; দেশ নিয়ম বদলালেও ঠিক থাকে।
  4. রূপান্তর একদম শেষে: সার্ভার আর ডেটাবেস শুধু UTC চেনে; পাঠকের জোন আর ভাষায় রূপ দেওয়া হয় দেখানোর মুহূর্তে, Intl দিয়ে।

৯ · AI-এর যুগে — তারিখের কোডে AI-এর চেনা ভুল

তারিখের কোড AI খুব আত্মবিশ্বাসের সাথে লেখে, আর আপনার কম্পিউটারে সেটি ঠিকঠাক চলেও যায় — কারণ আপনার মেশিন, আপনার জোন আর আজকের তারিখে পরীক্ষা করছেন। ভুল ধরা পড়ে অন্য জোনের সার্ভারে, মাসের শেষ দিনে বা DST-এর রাতে।

AI যা প্রায়ই লেখেসমস্যাযা চাইবেন
লজিকের ভেতরে new Date() বা Date.now()প্রতিবার ফল আলাদা — টেস্ট লেখা যায় না“এখন” argument হিসেবে নেওয়া
new Date("2026-03-15 10:00")offset নেই → মেশিনভেদে আলাদা মুহূর্ত; ফাঁকা দিয়ে লেখা রূপ ISO-ও নয়Z বা +06:00-সহ ISO string
new Date(2026, 3, 15) “১৫ মার্চ” ভেবেমাস ০ থেকে — এটি এপ্রিলDate.UTC-এ মাস − ১, অথবা ISO string
d.getDate() + "/" + (d.getMonth()+1), মাসের নামের নিজের arrayস্থানীয় জোন, ইংরেজি অঙ্ক, ভাষা বদলানো যায় নাIntl.DateTimeFormat("bn-BD", { timeZone })
(b - a) / 86400000 দিয়ে দিন, / 365 দিয়ে বয়সDST আর লিপ ইয়ারে এক দিন/এক বছর ভুলক্যালেন্ডারের তারিখ UTC-তে, বছর-মাস-দিন মিলিয়ে বয়স
হার্ডকোড “+6 ঘণ্টা” বা “−10 ঘণ্টা”DST-ওয়ালা জোনে বছরের কয়েক মাস ভুলIANA জোনের নাম আর Intl/Temporal

AI-কে তারিখের কোড লিখতে বললে জোন, রূপ আর টেস্টের শর্ত আগেই দিয়ে দিন:

আমি JavaScript-এ একটি অনলাইন দোকানের জন্য ডেলিভারির তারিখ দেখানোর কোড লিখছি। নিচের শর্ত মেনে লেখো: ১. ফাংশনের ভেতরে new Date() বা Date.now() ব্যবহার করবে না; "এখন" একটি Date argument হিসেবে আসবে। ২. অর্ডারের সময় আসবে ISO 8601 UTC string হিসেবে (যেমন "2026-03-15T10:30:00Z"); offset ছাড়া কোনো string parse করবে না। ৩. ডেলিভারি = অর্ডারের ৩ দিন পর; দিনের হিসাব ক্যালেন্ডারের তারিখে, Asia/Dhaka জোনে। ৪. দেখানোর জন্য Intl.DateTimeFormat("bn-BD") আর Intl.RelativeTimeFormat("bn") ব্যবহার করবে; মাস বা বারের নাম নিজে লিখবে না। ৫. শুধু UTC method (getUTC…) বা Intl ব্যবহার করবে, স্থানীয় get… method নয়। শেষে ৫টি টেস্ট কেস দাও — একটি মাসের শেষ দিনে, একটি ঢাকার মধ্যরাতের কাছাকাছি (UTC 18:00-এর পরে), একটি America/New_York-এ DST শুরুর সপ্তাহে — আর প্রতিটির প্রত্যাশিত ফল হাতে হিসাব করে দেখাও।

AI-এর লেখা তারিখের কোড — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১JavaScript-এর Date-এ মাস কেন ০ থেকে শুরু? ১৯৯৫ সালের দশ দিনের তাড়াহুড়ো আজও কীভাবে আমাদের কোডে বেঁচে আছে?

১৯৯৫ সালের মে মাসে Netscape-এ Brendan Eich প্রায় দশ দিনে JavaScript-এর প্রথম রূপ (তখন নাম Mocha) বানান। Netscape তখন Sun Microsystems-এর সাথে Java-কে ব্রাউজারে আনার চুক্তি করেছে, আর ব্যবস্থাপনার নির্দেশ ছিল নতুন script ভাষাটি “Java-র ছোট ভাই”-এর মতো দেখাবে। এত কম সময়ে তারিখের মতো জটিল বিষয়ের নিজস্ব নকশা করার সুযোগ ছিল না। Eich পরে নিজেই বলেছেন, Date অংশটি তিনি Java 1.0-এর java.util.Date থেকে প্রায় হুবহু নিয়েছিলেন।

সেই Java ক্লাসের নকশা এসেছিল C-র struct tm থেকে, যেখানে tm_mon ০ থেকে ১১ — কারণ C প্রোগ্রামাররা মাসকে একটি মাসের নামের array-র index হিসেবে ব্যবহার করতেন (names[tm_mon]). আর tm_year রাখত “১৯০০ থেকে কত বছর” — সেখান থেকেই getYear()-এর ২০২৬-এ 126 দেওয়ার মতো অদ্ভুত আচরণ, যা ২০০০ সালের সমস্যার (Y2K) এক ক্ষুদ্র উদাহরণ। অর্থাৎ JavaScript-এর ০-ভিত্তিক মাস আসলে ১৯৭০-এর দশকের C-র একটি সুবিধার সিদ্ধান্ত, যা Java হয়ে ওয়েবে এসেছে।

মজার ব্যাপার, Java নিজেই খুব দ্রুত বুঝেছিল নকশাটি দুর্বল। ১৯৯৭ সালের JDK 1.1-এ java.util.Date-এর বেশিরভাগ method deprecated করে Calendar আনা হয়; সেটিও জটিল হওয়ায় শেষ পর্যন্ত ২০১৪ সালে Java 8-এ পুরোপুরি নতুন, immutable java.time আসে (Joda-Time library-র অভিজ্ঞতা থেকে)। Java পুরোনো ক্লাস রেখে নতুনটি পাশে দিতে পেরেছে। JavaScript-এর সেই বিলাসিতা ছিল না: ওয়েবের অলিখিত নিয়ম “don't break the web” — কোটি কোটি পুরোনো পাতা getMonth()-এর ০-ভিত্তিক আচরণের ওপর নির্ভর করে। তাই এটি কখনো বদলানো যাবে না, শুধু পাশে নতুন API (Temporal) যোগ করা যায়।

এখান থেকে একটি গভীর শিক্ষা: একটি API একবার প্রকাশিত হয়ে লাখো মানুষ ব্যবহার শুরু করলে তার ভুলও “স্থায়ী বৈশিষ্ট্য” হয়ে যায়। দশ দিনের সিদ্ধান্ত ত্রিশ বছর পরেও প্রতিদিন নতুন বাগ তৈরি করছে — AI-এর লেখা কোডেও, কারণ AI সেই ত্রিশ বছরের কোড থেকেই শিখেছে।

মূল উপলব্ধি: পাবলিক API-র নকশা প্রায় অপরিবর্তনীয়; তাড়াহুড়োর সিদ্ধান্তের দাম দেয় পরের কয়েক প্রজন্মের প্রোগ্রামার। আর কোনো অদ্ভুত আচরণ দেখলে তার ইতিহাস খুঁজুন — প্রায়ই সেখানে একটি যুক্তিসঙ্গত (কিন্তু পুরোনো) কারণ লুকিয়ে থাকে।

প্র ০২ব্রাউজার বাংলায় “রবিবার, ১৫ মার্চ” বা “৩ দিন আগে” লিখতে পারে কীভাবে? ECMA-402 আর CLDR-এর পেছনের গল্প কী?

২০১০-এর আগে কোনো ওয়েবসাইটকে বাংলায় তারিখ দেখাতে হলে প্রোগ্রামার নিজে মাসের নাম, বারের নাম আর বাংলা অঙ্কের array লিখতেন — আর প্রতিটি ভাষার জন্য আলাদা করে। জার্মানে দশমিক বিন্দুর জায়গায় কমা, ভারতীয় উপমহাদেশে লাখ-কোটির কমা, আরবিতে ডান থেকে বাঁয়ে, জাপানিতে বছর-মাস-দিনের ক্রম — বড় কোম্পানিগুলো এসব নিয়ম নিয়ে বিশাল library বানাত, ছোটরা ইংরেজিতেই থেকে যেত।

এই নিয়মগুলো সংগ্রহের কাজটি শুরু করে Unicode Consortium, ২০০৩ সালে CLDR (Common Locale Data Repository) প্রকল্প দিয়ে। পৃথিবীর শত শত ভাষার জন্য স্বেচ্ছাসেবী আর কোম্পানির ভাষাবিদরা লেখেন: মাসের নাম কী, তারিখ কোন ক্রমে, “গতকাল” কীভাবে বলে, বহুবচনের কটি শ্রেণি, মুদ্রার চিহ্ন কোথায় বসে। এই ডেটা ব্যবহার করে IBM-এর শুরু করা open-source library ICU (International Components for Unicode) আসল ফরম্যাটিং করে। Android, iOS, Windows, Java — সবাই কোনো না কোনোভাবে CLDR ব্যবহার করে।

JavaScript-এ এই ক্ষমতা আনতে Ecma-তে আলাদা একটি মানদণ্ড লেখা হয় — ECMA-402, “ECMAScript Internationalization API”. এর প্রথম সংস্করণ প্রকাশিত হয় ২০১২ সালের ডিসেম্বরে, যাতে ছিল Intl.Collator, Intl.NumberFormat আর Intl.DateTimeFormat. পরের বছরগুলোতে যোগ হয় PluralRules, RelativeTimeFormat, ListFormat, Segmenter, DisplayNames — প্রতিটি TC39-এর একই stage প্রক্রিয়া পেরিয়ে। মানদণ্ড শুধু API-র আকার বলে; আসল ভাষার ডেটা ব্রাউজার নিজে সাথে আনে (সাধারণত ICU + CLDR).

এর একটি ব্যবহারিক দিক আছে। Node.js দীর্ঘদিন ডাউনলোডের আকার ছোট রাখতে শুধু ইংরেজির ডেটা (“small-icu”) নিয়ে আসত — তখন bn-BD দিলেও ইংরেজি আসত। Node 13 থেকে পুরো ICU ডিফল্টে আসে। আর এই পর্বেই দেখেছেন, Edge আর Node 20-এর CLDR সংস্করণ আলাদা বলে “জানুয়ারি/জানুয়ারী”-র বানান আলাদা হয়। অর্থাৎ ফরম্যাটের লেখা কোডের নয়, ডেটার অংশ — আর ডেটা হালনাগাদ হয়।

বাংলার জন্য এটি বড় ঘটনা: একটি লাইন new Intl.NumberFormat("bn-BD") লিখলেই লাখ-কোটির কমা আর বাংলা অঙ্ক আসে, কারণ কোনো একদিন কেউ CLDR-এ বাংলার নিয়ম যত্ন করে লিখেছিলেন। ভুল চোখে পড়লে CLDR-এর জরিপে যে কেউ সংশোধনের প্রস্তাব দিতে পারেন।

মূল উপলব্ধি: ভালো আন্তর্জাতিকীকরণ মানে কোডে ভাষার নিয়ম না লেখা, বরং যৌথভাবে রক্ষণাবেক্ষণ করা ডেটার ওপর নির্ভর করা। আপনার কাজ সঠিক locale আর জোন দেওয়া; বাকিটা পৃথিবীর হাজারো ভাষাবিদের সম্মিলিত শ্রম।

প্র ০৩Temporal API আসতে এত বছর লাগছে কেন? তারিখ আর সময় প্রোগ্রামিংয়ে এত কঠিন কেন?

Temporal প্রস্তাবের কাজ শুরু হয় ২০১৭ সালে, মূলত Moment.js-এর রক্ষণাবেক্ষণকারী আর Bloomberg ও Igalia-র প্রকৌশলীদের উদ্যোগে। ২০২১ সালে এটি Stage 3-এ পৌঁছায় — অর্থাৎ নকশা প্রায় চূড়ান্ত, এবার ব্রাউজারগুলো বাস্তবায়ন করুক। তবু পুরো ব্রাউজার-জগতে পৌঁছাতে আরও কয়েক বছর লেগেছে; Firefox ২০২৫ সালে প্রথম চালু করে, অন্যরা ধীরে ধীরে আসছে। একটি API-র জন্য এত সময় কেন?

কারণ সময় নিজেই কঠিন, আর কঠিনতা প্রযুক্তিগত নয়, মানবিক। এক — time zone রাজনৈতিক সিদ্ধান্ত। সরকার যেকোনো সময় নিয়ম বদলাতে পারে; বাংলাদেশ ২০০৯ সালে অল্প সময়ের নোটিশে DST চালু করেছিল, অনেক দেশ মাত্র কয়েক সপ্তাহ আগে ঘোষণা দেয়। IANA database বছরে কয়েকবার হালনাগাদ হয়, আর প্রতিটি ব্রাউজারকে সেটি পৌঁছে দিতে হয়। দুই — দিন সবসময় ২৪ ঘণ্টা নয়, মাস সমান নয়, বছরও সমান নয়। “এক মাস যোগ” কথাটির কোনো একক গাণিতিক অর্থ নেই — ৩১ জানুয়ারির পরে কী? তিন — ক্যালেন্ডার একটি নয়। বাংলাদেশেই তিনটি চলে: গ্রেগরীয়, বঙ্গাব্দ আর হিজরি। Temporal-কে ICU-র জানা নানা ক্যালেন্ডার সমর্থন করার মতো করে নকশা করতে হয়েছে।

চার — মানুষের প্রশ্ন অস্পষ্ট। “প্রতিদিন সকাল ৯টায় অ্যালার্ম” মানে কী, যখন DST-এর দিনে সকাল ২টা থেকে ৩টা পর্যন্ত সময় অস্তিত্বহীন, বা শেষের দিনে একই ঘণ্টা দুবার আসে? Temporal প্রতিটি ক্ষেত্রে স্পষ্ট নীতি (disambiguation) বেছে নিতে বাধ্য করে। পাঁচ — মানদণ্ডের নির্ভরতা। 2026-03-15T16:30+06:00[Asia/Dhaka]-এর মতো জোনের নামসহ string-এর কোনো আনুষ্ঠানিক মানদণ্ড ছিল না; Temporal দলকে IETF-এ কাজ করে ২০২৪ সালে RFC 9557 প্রকাশ করাতে হয়েছে। ছয় — আকার। Temporal-এ প্রায় এক ডজন টাইপ আর কয়েকশ method; প্রতিটি engine-কে সব নিয়ম হুবহু একইভাবে বাস্তবায়ন করতে হয়, হাজারো test পাস করে। বাস্তবায়নের সময় পাওয়া সমস্যায় Stage 3-এর পরেও কয়েকবার নকশা ছাঁটাই ও সংশোধন হয়েছে।

Temporal-এর মূল দর্শন হলো “ভিন্ন ধারণা, ভিন্ন টাইপ” — Instant (মুহূর্ত), PlainDate (শুধু তারিখ), PlainTime, ZonedDateTime, Duration. পুরোনো Date সব কিছু একটি টাইপে ঠেসে দিয়েছিল বলেই এত বাগ।

মূল উপলব্ধি: বাস্তব জগতের জটিলতা কোড দিয়ে মুছে ফেলা যায় না, শুধু স্পষ্ট করা যায়। ভালো API সেই জটিলতাকে টাইপ আর নামের মাধ্যমে চোখের সামনে আনে — যাতে ভুলটি লেখার মুহূর্তেই ধরা পড়ে।

প্র ০৪২০৩৮ সালের সমস্যা কী, আর JavaScript-এর Date কেন তাতে আক্রান্ত নয়? Date-এর সর্বোচ্চ তারিখ কত?

Unix আর C-র ঐতিহ্যে সময় রাখা হয় time_t-এ — ১৯৭০-০১-০১ UTC থেকে কত সেকেন্ড। বহু পুরোনো সিস্টেমে time_t একটি 32-bit signed integer, যার সর্বোচ্চ মান 231 − 1 = 2147483647. এই সেকেন্ড শেষ হয় ২০৩৮ সালের ১৯ জানুয়ারি, 03:14:07 UTC-এ। পরের সেকেন্ডে সংখ্যাটি ঘুরে সবচেয়ে ছোট ঋণাত্মক মানে চলে যায় — অর্থাৎ ঘড়ি হঠাৎ ১৯০১ সালের ডিসেম্বর দেখাবে। একে বলে “Year 2038 problem”, Y2K-র ছোট ভাই। (JavaScript-এ যাচাই: new Date(2 ** 31 * 1000).toISOString() দেয় "2038-01-19T03:14:08.000Z" — সীমা পেরোনোর প্রথম সেকেন্ড।)

আধুনিক 64-bit অপারেটিং সিস্টেমগুলো বহু আগেই 64-bit time_t-এ চলে গেছে; Linux কার্নেল 32-bit সিস্টেমের জন্যও সমাধান যোগ করেছে। কিন্তু ঝুঁকি থেকে যায় embedded যন্ত্রে — রাউটার, শিল্প-কারখানার নিয়ন্ত্রক, গাড়ির যন্ত্রাংশ, পুরোনো ডেটাবেসের কলাম আর ফাইল ফরম্যাটে, যেগুলো বছরের পর বছর হালনাগাদ ছাড়া চলে। ৩০ বছরের ঋণ বা বিমার মেয়াদ হিসাব করা সফটওয়্যার তো এখনই ২০৩৮ পার করা তারিখ নিয়ে কাজ করছে।

JavaScript এই ফাঁদে পড়ে না, কারণ Date-এর timestamp একটি সাধারণ number — 64-bit double (পর্ব ১০)। এটি 253 পর্যন্ত পূর্ণসংখ্যা হুবহু রাখে, অর্থাৎ মিলিসেকেন্ডে কয়েক লক্ষ বছর। তবু ECMAScript মানদণ্ড ইচ্ছা করে একটি সীমা টেনেছে: ১৯৭০ থেকে দুই দিকে ঠিক ১০ কোটি দিন, অর্থাৎ ±8.64 × 1015 মিলিসেকেন্ড। তাই new Date(8.64e15).toISOString() দেয় "+275760-09-13T00:00:00.000Z", আর সবচেয়ে পুরোনো new Date(-8.64e15) হলো "-271821-04-20T00:00:00.000Z". সীমার এক মিলিসেকেন্ড বাইরে (new Date(8.64e15 + 1)) পাওয়া যায় Invalid Date, getTime() → NaN. বছরটি ছয় অঙ্কের হলে ISO রূপে সামনে + চিহ্ন বসে — এটিও ISO 8601-এর “expanded year” নিয়ম।

তবে সাবধান: JavaScript নিরাপদ বলে আপনার পুরো সিস্টেম নিরাপদ নয়। সেকেন্ড আর মিলিসেকেন্ড গুলিয়ে ফেলা (Unix timestamp-কে ১০০০ দিয়ে গুণ না করা) খুব সাধারণ বাগ — তখন তারিখ আসে ১৯৭০ সালের জানুয়ারিতে। আবার JavaScript থেকে timestamp যদি কোনো 32-bit integer কলামে বা C-তে লেখা পুরোনো সার্ভিসে যায়, ২০৩৮-এর সমস্যা সেখানে ফিরে আসবে।

মূল উপলব্ধি: প্রতিটি সংখ্যার প্রতিনিধিত্বের একটি সীমা আছে — প্রশ্ন শুধু সেটি কত দূরে। সিস্টেমের দুর্বলতম অংশই সীমা ঠিক করে, তাই ডেটা যেখানে যেখানে যায়, সব জায়গার টাইপ আর একক মিলিয়ে দেখুন।

প্র ০৫Leap second কী, আর JavaScript-এর সময় কেন সেটি উপেক্ষা করে? “প্রতিটি দিন ঠিক ৮৬,৪০০ সেকেন্ড” — এই সরলীকরণের দাম কী?

পৃথিবীর ঘূর্ণন একদম নিয়মিত নয় — জোয়ার-ভাটা, ভূমিকম্প, বরফ গলা এর গতি সামান্য বদলায়। অন্যদিকে পারমাণবিক ঘড়ি (TAI) প্রায় নিখুঁত সমান তালে চলে। UTC এই দুইয়ের সমন্বয়: পারমাণবিক সেকেন্ডে চলে, কিন্তু পৃথিবীর ঘূর্ণন থেকে ০.৯ সেকেন্ডের বেশি সরে গেলে একটি বাড়তি সেকেন্ড ঢোকানো হয় — leap second. তখন ঘড়ি দেখায় 23:59:60. ১৯৭২ থেকে এ পর্যন্ত ২৭টি leap second যোগ হয়েছে; সর্বশেষটি ২০১৬ সালের ৩১ ডিসেম্বর।

JavaScript (এবং POSIX/Unix সময়) সিদ্ধান্ত নিয়েছে এগুলো উপেক্ষা করবে। ECMAScript মানদণ্ডে লেখা আছে: প্রতিটি দিন ঠিক 86,400,000 মিলিসেকেন্ড। তাই new Date("2016-12-31T23:59:60Z") বৈধ মুহূর্ত নয়, আর দুই তারিখের ব্যবধান হিসাবে leap second কখনো গোনা হয় না। ফলে ১৯৭০ থেকে আজ পর্যন্ত “আসল” পেরোনো সেকেন্ড timestamp-এর চেয়ে প্রায় ২৭ সেকেন্ড বেশি। বিনিময়ে পাওয়া যায় বিশাল সরলতা: যেকোনো তারিখ থেকে timestamp বের করা একটি সহজ সূত্র, ভবিষ্যতের leap second-এর তালিকা জানার দরকার নেই (যা আগে থেকে জানা যায়ও না — মাত্র কয়েক মাস আগে ঘোষণা হয়)।

কিন্তু যে সিস্টেম leap second সামলাতে যায়, সেখানে বিপদ। ২০১২ সালের ৩০ জুনের leap second-এ Linux কার্নেলের একটি বাগে বহু সার্ভার CPU ১০০% ব্যবহার করে আটকে যায়; Reddit, Mozilla-সহ বেশ কিছু সাইট সমস্যায় পড়ে। ২০১৭ সালের শুরুর leap second-এ Cloudflare-এর DNS সফটওয়্যারে সময় পিছিয়ে যাওয়ায় একটি ঋণাত্মক সংখ্যা ঢোকে, আর কিছু সার্ভিস বিঘ্নিত হয়। এজন্য Google, Amazon-এর মতো কোম্পানি “leap smear” কৌশল নেয় — বাড়তি সেকেন্ডটি কয়েক ঘণ্টা ধরে খুব সামান্য করে ছড়িয়ে দেয়, যাতে ঘড়ি কখনো 23:59:60 দেখায় না বা পেছনে যায় না।

এত ঝামেলার কারণে ২০২২ সালে আন্তর্জাতিক ওজন ও পরিমাপ সম্মেলন (CGPM) সিদ্ধান্ত নিয়েছে, ২০৩৫ সালের মধ্যে leap second প্রথা বন্ধ করা হবে — UTC আর পৃথিবীর ঘূর্ণনের পার্থক্য আরও বড় হতে দেওয়া হবে। অর্থাৎ JavaScript-এর “উপেক্ষা করো” সিদ্ধান্তের দিকেই পৃথিবী এগোচ্ছে। একটি শিক্ষা আরও আছে: Date.now() দিয়ে কোড কত সময় নিল মাপবেন না — সিস্টেমের ঘড়ি NTP সমন্বয়ে পেছনেও যেতে পারে। সময়কাল মাপতে performance.now(), যা একমুখী (monotonic) ঘড়ি।

মূল উপলব্ধি: প্রতিটি মডেল বাস্তবতার একটি সরলীকরণ। ভালো প্রকৌশলী জানেন কোন সরলীকরণ নিচ্ছেন, তার সীমা কোথায়, আর কোন কাজে সেটি যথেষ্ট নয় — যেমন উপগ্রহের কক্ষপথ বা বৈজ্ঞানিক মাপজোখে JavaScript-এর Date নয়, বিশেষ সময়-library লাগে।

অনুশীলন

বেশিরভাগ কাজ browser console-এ (F12) কয়েকটি লাইন লিখেই করা যায় — ফল আগে অনুমান করুন, তারপর মিলিয়ে নিন।

  1. জন্মদিনের বাগ: নিউ ইয়র্কে থাকা একজন ব্যবহারকারী প্রোফাইলে জন্মতারিখ "2000-10-25" দিলেন। অ্যাপ new Date("2000-10-25").getDate() দিয়ে দিন দেখায়। তিনি কী দেখবেন, কেন? দুটি সমাধান দিন।

    তিনি দেখবেন ২৪। শুধু-তারিখের string পড়া হয় UTC মধ্যরাত হিসেবে (2000-10-25T00:00Z), আর getDate() পড়ে স্থানীয় জোনে — নিউ ইয়র্কে তখন ২৪ অক্টোবর রাত ৮টা (EDT, −4). সমাধান ১: জন্মতারিখ মুহূর্ত নয়, ক্যালেন্ডারের তারিখ — string-টিকেই split("-") করে বছর-মাস-দিন হিসেবে রাখুন ও দেখান। সমাধান ২: Date রাখতেই হলে পড়ুন UTC method দিয়ে (getUTCDate() → 25), অথবা ফরম্যাট করুন timeZone: "UTC" দিয়ে। ঢাকায় (UTC+6) এই বাগ চোখে পড়ে না — UTC মধ্যরাত এখানে সকাল ৬টা, একই দিন। তাই ঢাকায় বসে টেস্ট করলে বাগটি লুকিয়ে থাকে।

  2. ফল অনুমান করুন: (ক) new Date(Date.UTC(2026, 11, 32)).toISOString().slice(0, 10) (খ) new Date(Date.UTC(2024, 2, 0)).getUTCDate() (গ) new Date("2026-03-15T10:30:00Z") === new Date("2026-03-15T10:30:00Z") (ঘ) new Date("2026-03-15T16:30:00+06:00").toISOString() (ঙ) new Date(0).toISOString()

    (ক) "2027-01-01" — মাস 11 মানে ডিসেম্বর, আর ৩২ ডিসেম্বর গড়িয়ে পরের বছরের ১ জানুয়ারি। (খ) 29 — মার্চের “০ তারিখ” মানে ফেব্রুয়ারির শেষ দিন, আর ২০২৪ লিপ ইয়ার। (গ) false — দুটি আলাদা object; মুহূর্ত মেলাতে getTime(). (ঘ) "2026-03-15T10:30:00.000Z" — ঢাকার বিকাল ৪:৩০ = UTC সকাল ১০:৩০; toISOString সবসময় UTC-তে লেখে। (ঙ) "1970-01-01T00:00:00.000Z" — epoch নিজেই।

  3. “আজকের অর্ডার” গণনা: সার্ভার (UTC-তে চলে) order.toISOString().slice(0, 10) === "2026-03-16" দিয়ে ঢাকার দোকানের “১৬ মার্চের অর্ডার” গুনছে। "2026-03-15T20:00:00Z"-এর অর্ডারটি কোন দিনে গোনা হবে, আর ঢাকার দোকানদার কোন দিন ধরবেন? ঠিক করে লিখুন।

    সার্ভার গুনবে ১৫ মার্চে, কারণ UTC তারিখ 2026-03-15. কিন্তু ঢাকায় মুহূর্তটি ১৬ মার্চ রাত ২টা — দোকানদারের কাছে এটি ১৬ মার্চের অর্ডার। ঢাকার রাত ১২টা থেকে সকাল ৬টার সব অর্ডার এভাবে আগের দিনে চলে যায়। সমাধান: দিন ঠিক করুন দোকানের জোনে — new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Dhaka" }).format(order) দেয় "2026-03-16". অথবা ঢাকার দিনের সীমা দুটি UTC মুহূর্তে বদলে নিন: ১৬ মার্চ ঢাকা = 2026-03-15T18:00:00Z থেকে 2026-03-16T18:00:00Z-এর আগে পর্যন্ত — ডেটাবেস query-তে এটিই দ্রুত ও সঠিক।

  4. AI অডিট: যেকোনো AI-কে বলুন “JavaScript-এ একটি ফাংশন লেখো যা একটি তারিখ নিয়ে ‘কত দিন আগে’ বাংলায় দেখায়।” তারপর চেকলিস্ট মিলিয়ে কোডটি পরীক্ষা করুন।

    সাধারণত যা দেখা যায়: ফাংশনের ভেতরে new Date() (টেস্ট করা যায় না — “এখন” argument হওয়া উচিত); Math.floor((now - date) / 86400000) (DST-এ এক দিন কম, আর গতকাল রাত ১১টার অর্ডার আজ সকালে “০ দিন আগে”); বাংলা শব্দ আর অঙ্ক হাতে লেখা ("দিন আগে" আর অঙ্ক বদলের নিজের array) — যেখানে new Intl.RelativeTimeFormat("bn", { numeric: "auto" }) নিজেই “গতকাল”, “৩ দিন আগে” দেয়। ভালো সংস্করণে: “এখন” argument, দুটি মুহূর্তকে ব্যবহারকারীর জোনে ক্যালেন্ডারের তারিখে এনে ব্যবধান, আর লেখার কাজ RelativeTimeFormat-এ। টেস্টে নির্দিষ্ট তারিখ দিয়ে “গতকাল”, “আজ” আর ৩০ দিনের বেশি (সপ্তাহ/মাসে বদল) পরীক্ষা করুন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি অর্ডারের সময় ব্রাউজার থেকে ডেটাবেস হয়ে আবার পাঠকের পর্দায় — ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ব্যবহারকারীর দেওয়া সময়কে জোনসহ একটি মুহূর্তে রূপান্তর
  2. মুহূর্তটি ISO 8601 UTC string হিসেবে JSON-এ পাঠানো
  3. ডেটাবেসে UTC মুহূর্ত হিসেবে সংরক্ষণ
  4. পড়ার সময় ISO string থেকে আবার Date তৈরি
  5. পাঠকের locale আর time zone দিয়ে Intl-এ ফরম্যাট করে দেখানো

JavaScript-এ new Date(2026, 1, 15) কোন তারিখ তৈরি করে?

  • ১৫ জানুয়ারি ২০২৬
  • ২ মার্চ ২০২৬
  • ১৫ ফেব্রুয়ারি ২০২৬, স্থানীয় সময়ে
  • ১৫ জানুয়ারি ২০২৬, UTC-তে

JavaScript-এর একটি Date object ভেতরে আসলে কী সংরক্ষণ করে?

  • বছর, মাস, দিন, ঘণ্টা আলাদা আলাদা ঘরে
  • ১৯৭০-০১-০১ UTC থেকে পেরোনো মিলিসেকেন্ডের একটি সংখ্যা
  • সময়ের সংখ্যা আর তৈরির সময়ের time zone-এর নাম
  • একটি ISO 8601 string

নিউ ইয়র্কের (UTC−4) একটি কম্পিউটারে new Date("2026-03-15").getDate() কী দেয়?

  • 15
  • NaN
  • 0
  • 14

একটি অ্যাপ সব ব্যবহারকারীকে ঢাকার সময়ে অর্ডারের সময় দেখাতে চায়। কোনটি সঠিক পদ্ধতি?

  • Intl.DateTimeFormat("bn-BD", { timeZone: "Asia/Dhaka" }) দিয়ে ফরম্যাট
  • getHours() আর getMinutes() জোড়া দিয়ে
  • স্থানীয় সময়ের সাথে ৬ ঘণ্টা যোগ করে
  • toISOString() দেখিয়ে

DST আছে এমন জোনে দুটি ক্যালেন্ডারের তারিখের মধ্যে কত দিন, তা নির্ভরযোগ্যভাবে বের করার উপায় কোনটি?

  • স্থানীয় মধ্যরাতের দুটি Date বিয়োগ করে 86400000 দিয়ে ভাগ, তারপর Math.floor
  • দুটির getDate() বিয়োগ করে
  • দুটি তারিখকেই UTC মধ্যরাতে বসিয়ে (Date.UTC) বিয়োগ করে 86400000 দিয়ে ভাগ
  • Intl.RelativeTimeFormat দিয়ে

new Intl.NumberFormat("bn-BD", { style: "currency", currency: "BDT" }).format(1234567.5)-এর ফল কোনটি?

  • ৳1,234,567.50
  • ১২,৩৪,৫৬৭.৫০৳
  • ১২৩৪৫৬৭.৫৳
  • BDT 12,34,567.50

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Date = একটি মুহূর্ত, ভেতরে epoch (১৯৭০-০১-০১ UTC) থেকে মিলিসেকেন্ড; তারিখ-ঘণ্টা দেখানোর রূপ, জোন আর locale-এর ওপর নির্ভর করে।
  2. String-এ সবসময় Z বা offset লিখুন: শুধু-তারিখ = UTC, offset-ছাড়া তারিখ-সময় = স্থানীয়; ISO-র বাইরের রূপ engine-এর অনুমান।
  3. মাস ০ থেকে, set-method object বদলায়, অস্তিত্বহীন তারিখ গড়িয়ে যায়; হিসাবে getUTC/Date.UTC, আর দিনের ব্যবধান ক্যালেন্ডারের তারিখে UTC-তে।
  4. দেখানোয় Intl: DateTimeFormat (timeZone-সহ), RelativeTimeFormat, NumberFormat (লাখ-কোটি, ৳), PluralRules, ListFormat, Collator.
  5. সংরক্ষণে UTC (ISO string), ক্যালেন্ডারের তারিখ তারিখ হিসেবে, ভবিষ্যতের স্থানীয় ঘটনায় জোনের নাম; লজিকে “এখন” argument হিসেবে। ভবিষ্যৎ Temporal, আজ দরকারে library.

পরের পর্বে: মডিউল ৩-এর সব টুকরো একসাথে — object, array, map/filter/reduce, Map, JSON আর আজকের Intl দিয়ে বানাব একটি খরচের খাতা: তারিখসহ খরচ লেখা, মাস ধরে ভাগ করা আর বাংলায় টাকার হিসাব দেখানো।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান