টেম্পোরাল সহ লোকেশন-অ্যাগনস্টিক ডেটা এবং আংশিক তারিখ ক্যাপচার করা

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

ক্রোনোলজিক্যাল ডেটা রেকর্ড করা যা দর্শকের লোকেশন নির্বিশেষে অভিন্ন থাকা উচিত—যেমন জন্মতারিখ, দৈনিক অ্যালার্ম, ক্রেডিট কার্ডের মেয়াদ শেষ হওয়া বা মাসিক বিলিং চক্র—ঐতিহাসিকভাবে লিগ্যাসি Date অবজেক্টের সাথে ত্রুটি-প্রবণ হয়েছে। যেহেতু Date সবসময় সময়ের একটি নির্দিষ্ট মুহূর্তকে দেখায় এবং দর্শকের স্থানীয় টাইম জোন ব্যবহার করে দেখানো হয়, তাই "1990-01-01"-এর মতো তারিখ সেভ করলে UTC-এর পশ্চিমে থাকা ব্যবহারকারীরা "1989-12-31" দেখতে পারেন, যেখানে আংশিক তারিখ (যেমন, শুধুমাত্র মাস উল্লেখ করা কার্ডের মেয়াদ শেষ হওয়ার তারিখ) মডেল করার ফলে ইচ্ছামতো প্লেসহোল্ডার দিন দেখানো হয় যার ফলে ওভারফ্লো সংক্রান্ত সমস্যা হয়।

Temporal API লোকেশন-অ্যাগনস্টিক "প্লেন" ধরনের একটি ফ্যামিলি প্রদান করে যা কোনও টাইম জোন অফসেট বহন করে না এবং ক্যালেন্ডারের তারিখ ও ওয়াল-ক্লকের সময় ঠিক ক্যালেন্ডার বা ঘড়ি থেকে পড়ার মতোই দেখায়:

ধরন এটি কী মডেল করে সাধারণ ব্যবহারিক প্রয়োগ
Temporal.PlainDate ক্যালেন্ডারের সম্পূর্ণ তারিখ (year, month, day) জন্মতারিখ, ইভেন্টের তারিখ, সাবস্ক্রিপশন বিলিংয়ের তারিখ
Temporal.PlainTime ওয়াল-ক্লক টাইম (hour, minute, second, ...) দৈনিক অ্যালার্ম (ব্যবহারকারী যেখানেই থাকুন না কেন সকাল ৯:০০টা), স্টোর খোলা থাকার সময়
Temporal.PlainDateTime ক্যালেন্ডারের তারিখ + টাইম জোন ছাড়া দেওয়াল ঘড়ির সময় ফ্লোটিং লোকাল শিডিউল, কোনও জোন অ্যাটাচ করার আগে PlainDate ও PlainTime একত্রিত করা
Temporal.PlainMonthDay বছর ছাড়া মাস ও দিন প্রতি বছর একই তারিখে জন্মদিন, বার্ষিকী, নির্দিষ্ট ছুটির দিন
Temporal.PlainYearMonth দিন ছাড়া বছর ও মাস ক্রেডিট কার্ডের মেয়াদ শেষ হওয়ার তারিখ (2027-12), মাসিক বিলিংয়ের মেয়াদ

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

  1. আপনার ডেটার সাথে ম্যাচ করে এমন সবচেয়ে নির্দিষ্ট Temporal.Plain* ধরন বেছে নিন:
    • সম্পূর্ণ ক্যালেন্ডার তারিখের জন্য Temporal.PlainDate.from() এবং ওয়াল-ক্লক সময়ের জন্য Temporal.PlainTime.from() ব্যবহার করুন।
    • কোনও টাইম জোনের সাথে যুক্ত না করে ক্যালেন্ডারের তারিখ ও দেওয়াল ঘড়ির সময় একসাথে উল্লেখ করার সময় Temporal.PlainDateTime.from() (বা plainDate.toPlainDateTime(plainTime)) ব্যবহার করুন।
    • বছর ছাড়া বার্ষিক তারিখের জন্য Temporal.PlainMonthDay.from() ব্যবহার করুন এবং তারিখের পাটিগণিত করার আগে .toPlainDate({ year }) সহ PlainDate-এ কনভার্ট করুন।
    • বছর ও মাসের মানের জন্য Temporal.PlainYearMonth.from() ব্যবহার করুন যাতে গণনার সময় ইচ্ছামতো দিনের সংখ্যা কখনও প্রকাশ না পায়।
  2. Plain টাইপ তৈরি করার সময় calendar প্যারামিটারটি লক্ষ্য করুন: ISO স্ট্রিং পার্স করার সময় বা PlainDate.from({ year, month, day })-এর মাধ্যমে তৈরি করার সময় ISO 8601 ('iso8601') হল ডিফল্ট ক্যালেন্ডার। প্রপার্টি ব্যাগ থেকে PlainMonthDay বা PlainYearMonth তৈরি করার সময়—অথবা ISO নয় এমন ক্যালেন্ডার নিয়ে কাজ করার সময়—calendar স্পষ্টভাবে পাস করুন (যেমন, { month: 10, day: 31, calendar: 'iso8601' })।
  3. পুনরাবৃত্ত ক্যালেন্ডার ইন্টারভ্যালের জন্য overflow বিকল্পের সাথে .add() ব্যবহার করুন: plainDate.add({ months: 1 }) কল করলে ডিফল্ট হিসেবে { overflow: 'constrain' } হয়, ৩১ জানুয়ারির মতো মাসের শেষের তারিখ ২৮/২৯ ফেব্রুয়ারিতে ক্ল্যাম্প করে। ক্ল্যাম্পিংয়ের পরিবর্তে ভুল টার্গেট তারিখের জন্য RangeError থ্রো করতে হলে { overflow: 'reject' } পাস করুন।

