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ım: "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. Son beş yıldır üç farklı şirkette benzer zaman yönetimi sorunlarıyla uğraştım ve şunu öğrendim: 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 kod
const 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:     232KB
dayjs:         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 identical
const dayjs = require('dayjs');
const utc = require('dayjs/plugin/utc');
const timezone = require('dayjs/plugin/timezone');

dayjs.extend(utc);
dayjs.extend(timezone);

// Moment.js kodu
const 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 immutable
const 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 handling
const date = new Date('2025-01-01T12:00:00Z');

// Intl API ile locale-aware formatting
const 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 kaydet
const 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 et
const 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 setup
const iterations = 100000;
const testDate = '2025-01-01T12:00:00Z';

// Test 1: Date parsing
console.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 kod
const 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 math
const 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 logic
const 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ı bul
grep -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ştur
const 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ştir
const 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 utilities
export const mockDate = (mockDateString: string) => {
  const mockDate = new Date(mockDateString);
  const originalNow = Date.now;
  
  Date.now = jest.fn(() => mockDate.getTime());
  
  return () => {
    Date.now = originalNow;
  };
};

// Test example
describe('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 tracking
const 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 importable
const 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.

Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts