7 মিনিট

JavaScript সময় ফরম্যাটিং ও রূপান্তর: সাধারণ ভুলগুলো

জানুন কিভাবে JavaScript-এ সময় ফরম্যাট ও রূপান্তর করবেন যাতে অপ্রত্যাশিত ফলাফল না আসে: টাইমস্ট্যাম্প, ISO স্ট্রিং, টাইমজোন, DST, পার্সিং নিয়ম ও নির্ভরযোগ্য প্যাটার্ন।

JavaScript সময় ফরম্যাটিং ও রূপান্তর: সাধারণ ভুলগুলো

JavaScript সময় নিয়ে সাধারণত কোন ভুলগুলো ঘটে

JavaScript সময় বাগগুলো সচরাচর 'ঘড়িটা ভুল' হিসেবে দেখায় না। বরং তারা ছোট কিন্তু বিভ্রান্তিকর শিফট হিসেবে আসে: আপনার ল্যাপটপে একটি তারিখ ঠিক দেখায় কিন্তু সহকর্মীর মেশিনে ভুল, একটি API উত্তর যা ঠিক মনে হয় যতক্ষণ না সেটা অন্য টাইমজোনে রেন্ডার করা হয়, বা মৌসুমী সময় পরিবর্তনের সময় 'এক ঘন্টা' বা 'এক দিন' লাফ।

সবচেয়ে সাধারণ লক্ষণ

আপনি সাধারণত নিম্নলিখিত কোনটি (বা একাধিক) লক্ষ্য করবেন:

  • এক ঘন্টা লাফ: বিশেষ করে Daylight Saving Time-এ, বা যখন একটি মান অনিচ্ছাকৃতভাবে লোকাল টাইম এবং UTC-এর মধ্যে কনভার্ট হয়।
  • এক দিন লাফ: কেবল-তারিখ মান (যেমন '2025-12-23') টাইমজোন নির্ভর হয়ে আগের/পরের দিন হিসেবে প্রদর্শিত হতে পারে।
  • ভুল টাইমজোন: সময়গুলো সঠিক দেখালেও অফসেট (উদাহরণ: +02:00) আপনার প্রত্যাশার সাথে মিলছে না।
  • অসমঞ্জস্যপূর্ণ ফরম্যাটিং: 'Chrome-এ কাজ করে' কিন্তু Safari-তে আলাদা দেখায়, বা সার্ভার ও ব্রাউজার স্ট্রিং পার্সিংয়ে ভিন্ন আচরণ করে।

কেন এগুলো হয়: 'সময়' বিভিন্ন জিনিস বোঝায়

বিরাট সমস্যা হল 'সময়' শব্দটি বিভিন্ন ধারণা বোঝাতে পারে:

  • একটি মুহূর্ত (instant): বিশ্বের জন্য নির্দিষ্ট একটি মুহূর্ত (উদাহরণ: '2025-12-23T10:00:00Z')। লগ, ইভেন্ট, API স্টোরেজের জন্য সাধারণত यही দরকার।
  • একটি ক্যালেন্ডার তারিখ: টাইমজোন ছাড়া কেবল দিন (যেমন জন্মদিন বা ইনভয়েসের তারিখ)। এটিকে একটা মুহূর্ত হিসেবে扱লে দিন লিমিট পার হতে পারে।
  • ওয়াল-ক্লক টাইম (wall-clock time): 'বার্লিনে সকাল 9:00', যা টাইমজোন নিয়ম ও DST-এ নির্ভর করে।

JavaScript-এর বিল্ট-ইন Date এগুলোই কভার করার চেষ্টা করে, কিন্তু এটি মূলত একটি মুহূর্ত প্রতিনিধিত্ব করে এবং প্রদর্শনের সময় স্বয়ংক্রিয়ভাবে লোকাল নিয়মের দিকে ঠেলছে—এটাই অনিচ্ছাকৃত রূপান্তরকে সহজ করে।

এই আর্টিকেলে কি কভার করা হবে

এই গাইডটি বাস্তবমুখী: কিভাবে ব্রাউজার ও সার্ভার জুড়ে প্রেডিক্টেবল রূপান্তর করবেন, কোন ফরম্যাট নিরাপদ (যেমন ISO 8601) এবং সাধারণ ফাঁদগুলো কিভাবে শনাক্ত করবেন (সেকেন্ড বনাম মিলিসেকেন্ড, UTC বনাম লোকাল, পার্সিং পার্থক্য)। লক্ষ্য বেশি তত্ত্ব নয়—কম 'কেন এটা শিফট করছে' বিস্ময়।

টাইম ডেটা টাইপ: টাইমস্ট্যাম্প, Date, এবং স্ট্রিং

JavaScript সময় বাগও সাধারণত এমন প্রতিনিধিত্বগুলো মিলিয়ে ফেলার কারণে শুরু হয় যা একে অপরের মতো দেখলেও বাস্তবে ভিন্ন।

সবচেয়ে দেখা যায় এমন তিনটি প্রতিনিধিত্ব

1) এপক মিলিসেকেন্ড (সংখ্যা)

একটি সাদাসিধে সংখ্যা যেমন 1735689600000 সাধারণত '1970-01-01T00:00:00Z থেকে মিলিসেকেন্ড' হিসেবে থাকে। এটি একটি মুহূর্ত প্রতিনিধিত্ব করে এবং কোন ফরম্যাট বা টাইমজোন জোড়া থাকে না।

2) Date অবজেক্ট (একটি মুহূর্তের ওয়্র্যাপার)

Date একই ধরনের মুহূর্ত রাখে যতটা একটি টাইমস্ট্যাম্প রাখে। বিভ্রান্তিকর অংশ: যখন আপনি Date প্রিন্ট করেন, JavaScript পরিবেশের লোকাল নিয়ম অনুসারে ফরম্যাট করে যদি না আপনি অন্য কিছু চান।

3) ফরম্যাট করা স্ট্রিং (মানুষ-পাঠযোগ্য)

