Temporal ile Etkinlik Farklarını Hesaplama ve Süreleri Biçimlendirme

Web özellikleri: Temporal Intl.DurationFormat

Etkinlikler arasında geçen veya kalan süreyi (ör. deneme süresinin sona ermesi, abonelik geri sayımları veya SLA pencereleri) hesaplamak ve bu süreleri yerelleştirilmiş, okunabilir bir biçimde sunmak için geçmişte manuel matematik veya harici kitaplıklar gerekiyordu. Eski Date nesnesi yalnızca milisaniye cinsinden zamanı izler. Bu nedenle takvim ayı aritmetiği, yaz saati uygulaması (DST) geçişleri ve birim dengeleme işlemleri hataya açık olur.

Temporal API'si, yaz saati uygulaması için güvenli zaman farkları, yapılandırılabilir birim dengeleme, takvime duyarlı süre karşılaştırmaları ve yerelleştirilmiş biçimlendirme sağlamak üzere Temporal.ZonedDateTime ile Temporal.Duration ve Intl.DurationFormat'yi eşleştirir.

Nasıl uygulanır?

  1. Temporal.ZonedDateTime uç noktalarını alma: Sistem saat dilimindeki geçerli saat için Temporal.Now.zonedDateTimeISO() kullanın veya hedef etkinliğin saat dilimi kullanıcının sistem varsayılanından farklı olduğunda açık bir IANA saat dilimi dizesi (Temporal.Now.zonedDateTimeISO(timeZone)) iletin.
  2. .since() ve .until() ile farkları hesaplama:
    • Geçmiş bir etkinlikten bu yana geçen süreyi hesaplamak için now.since(start, options) işlevini kullanın.
    • Gelecekteki bir etkinliğe kadar kalan süreyi hesaplamak için now.until(end, options) işlevini kullanın.
    • Elde edilen Temporal.Duration değerini dengelemek ve yuvarlamak için largestUnit (ör. 'year', 'month', 'day', 'hour') ve smallestUnit (ör. 'minute') değerlerini iletin.
  3. Bağımsız Temporal.Duration nesnelerini dengeleme veya karşılaştırma:
    • Temporal.Duration.from({ minutes: 90 }) ile bağımsız süreler oluşturun ve duration.round({ largestUnit: 'hour' }) ile birimleri yeniden dengeleyin (takvim ayları, yılları veya yaz saati geçişleri arasında dengeleme yaparken relativeTo değerini sağlayın).
    • İki süreyi Temporal.Duration.compare(d1, d2, { relativeTo }) ile karşılaştırın. Sürelerden biri takvim birimleri (years, months, weeks) içeriyorsa veya yaz saati uygulaması sınırlarını aşıyorsa relativeTo olarak PlainDate veya ZonedDateTime iletmek gerekir.
  4. İnsanlar ve makineler için biçimlendirme:
    • Yerelleştirilmiş, kullanıcılara yönelik metinler için duration.toLocaleString(locale, options) (Intl.DurationFormat öğesine yetki verir) kullanın.
    • duration.toString() özelliğini yalnızca makine tarafından okunabilen ISO 8601 süre dizeleri (ör. P1M2DT4H) için kullanın.

Örnek: Deneme süresi sona erme farkı ve yerelleştirilmiş biçimlendirme

// 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;

Örnek: Bağımsız Süreleri Dengeleme (round ve 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"

Stratejik Uygulama ve En İyi Uygulamalar

  • Belirli saat dilimlerinde gerçekleşen gerçek dünya etkinliklerini (ör. abonelik yenilemeleri veya etkinlik geri sayımları) içeren hesaplamalar için Temporal.ZonedDateTime KULLANIN ve sistem varsayılanı dışında bir bölgeye göre hesaplama yaparken Temporal.Now.zonedDateTimeISO(timeZone) için açık bir timeZone iletin.
  • `${dateStr}T${timeStr}` gibi bir ISO dizesini birleştirmek yerine, ayrı tarih ve saat dizelerini Temporal yöntemleriyle (ör. Temporal.PlainDate.from(dateStr).toPlainDateTime(timeStr).toZonedDateTime(tz) veya Temporal.PlainDate.from(dateStr).toZonedDateTime({ timeZone: tz, plainTime: timeStr })) birleştirin (DO).
  • DO pass largestUnit (e.g. 'day', 'month', 'year') in .since(), .until(), or .round() when you want durations balanced beyond hours; ZonedDateTime difference methods default to 'hour'.
  • İki süreyi karşılaştırırken Temporal.Duration.compare(d1, d2, { relativeTo }) KULLANIN. Sürelerden biri takvim birimleri (months, years) veya saat dilimine duyarlı günler içeriyorsa relativeTo sabitleyici sağlayın.
  • ${duration.years}y ${duration.months}m... değerini manuel olarak eklemek yerine duration.toLocaleString() veya new Intl.DurationFormat(locale, { style }) ('long', 'short', 'narrow' veya 'digital') kullanarak süreleri kullanıcıya gösterilecek şekilde biçimlendirin (DO).
  • duration.toString(), makine tarafından okunabilen ISO 8601 süre dizeleri (ör. PT1H30M) için KULLANILMALIDIR. Kullanıcılara yönelik metinlerde asla kullanılmamalıdır.
  • PlainTime değerini HH:MM olarak (ör. <input type="time"> için) oluştururken dize .slice(0, 5) yerine .toPlainTime().toString({ smallestUnit: 'minute' }) KULLANIN.
  • Örnekleri doğrudan DEĞİŞTİRMEYİN; Temporal nesneleri değiştirilemez. .add(), .subtract(), .round() veya .with() gibi işlemler yeni bir örnek döndürür.
  • Farkın kendisi için Date / .getTime() aritmetiğine DÖNMEYİN; Date, saat dilimine duyarlı takvim matematiğine sahip değildir ve yalnızca milisaniye hassasiyetine sahiptir.
  • Bir sürenin geçmiş/süresi dolmuş bir zaman noktasını (sign < 0) mı yoksa gelecekteki bekleyen bir başlangıcı mı temsil ettiğini kontrol etmek için hesaplanan Temporal.Duration (veya Temporal.ZonedDateTime.compare()) öğesinin yerel .sign özelliğini KULLANIN.

Yedek strateji

Temporal için yedekler ve tarayıcı desteği

Yerel Temporal API'yi henüz desteklemeyen tarayıcılar için özellik algılamayı kullanın ve temporal-polyfill'yi dinamik olarak yükleyin (~15 KB min+gz).

temporal-polyfill öğesini içe aktarmanın, Temporal nesnesini otomatik olarak global kapsama atamadığını unutmayın. Kodunuz globalThis.Temporal değerine bağlıysa bunu açıkça atayın.

// 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