Skip to main content
DevOps ve Altyapı•19 dk okuma

Cron ve Saat Dilimi: Türkiye'nin 2016 Kararı ve Yaz Saatinde Kaybolan İşler

Mustafa Kürşad BaşerMustafa Kürşad BAŞER
•
Cron ve Saat Dilimi: Türkiye'nin 2016 Kararı ve Yaz Saatinde Kaybolan İşler

Cron duvar saatine bakar, duvar saati de kararnameyle değişir

Özet (TL;DR)

  • POSIX crontab saat dilimi hakkında tek kelime etmez. CRON_TZ, TZ ve yaz saati geçişindeki davranış tamamen kullandığınız cron yazılımına bağlıdır.
  • Türkiye 8 Eylül 2016'da kalıcı +03'e geçti ve IANA bunu tzdata 2016g ile aldı. Daha eski veriyle kalan bir sistem bugün hâlâ kışın bir saat geride, yazın ise doğru görünür.
  • Etc/GMT-3 UTC+3 demektir, işaret POSIX gereği terstir. Türkiye için doğru ad Europe/Istanbul, çünkü 2016 öncesi geçmişi ve olası yeni kararları yalnızca o taşır.
  • Yaz saati uygulanan bir bölgede 02:30'a kurulu iş ilkbaharda hiç çalışmayabilir, sonbaharda iki kez çalışabilir. Alpine'daki BusyBox crond ikisini de yapar.
  • Sağlam kurulum üç parçalıdır: zamanlayıcı UTC'de tetikler, yerel saat kararını uygulama verir, iş de ait olduğu iş gününe göre bir anahtarla tekrar çalışmaya karşı kilitlenir.

Her gece 03:00'te başlaması gereken yedekleme, loglarda 02:00'de başlamış görünüyor. Crontab'a kimse dokunmamış, NTP senkron, sunucu aylardır yeniden başlatılmamış. Sunucunun date çıktısıyla telefonunuzdaki saat arasında tam bir saat fark var ve bu fark yalnızca kış aylarında ortaya çıkıyor.

Cron'un burada bir kabahati yok. Kendisine söylenen yerel saate bakıyor, o yerel saati hesaplayan veri ise eski. Yaz saati uygulayan ülkelerde aynı kökten yılda iki gece ayrıca hasar çıkar: hiç çalışmayan ve iki kez çalışan işler. Sırayla cron'un saati nereden okuduğuna, Türkiye'nin 2016 kararına ve kalıcı çözüme bakalım.

Cron saati nereden okur

Cron daemon'ı her dakika uyanır, o anki yerel saati hesaplar ve crontab satırlarını bu saatle karşılaştırır. Yerel saat, daemon sürecinin ortamındaki TZ değişkeninden, o yoksa /etc/localtime dosyasından gelir. Debian'ın cron man sayfası daemon'ın varsa /etc/timezone dosyasındaki tanımı kullandığını da yazar. Saat dilimini değiştirdiyseniz cron servisini yeniden başlatın. Çalışan bir sürecin eski ayarı tutmadığından ancak böyle emin olursunuz.

Standart bu konuda şaşırtıcı derecede sessiz. POSIX'in crontab tanımı (IEEE Std 1003.1-2017) beş zaman alanını ve komutun hangi ortam değişkenleriyle çalışacağını anlatır ama saat diliminden, yaz saatinden ya da saat geri alındığında ne olacağından hiç söz etmez. Boşluğu her cron yazılımı kendince doldurmuş:

Cron yazılımıNeredeCrontab başına saat dilimiYaz saati geçişi
Vixie cron (Debian paketi)Debian, UbuntuYok. TZ= yalnızca komutun ortamını değiştirir3 saatten küçük kaymada sabit saatli işleri telafi eder
cronieRHEL, CentOS, FedoraCRON_TZ= ile varAynı telafi: atlanan iş hemen çalışır, tekrarlanan saatte ikinci kez çalışmaz
BusyBox crondAlpine ve küçük imajlarYokTelafi yok: atlanan saat atlanır, tekrarlanan saat yeniden çalışır
Kubernetes CronJobClusterspec.timeZone (1.24 alfa, 1.25 beta)Belge nadiren iki Job ya da hiç Job oluşabileceğini yazar

Uyarı