স্ট্রিংগুলো যেমন '2025-01-01', '01/01/2025 10:00', বা '2025-01-01T00:00:00Z' একটাই নয়। কিছু অনির্বচনীয় নয় (ISO 8601 Z সহ), কিছু লোকেল-নির্ভর, এবং কিছুতে টাইমজোন থাকে না।

'মুহূর্ত' বনাম 'মানুষের দেখার সময়'

  • মুহূর্ত: 'বিশেষ এই মূহুর্তটি বৈশ্বিকভাবে' (স্টোর করতে এপক মিলিসেকেন্ড বা UTC ISO স্ট্রিং ভাল)।
  • মানুষের দেখার সময়: 'ব্যবহারকারী কী দেখতে চাইবে' (লোকেল ও টাইমজোন নির্ভর)।

একই মুহূর্ত টাইমজোন অনুযায়ী আলাদা দেখাতে পারে:

const instant = new Date("2025-01-01T00:00:00Z");

instant.toLocaleString("en-US", { timeZone: "UTC" });
// "1/1/2025, 12:00:00 AM"

instant.toLocaleString("en-US", { timeZone: "America/Los_Angeles" });
// "12/31/2024, 4:00:00 PM" (আগের দিন)

একটি 'source of truth' বেছে নিন

একটি একক অভ্যন্তরীণ প্রতিনিধিত্ব (সাধারণত এপক মিলিসেকেন্ড বা UTC ISO 8601) বেছে নিন এবং পুরো অ্যাপ ও API জুড়ে সেটিই অনুসরণ করুন। Date বা ফরম্যাট স্ট্রিং-এ কনভার্ট করুন কেবল প্রান্তে: ইনপুট পার্সিং ও UI প্রদর্শনে।

টাইমস্ট্যাম্প: সেকেন্ড বনাম মিলিসেকেন্ড (মিশে যাওয়া সহজ)

'টাইমস্ট্যাম্প' সাধারণত অর্থ করে এপক সময় (Unix time): 1970-01-01 00:00:00 UTC থেকে কটা সময় পার হয়েছে। কষ্টকর অংশ: বিভিন্ন সিস্টেম বিভিন্ন ইউনিটে গণনা করে।

JavaScript-এর Date মূল বিভ্রান্তি কারণ এটি মিলিসেকেন্ড ব্যবহার করে। অনেক API, ডিবি, ও লগ সিস্টেম সেকেন্ড ব্যবহার করে।

রুল অফ থাম্ব

  • Unix টাইমস্ট্যাম্প (সেকেন্ড): 1704067200
  • JavaScript টাইমস্ট্যাম্প (মিলিসেকেন্ড): 1704067200000

একই মুহূর্ত, কিন্তু মিলিসেকেন্ড সংস্করণে তিনটি অতিরিক্ত সংখ্যা।

নিরাপদ রূপান্তর (সেকেন্ড ↔ মিলিসেকেন্ড)

স্পষ্ট গুণ/ভাগ ব্যবহার করুন যাতে ইউনিট বোঝা যায়:

// seconds -> Date
const seconds = 1704067200;
const d1 = new Date(seconds * 1000);

// milliseconds -> Date
const ms = 1704067200000;
const d2 = new Date(ms);

// Date -> seconds
const secondsOut = Math.floor(d2.getTime() / 1000);

// Date -> milliseconds
const msOut = d2.getTime();

ক্লাসিক বাগ: সেকেন্ড পাস করে Date()-এ

এটা দেখে সঠিক মনে হয়, কিন্তু যদি ts সেকেন্ডে থাকে তাহলে এটা ভুল:

const ts = 1704067200;      // seconds
const d = new Date(ts);     // WRONG: treated as milliseconds

ফলাফল হবে 1970 সালে একটি দিন, কারণ 1,704,067,200 মিলিসেকেন্ড কেবল এপকের প্রায় 19 দিনই হয়।

দ্রুত ভ্যালিডেশন ও ডিবাগ চেক

আপনি যদি নিশ্চিত না হন কোন ইউনিট আছে, হালকা গার্ডরেইল যোগ করুন:

function asDateFromUnknownEpoch(x) {
  // crude heuristic: seconds are ~1e9-1e10, milliseconds are ~1e12-1e13
  if (x < 1e11) return new Date(x * 1000); // assume seconds
  return new Date(x);                      // assume milliseconds
}

const input = Number(valueFromApi);
console.log({ input, digits: String(Math.trunc(input)).length });
console.log('as ISO:', asDateFromUnknownEpoch(input).toISOString());

যদি 'digits' ~10 হয়, সম্ভবত সেকেন্ড; যদি ~13 হয়, সম্ভবত মিলিসেকেন্ড। ডিবাগিং-এ toISOString() প্রিন্ট করুন: এটি অস্পষ্ট নয় এবং ইউনিট ভুল সহজে ধরতে সাহায্য করে।

লোকাল সময় বনাম UTC: কেন আউটপুট শিফট হয়

JavaScript-এর Date বিভ্রান্তিকর কারণ এটি সংরক্ষণ করে একটি একক মুহূর্ত কিন্তু সেটি উপস্থাপন করতে পারে বিভিন্ন টাইমজোনে।

অভ্যন্তরীণভাবে, Date মূলত 'Unix epoch থেকে মিলিসেকেন্ড'। এই সংখ্যা একটি UTC মুহূর্ত নির্দেশ করে। শিফট ঘটে যখন আপনি JavaScript-কে সেই মুহূর্তকে লোকাল সময়ে (কম্পিউটার/সার্ভারের সেটিংস অনুযায়ী) বা UTC-তে ফরম্যাট করতে বলেন।

লোকাল গেটার বনাম UTC গেটার

অনেক Date API-র লোকাল এবং UTC ভ্যারিয়েন্ট থাকে। একই মুহূর্তের জন্য তারা আলাদা মান রিটার্ন করে:

const d = new Date('2025-01-01T00:30:00Z');

