Skip to content
~/sph.sh

Node.js ile Zaman Yönetimi: Moment.js Olmadan Zamana Hükmetmek

Production'da yaşanan zaman yönetimi problemleri, Moment.js'den modern alternatiflere geçiş stratejileri ve UTC handling best practice'leri. Timezone savaşlarından çıkışın yolu.

Geçen hafta Slack'te bir mesaj aldık: "Müşteriler ödeme yaparken 'geçmiş tarihli işlem' hatası alıyor, ama tarih bugün!" Bu tip problemlerle karşılaştığınızda, production sistemlerde zaman yönetiminin ne kadar karmaşık olduğunu anlıyorsunuz. Zaman yönetimi sorunlarıyla uğraşmak şunu öğretti: En büyük düşmanımız timezone confusion, en büyük dostumuz ise UTC standardı.

Production'da Zaman Savaşları

Quarterly Report Sunum Trajedisi

İlk karşılaştığım büyük zaman problemi, quarterly board toplantısı sırasında yaşandı. CEO yatırımcılara quarterly sonuçları sunuyor, dashboard'da transaction analitiği gösteriliyor, tam o sırada grafik bomba gibi patlıyor: "Invalid Date" her yerde.

Problem şuydu: Moment.js kullanıyorduk ve bir timezone dönüştürme işleminde mutable object'ler yüzünden orijinal date object'i bozuluyordu.

