Penjadwalan Otomatis & Solusi Bug Timezone H-1
Worker terjadwal yang mengeksekusi transaksi harian/bulanan otomatis dan mengirimkan email pengingat harian kepada pengguna aktif.
Membaca sekitar 2 menit
Worker terjadwal yang mengeksekusi transaksi harian/bulanan otomatis dan mengirimkan email pengingat harian kepada pengguna aktif.
Mengecek seluruh jadwal berulang, membandingkan tanggal eksekusi terakhir (lastgenerated), menyisipkan transaksi baru ke arus kas, lalu mengecek aktivitas hari ini untuk mengirim email pengingat via Nodemailer.
Menggunakan Intl.DateTimeFormat dengan locale 'en-CA' dan timezone 'Asia/Jakarta' untuk menghasilkan format YYYY-MM-DD yang kebal terhadap jebakan pergeseran tanggal UTC (bug H-1).
Masalahnya dulu
Tagihan bulanan seperti kos, internet, langganan aplikasi, maupun gaji bulanan selalu berulang di tanggal yang sama. Meminta pengguna menginputnya secara manual setiap bulan rawan terlupa. Namun saat membangun worker otomatis terjadwal, muncul jebakan klasik di server Node.js: mengambil tanggal dengan new Date().toISOString() menggunakan zona waktu UTC. Jika worker berjalan pukul 01.00 dini hari WIB (UTC+7), waktu di UTC masih pukul 18.00 hari sebelumnya (H-1)!
Formatting Tanggal Lokal Asia/Jakarta & Idempotensi lastgenerated
Untuk membasmi bug H-1 secara permanen, worker penjadwal menggunakan Intl.DateTimeFormat dengan locale 'en-CA' (yang secara native menghasilkan format standar ISO YYYY-MM-DD) dan mengunci timeZone ke 'Asia/Jakarta'. Selanjutnya, fungsi shouldGenerate memeriksa kolom lastgenerated pada setiap rencana berulang agar meskipun worker dipicu berkali-kali dalam sehari, transaksi hanya akan dibuat tepat satu kali per periode.
Jangan pernah mempercayai toISOString() untuk logika pergantian hari lokal; selalu kunci zona waktu sebelum memotong string tanggal.
Implementasi di ngodingan ini
Berikut adalah helper anti bug H-1 beserta logika evaluasi jadwal harian dan bulanan pada worker penjadwal otomatis:
// Helper anti bug H-1: selalu menghasilkan YYYY-MM-DD sesuai waktu WIB
function getLocalDateStr(date: Date | string = new Date()) {
return new Intl.DateTimeFormat("en-CA", {
timeZone: "Asia/Jakarta",
}).format(new Date(date));
}
function getLocalMonthStr(date: Date | string = new Date()) {
return getLocalDateStr(date).slice(0, 7); // YYYY-MM
}
function shouldGenerate(
repeattype: string,
repeatdate: number,
lastgenerated: string | Date | null,
todayDate: number,
monthStr: string
): boolean {
if (repeattype === "daily") {
const today = getLocalDateStr();
const last = lastgenerated ? getLocalDateStr(lastgenerated) : null;
return last !== today;
}
if (repeattype === "monthly") {
const lastMonth = lastgenerated ? getLocalMonthStr(lastgenerated) : null;
return todayDate === repeatdate && lastMonth !== monthStr;
}
return false;
}Jebakan dan detail yang harus dijaga
- Mengamankan worker terjadwal dengan verifikasi token rahasia (CRON_SECRET) agar hanya penjadwal resmi yang dapat memicu eksekusi transaksi otomatis.
- Menambahkan export const dynamic = 'force-dynamic' pada route handler agar Next.js tidak pernah melakukan caching statis pada eksekusi penjadwal.
- Mengirimkan pengingat email harian yang kontekstual: setelah selesai men-generate transaksi terjadwal, worker memeriksa setiap pengguna aktif yang mengaktifkan notifikasi apakah sudah memiliki catatan pemasukan atau pengeluaran hari ini, lalu mengirimkan email sapaan hanya untuk pos yang masih kosong.