Debian ve Ubuntu'da crontab'ın başına TZ=Europe/Berlin yazmak işlerin saatini değiştirmez. Debian'ın crontab(5) sayfasına göre daemon tek bir saat diliminde çalışır ve TZ yalnızca çalıştırılan komutların ortamına geçer. cronie'de CRON_TZ= satırı takvimi gerçekten değiştirir. Bir dağıtımdan diğerine taşınan crontab bu yüzden sessizce farklı davranabilir.

Türkiye 2016: saat kımıldamadı, veri eskidi

Türkiye 2016'ya her yıl olduğu gibi yaz saatiyle girdi. 14 Mart 2016 tarihli ve 2016/8589 sayılı Bakanlar Kurulu Kararı, saatlerin 30 Ekim 2016 Pazar günü 04.00'te bir saat geri alınacağını söylüyordu. Sonra 8 Eylül 2016 tarihli ve 29825 sayılı Resmî Gazete'de 2016/9154 sayılı karar yayımlandı. Yaz saati yıl boyu sürecek, 30 Ekim'deki geri alma ibaresi yürürlükten kalkacaktı. Duvardaki saat açısından bunun tek sonucu sonbaharda saatin geri alınmamasıydı. tzdata açısından ise Türkiye EET/EEST düzeninden çıkıp kalıcı +03'e geçti.

IANA Time Zone Database bu değişikliği 13 Eylül 2016 tarihli 2016g sürümüyle aldı. NEWS dosyasındaki kayıt iki satır. Türkiye EET/EEST'den (+02/+03) kalıcı +03'e geçmiş, yürürlük tarihi 2016-09-07. Duyuru tz-announce listesine 28 Eylül'de düştü. Sürümden 30 Ekim'e yedi haftadan az vardı ve bu sürede verinin dağıtım paketlerine, oradan sunuculara, gömülü cihazlara ve imajlara ulaşması gerekiyordu. O gece güncellenmemiş her sistem eski kurala uydu ve saati 04:00'te 03:00'e aldı.

Sinsi olan kısım sonrası. Eski tzdata Türkiye için AB kuralını her yıl uygulamaya devam eder. Mart sonundan ekim sonuna kadar +03, geri kalanında +02. 2016f'de kalmış bir sistem yazın doğru saati gösterir, kışın bir saat geride kalır. Hata mevsimlik olduğu için temmuzda yapılan bir testte görünmez, ekim sonunda ortaya çıkar. Aşağıdaki betik IANA'nın 2016f ve 2016g arşivlerini zic ile derleyip aynı UTC anlarını üç ayrı bölgeye çeviriyor:

python
from datetime import datetime, timezonefrom zoneinfo import ZoneInfo def yukle(yol, ad):    with open(yol, "rb") as f:        return ZoneInfo.from_file(f, key=ad) bolgeler = [    yukle("zi2016f/Europe/Istanbul", "2016f Istanbul"),    yukle("zi2016g/Europe/Istanbul", "2016g Istanbul"),    yukle("zi2016g/Etc/GMT-3", "Etc/GMT-3"),]anlar = [datetime(2015, 12, 1, 9, 0, tzinfo=timezone.utc),         datetime(2022, 1, 15, 9, 0, tzinfo=timezone.utc),         datetime(2022, 7, 15, 9, 0, tzinfo=timezone.utc)] for an in anlar:    print(f"UTC {an:%Y-%m-%d %H:%M}")    for tz in bolgeler:        yerel = an.astimezone(tz)        print(f"  {tz.key:15} {yerel:%H:%M} {yerel.tzname():4} {yerel:%z}")
bash
$ python3 eski_tzdata.pyUTC 2015-12-01 09:00  2016f Istanbul  11:00 EET  +0200  2016g Istanbul  11:00 EET  +0200  Etc/GMT-3       12:00 +03  +0300UTC 2022-01-15 09:00  2016f Istanbul  11:00 EET  +0200  2016g Istanbul  12:00 +03  +0300  Etc/GMT-3       12:00 +03  +0300UTC 2022-07-15 09:00  2016f Istanbul  12:00 EEST +0300  2016g Istanbul  12:00 +03  +0300  Etc/GMT-3       12:00 +03  +0300