d.getHours();      // hour in *local* time zone
d.getUTCHours();   // hour in UTC

d.toString();      // local time string
d.toISOString();   // UTC (always ends with Z)

যদি আপনার মেশিন নিউ ইয়র্ক (UTC-5) এ থাকে, ঐ UTC সময় লোকালভাবে পূর্ববর্তী দিনে '19:30' হিসেবে দেখাতে পারে। সার্ভারে যদি UTC সেট থাকে, এটি '00:30' হিসেবে দেখাবে। একই মুহূর্ত, ভিন্ন প্রদর্শন।

কেন লগগুলি 'ভুল' দেখায়

লগ প্রায়ই Date#toString() ব্যবহার করে বা ইমপ্লিসিটলি Date ইন্টারপোলেট করে, যা পরিবেশের লোকাল টাইমজোন ব্যবহার করে। এর মানে একই কোড ল্যাপটপ, CI ও প্রোডাকশনে ভিন্ন টাইমস্ট্যাম্প প্রিন্ট করবে যদি টাইমজোন আলাদা।

প্রাকটিক্যাল দিকনির্দেশনা

স্টোর ও ট্রান্সমিট করুন UTC হিসেবে (উদাহরণ: এপক মিলিসেকেন্ড বা ISO 8601 toISOString()), এবং কেবল প্রদর্শনের সময় ব্যবহারকারীর লোকালে কনভার্ট করুন:

  • API-র জন্য: toISOString() বা এপক মিলিসেকেন্ড পাস করা বেশি ভাল
  • UI-র জন্য: Intl.DateTimeFormat ব্যবহার করে ব্যবহারকারীর টাইমজোনে ফরম্যাট করুন

যদি দ্রুত অ্যাপ বানাচ্ছেন, তাহলে আপনার জেনারেট করা API কন্ট্রাক্টে এটি শুরুর দিকে স্পষ্টভাবে ধারণা করে রাখুন: ফিল্ডের নাম পরিষ্কার রাখুন (createdAtMs, createdAtIso) এবং সার্ভার (Go + PostgreSQL) ও ক্লায়েন্ট (React) একই অর্থে ফিল্ডগুলো বোঝে।

ISO 8601 স্ট্রিং: API-গুলোর জন্য সবচেয়ে নিরাপদ ফরম্যাট

ব্রাউজার, সার্ভার এবং ডেটাবেসের মধ্যে তারিখ/সময় পাঠাতে হলে ISO 8601 স্ট্রিং সাধারণত সবচেয়ে নিরাপদ ডিফল্ট। এগুলো স্পষ্ট, ব্যাপকভাবে সাপোর্টেড এবং টাইমজোন তথ্য বহন করে।

স্পষ্ট UTC বা স্পষ্ট অফসেট ব্যবহার করুন

দুইটি ভালো বিনিময় ফরম্যাট:

  • UTC টাইম (যখন লোকাল জোন প্রাসঙ্গিক নয়): 2025-03-04T12:30:00Z
  • লোকাল টাইম সহ অফসেট (যখন লোকাল ক্লক টাইম গুরুত্বপূর্ণ): 2025-03-04T12:30:00+02:00

'Z' মানে কি?

Z হলো 'Zulu time'—আরেক নাম UTC-র জন্য। তাই 2025-03-04T12:30:00Z হলো 'UTC-এ 12:30'।

কবে +02:00 মতো অফসেট গুরুত্বপূর্ণ?

যখন ইভেন্টটি লোকাল টাইম প্রসঙ্গে বন্ধনযুক্ত (অ্যাপয়েন্টমেন্ট, বুকিং, দোকানের খোলার সময়) তখন অফসেট জরুরি। 2025-03-04T12:30:00+02:00 UTC-র থেকে দুই ঘন্টা এগিয়ে থাকা একটি মুহূর্ত নির্দেশ করে এবং এটি 2025-03-04T12:30:00Z-এর সমান নয়।

অস্পষ্ট তারিখ স্ট্রিং এড়ান

'03/04/2025' এর মত স্ট্রিং ফাঁদ: এটা মার্চ 4 নাকি এপ্রিল 3? বিভিন্ন লোকেল ও পরিবেশ আলাদা ভাবে ব্যাখ্যা করে। 2025-03-04 (ISO তারিখ) বা পূর্ণ ISO datetime ব্যবহার করুন।

নিরাপদ রাউন্ড-ট্রিপ (স্ট্রিং → Date → স্ট্রিং)

const iso = '2025-03-04T12:30:00Z';
const d = new Date(iso);
const back = d.toISOString();

console.log(iso);  // 2025-03-04T12:30:00Z
console.log(back); // 2025-03-04T12:30:00.000Z

এই 'রাউন্ড-ট্রিপ' আচরণ API-গুলোর জন্য প্রত্যাশিত—সুস্পষ্ট, পূর্বানুমেয় এবং টাইমজোন-সচেতন।

পার্সিং ফাঁদ: Date.parse এবং ব্রাউজার পার্থক্য

সোর্স কোড আপনার নিয়ন্ত্রণে রাখুন
রিভিউ, অডিট বা কাস্টম টুলিং-এর জন্য সোর্স কোড এক্সপোর্ট করে পুরো নিয়ন্ত্রণ রাখুন।

Date.parse() ব্যবহার করা সুবিধাজনক লাগতে পারে: একটি স্ট্রিং দিন, একটি টাইমস্ট্যাম্প পান। সমস্যা হচ্ছে ISO 8601 ছাড়া অন্য যে কোনও ফরম্যাটের জন্য পার্সিং ব্রাউজারের হিউরিস্টিক্সে নির্ভর করে। সেই হিউরিস্টিক্স ইঞ্জিন ও সংস্করণ অনুসারে ভিন্ন হয়েছে, যার ফলে একই ইনপুট বিভিন্ন স্থানে ভিন্নভাবে পার্স হতে পারে (বা পার্স না-ও হতে পারে)।

কেন Date.parse() পরিবর্তনশীল হতে পারে

