টেম্পোরাল ব্যবহার করে ইভেন্ট ডিফারেন্সিয়াল গণনা করা এবং সময়সীমা ফর্ম্যাট করা

ওয়েব ফিচার: Temporal Intl.DurationFormat

ইভেন্টের (যেমন, ট্রায়ালের মেয়াদ শেষ হওয়া, সাবস্ক্রিপশনের কাউন্টডাউন বা SLA উইন্ডো) মধ্যে অতিবাহিত বা বাকি থাকা সময় গণনা করা এবং সেই সময়সীমা স্থানীয়, মানুষের-পঠনযোগ্য ফর্ম্যাটে উপস্থাপন করার জন্য ঐতিহাসিকভাবে ম্যানুয়াল গণিত বা এক্সটার্নাল লাইব্রেরির প্রয়োজন হয়। পুরনো Date অবজেক্ট শুধুমাত্র ইপক মিলিসেকেন্ড ট্র্যাক করে, যার ফলে ক্যালেন্ডার-মাস পাটিগণিত, ডেলাইট সেভিং টাইম (DST) ট্রানজিশন এবং ইউনিট ব্যালেন্সিংয়ে ভুল হওয়ার সম্ভাবনা থাকে।

Temporal API, Temporal.ZonedDateTime-এর সাথে Temporal.Duration ও Intl.DurationFormat-কে পেয়ার করে যাতে DST-নিরাপদ সময়ের পার্থক্য, কনফিগার করা যায় এমন ইউনিট ব্যালেন্সিং, ক্যালেন্ডার-সচেতন সময়সীমার তুলনা ও লোকালাইজ করা ফর্ম্যাটিং প্রদান করা যায়।

কীভাবে প্রয়োগ করতে হয়

  1. Temporal.ZonedDateTime এন্ডপয়েন্ট পান: সিস্টেম টাইম জোনে বর্তমান সময়ের জন্য Temporal.Now.zonedDateTimeISO() ব্যবহার করুন অথবা টার্গেট ইভেন্টের টাইম জোন ব্যবহারকারীর সিস্টেম ডিফল্ট থেকে আলাদা হলে, একটি স্পষ্ট IANA টাইম জোন স্ট্রিং—Temporal.Now.zonedDateTimeISO(timeZone)—পাস করুন।
  2. .since() ও .until()-এর সাথে ডিফারেনশিয়াল গণনা করুন:
    • কোনও পুরনো ইভেন্ট থেকে কত সময় অতিবাহিত হয়েছে তা গণনা করতে now.since(start, options) ব্যবহার করুন।
    • ভবিষ্যতের কোনও ইভেন্ট পর্যন্ত বাকি সময় গণনা করতে now.until(end, options) ব্যবহার করুন।
    • Temporal.Duration-এর ফলাফল ব্যালেন্স ও রাউন্ড করতে largestUnit (যেমন, 'year', 'month', 'day', 'hour') এবং smallestUnit (যেমন, 'minute') পাস করুন।
  3. ব্যালেন্স বা স্বতন্ত্র Temporal.Duration অবজেক্টের তুলনা করুন:
    • Temporal.Duration.from({ minutes: 90 })-এর সাহায্যে স্ট্যান্ডঅ্যালোন সময়সীমা তৈরি করুন এবং duration.round({ largestUnit: 'hour' })-এর সাহায্যে ইউনিট রিব্যালেন্স করুন (ক্যালেন্ডার মাস, বছর বা DST ট্রানজিশন জুড়ে ব্যালেন্স করার সময় relativeTo প্রদান করে)।
    • Temporal.Duration.compare(d1, d2, { relativeTo })-এর সাথে দুটি সময়সীমা তুলনা করুন—যেকোনও সময়সীমাতে ক্যালেন্ডার ইউনিট (years, months, weeks) থাকলে বা ডিএসটি (DST) সীমা অতিক্রম করলে, relativeTo হিসেবে PlainDate বা ZonedDateTime পাস করতে হবে।
  4. মানুষ বনাম মেশিনের জন্য ফর্ম্যাট:
    • ব্যবহারকারী নিজে ইন্টার‌্যাক্ট করতে পারেন এমন স্থানীয়করণ করা টেক্সটের জন্য duration.toLocaleString(locale, options) (যা Intl.DurationFormat-এ ডেলিগেট করে) ব্যবহার করুন।
    • শুধুমাত্র মেশিনের ক্ষেত্রে গ্রহণযোগ্য ISO 8601 সময়সীমা স্ট্রিংয়ের (যেমন P1M2DT4H) জন্য duration.toString() ব্যবহার করুন।