typescript
// Felakete yol açan kodconst moment = require('moment');
const generateReport = (startDate) => {  const reportStart = moment(startDate);  const reportEnd = reportStart.add(7, 'days'); // MUTABLE! startDate'i bozdu    // startDate artık 7 gün ileride  return getTransactionsBetween(startDate, reportEnd);};

Bu utanç verici deneyimden sonra mutable time objects'lerle asla daha çalışmadım. Immutability, zaman işlemlerinde can kurtarıcı.

Müşteri Ödeme Sistemi Krizi

İkinci büyük incident'ım yoğun alışveriş sezonumuz sırasında yaşandı. Kullanıcılar akşam saatlerinde ödeme yapmaya çalışırken sürekli "geçmiş tarihli işlem" hatası alıyorlardı. İlk başta business logic'te sorun arıyorduk, ama asıl problem timezone handling'deydi.

typescript
// Sorunlu kod: Timezone karışıklığıconst processPayment = (paymentDate: string) => {  const localDate = moment(paymentDate).format('YYYY-MM-DD');  const utcDate = moment.utc(paymentDate).format('YYYY-MM-DD');    if (localDate !== utcDate) {    throw new Error('Geçmiş tarihli işlem');  }    return processPaymentLogic();};

Istanbul timezone'ında saat 23:30'da yapılan bir ödeme, UTC'de ertesi günün 00:30'una denk geliyordu ve bir tarih sınırı problemi yaratıyordu. Bu yüzden localDate ve utcDate farklı çıkıyordu ve ödeme reddediliyordu.

Moment.js'den Neden Vazgeçtik?

Moment.js uzun yıllar boyunca JavaScript zaman işlemlerinin kralıydı, ama zamanla problems accumulate oldu:

1. Bundle Size Problemi

232KB! Bu, küçük bir React app'inin yarısı kadar. Modern web development'ta bundle size critical metric olduğu için, bu acceptable değil.

bash
# Bundle size karşılaştırmasımoment.js:     232KBdayjs:         6.5KB  (96% daha küçük)date-fns:      13.1KB (94% daha küçük)vanilla Date:  0KB    (100% daha küçük)

2. Mutable Objects Felaketi

Moment.js'in en büyük design flaw'u mutability. Bir date object'i modify ettiğinizde, original reference da değişiyor:

typescript
const moment = require('moment');
const originalDate = moment('2025-01-01');const nextWeek = originalDate.add(7, 'days');
console.log(originalDate.format()); // 2025-01-08 (!)console.log(nextWeek.format());     // 2025-01-08

Bu, özellikle React component'larında unexpected re-renders'e yol açıyor.

3. Tree-shaking Yokluğu

Moment.js monolithic bir yapıda. Sadece format() method'unu kullansan bile, tüm kütüphane bundle'a dahil oluyor. Modern bundler'lar bunu optimize edemiyor.

Modern Alternatifler: Gerçek Dünya Karşılaştırması

Üç farklı projede farklı alternatifler denedim. Her birinin kendine göre use case'leri var.

Day.js: En Kolay Migration

Artıları:

  • Moment.js API'sine neredeyse identical
  • Bundle size 6.5KB
  • Immutable objects
  • Plugin sistemi ile extensible

Eksileri:

  • Core feature'ları için plugin yüklemen gerekiyor
  • Documentation bazen eksik
  • Smaller community
typescript
// Moment.js'den Day.js'e migration - almost identicalconst dayjs = require('dayjs');const utc = require('dayjs/plugin/utc');const timezone = require('dayjs/plugin/timezone');
dayjs.extend(utc);dayjs.extend(timezone);
// Moment.js koduconst moment = require('moment');const istanbulTime = moment.utc('2025-01-01').tz('Europe/Istanbul');
// Day.js kodu (neredeyse aynı)const istanbulTime = dayjs.utc('2025-01-01').tz('Europe/Istanbul');

date-fns: Functional Programming Yaklaşımı

Artıları:

  • Mükemmel tree-shaking (sadece kullandığın function'lar bundle'a dahil)
  • Immutable by design
  • TypeScript support harika
  • Lodash-style API

Eksileri:

  • Öğrenme curve'u var
  • Timezone support için date-fns-tz package'i gerekiyor
  • Verbose syntax
typescript
import { format, addDays, parseISO } from 'date-fns';import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
// Functional approach - her function immutableconst originalDate = parseISO('2025-01-01');const nextWeek = addDays(originalDate, 7);
console.log(format(originalDate, 'yyyy-MM-dd')); // 2025-01-01 (unchanged!)console.log(format(nextWeek, 'yyyy-MM-dd'));     // 2025-01-08

Vanilla JavaScript: Modern API'lerle Yeniden Değerlendirmek

ES2015'ten sonra JavaScript'in date handling capabilities'leri significantly improved. Özellikle Intl API modern browser'larda çok güçlü.

typescript
// Modern JavaScript ile timezone handlingconst date = new Date('2025-01-01T12:00:00Z');
// Intl API ile locale-aware formattingconst istanbulTime = new Intl.DateTimeFormat('tr-TR', {  timeZone: 'Europe/Istanbul',  year: 'numeric',  month: '2-digit',  day: '2-digit',  hour: '2-digit',  minute: '2-digit'}).format(date);
console.log(istanbulTime); // 01.01.2025 15:00

Production-Ready UTC Strategy

Yıllar boyunca öğrendiğim en önemli ders: Her şeyi UTC'de sakla, conversion'ı client-side yap.

Database Layer: UTC Only

typescript
// Database'e her zaman UTC olarak kaydetconst saveUserAction = async (userId: number, action: string) => {  const timestamp = new Date().toISOString(); // UTC ISO string    await db.query(    'INSERT INTO user_actions (user_id, action, created_at) VALUES (?, ?, ?)',    [userId, action, timestamp]  );};

API Layer: UTC to Local Conversion

typescript
// API response'unda client timezone'ına göre convert etconst getUserActions = async (userId: number, clientTimezone: string) => {  const actions = await db.query(    'SELECT * FROM user_actions WHERE user_id = ? ORDER BY created_at DESC',    [userId]  );    return actions.map(action => ({    ...action,    created_at: action.created_at, // UTC olarak bırak    local_time: new Intl.DateTimeFormat('en-US', {      timeZone: clientTimezone,      year: 'numeric',      month: '2-digit',       day: '2-digit',      hour: '2-digit',      minute: '2-digit'    }).format(new Date(action.created_at))  }));};

Performance Benchmarks: Gerçek Dünya Testleri

100,000 date operation'ı ile yaptığım benchmark sonuçları (Node.js 18.x):

typescript
// Benchmark setupconst iterations = 100000;const testDate = '2025-01-01T12:00:00Z';
// Test 1: Date parsingconsole.time('Moment.js parsing');for (let i = 0; i < iterations; i++) {  moment(testDate).format('YYYY-MM-DD');}console.timeEnd('Moment.js parsing'); // ~1,847ms
console.time('Day.js parsing');  for (let i = 0; i < iterations; i++) {  dayjs(testDate).format('YYYY-MM-DD');}console.timeEnd('Day.js parsing'); // ~284ms
console.time('date-fns parsing');for (let i = 0; i < iterations; i++) {  format(parseISO(testDate), 'yyyy-MM-dd');}console.timeEnd('date-fns parsing'); // ~198ms
console.time('Vanilla JS parsing');for (let i = 0; i < iterations; i++) {  new Date(testDate).toISOString().split('T')[0];}console.timeEnd('Vanilla JS parsing'); // ~67ms

Sonuçlar:

  • Vanilla JavaScript: ~67ms (en hızlı)
  • date-fns: ~198ms (3x daha yavaş)
  • Day.js: ~284ms (4.2x daha yavaş)
  • Moment.js: ~1,847ms (27x daha yavaş!)

DST ve Timezone Edge Cases

DST Transition Problemi

Daylight Saving Time transition'larında saatler ya geri ya da ileri alınır. Bu, business logic'te unexpected behavior'lara yol açabilir.

typescript
// DST transition'ında tehlikeli kodconst calculateBusinessHours = (startDate: string, endDate: string) => {  const start = new Date(startDate);  const end = new Date(endDate);    const hours = (end.getTime() - start.getTime()) / (1000 * 60 * 60);  return hours; // DST'de 23 veya 25 saat olabilir!};
// Güvenli UTC yaklaşımıconst calculateBusinessHoursUTC = (startDate: string, endDate: string) => {  const start = new Date(startDate);  const end = new Date(endDate);    // Her zaman UTC'de hesapla  const utcHours = (end.getUTCTime() - start.getUTCTime()) / (1000 * 60 * 60);  return utcHours; // Her zaman predictable};

Calendar Math Edge Cases

typescript
// Tehlikeli: Local timezone'da date mathconst addBusinessDays = (date: Date, days: number) => {  const result = new Date(date);  let addedDays = 0;    while (addedDays < days) {    result.setDate(result.getDate() + 1);    // Weekend check local timezone'da yanlış olabilir    if (result.getDay() !== 0 && result.getDay() !== 6) {      addedDays++;    }  }  return result;};
// Güvenli: UTC'de business logicconst addBusinessDaysUTC = (date: Date, days: number) => {  const result = new Date(date);  let addedDays = 0;    while (addedDays < days) {    result.setUTCDate(result.getUTCDate() + 1);    // UTC day check    if (result.getUTCDay() !== 0 && result.getUTCDay() !== 6) {      addedDays++;    }  }  return result;};

Migration Strategy: Adım Adım Geçiş

1. Audit Phase

bash
# Codebase'de Moment.js usage'ını bulgrep -r "moment\|\.format\|\.add\|\.subtract" src/rg "require.*moment|import.*moment" --type ts --type js

2. Gradual Migration

typescript
// 1. Adım: Utility functions oluşturconst dateUtils = {  format: (date: Date | string, format: string) => {    // İlk başta Moment.js wrapper    return moment(date).format(format);  },    addDays: (date: Date | string, days: number) => {    return moment(date).add(days, 'days').toDate();  }};
// 2. Adım: Moment.js'i utility functions'a replace et// Before:const formatted = moment(date).format('YYYY-MM-DD');// After:const formatted = dateUtils.format(date, 'YYYY-MM-DD');
// 3. Adım: Utility functions'ın implementation'ını değiştirconst dateUtils = {  format: (date: Date | string, format: string) => {    // Artık Day.js kullan    return dayjs(date).format(format);  },    addDays: (date: Date | string, days: number) => {    return dayjs(date).add(days, 'day').toDate();  }};

3. Testing Strategy

typescript
// Time-dependent kod için test utilitiesexport const mockDate = (mockDateString: string) => {  const mockDate = new Date(mockDateString);  const originalNow = Date.now;    Date.now = jest.fn(() => mockDate.getTime());    return () => {    Date.now = originalNow;  };};
// Test exampledescribe('Payment processing', () => {  it('should handle timezone correctly', () => {    const restoreDate = mockDate('2025-01-01T23:30:00.000Z');        // Istanbul timezone'da test    process.env.TZ = 'Europe/Istanbul';        const result = processPayment('2025-01-01T23:30:00.000Z');    expect(result).toBeTruthy();        restoreDate();  });});

Monitoring ve Alerting

Production'da zaman ile ilgili problem'ları early detect etmek için monitoring strategy'si geliştirdim:

typescript
// Time-related metrics trackingconst trackTimeOperation = async (operation: string, fn: () => Promise<any>) => {  const start = process.hrtime.bigint();  const startDate = new Date();    try {    const result = await fn();        const duration = Number(process.hrtime.bigint() - start) / 1000000;        // Metrics'e gönder    metrics.histogram('time_operation_duration', duration, {      operation,      success: 'true'    });        // Timezone sanity check    const endDate = new Date();    const expectedDuration = endDate.getTime() - startDate.getTime();        if (Math.abs(duration - expectedDuration) > 1000) { // 1 saniye threshold      logger.warn('Time drift detected', {        operation,        measured: duration,        expected: expectedDuration,        drift: Math.abs(duration - expectedDuration)      });    }        return result;  } catch (error) {    metrics.histogram('time_operation_duration', 0, {      operation,      success: 'false'    });    throw error;  }};

Yeni Projeler İçin Öneriler

Yılların deneyimi sonrasında tavsiyelerim:

Küçük Projeler (< 10 developer)

Vanilla JavaScript + Intl API kullan

Advantages:

  • Zero bundle size impact
  • Native performance
  • Modern browser support excellent
  • No dependency management
typescript
// Basit ama güçlüconst formatDate = (date: Date, locale: string, timeZone: string) => {  return new Intl.DateTimeFormat(locale, {    timeZone,    year: 'numeric',    month: 'long',     day: 'numeric'  }).format(date);};

Orta Ölçekli Projeler (10-50 developer)

Day.js kullan

Moment.js'den migration ease'i ve küçük bundle size avantajı var. Plugin system ile ihtiyaca göre extend edebiliyorsun.

typescript
import dayjs from 'dayjs';import utc from 'dayjs/plugin/utc';import timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc);dayjs.extend(timezone);
const formatForTimezone = (date: string, tz: string) => {  return dayjs.utc(date).tz(tz).format('MMM DD, YYYY HH:mm');};

