Temporal ile Konumdan Bağımsız Verileri ve Kısmi Tarihleri Yakalama

Web özellikleri: Temporal

Görüntüleyenin konumundan bağımsız olarak aynı kalması gereken kronolojik verileri (ör. doğum tarihleri, günlük alarmlar, kredi kartı son kullanma tarihleri veya aylık faturalandırma dönemleri) kaydetmek, eski Date nesnesiyle geçmişte hataya açık bir işlemdi. Date her zaman belirli bir anı temsil ettiğinden ve görüntüleyenin yerel saat dilimi kullanılarak gösterildiğinden, "1990-01-01" gibi bir tarihin kaydedilmesi UTC'nin batısındaki kullanıcıların "1989-12-31" tarihini görmesine neden olabilir. Kısmi tarihlerin (yalnızca ay içeren kart son kullanma tarihi gibi) modellenmesi ise taşma hatalarına neden olan rastgele yer tutucu günleri zorunlu kılar.

Temporal API'si, saat dilimi farkı içermeyen ve takvim tarihlerini ve saatleri tam olarak bir takvim veya saatten okunduğu gibi temsil eden, konumdan bağımsız "Plain" türleri ailesi sağlar:

Tür Modelleme Kapsamı Tipik Kullanım Alanları
Temporal.PlainDate Tam takvim tarihi (year, month, day) Doğum tarihleri, etkinlik tarihleri, abonelik fatura tarihleri
Temporal.PlainTime Gerçek zaman (hour, minute, second, ...) Günlük alarmlar (kullanıcının bulunduğu yerde saat 09:00), mağaza çalışma saatleri
Temporal.PlainDateTime Saat dilimi olmadan takvim tarihi ve saat dilimi saati Değişken yerel programlar, bir bölge eklemeden önce PlainDate ve PlainTime birleştiriliyor
Temporal.PlainMonthDay Yıl olmadan ay ve gün Yıllık olarak yinelenen doğum günleri, yıl dönümleri, sabit tatiller
Temporal.PlainYearMonth Gün olmadan yıl ve ay Kredi kartı geçerlilik sonu (2027-12), aylık fatura dönemleri

Nasıl uygulanır?

  1. Verilerinizle eşleşen en dar Temporal.Plain* türünü seçin:
    • Tam takvim tarihleri için Temporal.PlainDate.from(), saatler için Temporal.PlainTime.from() kullanın.
    • Bir takvim tarihini ve duvar saati saatini bir saat dilimine bağlamadan birleştirirken Temporal.PlainDateTime.from() (veya plainDate.toPlainDateTime(plainTime)) kullanın.
    • Yıl bilgisi içermeyen yıllık tarihler için Temporal.PlainMonthDay.from() kullanın ve tarih aritmetiği yapmadan önce .toPlainDate({ year }) ile PlainDate biçimine dönüştürün.
    • Rastgele gün sayılarının hesaplamalara dahil edilmemesi için yıl ve ay değerlerinde Temporal.PlainYearMonth.from() kullanın.
  2. Plain türlerini oluştururken calendar parametresine dikkat edin: ISO dizelerini ayrıştırırken veya PlainDate.from({ year, month, day }) ile oluştururken varsayılan takvim ISO 8601'dir ('iso8601'). Özellik paketlerinden PlainMonthDay veya PlainYearMonth oluştururken ya da ISO dışı takvimlerle çalışırken calendar değerini açıkça iletin (ör. { month: 10, day: 31, calendar: 'iso8601' }).
  3. Yinelenen takvim aralıkları için overflow seçenekleriyle .add() kullanın: plainDate.add({ months: 1 }) çağrısı varsayılan olarak { overflow: 'constrain' } değerine ayarlanır ve ay sonu tarihlerini (ör. 31 Ocak) 28/29 Şubat'a sabitler. Geçersiz hedef tarihler, sıkıştırma yerine RangeError hatası vermesi gerektiğinde { overflow: 'reject' } değerini iletin.

Örnek: Tam Tarihler, Saatler ve 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;

Örnek: Kısmi Tarihler (PlainMonthDay ve 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;

Örnek: Yinelenen Aylık Aralıklar (overflow: 'constrain' ve '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
}

Örnek: Eski Date ile birlikte çalışma

Eski Date, takvim tarihi değil, zaman damgasıdır (anlık). "1990-01-01" değerini Date olarak depolamak, belirli bir anı (genellikle UTC gece yarısı) seçmenizi zorunlu kılar ve UTC'nin batısındaki bir saat diliminde geri okunduğunda "31/12/1989" değerini verir. Bir kitaplık veya API için hâlâ Date gerekiyorsa ISO dizelerini manuel olarak oluşturmak yerine Temporal.Instant aracılığıyla açıkça dönüştürün:

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

Stratejik Uygulama ve En İyi Uygulamalar

  • Tam takvim tarihleri için Temporal.PlainDate, saatler için Temporal.PlainTime, bölge içermeyen yerel tarih-saat çiftleri için Temporal.PlainDateTime, yinelenen yıllık tarihler için Temporal.PlainMonthDay ve yıl-ay son kullanma tarihleri veya fatura dönemleri için Temporal.PlainYearMonth kullanın.
  • Temporal.PlainMonthDay üzerinde doğrudan aritmetik işlem YAPMAYIN. Ay uzunlukları yıla göre değiştiğinden, önce .toPlainDate({ year }) aracılığıyla Temporal.PlainDate değerine dönüştürün.
  • Temporal.PlainDate üzerinde .add()'yi yinelenen takvim aralıkları için KULLANIN. Ay sonu tarihlerini sabitlemek için overflow: 'constrain' (varsayılan) veya mevcut olmayan tarihlerde RangeError hatası vermesi için overflow: 'reject' kullanın.
  • Nesnelerden PlainMonthDay veya ISO dışı düz türler oluştururken calendar (ör. calendar: 'iso8601') BELİRTİN.
  • Fiziksel zamandaki belirli bir anı (bir an) temsil etmeniz gerekiyorsa düz türleri KULLANMAYIN. Günlükler, etkinlik zaman damgaları veya farklı saat dilimleri arasında planlama yapmak için Temporal.Instant ya da Temporal.ZonedDateTime kullanın.
  • Date nesnelerini, elle oluşturulmuş ISO dizelerinden (ör. `${date}T00:00:00Z`) OLUŞTURMAYIN. Temporal.Instant veya Temporal.ZonedDateTime nesnesinin epochMilliseconds değerinden bir Date oluşturun ve Temporal.Instant.fromEpochMilliseconds(date.getTime()) ya da date.toTemporalInstant() ile bir nesneyi geri okuyun. Böylece saat dilimi varsayımı açık olur.
  • Yıl, ay veya gün değerlerini ayıklamak ya da karşılaştırmak için Intl.DateTimeFormat.prototype.formatToParts() işlevini KULLANMAYIN. .year, .month ve .day değerlerini doğrudan Plain türünden okuyun ve .equals() ile karşılaştırın.
  • Saat dilimlerini IANA adıyla (ör. America/New_York) tanımlayın, sabit bir UTC farkıyla tanımlamayın. Farklar, yaz saati uygulaması ve mevzuata göre değişir. Bu nedenle, "UTC-5" gibi etiketler yılın bir bölümünde yanlış olur.
  • Temporal nesnelerinin değiştirilemez olduğunu UNUTMAYIN. .add(), .subtract() veya .with() gibi yöntemler, orijinal nesneyi yerinde değiştirmek yerine her zaman yeni bir referans içeren yeni bir örnek döndürür. Bu sayede, doğrudan reaktif kullanıcı arayüzü durumunda güvenli bir şekilde depolanabilirler.

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();
})();