জাভাস্ক্রিপ্ট শুধুমাত্র ISO 8601 স্টাইল স্ট্রিং-এর জন্য পার্সিংকে নির্দিষ্ট করেছে (এবং তাও টাইমজোনের মতো বিবরণে কিছু টার্মস আছে)। 'বন্ধুবৎসল' ফরম্যাট যেমন '03/04/2025', 'March 4, 2025', বা '2025-3-4'—ব্রাউজারগুলি:

  • মাস/দিন বনাম দিন/মাস লোকেল অনুমান অনুযায়ী ব্যাখ্যা করতে পারে
  • টাইমজোন অনুপস্থিত থাকলে এক ইঞ্জিন লোকাল টাইম ধরে নিতে পারে, অন্যটি অগ্রাহ্য করতে পারে
  • সামান্য খারাপ ফরম্যাটকে 'প্রায় যথেষ্ট' ধরে নেবে… যতক্ষণ না তা নয়

আপনি যদি নির্দিষ্ট স্ট্রিং শেপ অনুমান করতে না পারেন, তাহলে ফলাফল পূর্বানুমেয় হবে না।

অভাবনীয় কেস: YYYY-MM-DD

একটি সাধারণ ফাঁদ হলো সরল তারিখ ফর্ম 'YYYY-MM-DD' (উদাহরণ: '2025-01-15')। অনেক ডেভেলপার আশা করেন এটি লোকাল মিদনাইট হিসেবে ব্যাখ্যা হবে। বাস্তবে, কিছু পরিবেশ এই ফর্মকে UTC মিদনাইট হিসেবে ব্যাখ্যা করে।

এই পার্থক্য গুরুত্বপূর্ণ: UTC মিদনাইট লোকাল টাইমে কনভার্ট করলে নেতিবাচক অফসেট জোনে (উদাহরণ আমেরিকাস) এটি আগের দিন হয়ে যেতে পারে বা ঘন্টা শিফট হতে পারে। এটি 'কেন আমার তারিখ এক দিন পিছিয়ে?' বাগের সাধারণ কারণ।

পার্সিং চেকলিস্ট: ইউজার ইনপুট বনাম সার্ভার ইনপুট

সার্ভার/API ইনপুটের জন্য:

  • স্পষ্ট টাইমজোনসহ পূর্ণ ISO 8601 বাঞ্ছনীয়, উদাহরণ: 2025-01-15T13:45:00Z বা 2025-01-15T13:45:00+02:00
  • কেবল-তারিখ মানকে একটি 'মুহূর্ত' হিসেবে নয়, ডাটা হিসেবে বিবেচনা করুন। যদি এটি জন্মদিন বা ডিউ-ডেট হয়, এটিকে কেবল স্ট্রিং (YYYY-MM-DD) হিসেবে রাখুন এবং টাইমজোন সংজ্ঞায়িত না করলে Date-এ কনভার্ট করবেন না।

ব্যবহারকারীর ইনপুটের জন্য:

  • '03/04/2025' মত অস্পষ্ট ফরম্যাট গ্রহণ করবেন না যদি না UI মর্মে তা নিশ্চিত না করা হয়।
  • নিয়ন্ত্রিত ইনপুট (date picker) ব্যবহার করা ভাল যাতে জানা যায় ফরম্যাট কী হবে।
  • যদি ফ্রি-টেক্সট পার্স করতে হয়, তাহলে গ্রহণযোগ্য ফরম্যাটগুলি আগেই নির্দিষ্ট ও প্রয়োগ করুন।

হিউরিস্টিক্স নয়—স্পষ্ট নিয়ম ব্যবহার করুন

Date.parse()-কে 'ফিগার করে নিতে' দেবেন না; নিচের কোনো প্যাটার্ন বেছে নিন:

  • সার্ভার থেকে কেবল ISO 8601 নিন; অন্য সব কিছু প্রত্যাখ্যান করুন
  • পরিচিত ফরম্যাট ম্যানুয়ালি পার্স করুন (স্ট্রিং ভাগ করে new Date(year, monthIndex, day) ব্যবহার করে লোকাল ডেট তৈরি করুন)
  • সঠিক মুহূর্তের জন্য টাইমস্ট্যাম্প (এপক মিলিসেকেন্ড) স্টোর ও ট্রান্সমিট করুন, এবং পরে প্রদর্শনের জন্য ফরম্যাট করুন

সময়-সংবেদনশীল ডাটা হলে 'আমার মেশিনে পার্স হচ্ছে' যথেষ্ট নয়—আপনার পার্সিং নিয়ম স্পষ্ট ও ধারাবাহিক হতে হবে।

মানুষের জন্য ফরম্যাটিং: Intl.DateTimeFormat

যদি লক্ষ্য হলো 'ব্যবহারকারীদের প্রত্যাশা অনুযায়ী একটি তারিখ/সময় দেখানো', তাহলে JavaScript-এ সেরা টুল হলো Intl.DateTimeFormat। এটি ব্যবহারকারীর লোকেল নিয়ম অনুসারে (অর্ডার, সেপারেটর, মাসের নাম) আউটপুট দেয় এবং ম্যানুয়ালি স্ট্রিং জোড়ার ঝুঁকিকে কমায়।

কেন এটি ম্যানুয়াল স্ট্রিং বিল্ডিংয়ের চেয়ে ভাল

ম্যানুয়াল ফরম্যাটিং প্রায়শই US-স্টাইল ধরে নেয়, লিডিং জিরো ভুলে যায়, বা 24/12-ঘন্টার বিভ্রান্তি সৃষ্টি করে। Intl.DateTimeFormat স্পষ্ট করে দেয় কোন টাইমজোনে প্রদর্শন করা হবে—বিশেষত গুরুত্বপূর্ণ যখন ডেটা UTC-তে স্টোর করা থাকে কিন্তু UI-তে লোকাল টাইম দেখানো দরকার।

সাধারণ অপশন যা আপনি বাস্তবে ব্যবহার করবেন