উদাহরণ: সম্পূর্ণ তারিখ, ওয়াল-ক্লক টাইম এবং PlainDateTime

// 1. Parse a calendar date and a wall-clock time from input strings
const birthdate = Temporal.PlainDate.from('1990-01-01');
const reminderTime = Temporal.PlainTime.from('09:00');

// Combine into a location-agnostic PlainDateTime without string interpolation
const localAppointment = birthdate.toPlainDateTime(reminderTime);

// 2. Display without time zone conversion; outputs the same date in any time zone
console.log(birthdate.toLocaleString('en-GB'));
console.log(localAppointment.toString()); // "1990-01-01T09:00:00"

// 3. Read calendar fields directly from the Plain type. A PlainDate already *is*
// a calendar date, so there is no need for the formatToParts() workaround that
// legacy Date code uses to recover year/month/day from an instant.
const { year, month, day } = birthdate;

উদাহরণ: আংশিক তারিখ (PlainMonthDay এবং PlainYearMonth)

// Annual recurring event (no year component)
// Include explicit calendar when constructing from an object
const annualHoliday = Temporal.PlainMonthDay.from({
  month: 10,
  day: 31,
  calendar: 'iso8601',
});

const today = Temporal.Now.plainDateISO();
const isToday = annualHoliday.equals(today.toPlainMonthDay());

// Convert PlainMonthDay to a PlainDate with a specific year before doing arithmetic,
// because month lengths (such as February) depend on whether the year is a leap year.
const occurrenceThisYear = annualHoliday.toPlainDate({ year: today.year });

// Monthly expiration (no day component, preventing day-of-month overflow bugs)
const cardExpiry = Temporal.PlainYearMonth.from({
  year: 2027,
  month: 12,
  calendar: 'iso8601',
});
const currentMonth = today.toPlainYearMonth();
const isExpired = Temporal.PlainYearMonth.compare(cardExpiry, currentMonth) < 0;

উদাহরণ: রেকারিং মাসিক ইন্টারভাল (overflow: 'constrain' বনাম 'reject')

const billingStart = Temporal.PlainDate.from('2024-01-31');

// Default overflow: 'constrain' clamps Jan 31 + 1 month to the last valid day of February
const nextBilling = billingStart.add({ months: 1 }, { overflow: 'constrain' });
console.log(nextBilling.toString()); // "2024-02-29" (2024 is a leap year)

// Use overflow: 'reject' to throw a RangeError when the target day does not exist
try {
  billingStart.add({ months: 1 }, { overflow: 'reject' });
} catch (err) {
  // RangeError: day 31 does not exist in February
}

উদাহরণ: লিগ্যাসি Date-এর সাথে ইন্টারঅপারেট করা

পুরনো Date হল টাইমস্ট্যাম্প (একটি মুহূর্ত), ক্যালেন্ডারের তারিখ নয়। "1990-01-01"-কে Date হিসেবে স্টোর করলে আপনাকে কোনও ইনস্ট্যান্ট (সাধারণত মধ্যরাত UTC) বেছে নিতে হবে এবং UTC-এর পশ্চিমে কোনও টাইম জোনে এটি আবার পড়লে "31/12/1989" দেখাবে। কোনও লাইব্রেরি বা API-এর জন্য এখনও Date-এর প্রয়োজন হলে, ISO স্ট্রিং ম্যানুয়ালি তৈরি করার পরিবর্তে Temporal.Instant-এর মাধ্যমে স্পষ্টভাবে কনভার্ট করুন:

// PlainDate -> Date: attach a time zone (time of day defaults to the start of
// that day) to get an Instant, then hand the epoch milliseconds to Date.
// 'UTC' is a deliberate choice and must be documented; any zone would do,
// but the stored instant depends on it.
const instant = birthdate.toZonedDateTime('UTC').toInstant();
const legacyDate = new Date(instant.epochMilliseconds);

