בעבר, תיעוד נתונים כרונולוגיים שאמורים להישאר זהים בלי קשר למיקום של הצופה – כמו תאריכי לידה, התראות יומיות, תאריכי תפוגה של כרטיסי אשראי או מחזורי חיוב חודשיים – היה מועד לשגיאות באובייקט Date הקודם. מכיוון ש-Date תמיד מייצג רגע ספציפי בזמן ומוצג באמצעות אזור הזמן המקומי של הצופה, שמירת תאריך כמו "1990-01-01" יכולה לגרום למשתמשים ממערב ל-UTC לראות את "1989-12-31", בעוד שמידול תאריכים חלקיים (כמו תפוגת כרטיס בחודש בלבד) מאלץ ימי פלייסהולדר שרירותיים שגורמים לבאגים של גלישה.
API של Temporal מספק משפחה של סוגים "פשוטים" שלא תלויים במיקום, לא כוללים הפרש של אזור זמן ומייצגים תאריכים ביומן ושעות בשעון בדיוק כמו שהם מופיעים ביומן או בשעון:
| סוג | מה המודל מודד | תרחישים נפוצים לדוגמה |
|---|---|---|
Temporal.PlainDate |
תאריך מלא ביומן (year, month, day) |
תאריכי לידה, תאריכי אירועים, תאריכי חיוב של מינויים |
Temporal.PlainTime |
זמן שעון קיר (hour, minute, second, ...) |
התראות יומיות (9:00 בבוקר לפי המיקום של המשתמש), שעות פתיחה של חנויות |
Temporal.PlainDateTime |
תאריך ביומן + שעה בשעון ללא אזור זמן | לוחות זמנים מקומיים צפים, שילוב של PlainDate ו-PlainTime לפני צירוף אזור |
Temporal.PlainMonthDay |
חודש ויום בלי שנה | ימי הולדת, ימי נישואין וחגים קבועים שחוזרים מדי שנה |
Temporal.PlainYearMonth |
שנה וחודש בלי יום | תאריכי תפוגה של כרטיסי אשראי (2027-12), תקופות חיוב חודשיות |
איך להטמיע
- בוחרים את סוג ה
Temporal.Plain*המצומצם ביותר שמתאים לנתונים:- צריך להשתמש ב-
Temporal.PlainDate.from()לתאריכים מלאים ביומן וב-Temporal.PlainTime.from()לשעות בשעון. - משתמשים ב-
Temporal.PlainDateTime.from()(או ב-plainDate.toPlainDateTime(plainTime)) כשמשלבים תאריך קלנדרי ושעה בלי לקשר אותם לאזור זמן. - משתמשים ב-
Temporal.PlainMonthDay.from()לתאריכים שנתיים בלי שנה, וממירים ל-PlainDateעם.toPlainDate({ year })לפני שמבצעים חישובים של תאריכים. - כדאי להשתמש ב-
Temporal.PlainYearMonth.from()לערכים של שנה וחודש כדי שמספרי ימים שרירותיים לא ישפיעו על החישובים.
- צריך להשתמש ב-
- שימו לב לפרמטר
calendarכשאתם יוצרים סוגי Plain: ISO 8601 ('iso8601') הוא לוח השנה שמוגדר כברירת מחדל כשמנתחים מחרוזות ISO או יוצרים באמצעותPlainDate.from({ year, month, day }). כשיוצריםPlainMonthDayאוPlainYearMonthמ-property bags – או כשעובדים עם לוחות שנה שהם לא ISO – צריך להעביר אתcalendarבאופן מפורש (למשל,{ month: 10, day: 31, calendar: 'iso8601' }). - שימוש ב-
.add()עם אפשרויותoverflowלמרווחים חוזרים ביומן: קריאה ל-plainDate.add({ months: 1 })מוגדרת כברירת מחדל ל-{ overflow: 'constrain' }, ומצמצמת תאריכים של סוף החודש כמו 31 בינואר ל-28 או 29 בפברואר. צריך להעביר את הערך{ overflow: 'reject' }כשתאריכי יעד לא חוקיים אמורים להקפיץRangeErrorבמקום להצמיד.
דוגמה: תאריכים מלאים, שעות שעון ו-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, צריך להמיר באופן מפורש באמצעות Temporal.Instant במקום ליצור מחרוזות ISO באופן ידני:
// 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. קודם צריך להמיר אותו ל-Temporal.PlainDateבאמצעות.toPlainDate({ year }), כי אורך החודשים משתנה לפי השנה. - מומלץ להשתמש ב-
.add()ב-Temporal.PlainDateלמרווחי זמן קבועים ביומן, ולהסתמך עלoverflow: 'constrain'(ברירת המחדל) כדי להצמיד תאריכים בסוף החודש או עלoverflow: 'reject'כדי להציגRangeErrorבתאריכים שלא קיימים. - כן מציינים את
calendar(למשל,calendar: 'iso8601') כשיוצרים סוגיPlainMonthDayאו סוגי Plain שאינם ISO מאובייקטים. - אל תשתמשו בסוגים פשוטים אם אתם צריכים לייצג רגע ספציפי בזמן פיזי (רגע). משתמשים ב-
Temporal.Instantאו ב-Temporal.ZonedDateTimeליומנים, חותמות זמן של אירועים או תזמון בין אזורי זמן. - אל תיצרו אובייקטים מסוג
Dateממחרוזות ISO שנוצרו באופן ידני (כמו`${date}T00:00:00Z`). צרוDateמה-epochMillisecondsשלTemporal.InstantאוTemporal.ZonedDateTime, וקראו אחד בחזרה עם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()תמיד מחזירות מופע חדש עם קובץ עזר חדש במקום לשנות את האובייקט המקורי, כך שאפשר לאחסן אותן בבטחה ישירות במצב ממשק משתמש ריאקטיבי.
אסטרטגיית גיבוי
תמיכה ב-Temporal בדפדפנים ופתרונות חלופיים
בדפדפנים שעדיין לא תומכים ב-API המקורי של Temporal, צריך להשתמש בזיהוי תכונות ולטעון באופן דינמי את 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();
})();