'ভালোভাবে ফরম্যাট' করার জন্য dateStyletimeStyle সহজ এবং কার্যকর:

const d = new Date('2025-01-05T16:30:00Z');

// User's locale + user's local time zone
console.log(new Intl.DateTimeFormat(undefined, {
  dateStyle: 'medium',
  timeStyle: 'short'
}).format(d));

// Force a specific time zone (great for event times)
console.log(new Intl.DateTimeFormat('en-GB', {
  dateStyle: 'full',
  timeStyle: 'short',
  timeZone: 'UTC'
}).format(d));

আপনি যদি নির্দিষ্ট hour cycle চান (যেমন সেটিং-এ 24/12 ঘন্টার স্যুইচ), তখন hour12 ব্যবহার করুন:

console.log(new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: '2-digit',
  hour12: true
}).format(d));

একটি বাস্তব UI প্যাটার্ন

প্রতিটি UI টাইপের জন্য (মেসেজ টাইম, লগ এন্ট্রি, ইভেন্ট স্টার্ট) একটি ফরম্যাট ফাংশন বেছে নিন এবং timeZone সিদ্ধান্তটি ইচ্ছাকৃত রাখুন:

  • 'আমার জন্য কখন এটা ঘটেছিল'–এ লোকাল টাইম ব্যবহার করুন
  • অডিট লগ, সার্ভার ইভেন্ট, বা ক্রস-টিম সমন্বয়ের জন্য স্থির জোন (প্রায়শই UTC) ব্যবহার করুন

এতে আপনি কনসিস্টেন্ট, লোকেল-ফ্রেন্ডলি আউটপুট পাবেন এবং ঝুঁকিপূর্ণ কাস্টম ফরম্যাট স্ট্রিং বজায় রাখতে হবে না।

Daylight Saving Time: লুকানো এক-ঘণ্টার বাগ

অ্যাপ জুড়ে টাইমস্ট্যাম্প স্ট্যান্ডার্ড করুন
সেকেন্ড বনাম মিলিসেকেন্ড বিষয়ক শেয়ারড হেল্পার তৈরি করুন যাতে প্রতিটি সার্ভিস একই ইউনিট ব্যবহার করে।

Daylight Saving Time (DST) হল যখন একটি টাইমজোন নির্দিষ্ট তারিখে UTC অফসেট পরিবর্তন করে (সাধারণত এক ঘন্টা)। জটিল অংশ: DST কেবল অফসেট পরিবর্তন করে না—কিছু লোকাল টাইমসের অস্তিত্বই বদলে দেয়।

অনুপস্থিত ও ডুপ্লিকেট ওয়াল-ক্লক টাইমস

ক্লক যখন 'spring forward' করে, কিছু লোকাল টাইম ঘটেই না। উদাহরণ: অনেক এলাকায় ঘড়ি 01:59 থেকে 03:00 এ লাফ, তাই 02:30 লোকাল সময় হলো 'মিসিং'

ক্লক যখন 'fall back' করে, কিছু লোকাল টাইম দুইবার ঘটে। উদাহরণ: 01:30 শিফটের আগে ও পরে দুইবার ঘটতে পারে, তাই একই ওয়াল-ক্লক টাইম দুটি ভিন্ন মুহূর্ত নির্দেশ করতে পারে।

24 ঘন্টা যোগ করা বনাম 'একই লোকাল সময় পরের দিন'

DST বর্ডারের আশেপাশে এগুলো সমান নয়:

  • 24 ঘন্টা যোগ করা: 'নির্দিষ্টভাবে 246060 সেকেন্ড পরে'
  • একই লোকাল সময় পরের দিন: 'টনিভারেই কালকে একই লোকাল 9:00'

যদি আজ DST শুরু করে, 'আগামীকাল একই লোকাল সময় 9:00' হতে পারে কেবল 23 ঘন্টা দূরে; যদি DST শেষ করে, এটি হতে পারে 25 ঘন্টা দূরে।

// Scenario: schedule 'same local time tomorrow'
const d = new Date(2025, 2, 8, 9, 0); // Mar 8, 9:00 local

const plus24h = new Date(d.getTime() + 24 * 60 * 60 * 1000);
const nextDaySameLocal = new Date(d);
nextDaySameLocal.setDate(d.getDate() + 1);

// Around DST, plus24h and nextDaySameLocal can differ by 1 hour.

কেন setHours চমক দিতে পারে

আপনি যদি এমন কিছু করেন: date.setHours(2, 30, 0, 0) একটি 'spring forward' দিনে, JavaScript এটি একটি ভিন্ন বৈধ সময়ে নরমালাইজ করতে পারে (প্রায়ই 03:30), কারণ 02:30 লোকাল সময়ে বিদ্যমান নয়।

নিরাপদ পদ্ধতি

  • ক্যালকুলেশন UTC-এ করুন যদি উদ্দেশ্য থাকে elapsed time-এ (দৈর্ঘ্য): এপক মিলিসেকেন্ড ও UTC মেথড ব্যবহার করুন।
  • লোকাল শিডিউলিং এর জন্য স্পষ্ট থাকুন: 'আগামীকাল একই লোকাল 9:00' ক্যালেন্ডার অপারেশন (setDate) ব্যবহার করে করুন পরিবর্তে মিলিসেকেন্ড যোগ করার।
  • API-র মাধ্যমে সময় বিনিময় করলে ISO 8601 সহ অফসেট বা Z পাস করুন যাতে মুহূর্ত অস্পষ্ট না থাকে।

ডিউরেশন বনাম ডেট: টাইমার হিসেবে Date ব্যবহার করবেন না

সাধারণ বাগ হলো Date ব্যবহার করে এমন কিছু উপস্থাপন করা যা ক্যালেন্ডার মুহূর্ত নয়।

টাইমস্ট্যাম্প উত্তর দেয় 'এটা কবে ঘটল?' (উদাহরণ: '2025-12-23T10:00:00Z')। ডিউরেশন উত্তর দেয় 'কত সময়?' (যেমন '3 মিনিট 12 সেকেন্ড')। এগুলো আলাদা ধারণা, এবং মিশালে বিভ্রান্তি ও অপ্রত্যাশিত টাইমজোন/DST প্রভাব আসে।

কেন Date ডিউরেশনের জন্য ভুল টুল

Date সবসময় একটি পয়েন্ট অন টাইমলাইনে স্টোর করে এপকের আপেক্ষিক। যদি আপনি '90 সেকেন্ড' একটি Date হিসেবে রাখেন, আপনি প্রকৃতপক্ষে '1970-01-01 plus 90 seconds' স্টোর করছেন কোনো নির্দিষ্ট টাইমজোনে। ফরম্যাট করলে এটি হঠাৎ '01:01:30' দেখাতে পারে, এক ঘন্টা শিফট পেতে পারে, বা এমন একটি দিন দেখাতে পারে যা আপনার উদ্দেশ্য ছিল না।

ডিউরেশনের জন্য বেছে নিন সাদাসিধে সংখ্যা:

  • ডিউরেশনগুলো সেকেন্ড বা মিলিসেকেন্ড হিসেবে রাখুন (একটি ইউনিট চয়ন করুন ও টানা রাখুন)
  • গাণিতিক অপারেশন সংখ্যা দিয়ে করুন
  • কেবল শেষে প্রদর্শনের সময় স্ট্রিং এ রূপান্তর করুন

সেকেন্ডকে HH:mm:ss-এ রূপান্তর

গননা টাইমার বা মিডিয়া দৈর্ঘ্যের জন্য একটি সহজ ফরম্যাটার:

function formatHMS(totalSeconds) {
  const s = Math.max(0, Math.floor(totalSeconds));
  const hh = String(Math.floor(s / 3600)).padStart(2, "0");
  const mm = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
  const ss = String(s % 60).padStart(2, "0");
  return `${hh}:${mm}:${ss}`;
}

formatHMS(75);    // "00:01:15" (countdown timer)
formatHMS(5423);  // "01:30:23" (media duration)

যদি মিনিট থেকে কনভার্ট করেন, আগে গুণ করুন (minutes * 60) এবং রেন্ডার করা পর্যন্ত মানটি সংখ্যায় রাখুন।

তুলনা, সোর্টিং এবং রেঞ্জগুলো নিঃসন্দেহভাবে করা

JavaScript-এ সময় তুলনা করার সময় সবচেয়ে নিরাপদ হলো সংখ্যার বিরুদ্ধে সংখ্যা তুলনা করা, না ফরম্যাট করা টেক্সটের। একটি Date অবজেক্ট মূলত একটি এপক মিলিসেকেন্ড সংখ্যার র‍্যাপার, তাই তুলনা করলে ইচ্ছাকৃতভাবে 'নাম্বার বনাম নাম্বার' হওয়া উচিত।

নিরাপদ তুলনা (টাইমস্ট্যাম্প জিতে)

ভরসাযোগ্যভাবে তুলনা করতে getTime() (বা Date.valueOf()) ব্যবহার করুন যা একই সংখ্যা রিটার্ন করে:

const a = new Date('2025-01-10T12:00:00Z');
const b = new Date('2025-01-10T12:00:01Z');

if (a.getTime() < b.getTime()) {
  // a is earlier
}

// Also works:
if (+a < +b) {
  // unary + calls valueOf()
}

ফরম্যাট করা স্ট্রিং যেমন '1/10/2025, 12:00 PM' তুলনা করা এড়ান—সেগুলো লোকেল-নির্ভর এবং সঠিকভাবে সোর্ট করবে না। একমাত্র ব্যতিক্রম হলো একই ফরম্যাটে থাকা ISO 8601 স্ট্রিং (উদাহরণ: সবই '...Z')—সেগুলো লেক্সিকোগ্রাফিকালি সোর্টেবল।

সোর্টিং ও রেঞ্জ ফিল্টার

টাইম দিয়ে সোর্ট করা সহজ যদি আপনি এপক মিলিসেকেন্ড দিয়ে সোর্ট করেন:

items.sort((x, y) => new Date(x.createdAt).getTime() - new Date(y.createdAt).getTime());

রেঞ্জের মধ্যে ফিল্টার করাও একই ধারণা:

const start = new Date('2025-01-01T00:00:00Z').getTime();
const end   = new Date('2025-02-01T00:00:00Z').getTime();

const inRange = items.filter(i => {
  const t = new Date(i.createdAt).getTime();
  return t >= start && t < end;
});

'দিনের শুরু/শেষ' (লোকাল বনাম UTC)

'দিনের শুরু' নির্ভর করে আপনি লোকাল সময় বোঝাতে চান নাকি UTC:

// Local start/end of day
const d = new Date(2025, 0, 10); // Jan 10 in local time
const localStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
const localEnd   = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);

// UTC start/end of day
const utcStart = new Date(Date.UTC(2025, 0, 10, 0, 0, 0, 0));
const utcEnd   = new Date(Date.UTC(2025, 0, 10, 23, 59, 59, 999));

একটি সংজ্ঞা আগে বেছে নিন এবং আপনার তুলনা ও রেঞ্জ লজিকে সেটি ধারাবাহিকভাবে অনুসরণ করুন।

ডিবাগ চেকলিস্ট: কিভাবে একটি টাইম রূপান্তর বাগ নির্ণয় করবেন

মোবাইল শিডিউলিং UI তৈরি করুন
স্থানীয় সময়গুলো স্পষ্ট টাইমজোনসহ দেখায় এমন একটি Flutter শিডিউলিং স্ক্রিন তৈরি করুন।

টাইম বাগগুলো র‍্যান্ডম মনে হয় যতক্ষণ না আপনি নির্দিষ্ট করেন 'আপনার কাছে কি আছে' (টাইমস্ট্যাম্প? স্ট্রিং? Date?) এবং 'কোথায় শিফট আসছে' (পার্সিং, টাইমজোন কনভার্শন, ফরম্যাটিং)।

1) একই মুহূর্তের 'তিনটি ভিউ' ক্যাপচার করুন

প্রথমে একই মান তিনভাবে লগ করুন। এটি দ্রুত জানিয়ে দেয় সমস্যা সেকেন্ড বনাম মিলিসেকেন্ড, লোকাল বনাম UTC, বা স্ট্রিং পার্সিং কিনা।

console.log('raw input:', input);

const d = new Date(input);
console.log('toISOString (UTC):', d.toISOString());
console.log('toString (local):', d.toString());
console.log('timezone offset (min):', d.getTimezoneOffset());

খেয়াল রাখার বিষয়:

  • যদি toISOString() অত্যন্ত ভুল (উদাহরণ: বছর 1970 বা অনেক দূরের ভবিষ্যৎ), সন্দেহ করুন সেকেন্ড বনাম মিলিসেকেন্ড
  • যদি toISOString() ঠিক দেখায় কিন্তু toString() শিফট করে, আপনি লোকাল টাইমজোন প্রদর্শন সমস্যার সম্মুখীন
  • যদি getTimezoneOffset() তারিখ অনুযায়ী পরিবর্তিত হয়, আপনি DST অতিক্রম করছেন

2) পরিবেশ ভেরিফাই করুন: টাইমজোন ও লোকেল

অনেক 'আমার মেশিনে কাজ করছে' রিপোর্ট কেবল বিভিন্ন পরিবেশ ডিফল্টের কারণ।

  • ব্রাউজার: OS টাইমজোন সেটিং ও ব্রাউজার ভাষা চেক করুন, তারপর লগ করুন:
console.log(Intl.DateTimeFormat().resolvedOptions());
  • Node.js / সার্ভার: প্রোসেস টাইমজোন নিশ্চিত করুন:
console.log('TZ:', process.env.TZ);
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);

যদি আপনার সার্ভার UTC-এ চলে কিন্তু আপনার ল্যাপটপ লোকাল জোনে থাকে, ফরম্যাট করা আউটপুট আলাদা হবে যদি না আপনি স্পষ্টভাবে timeZone নির্দিষ্ট করেন।

3) যেখানে সময় সবচেয়ে ভেঙে পড়ে সেখানে টেস্ট যোগ করুন

DST বর্ডার ও 'এজ' টাইমগুলোর চারপাশে ইউনিট টেস্ট তৈরি করুন:

  • লক্ষ্য অঞ্চলগুলোর DST সুইচের এক ঘণ্টা আগে ও পরে
  • মাস/বছর শেষ, এবং 23:3000:30 ক্রসোভার
  • যদি আপনার প্রোডাক্ট একাধিক টাইমজোন সাপোর্ট করে, বিভিন্ন জোনে টেস্ট

যদি দ্রুত iteration করছেন, এই টেস্টগুলো আপনার স্ক্যাফোল্ডিং-এ রাখুন। উদাহরণস্বরূপ, React + Go অ্যাপ জেনারেট করলে একটি ছোট 'টাইম কন্ট্রাক্ট' টেস্টসুট আগে থেকেই যোগ করুন (API পে-লোড উদাহরণ + পার্স/ফরম্যাট অ্যাসার্শন) যাতে রিগ্রেশন প্রোডাকশনে পৌঁছার আগে ধরা পড়ে।

প্রি-শিপ দ্রুত চেকলিস্ট

  • ইনপুটগুলোর কনট্রাক্ট পরিষ্কার: এপক মিলিসেকেন্ড বা ISO 8601 সহ অফসেট
  • '2025-03-02 10:00' মত অস্পষ্ট স্ট্রিং নেই
  • ফরম্যাটিং সর্বদা locale এবং (প্রয়োজনে) timeZone নির্দিষ্ট করে
  • টেস্টগুলো আপনার টার্গেট অঞ্চলের DST বর্ডার কভার করে

নির্ভরযোগ্য সময় হ্যান্ডলিংয়ের সুপারিশকৃত প্যাটার্ন

JavaScript-এ নির্ভরযোগ্য সময় হ্যান্ডলিং বেশিরভাগই একটি 'source of truth' বেছে নেওয়া এবং স্টোরেজ থেকে প্রদর্শন পর্যন্ত ধারাবাহিক থাকা।

একটি সাধারণ সেরা প্র্যাকটিস সেট

UTC তে স্টোর ও ক্যালকুলেট করুন। ব্যবহারকারীর লোকাল সময়কে কেবল উপস্থাপনা বিবরণ হিসেবে বিবেচনা করুন।

সিস্টেমগুলোর মধ্যে তারিখ ট্রান্সমিট করুন ISO 8601 স্ট্রিং হিসেবে স্পষ্ট অফসেট সহ (পছন্দসইভাবে Z)। যদি আপনাকে সংখ্যাস্বরূপ এপক পাঠাতে হয়, ইউনিট ডকুমেন্ট করুন এবং ধারাবাহিক রাখুন (JS-এ মিলিসেকেন্ড কমন)।

মানুষের জন্য ফরম্যাটিং-এ Intl.DateTimeFormat (বা toLocaleString) ব্যবহার করুন, এবং যখন নির্ধারিত আউটপুট দরকার তখন স্পষ্ট timeZone পাঠান (উদাহরণ: সবসময় UTC দেখানো বা নির্দিষ্ট ব্যবসায়িক অঞ্চল)।

সিদ্ধান্ত নির্দেশিকা: DB বনাম API বনাম UI

  • ডেটাবেস: একটি মুহূর্ত UTC হিসেবে সংরক্ষণ করুন (এপক মিলিসেকেন্ড বা UTC datetime টাইপ)। লোকাল ডেটটাইম এড়িয়ে চলুন যদি না আপনার ডোমেইনে বাস্তবে ওয়াল-ক্লক টাইম সংরক্ষণ করা লাগে (যেমন 'দোকান খুলে 09:00')।
  • API বাউন্ডারি: Z সহ ISO 8601 পছন্দ করুন (যেমন '2025-12-23T10:15:00Z')। যদি এপক ব্যবহার করেন, ফিল্ডের নাম যেমন createdAtMs ব্যবহার করে ইউনিট স্পষ্ট করুন।
  • UI: স্টোর করা UTC মুহূর্ত নিয়ে ব্যবহারকারীর লোকেল অনুযায়ী ফরম্যাট করুন। শিডিউলিং UI-র ক্ষেত্রে টাইমজোন স্পষ্টভাবে লেবেল করুন এবং কনভার্শনগুলো ইচ্ছাকৃত রাখুন।

কখন লাইব্রেরি দরকার

যদি আপনি recurring events, জটিল টাইমজোন নিয়ম, DST-সেইফ আরিথমেটিক ('আগামীকাল একই লোকাল টাইম'), অথবা অসঙ্গত ইনপুট থেকে অনেক পার্সিং দরকার—তখন একটি ডেডিকেটেড ডেট-টাইম লাইব্রেরি বিবেচনা করুন। মূল ভ্যালু হলো পরিষ্কার API এবং কম এজ-কেস বাগ।

আরও জানতে চাইলে /blog দেখুন। টুলিং বা সাপোর্ট অপশন মূল্যায়ন করছেন? দেখুন /pricing।

সাধারণ প্রশ্ন

JavaScript-এ টাইমস্ট্যাম্পের জন্য কোন ফরম্যাট ব্যবহার করা উচিত?

অভ্যন্তরীণ ফরম্যাট হিসেবে epoch milliseconds বা UTC ISO 8601 স্ট্রিং ব্যবহার করুন। কাউকে মানটি দেখানোর সময়ই কেবল সেটিকে স্থানীয় ফরম্যাটে রূপান্তর করুন।

আমার Unix টাইমস্ট্যাম্প 1970 সালের তারিখে পরিণত হয় কেন?

JavaScript-এর Date milliseconds আশা করে। সেকেন্ডে থাকা Unix টাইমস্ট্যাম্প new Date()-এ দেওয়ার আগে 1000 দিয়ে গুণ করুন।

একই তারিখ ভিন্ন কম্পিউটারে ভিন্ন সময় দেখায় কেন?

একটি Date একটি নির্দিষ্ট মুহূর্ত সংরক্ষণ করে, কিন্তু JavaScript প্রায়ই সেটি কম্পিউটারের স্থানীয় সময় অঞ্চলে দেখায়। ডেটা বদলেছে, নাকি শুধু প্রদর্শন বদলেছে, তা দেখতে toISOString()toString() তুলনা করুন।

তারিখ এক দিন সরে যাওয়া কীভাবে ঠেকাব?

জন্মদিন বা ইনভয়েসের তারিখের মতো শুধু-তারিখের মান YYYY-MM-DD হিসেবে রাখুন। নির্ধারিত সময় অঞ্চলও সংজ্ঞায়িত না করলে এটিকে Date-এ রূপান্তর করবেন না।

API-এর জন্য কোন তারিখের ফরম্যাট সবচেয়ে নিরাপদ?

UTC-এর জন্য Z-সহ পূর্ণ ISO 8601 মান পাঠান, যেমন 2025-03-04T12:30:00Z, অথবা একটি স্পষ্ট offset যোগ করুন। 03/04/2025-এর মতো স্ট্রিং এড়িয়ে চলুন, কারণ মানুষ ও ব্রাউজার এগুলো ভিন্নভাবে পড়তে পারে।

ব্যবহারকারীদের জন্য তারিখ কীভাবে ফরম্যাট করা উচিত?

Intl.DateTimeFormat ব্যবহার করুন এবং স্ক্রিনে নির্দিষ্ট অঞ্চল দরকার হলে একটি timeZone উল্লেখ করুন। এটি নিজে হাতে স্ট্রিং তৈরি না করেই ব্যবহারকারীর locale অনুযায়ী তারিখ ফরম্যাট করে।

ব্যবহারকারীর লেখা তারিখের জন্য কি Date.parse ব্যবহার করা উচিত?

অনানুষ্ঠানিক বা দ্ব্যর্থক স্ট্রিংয়ের জন্য এর ওপর নির্ভর করবেন না। API থেকে পরিচিত ISO ফরম্যাট গ্রহণ করুন, অথবা নিজের নির্ধারিত নিয়মে নিয়ন্ত্রিত ব্যবহারকারী ইনপুট পার্স করুন।

কাউন্টডাউন বা duration-এর জন্য Date ব্যবহার এড়ানো উচিত কেন?

একটি Date ক্যালেন্ডারের একটি মুহূর্ত চিহ্নিত করে, আর duration শুধু সময়ের পরিমাণ। duration সেকেন্ড বা milliseconds হিসেবে সংরক্ষণ করুন, তারপর রেন্ডার করার সময় সংখ্যাটি ফরম্যাট করুন।

Daylight Saving Time JavaScript-এর তারিখকে কীভাবে প্রভাবিত করে?

অতিক্রান্ত সময়ের হিসাবের জন্য epoch milliseconds ব্যবহার করুন। কোনো নির্দিষ্ট অঞ্চলে «আগামীকাল সকাল 9:00 AM»-এর মতো সময়সূচির জন্য ক্যালেন্ডারভিত্তিক অপারেশন ব্যবহার করুন এবং DST পরিবর্তনের তারিখগুলো পরীক্ষা করুন।

JavaScript-এর সময়-সংক্রান্ত বাগ ডিবাগ করার দ্রুততম উপায় কী?

কাঁচা ইনপুট, toISOString(), toString() এবং getTimezoneOffset() লগ করুন। ভুল ISO মান প্রায়ই পার্সিং বা এককের সমস্যা বোঝায়, আর ভিন্ন স্থানীয় স্ট্রিং সাধারণত সময়-অঞ্চল প্রদর্শনের পার্থক্য বোঝায়।

Related posts