15 Ocak 2022 satırı meselenin özeti. 2016f'ye takılmış bir makine bugün 11:00 diyor, doğrusu 12:00. Container imajları burada ayrıca risklidir, çünkü bir imaj oluşturulduğu günün tzdata paketini dondurur ve altındaki sunucuyu güncellemek içindeki saati düzeltmez. Kendi saat dilimi kopyasını taşıyan yazılımlar da işletim sisteminin paketinden habersizdir. JDK bunlardan biri ve Oracle onun için ayrı bir tzupdater aracı yayımlar. Kaynaktan varsayılan ayarlarla derlenmiş PostgreSQL de kendi kopyasını kullanır.

Bir sistemin hangi kurala inandığını görmek için zdump yeter. Güncel veride 2016'nın sonbaharında tek geçiş 7 Eylül'dedir, eski veride ise 30 Ekim'deki geri alma hâlâ durur:

bash
$ zdump -v -c 2016,2018 Europe/Istanbul | grep -E '(Sep|Oct) .* 2016 UT'# tzdata 2016gEurope/Istanbul  Tue Sep  6 20:59:59 2016 UT = Tue Sep  6 23:59:59 2016 EEST isdst=1 gmtoff=10800Europe/Istanbul  Tue Sep  6 21:00:00 2016 UT = Wed Sep  7 00:00:00 2016 +03 isdst=0 gmtoff=10800# tzdata 2016fEurope/Istanbul  Sun Oct 30 00:59:59 2016 UT = Sun Oct 30 03:59:59 2016 EEST isdst=1 gmtoff=10800Europe/Istanbul  Sun Oct 30 01:00:00 2016 UT = Sun Oct 30 03:00:00 2016 EET isdst=0 gmtoff=7200

Kısaltma da değişti. 2016g yeni saat için uydurma bir ad yerine +03 kullanıyor. Logları EEST ya da EET kısaltmasıyla ayrıştıran bir betik 7 Eylül 2016'dan beri hiçbir şey bulamaz.

Europe/Istanbul mu, Etc/GMT-3 mü

2016'dan sonra sorunu kökünden çözmek isteyen bazı kurulumlar Etc/GMT-3 gibi sabit bir bölgeye geçti. Bugün ve gelecek için iki seçenek aynı saati verir, geçmiş için vermez. Yukarıdaki çıktının ilk bloğu 1 Aralık 2015'e ait. O sabah İstanbul'da saat 11:00'di (+02), Etc/GMT-3 ise 12:00 diyor. 2016 öncesi bir kaydı yerel saate çeviren her rapor bir saat kayar. Türkiye saat politikasını bir daha değiştirirse güncellemeyi de yalnızca Europe/Istanbul alır, Etc/GMT-3 sonsuza kadar +03 kalır.

İşaret ayrı bir tuzak. Etc bölgeleri POSIX TZ sözdizimini izler ve POSIX'te pozitif işaret Greenwich'in batısı demektir. tzdata'nın etcetera dosyası da bunun çoğu kişinin beklediğinin tersi olduğunu açıkça yazar. Yani Etc/GMT-3 UTC+3, Etc/GMT+3 ise UTC-3'tür:

bash
$ TZ=Etc/GMT-3 date -d @1663095120 "+%F %R %Z %z"2022-09-13 21:52 +03 +0300$ TZ=Etc/GMT+3 date -d @1663095120 "+%F %R %Z %z"2022-09-13 15:52 -03 -0300

İnsanların yaşadığı bir yer için her zaman bölge adını kullanın. Etc/GMT ailesi, tzdata'nın kendi notuna göre POSIX TZ dizesi kullanamayan denizdeki gemiler gibi özel durumlar için duruyor.

Yaz saati olan ülkelerde 02:30 işi

Türkiye 2016'dan beri yaz saati uygulamıyor, ama sunucularınız ya da kullanıcılarınız Avrupa'daysa mesele kapanmış değil. Orta Avrupa'da saat mart ayının son pazarı 02:00'de 03:00'e atlar, ekimin son pazarı 03:00'te 02:00'ye döner. 2022'de bu geceler 27 Mart ve 30 Ekim. İlkinde 02:00 ile 03:00 arası hiç yaşanmaz, ikincisinde iki kez yaşanır.

BusyBox crond'un mantığı çok düz. Her döngüde geçen süreyi dakika dakika yürür, her dakikayı localtime() ile yerel saate çevirir ve eşleşen işleri başlatır. Saniyeler UTC'de aktığı için yerel saat ilkbaharda 02:30'u hiç görmez, sonbaharda iki kez görür. Aynı algoritmayı Python'da kurup 30 2 * * * için Berlin saatiyle çalıştırdığımızda tablo şu:

