ระบบไม่ได้เตือนผิดเวลา มันเตือนถูกเวลา แค่คนละที่กับที่คุณอยู่
เวลาที่เก็บกับเวลาที่แสดงเป็นคนละเรื่อง เก็บเป็น UTC รับ input พร้อม timezone แล้วแปลงที่เดียวตอนแสดง เทสต์ด้วย TZ สองค่า ไม่งั้นจะผ่านตลอดไปบนเครื่องเรา
ลูกค้าทักมาว่าได้อีเมลแจ้งเตือนนัดตอนตีสอง
นัดจริงคือเก้าโมงเช้า
เปิดดูในระบบ เวลาถูกทุกอย่าง กดเข้าไปดูในแอดมิน ก็ขึ้นเก้าโมง ลองสร้างนัดใหม่เอง ส่งเตือนมาตรงเป๊ะ
แล้วก็สรุปกับตัวเองว่าคงเป็นที่อีเมลเขา
ไม่ใช่ที่อีเมลเขา
เวลาที่เราเห็นบนจอ กับเวลาที่เก็บอยู่ในฐานข้อมูล ไม่ใช่ตัวเดียวกัน
เครื่องเราตั้งเป็นเวลาไทย เปิดแอดมินขึ้นมาก็เลยเห็นเก้าโมง เพราะเบราว์เซอร์แปลงให้ก่อนวาดลงจอ
ตัวเลขที่เก็บจริงคือ 02:00 UTC
ใครก็ตามที่เปิดดูจากเครื่องที่ไม่ได้ตั้งเป็นเวลาไทย จะเห็นคนละเวลากับเรา และ cron ที่ยิงอีเมลก็รันบนเซิร์ฟเวอร์ ซึ่งแทบทุกเจ้าตั้งเป็น UTC มาให้ตั้งแต่แรก
เราไม่เคยเจอ เพราะเราเป็นคนเดียวที่ใช้ และเราอยู่ไทยตลอด
หลักการมีข้อเดียว
เวลาที่เก็บ กับเวลาที่แสดง เป็นคนละเรื่องกัน ห้ามปนกัน
เก็บเป็น UTC เสมอ แปลงตอนจะเอาขึ้นจอเท่านั้น
ไม่ใช่เก็บเวลาไทยแล้วหวังว่าทุกเครื่องจะอ่านเหมือนกัน
ทำจริงมีสามชั้น แต่ละชั้นทำหน้าที่ต่างกัน
ชั้นแรก ฐานข้อมูลเก็บ UTC อย่างเดียว
ใช้ชนิดข้อมูลที่มี timezone ติดมาด้วย ไม่ใช่ตัวที่เก็บแต่ตัวเลขเปล่า ๆ ถ้าเก็บเปล่า ๆ อีกหกเดือนไม่มีใครตอบได้ว่าเลขนั้นคือเวลาที่ไหน
ชั้นที่สอง ตอนรับค่าจากผู้ใช้ ต้องรู้ว่าเขาหมายถึงเวลาที่ไหน
คนกรอก "9 โมง" เขาหมายถึงเก้าโมงในเมืองที่เขานั่งอยู่ ไม่ใช่เก้าโมงของเซิร์ฟเวอร์ ถ้าไม่ส่ง timezone มาด้วย ค่านั้นแปลไม่ได้ เดาได้อย่างเดียว
ชั้นที่สาม ตอนแสดงผลค่อยแปลง
แปลงตรงที่จะวาดลงจอ ตรงเดียว ไม่ใช่แปลงกระจายทั่วโค้ด สองที่แปลงคนละแบบเมื่อไหร่ คือได้บั๊กที่ไล่ยากที่สุดประเภทหนึ่ง
จำง่าย ๆ เก็บ UTC รับพร้อม timezone แสดงค่อยแปลง
ทีนี้เรื่องที่เกี่ยวกับ AI โดยตรง
บอก AI ว่า "เก็บเวลานัด" มันจะเขียน new Date() ให้ แล้วมันจะทำงานได้จริงบนเครื่องเรา
เพราะเครื่องเราตั้งเวลาไทย เซิร์ฟเวอร์ตอน dev ก็เครื่องเดียวกัน ทุกอย่างตรงกันหมดจนกว่าจะขึ้น production
มันไม่ได้เขียนผิด มันเขียนสิ่งที่เราขอ เราแค่ไม่ได้บอกว่าจะมีคนเปิดจากที่อื่น
เอาไปใช้วันนี้ เริ่มจากทดสอบก่อน
เปลี่ยน timezone ในเครื่องตัวเองเป็น UTC หรือจะเอาให้ชัดก็ New York แล้วรีเฟรชหน้าที่มีเวลา
ถ้าตัวเลขบนจอขยับ แปลว่าสิ่งที่เห็นอยู่ทุกวันคือเวลาของเครื่องเรา ไม่ใช่ของระบบ
แล้วลองกดสร้างนัดใหม่ตอนที่ยังตั้ง timezone นั้นอยู่ ดูว่ามันเก็บอะไรลงไป
แล้วบอก AI แบบนี้
ไล่เรื่องเวลาในโปรเจกต์นี้ให้หน่อย
- database: เก็บเป็น UTC ทั้งหมด ใช้ชนิดที่มี timezone ไม่ใช่ตัวเปล่า
- ตอนรับ input: ต้องรับ timezone ของผู้ใช้มาด้วย ห้ามเดาจากเซิร์ฟเวอร์
- ตอนแสดงผล: แปลงที่ layer เดียว ไม่แปลงกระจาย
- cron/job: ระบุ timezone ให้ชัด อย่าพึ่งค่า default ของเครื่อง
- เขียนเทสต์ที่รันด้วย TZ=UTC กับ TZ=Asia/Bangkok แล้ว assert ว่าได้ผลเดียวกัน
ข้อสุดท้ายสำคัญที่สุด ถ้าเทสต์รันแค่ timezone เดียว มันจะผ่านตลอดไป แล้วเราจะไม่มีวันรู้