উদাহরণ: ট্রায়ালের মেয়াদ শেষ হওয়ার ডিফারেনশিয়াল ও স্থানীয় ফর্ম্যাটিং

// 1. Get the current time in an explicit target IANA time zone (or omit the
// argument to use the system's current time zone).
const tz = 'America/New_York';
const now = Temporal.Now.zonedDateTimeISO(tz);

// 2. Combine separate date and time strings directly.
// Do not build an ISO string with template literals; chain Temporal methods instead.
const startDateStr = '2025-01-01';
const startTimeStr = '12:00:00';
const endDateStr = '2025-01-31';
const endTimeStr = '12:00:00';

const start = Temporal.PlainDate.from(startDateStr).toPlainDateTime(startTimeStr).toZonedDateTime(tz);
const end = Temporal.PlainDate.from(endDateStr).toPlainDateTime(endTimeStr).toZonedDateTime(tz);

// 3. Calculate differences using .since() and .until()
// ZonedDateTime differences default to largestUnit: 'hour' (days are not a fixed
// length across DST, so Temporal won't balance into days unless asked). Pass
// largestUnit: 'day' or a calendar unit ('week', 'month', 'year') when you want a
// human-scale breakdown instead of e.g. PT720H. smallestUnit rounds away
// seconds and sub-second noise so the formatted string stays readable.
const timeActive = now.since(start, { largestUnit: 'year', smallestUnit: 'minute' });
const timeRemaining = now.until(end, { largestUnit: 'year', smallestUnit: 'minute' });

// 4. Format durations for display with .toLocaleString() (Intl.DurationFormat)
// instead of manually concatenating unit numbers and suffixes. 'en-US' is used
// here for deterministic output; pass undefined to use the user's locale.
console.log(`Active: ${timeActive.toLocaleString('en-US', { style: 'narrow' })}`);
console.log(`Remaining: ${timeRemaining.toLocaleString('en-US', { style: 'long' })}`);

// Use .toString() for ISO 8601 serialization (e.g., API payloads or <time datetime="...">)
console.log(`ISO 8601: ${timeRemaining.toString()}`);

// 5. Check status via .sign, and compare durations with Temporal.Duration.compare()
const isExpired = timeRemaining.sign < 0;
// Pass relativeTo when comparing durations that may include calendar months/years or DST shifts
const isPastHalfway = Temporal.Duration.compare(timeActive, timeRemaining, { relativeTo: now }) > 0;

উদাহরণ: স্ট্যান্ডঅ্যালোন সময়সীমার ভারসাম্য বজায় রাখা (round এবং Intl.DurationFormat)

// Convert a raw unit count (e.g., 90 minutes) into a balanced breakdown (1 hour, 30 minutes)
const rawDuration = Temporal.Duration.from({ minutes: 90 });
const balanced = rawDuration.round({ largestUnit: 'hour' });

// Format via duration.toLocaleString() or directly with Intl.DurationFormat
const formatter = new Intl.DurationFormat('en-US', { style: 'long' });
console.log(formatter.format(balanced)); // "1 hour, 30 minutes"