// Date -> PlainDate: go back through Instant (legacyDate.toTemporalInstant()
// is equivalent), then project into an explicit time zone. The result depends
// on the zone, which is exactly the drift risk.
const viewedDate = Temporal.Instant.fromEpochMilliseconds(legacyDate.getTime())
  .toZonedDateTimeISO('America/New_York')
  .toPlainDate();

// Compare Plain types with .equals() (or Temporal.PlainDate.compare), not strings.
console.log(viewedDate.equals(birthdate)); // false: the calendar date drifted to 1989-12-31

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

  • সম্পূর্ণ ক্যালেন্ডার তারিখের জন্য Temporal.PlainDate, দেওয়াল ঘড়ির সময়ের জন্য Temporal.PlainTime, জোন ছাড়া স্থানীয় তারিখ-সময় পেয়ারের জন্য Temporal.PlainDateTime, বার্ষিক পুনরাবৃত্ত তারিখের জন্য Temporal.PlainMonthDay এবং বছর-মাস মেয়াদ শেষ হওয়া বা বিলিংয়ের মেয়াদের জন্য Temporal.PlainYearMonth ব্যবহার করুন।
  • Temporal.PlainMonthDay-এ সরাসরি অ্যারিথমেটিক পারফর্ম করবেন না; প্রথমে .toPlainDate({ year })-এর মাধ্যমে এটিকে Temporal.PlainDate-এ কনভার্ট করুন কারণ বছরের উপর নির্ভর করে মাসের দৈর্ঘ্য পরিবর্তিত হয়।
  • Temporal.PlainDate-এ .add() ব্যবহার করুন, যাতে ক্যালেন্ডারে রেকারিং ইন্টারভ্যাল সেট করা যায়। এর জন্য মাসের শেষ তারিখের ক্ষেত্রে overflow: 'constrain' (ডিফল্ট) অথবা অস্তিত্বহীন তারিখের ক্ষেত্রে overflow: 'reject' ব্যবহার করে RangeError থ্রো করুন।
  • অবজেক্ট থেকে PlainMonthDay বা ISO-নয় এমন প্লেন টাইপ তৈরি করার সময় calendar (যেমন, calendar: 'iso8601') উল্লেখ করুন।
  • আপনি যদি ফিজিক্যাল টাইমে (একটি মুহূর্ত) একটি নির্দিষ্ট মুহূর্ত উপস্থাপন করতে চান, তাহলে Plain টাইপ ব্যবহার করবেন না। লগ, ইভেন্ট টাইমস্ট্যাম্প বা ক্রস-টাইমজোন শিডিউলিংয়ের জন্য Temporal.Instant বা Temporal.ZonedDateTime ব্যবহার করুন।
  • ম্যানুয়ালি তৈরি করা ISO স্ট্রিং (যেমন `${date}T00:00:00Z`) থেকে Date অবজেক্ট তৈরি করবেন না। Temporal.Instant বা Temporal.ZonedDateTime-এর epochMilliseconds থেকে Date তৈরি করুন এবং Temporal.Instant.fromEpochMilliseconds(date.getTime()) বা date.toTemporalInstant()-এর মাধ্যমে আবার পড়ুন, যাতে টাইম জোন সংক্রান্ত অনুমান স্পষ্ট হয়।
  • বছর, মাস বা দিনের মান এক্সট্র্যাক্ট বা তুলনা করতে Intl.DateTimeFormat.prototype.formatToParts() ব্যবহার করবেন না। .year, .month ও .day সরাসরি Plain টাইপ থেকে পড়ুন এবং .equals()-এর সাথে তুলনা করুন।
  • অবশ্যই IANA নাম (যেমন, America/New_York) দিয়ে টাইম জোন শনাক্ত করুন এবং কখনও নির্দিষ্ট UTC অফসেট দিয়ে নয়। ডেলাইট সেভিং টাইম ও আইন অনুযায়ী অফসেট পরিবর্তিত হয়, তাই "UTC-5"-এর মতো লেবেল বছরের কিছু সময়ের জন্য ভুল হয়ে যায়।
  • মনে রাখবেন যে Temporal অবজেক্ট পরিবর্তন করা যায় না। .add(), .subtract() বা .with()-এর মতো পদ্ধতিগুলি আসল অবজেক্টকে মিউটেট করার পরিবর্তে সবসময় নতুন রেফারেন্স সহ নতুন ইনস্ট্যান্স রিটার্ন করে, যার ফলে রিঅ্যাক্টিভ UI স্টেটে সরাসরি স্টোর করার জন্য সেগুলি নিরাপদ হয়ে ওঠে।

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

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

যেসব ব্রাউজার এখনও নেটিভ 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();
})();