python
from datetime import datetime, timedelta, timezonefrom zoneinfo import ZoneInfo BERLIN = ZoneInfo("Europe/Berlin") def saf_zamanlayici(baslangic, bitis, saat, dakika, tz):    """Her UTC dakikasını yerel saate çevirip '30 2 * * *' ile eşleştirir."""    an = baslangic    while an < bitis:        yerel = an.astimezone(tz)        if (yerel.hour, yerel.minute) == (saat, dakika):            print(f"  çalıştı: {yerel:%Y-%m-%d %H:%M %Z}  (UTC {an:%H:%M})")        an += timedelta(minutes=1) for gun in (datetime(2022, 3, 26), datetime(2022, 10, 29)):    bas = gun.replace(tzinfo=timezone.utc)    print(f"{gun:%Y-%m-%d} itibarıyla üç gün:")    saf_zamanlayici(bas, bas + timedelta(days=3), 2, 30, BERLIN)
bash
$ python3 saf_cron.py2022-03-26 itibarıyla üç gün:  çalıştı: 2022-03-26 02:30 CET  (UTC 01:30)  çalıştı: 2022-03-28 02:30 CEST  (UTC 00:30)2022-10-29 itibarıyla üç gün:  çalıştı: 2022-10-29 02:30 CEST  (UTC 00:30)  çalıştı: 2022-10-30 02:30 CEST  (UTC 00:30)  çalıştı: 2022-10-30 02:30 CET  (UTC 01:30)  çalıştı: 2022-10-31 02:30 CET  (UTC 01:30)

27 Mart satırı yok, 30 Ekim iki kez var. Günlük rapor üreten bir iş için bu bir eksik ve bir çift rapor demek. Para çeken, e-posta gönderen ya da fatura kesen bir işte aynı hata müşteriye kadar ulaşır.

Berlin saatinde 27 Mart 2022 ileri geçişinde 02:00-03:00 arasının hiç yaşanmadığını, 30 Ekim 2022 geri geçişinde ise 02:30'un iki kez geldiğini gösteren iki zaman çizelgesi
Aynı UTC çizgisi, iki farklı gece: atlanan ve tekrarlanan saat

Vixie cron ve cronie bu iki geceyi telafi etmeye çalışır. Atlanan sabit saatli işi geçişten hemen sonra çalıştırır, geri dönüşte ikinci kez çalıştırmaz. Bu koruma 3 saatten küçük kaymalarla ve belirli bir saatte çalışan işlerle sınırlı. Debian'ın man sayfasına göre saat ya da dakika alanında * bulunan satırlar yeni saate göre hemen çalışır. */10 2 * * * gibi bir satır ilkbaharda o saatin altı çalışmasının hepsini kaybeder, sonbaharda iki katı çalışır.

Tehlikeli saat bölgeye göre değişir. ABD'de geçiş yerel 02:00'de olur ve geri dönüşte tekrarlanan aralık 01:00 ile 02:00 arasıdır. New York'ta 02:30 işi 13 Mart 2022'de atlanır ama 6 Kasım'da tek kez çalışır, iki kez çalışan 01:30'daki iştir. "Gece 02:00 ile 03:00 arasına iş koymayın" kuralı Berlin'de işe yarar, New York'ta yarı yarıya.

Kubernetes'te CronJob takvimi varsayılan olarak kube-controller-manager'ın saat dilimine göre yorumlanır. Mayıs 2022'de çıkan 1.24, spec.timeZone alanını CronJobTimeZone feature gate'inin arkasında alfa olarak getirdi. Ağustos 2022'de çıkan 1.25'te feature gate beta oldu ve varsayılan olarak açık geliyor. Alan bir IANA bölge adı alır, sistemde saat dilimi verisi yoksa binary'lere gömülü Go standart kütüphanesi verisi yedek olarak kullanılır. Belgenin "CronJob limitations" bölümü de bu konuda dürüst davranıyor ve planlanan tek bir çalışma için nadiren iki Job oluşabileceğini ya da hiç oluşmayabileceğini, bu yüzden işlerin idempotent olması gerektiğini açıkça yazıyor.

Saati cron'dan alıp uygulamaya vermek

Sunucu UTC'de durur

UTC'nin yaz saati yok ve hiçbir bakanlar kurulu onu değiştiremez. Sunucuyu, container'ları ve veritabanı oturumlarını UTC'de tutarsanız cron'un gördüğü saat yıl boyu düz bir çizgi olur, loglar da makineler arasında doğrudan karşılaştırılabilir hâle gelir. Linux'ta bunun yolu timedatectl set-timezone Etc/UTC. HTTP de Date başlığını her zaman GMT ile yazar, saat dilimi pazarlığına hiç girmez.

UTC'nin bir bedeli var. İstanbul'da 09:00'da çalışması gereken iş crontab'da 0 6 * * * olur. Türkiye için bu dönüşüm 2016'dan beri sabit. Berlin için değil. Orada 09:00 yazın 07:00 UTC, kışın 08:00 UTC eder. Yerel saate bağlı her işte hesabı cron'dan alıp uygulamaya vermek gerekir. Alanları tek tek doğrulamak için Cron İfadesi Oluşturucu işinizi görür.

Yerel saat kararını uygulama verir

Kalıp basit. Cron UTC'de sık aralıklarla, örneğin her beş dakikada bir tetik yollar, "bölgemde bu işin zamanı geldi mi" sorusunu ise uygulama cevaplar. Python 3.9 ile standart kütüphaneye giren zoneinfo (PEP 615) bu hesabı dış bağımlılık olmadan yapıyor. İncelik, var olmayan ve iki kez yaşanan saatler için bilinçli bir politika seçmekte:

python
from datetime import date, datetime, time, timezonefrom zoneinfo import ZoneInfo def calisma_ani(gun, saat, tz):    """Yerel 'gun saat' için tek bir UTC anı döndürür.     Var olmayan saat (ileri geçiş): boşluk kadar ileri kaydırılır.    İki kez yaşanan saat (geri geçiş): ilk geçiş seçilir (fold=0).    """    yerel = datetime.combine(gun, saat, tzinfo=tz)        # fold=0    utc = yerel.astimezone(timezone.utc)    geri = utc.astimezone(tz)    if geri.replace(tzinfo=None) != yerel.replace(tzinfo=None):        durum = "yok, kaydırıldı"    elif yerel.replace(fold=1).utcoffset() != yerel.utcoffset():        durum = "iki kez var, ilki"    else:        durum = "normal"    return utc, geri, durum berlin = ZoneInfo("Europe/Berlin")for gun in (date(2022, 3, 27), date(2022, 9, 13), date(2022, 10, 30)):    utc, yerel, durum = calisma_ani(gun, time(2, 30), berlin)    print(f"{gun}  UTC {utc:%H:%M}  yerel {yerel:%H:%M %Z}  {durum}")
bash
$ python3 gunluk_is.py2022-03-27  UTC 01:30  yerel 03:30 CEST  yok, kaydırıldı2022-09-13  UTC 00:30  yerel 02:30 CEST  normal2022-10-30  UTC 00:30  yerel 02:30 CEST  iki kez var, ilki

Kod iki şeye dayanıyor. Birincisi PEP 495 ile gelen fold özniteliği. İki kez yaşanan saatte fold=0 ilk geçişi, fold=1 ikincisini seçer. İkincisi round-trip kontrolü. Var olmayan bir yerel saati UTC'ye çevirip geri getirdiğinizde aynı saati bulamazsınız, 27 Mart'taki 02:30 böyle yakalanıyor ve boşluk kadar ileri, 03:30'a kayıyor. Siz 03:00'ü seçebilir ya da o günü bilerek atlayabilirsiniz. Yeter ki seçim kodda yazılı dursun, zamanlayıcının keyfine kalmasın.

İş iki kez gelirse ikincisi boşa düşer

Hiçbir zamanlayıcı tam olarak bir kez çalışmayı garanti etmiyor. Kubernetes bunu belgesine yazmış, cron'lar yaz saatinde kendi bildiğini okuyor, bir de elle tetiklenen ve yeniden denenen işler var. Savunmayı işin kendisine koyun. İşi çalıştığı ana göre değil, ait olduğu iş gününe göre bir anahtarla kilitleyin:

sql
CREATE TABLE is_calismasi (  is_adi     TEXT NOT NULL,  is_gunu    TEXT NOT NULL,           -- iş hangi günün işi (yerel takvim)  baslangic  TEXT NOT NULL,           -- RFC 3339, offset'li  PRIMARY KEY (is_adi, is_gunu)); -- 30 Ekim 2022, Berlin: 02:30 CEST ve 02:30 CET, iki tetikINSERT INTO is_calismasi VALUES ('gunluk-rapor', '2022-10-30', '2022-10-30T02:30:00+02:00')  ON CONFLICT DO NOTHING;SELECT changes() AS eklenen;INSERT INTO is_calismasi VALUES ('gunluk-rapor', '2022-10-30', '2022-10-30T02:30:00+01:00')  ON CONFLICT DO NOTHING;SELECT changes() AS eklenen;
bash
$ sqlite3 :memory: < kilit.sql10

İkinci tetik 30 Ekim 02:30 CET'te gelse de aynı (is_adi, is_gunu) çiftine çarpar ve hiçbir şey eklemez. Uygulama changes() sıfır döndüğünde işi atlar. Anahtarın UTC anı değil yerel iş günü olması bilinçli. 02:30 CEST ile 02:30 CET iki ayrı UTC anıdır, ama ikisi de 30 Ekim'in raporudur. PostgreSQL'de aynı şeyi INSERT ... ON CONFLICT DO NOTHING ve bir birincil anahtarla yaparsınız.

Zaman damgasını saat diliminden bağımsız saklayın

Olmuş bir olayı UTC olarak saklayın, Unix epoch ya da offset'li RFC 3339 biçiminde (2022-09-13T18:52:00Z veya 2022-09-13T21:52:00+03:00). Epoch tzdata'dan etkilenmez. 2016'daki karışıklıkta da UTC tutulan kayıtlar değil, yalnızca ekrandaki gösterim yanlıştı. Tek tek değerleri Unix Zaman Damgası Dönüştürücü ile çevirip kontrol edebilirsiniz. RFC 3339'da ince bir ayrım daha var. -00:00 offset'i "UTC anı biliniyor, yerel offset bilinmiyor" demektir, Z ya da +00:00 ise UTC'nin bu zaman için tercih edilen referans olduğunu söyler.

Gelecekteki yerel bir olayda kural tersine döner. Gelecek yıl İstanbul'da 10:00'a konmuş bir toplantıyı bugünden UTC'ye çevirip saklarsanız, kurallar değiştiğinde toplantı 09:00'a ya da 11:00'e kayar. 2016'da eski kuralla önceden hesaplanmış, 30 Ekim sonrasına düşen her UTC anı bu yüzden bir saat yanlıştı. Gelecek için yerel saati ve IANA bölge adını birlikte saklayın, UTC'ye çalışma anında çevirin.

Docker ve Alpine: tzdata yoksa sessizce UTC

Alpine imajı küçük kalmak için tzdata paketini içermez. Hata mesajı yok. musl libc TZ değişkenindeki adı dosya olarak bulamazsa UTC'ye düşer ve kısaltmayı bile UTC diye yazar. Yanlış yazılmış bir ad da aynı sessizlikle geçer:

bash
$ docker run --rm alpine:3.16 sh -c 'TZ="Europe/Istanbul" date -d @1663095120 "+%F %R %Z %z"'2022-09-13 18:52 UTC +0000$ docker run --rm alpine:3.16 sh -c 'TZ="Avrupa/Istanbul" date -d @1663095120 "+%F %R %Z %z"'2022-09-13 18:52 UTC +0000$ docker run --rm alpine:3.16 sh -c 'TZ="<+03>-3" date -d @1663095120 "+%F %R %Z %z"'2022-09-13 21:52 +03 +0300

<+03>-3 biçimindeki POSIX dizesi dosya gerektirmediği için tzdata olmadan da çalışır, ama Etc/GMT-3 ile aynı geçmiş sorununu taşır. Python tarafında durum daha iyi, zoneinfo sessiz kalmaz:

bash
$ docker run --rm alpine:3.16 sh -c 'apk add -q python3 && python3 -c "from zoneinfo import ZoneInfo; ZoneInfo(\"Europe/Istanbul\")"'...zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key Europe/Istanbul'

Çözüm imaja apk add --no-cache tzdata eklemek. UTC kuralıyla çelişmemek için TZ vermeyin, veriyi yalnızca uygulamanın Europe/Istanbul hesaplayabilmesi için kurun. Python'da dağıtım paketine bağlı kalmak istemiyorsanız PyPI'daki tzdata paketini bağımlılık olarak ekleyin, zoneinfo sistemde veri bulamazsa ona bakar. Alpine'da crond çalıştırıyorsanız iki tuzak üst üste biner. Veri yoksa TZ yok sayılır, varsa BusyBox'ın yaz saati davranışı devreye girer. Daemon'ı UTC'de bırakmak ikisinden de kurtarır.

Sonuç

Cron'un neye bakacağını siz seçemezsiniz, ona hangi saati göstereceğinizi seçersiniz. Ben bu konuda tek bir kurala bağlı kalırım: crontab'da yerel saat yazan tek satır bile kalmasın. Zamanlayıcı UTC'de tetiklesin, "İstanbul'da 09:00" kararını kod zoneinfo ile versin, her iş bir iş günü anahtarıyla kilitlensin. Bu üçü yerindeyse bir sonraki saat kararı Resmî Gazete'de çıktığında yapmanız gereken tek iş tzdata'yı güncellemek olur.

Sıkça Sorulan Sorular

Sunucuyu UTC'ye çektim, mevcut crontab satırlarını nasıl çevireyim?
Türkiye için fark sabit, her saati üç saat geri alın: 03:00'teki iş 0 0 * * * olur. Asıl dikkat isteyen yer gün sınırı. İstanbul'da pazartesi 01:30'da çalışan iş UTC'de pazar 22:30'dur, haftanın günü alanını da bir gün geri kaydırmanız gerekir. Ayın ilk günü 02:00'de çalışan iş UTC'de önceki ayın son gününe düşer ve standart cron sözdiziminde "ayın son günü" yazmanın bir yolu yoktur. Böyle işleri UTC'de her gün tetikleyip tarih kontrolünü koda bırakın.
Sistemdeki tzdata sürümünü nasıl öğrenirim?
Debian ve Ubuntu'da dpkg -s tzdata, RHEL ailesinde rpm -q tzdata, Alpine'da apk info -ev tzdata paket sürümünü verir ve sürüm adında IANA sürümü (örneğin 2022c) görünür. Paket güncel olsa bile JVM ya da kendi kopyasını taşıyan bir veritabanı eski veriyle çalışıyor olabilir. Uygulamanın gözünden en güvenilir test bilinen bir anı çevirmek: Ocak 2022'deki bir an için Europe/Istanbul +03 vermiyorsa veri 2016g'den eskidir.
Kubernetes 1.25'ten eski bir cluster'da saat dilimini nasıl veririm?
spec.timeZone yoksa takvim kube-controller-manager'ın saat dilimine göre yorumlanır. Takvimin başına CRON_TZ= eklemek bazı sürümlerde çalışıyor gibi görünür, ama Kubernetes belgesi bunun resmî olarak desteklenmediğini, kullanılan iç kütüphanenin bir implementasyon detayı olduğunu ve production'da önerilmediğini yazar. Bulut sağlayıcısının yönettiği cluster'larda control plane'in saat dilimine erişiminiz de olmayabilir. En temiz yol takvimi UTC kabul edip yerel saat kararını işin içine taşımak.
PostgreSQL'de `timestamp` mı, `timestamptz` mi kullanmalıyım?
Olmuş olaylar için timestamptz. Adı yanıltıcıdır. Saat dilimini saklamaz, değeri UTC'ye çevirip saklar, okurken oturumun TimeZone ayarına göre gösterir. timestamp ise duvar saatini offset'siz saklar. Bu sütuna now() yazdığınızda değer oturumun TimeZone ayarıyla duvar saatine çevrilir, farklı ayarla bağlanan iki istemci aynı anı farklı yazar. Temel kavramlar için SQL ve RDBMS rehberi iyi bir başlangıç.

Kaynakça

Bu yazıyı paylaş

Mustafa Kürşad Başer
Yazar

Mustafa Kürşad Başer

Kıdemli Yazılım Mühendisi

Karmaşık sorunlara zarif çözümler üretmekten keyif alan, tutkulu bir yazılım mühendisi. Kodlamanın ötesinde, teknoloji, sanat ve insan bilincinin kesişim noktalarını keşfetmekle derinden ilgileniyor.