স্ট্র্যাটেজিক প্রয়োগ ও পেশাদার পদ্ধতি

  • অবশ্যই Temporal.ZonedDateTime ব্যবহার করুন, বাস্তব-জগতের ইভেন্ট যা নির্দিষ্ট টাইম জোনে ঘটে (যেমন সাবস্ক্রিপশন রিনিউ করা বা ইভেন্ট কাউন্টডাউন) তার সাথে সম্পর্কিত গণনার জন্য এবং সিস্টেম ডিফল্ট ছাড়া অন্য কোনও জোনের জন্য গণনা করার সময় Temporal.Now.zonedDateTimeISO(timeZone)-এ একটি স্পষ্ট timeZone পাস করুন।
  • `${dateStr}T${timeStr}`-এর মতো ISO স্ট্রিং ইন্টারপোলেট করার পরিবর্তে, টেম্পোরাল মেথড, যেমন Temporal.PlainDate.from(dateStr).toPlainDateTime(timeStr).toZonedDateTime(tz) বা Temporal.PlainDate.from(dateStr).toZonedDateTime({ timeZone: tz, plainTime: timeStr })-এর সাহায্যে আলাদা তারিখ ও সময়ের স্ট্রিং কম্বাইন করুন।
  • DO largestUnit (যেমন, 'day', 'month', 'year') .since(), .until() বা .round()-এ পাস করুন যখন আপনি ঘণ্টার বাইরে সময়কাল ব্যালেন্স করতে চান; ZonedDateTime পার্থক্য পদ্ধতি ডিফল্ট হিসেবে 'hour'-এ সেট করা থাকে।
  • দুটি সময়সীমার তুলনা করার সময় Temporal.Duration.compare(d1, d2, { relativeTo }) ব্যবহার করুন, যেকোনও সময়সীমার সাথে ক্যালেন্ডার ইউনিট (months, years) বা টাইম-জোন-অ্যাওয়ার দিন যুক্ত থাকলে relativeTo অ্যাঙ্কর প্রদান করুন।
  • ব্যবহারকারীকে দেখানোর জন্য ${duration.years}y ${duration.months}m... ম্যানুয়ালি ইন্টারপোলেট করার পরিবর্তে duration.toLocaleString() বা new Intl.DurationFormat(locale, { style }) ('long', 'short', 'narrow' বা 'digital') ব্যবহার করে সময়সীমা ফর্ম্যাট করুন।
  • মেশিনের ক্ষেত্রে গ্রহণযোগ্য ISO 8601 সময়সীমা স্ট্রিংয়ের (যেমন, PT1H30M) জন্য duration.toString() ব্যবহার করুন, ব্যবহারকারী নিজে ইন্টার‌্যাক্ট করতে পারেন এমন কপি হিসেবে কখনও নয়।
  • .slice(0, 5) স্ট্রিংয়ের পরিবর্তে .toPlainTime().toString({ smallestUnit: 'minute' }) ব্যবহার করে PlainTime-কে HH:MM হিসেবে রেন্ডার করুন (যেমন, <input type="time">-এর জন্য)।
  • সরাসরি ইনস্ট্যান্স পরিবর্তন করবেন না; Temporal অবজেক্ট পরিবর্তন করা যায় না। .add(), .subtract(), .round() বা .with()-এর মতো অপারেশন নতুন ইনস্ট্যান্স রিটার্ন করে।
  • ডিফারেন্সিয়ালের জন্য Date / .getTime() অ্যারিথমেটিকে ফিরে যাবেন না; Date-এ টাইম-জোন-অ্যাওয়ার ক্যালেন্ডার ম্যাথ নেই এবং শুধুমাত্র মিলিসেকেন্ড প্রিসিশন আছে।
  • কোনও সময়সীমা অতীত/মেয়াদ শেষ হয়ে যাওয়া সময় (sign < 0) নাকি ভবিষ্যতে শুরু হবে এমন সময়কে বোঝাচ্ছে তা চেক করতে, গণনা করা Temporal.Duration (বা Temporal.ZonedDateTime.compare())-এর নেটিভ .sign প্রপার্টি ব্যবহার করুন।

ফলব্যাক স্ট্র্যাটেজি

টেম্পোরালের জন্য ফলব্যাক ও ব্রাউজার সহায়তা

যেসব ব্রাউজার এখনও নেটিভ Temporal API সাপোর্ট করে না, সেগুলির জন্য ফিচার শনাক্তকরণ ব্যবহার করুন এবং ডায়নামিকভাবে temporal-polyfill (~15 KB min+gz) লোড করুন।

মনে রাখবেন, temporal-polyfill ইমপোর্ট করলে, Temporal অবজেক্ট গ্লোবাল স্কোপে অটোমেটিক অ্যাসাইন হয় না। আপনার কোড globalThis.Temporal-এর উপর নির্ভর করলে, এটি স্পষ্টভাবে অ্যাসাইন করুন।

// Check if Temporal is supported natively
(async () => {
  if (typeof Temporal === 'undefined') {
    // Load the polyfill conditionally
    const module = await import('https://esm.sh/temporal-polyfill');
    globalThis.Temporal = module.Temporal;
    // Extend Date.prototype if needed
    Date.prototype.toTemporalInstant = module.toTemporalInstant;
  }
  initializeApp();
})();

Intl.DurationFormat