API من هر اول ژانویه از کار میافتاد
API من دقیقاً در ساعت ۰۰:۰۰ UTC در اول ژانویه از کار افتاد.
این اتفاق در نیمهشبِ کاربران رخ نداد، بلکه در نیمهشب UTC رخ داد. کاربران در توکیو از ساعت ۹ صبح به وقت خود، با دادههای خراب مواجه شدند.
تستها با موفقیت انجام شدند. محیط staging هم درست کار میکرد. خطا فقط در production وجود داشت، زیرا سرور production از منطقه زمانی متفاوتی نسبت به staging استفاده میکرد.
مشکل در این منطق نهفته بود:
function getDailyReport(date) {
const start = new Date(date).toISOString().split('T')[0];
const end = new Date(start + 'T23:59:59Z');
return db.reports.findMany({
where: {
createdAt: { gte: new Date(start), lt: end }
}
});
}
وقتی تاریخی مثل "2026-01-01" را بدون زمان ارسال میکنید، سیستم از منطقه زمانی محلی استفاده میکند.
سرور staging ما از UTC استفاده میکرد. سرور production ما از US-East استفاده میکرد. این موضوع باعث ایجاد یک اختلاف زمانی ۵ ساعته در هر کوئریِ تاریخ میشد.
ما در هر کوئری، ۵ ساعت از دادهها را از دست میدادیم. اعداد آنقدر به واقعیت نزدیک بودند که هیچکس تا یک سال کامل متوجه آنها نشد.
راه حل ساده بود. با اضافه کردن زمان به رشته (string)، استفاده از UTC را اجبار کردیم:
function getDailyReport(date: string) {
const start = new Date(`${date}T00:00:00Z`);
const end = new Date(`${date}T23:59:59.999Z`);
return db.reports.findMany({
where: {
createdAt: { gte: start, lt: end }
}
});
}
تغییر کد چند ثانیه طول کشید، اما اصلاح سیستم یک هفته زمان برد. ما این چهار کار را انجام دادیم:
• افزودن یک assert برای منطقه زمانی در CI جهت اطمینان از اینکه محیط همیشه روی UTC باقی میماند.
• تنظیم TZ=UTC در تمام Dockerfileها برای یکسان نگه داشتن تمام کانتینرها.
• افزودن بررسی منطقه زمانی به اسکریپت استقرار (deploy script).
• نوشتن یک قانون linter برای علامتگذاری هر سازنده (constructor) Date که فاقد اطلاعات منطقه زمانی باشد.
باگهای منطقه زمانی خطرناک هستند. آنها سیستم شما را از کار نمیاندازند، بلکه دادههای اشتباهی تولید میکنند که درست به نظر میرسند. کاربران شما صفحه خطا را نخواهند دید؛ آنها اعداد اشتباه را میبینند و به آنها اعتماد میکنند.
این سه قانون را رعایت کنید:
- هرگز به منطقه زمانی سیستم اعتماد نکنید. همیشه
TZ=UTCرا تنظیم کنید. - هرگز تاریخها را بدون منطقه زمانی parse نکنید.
- هرگز فرض نکنید که منطقه زمانی CI شما با production مطابقت دارد.