Büyük Projeler (50+ developer)

date-fns kullan

Tree-shaking benefits, functional programming approach ve excellent TypeScript support büyük codebase'lerde game-changer.

typescript
import { format, parseISO, addDays } from 'date-fns';import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
// Her function independently importableconst processDate = (dateString: string, timeZone: string) => {  const date = parseISO(dateString);  const zonedDate = utcToZonedTime(date, timeZone);  return format(zonedDate, 'yyyy-MM-dd HH:mm:ss zzz');};

Production Checklist: Zaman Yönetimi

  • UTC standardı: Tüm timestamp'ler UTC'de saklanıyor
  • Client-side conversion: Timezone conversion'ı UI layer'da yapılıyor
  • DST testing: DST transition date'lerinde testler var
  • Bundle size check: Date kütüphanesi bundle impact'i acceptable
  • Performance benchmark: Critical path'lerde date operation'ları test edildi
  • Timezone validation: User timezone input'ları validate ediliyor
  • Error handling: Invalid date'ler gracefully handle ediliyor
  • Monitoring: Time-related error'lar için alerting var

Sonuç: Zamana Hükmetmenin Sırrı

Üç farklı şirkette, on farklı zaman problemiyle uğraştıktan sonra şunu öğrendim: Zaman yönetiminde simplicity wins.

En önemli lesson learned:

  1. UTC standardını benimse - Her şeyi UTC'de sakla
  2. Immutable objects kullan - Mutable date'ler production nightmare'ı
  3. Bundle size'ı göz önünde bulundur - 232KB Moment.js modern app'te acceptable değil
  4. Client-side'da convert et - Timezone logic'ini UI layer'da tut
  5. Edge case'leri test et - DST, leap year, timezone transition'ları

Moment.js deprecated oldu, ama onun yerine gelen alternatifler çok daha iyi. Day.js migration ease'i sağlıyor, date-fns performance ve tree-shaking sunuyor, vanilla JavaScript ise zero-cost abstraction'ı getiriyor.

Hangi yaklaşımı seçersen seç, en critical kısım UTC standardı. Bu rule'u benimser ve uygulamanın her layer'ında consistently uygularsan, zamanla ilgili problem'ların %90'ı kaybolur.

Şimdiye kadar yaşadığım en büyük time-related incident, quarterly report sırasında yaşanan Moment.js mutable object problemi oldu. O günden sonra, zaman işlemlerinde mutable state'den uzak duruyorum ve production environment'ta timezone edge case'lerini comprehensive test ediyorum.

Time management, backend development'ta underestimated bir konu, ama doğru yaklaşımla production'da confident kod yazabiliyorsun. UTC standardı, immutable objects ve proper testing strategy ile zamana gerçekten hükmedebiliyorsun.

İlgili Yazılar