ข้าม​ไป​ยัง​เนื้อหา

เวลา​ที่​เชื่อ​ไม่​ได้ — ครึ่ง​ชั่วโมง​ที่​ไม่มี​อยู่​จริง และ​ภาษา​เดียว​ที่​บอก​ว่า​มัน​ไม่มี

ประเทศไทย​ไม่มี DST คน​ไทย​จึง​มัก​คิด​ว่า​เรื่อง timezone ไม่ใช่​ปัญหา​ของ​ตัว​เอง คอร์ส​นี้​หยิบ​เวลา​ขอ​งอ​อเด​อร์ คือ​เวลา​สั่ง เวลา​ตัด​รอบ วัน​ส่ง และ slot ส่ง​ของ แล้ว​เดิน​ตาม​มัน​ข้าม​ห้า​ภาษา เพื่อ​ถาม​คำถาม​เดียว​ว่า​เวลา​ที่​ถึง​ปลาย​ทาง​ยัง​ชี้​จุด​เดิม​บน​เส้น​เวลา​อยู่​หรือ​เปล่า คำ​ตอบ​ที่​วัด​ได้​คือ​ไม่​เสมอ​ไป และ​ใน​ทรง​ที่ probe วัด มี​ภาษา​เดียว​จาก​ห้า​ภาษา​ที่​บอก​คุณ
ความคืบหน้า0 / 8 บทเรียน

คำถาม​เดียว คือ​เวลา​ที่​ถึง​ปลาย​ทาง​ยัง​ชี้​จุด​เดิม​บน​เส้น​เวลา​หรือ​ไม่ · คำ​ตอบ​วัด​เป็น​สตริง​ของ​เวลา​ล้วน

คำถาม​คือ เวลา​ขอ​งอ​อเด​อร์​ที่​เดิน​ผ่าน​ระบบ ยัง​ชี้​จุด​เดิม​บน​เส้น​เวลา​อยู่​หรือ​เปล่า

ประเทศไทย​ใช้ +07:00 ตลอด​ปี แต่​ระบบ​ไทย​คุย​กับ​ระบบ​ใน​โซน​ที่​มี DST และ Bangkok เอง​ก็​เคย​ใช้ offset อื่น คอร์ส​นี้​จึง​เป็น​ปัญหา​ของ​คน​ไทย​ด้วย ไม่ใช่​แค่​ของ​คน​ที่​อยู่​ใน​โซน​ที่​มี DST

ที่ America/New_York วัน​ที่ 2026-03-08 offset เปลี่ยน​จาก -05:00 เป็น -04:00 และ​นาฬิกา​กระโดด​ข้าม​ไป​หนึ่ง​ช่วง เวลา 02:30 ของ​วัน​นั้น​จึง​ไม่มี​อยู่​จริง แล้ว​ห้า​ภาษา​ก็​ยัง​ตอบ​มัน​กลับ​มา:

  • Java ZonedDateTime.of(...) คืน 2026-03-08T03:30-04:00[America/New_York]
  • Node ที่ TZ=America/New_York คืน 03:30:00 GMT-0400
  • Python เก็บ 2026-03-08T02:30:00-05:00 ไว้​ตาม​เดิม ทั้ง​ที่​เวลา​นี้​ไม่มี​อยู่
  • Go คืน​เวลา​ค่า​หนึ่ง​ออก​มา แต่​เอกสาร​ไม่​รับปาก​ว่า​จะ​เป็น​ค่า​ไหน ดู​หัวข้อ​ถัด​ไป
  • Rust คืน LocalResult::None และ​เป็น​ภาษา​เดียว​ที่​บอก​ว่า​เวลา​นี้​ไม่มี​อยู่

ค่า​ทั้ง​ห้า​วัด​บน darwin/arm64 และ​ห้า​ภาษา​ใช้ tzdata สาม​รุ่น: Java กับ Node 2026b · Go กับ Python อ่าน 2026c จาก​ระบบ · chrono-tz ฝัง 2025b ไว้​ใน​ตัว

ธี​สิ​ส​ของ​คอร์ส​จึง​เป็น​ประโยค​เดียว คือ เวลา​ท้อง​ถิ่น​ค่า​หนึ่ง​ไม่​ได้​ระบุ​จุด​เดียว​บน​เส้น​เวลา มัน​อาจ​ไม่มี​จุด​เลย มี​สอง​จุด หรือ​มี​จุด​เดียว​ที่​แต่ละ​ภาษา​เลือก​ต่าง​กัน และ​ใน​ทรง​ที่ probe วัด มี​ภาษา​เดียว​จาก​ห้า​ภาษา​ที่​บอก​คุณ

Go เลือก​ฝั่ง​หนึ่ง​ของ​การ​เปลี่ยน DST ให้ แต่​เอกสาร​ของ​มัน​บอก​ตรงๆ ว่า​ไม่​รับปาก​ว่า​ฝั่ง​ไหน

go doc time.Date เขียน​ไว้​ว่า

“Date returns a time that is correct in one of the two zones involved in the transition, but it does not guarantee which.”

ค่าที่ Go คืนให้​ใน​เวลา​ที่​ไม่มี​อยู่​จริง​และ​ใน​เวลา​ที่​มี​สอง​ครั้ง จึง​เป็น​ค่า​ของ​เครื่อง​ที่​วัด​เท่านั้น คอร์ส​นี้​พิมพ์​มัน​พร้อม​ชื่อ​เครื่อง​เสมอ และ​ไม่​เคย​ใช้​มัน​เป็น​คำ​ตอบ​ของ Go ทั้ง​ภาษา

หก​หัวข้อ​นี้​คือ​โครง​ของ​แปดบท และ​ทุก​ค่า​ข้าง​ล่าง​เป็น​ค่าที่​วัด​มา​แล้ว

02:30 ของ​วัน​ที่ 2026-03-08 ที่ America/New_York ไม่มี​อยู่​จริง Java กับ Node เลื่อน​ไป​ข้าง​หน้า Python เก็บ​ไว้​ตาม​เดิม และ Rust ตอบ LocalResult::None

วัน​ส่ง​ของ 2026-10-02 ของ​ร้าน​ใน Bangkok คือ 2026-10-01T17:00:00Z ใน UTC ซึ่ง​เป็น​คนละ​วัน ที่ TZ=America/New_York สตริง​สอง​ตัว​ที่​ต่าง​กัน​แค่ T00:00 ทำให้ Node อ่าน​คนละ​โซน:

  • new Date("2026-10-02") ได้ 2026-10-02T00:00:00.000Z คือ​อ่าน​เป็น UTC
  • new Date("2026-10-02T00:00") ได้ 2026-10-02T04:00:00.000Z คือ​อ่าน​เป็น​เวลา​ท้อง​ถิ่น

parse "2026-03-07T12:00-05:00" แล้ว​บวก​หนึ่ง​วัน Java Go Python และ Rust ได้ 12:00-05:00 ตรง​กัน​ทั้ง​สี่​ภาษา ส่วน​ค่าที่​บวก​บน​โซน America/New_York ได้ 12:00-04:00 เพราะ offset ไม่​เดิน​ตาม DST

Node ทิ้ง offset ตั้งแต่ parse และ​ให้​คำ​ตอบ​ตาม TZ ของ​เครื่อง​ที่​รัน:

  • TZ=America/New_York ได้ 2026-03-08T16:00:00.000Z
  • TZ=Asia/Bangkok ได้ 2026-03-08T17:00:00.000Z

เมื่อ​เวลา​ขอ​งอ​อเด​อร์​ข้าม JSON Go ส่ง "2026-10-02T09:00:00+07:00" ซึ่ง​เหลือ​แค่ offset ส่วน json.dumps ของ Python โยน TypeError · ใน​ภาษา​ที่​วัด ไม่มี​ภาษา​ไหน​ส่ง​ชื่อ​โซน Asia/Bangkok ไป​บน​สาย

01:30 ของ​วัน​ที่ 2026-11-01 ที่ America/New_York เกิด​ขึ้น​สอง​ครั้ง Rust คืน LocalResult::Ambiguous(01:30-04:00, 01:30-05:00) ให้​คุณ​เลือก​เอง ส่วน Python ได้ -04:00 เมื่อ fold=0 และ​ได้ -05:00 เมื่อ fold=1

เริ่ม​ที่ 2026-03-07 12:00 ใน America/New_York แล้ว​บวก​สอง​แบบ:

  • Java plusDays(1) ได้ 12:00-04:00 ส่วน plus(Duration.ofHours(24)) ได้ 13:00-04:00
  • Python timedelta(days=1) ได้ 12:00-04:00 และ timedelta(hours=24) ก็ได้ 12:00-04:00 · Python ตอบ​สอง​คำถาม​เหมือน​กัน

Bangkok ในปี 1900 ใช้ +06:42:04 ไม่ใช่ +07:00 · Java กับ Python พิมพ์​ค่า​นี้​ครบ ส่วน time.RFC3339 ของ Go กับ to_rfc3339() ของ chrono พิมพ์ +06:42 เพราะ offset ของ RFC 3339 ไม่มี​ช่อง​วินาที · ค่าที่​พิมพ์​ออก​มา​จึง​ห่าง​จาก​เวลา​จริง​สี่​วินาที

ประกาศ​ไว้​ตรง​นี้ เพื่อ​ไม่​ให้​ใคร​อ่าน​จบ​แล้ว​เชื่อ​ว่า​ได้​ของ​ที่​คอร์ส​นี้​ไม่​ได้​ให้

  • ไม่​วัด​ความเร็ว ทั้ง​คอร์ส​ไม่มี​หน่วย​เวลา​ที่​วัด​จาก​การ​รัน​สัก​ตัว หน่วย​คือ​สตริง​ของ​เวลา offset ที่​อ่าน​จาก API ชื่อ class ของ exception และ variant ของ enum
  • ไม่​สอน​การ​แสดง​ปี ปฏิทิน​พุทธ​กับ th-TH เป็น​ของ​บทความ ภาษา​ไทย​ทำให้ code พัง​คนละ​ชั้น​กัน​สี่​ชั้น คอร์ส​นี้​เป็น​เจ้าของ​จุด​บน​เส้น​เวลา
  • ไม่​แตะ​นาฬิกา monotonic คอร์ส​นี้​ไม่​วัด​เวลา​ที่​ผ่าน​ไป​ของ​การ​รัน
  • ไม่​แตะ timestamptz ของ PostgreSQL มัน​ต้อง​ใช้ Docker ซึ่ง​จะ​ดัน probe ทั้ง​ตัว​ออก​ไป​นอก CI · ตัด​ออก​ตั้งแต่​ตอน​ออกแบบ
  • ไม่​สอน Temporal ของ JavaScript Node 24 ที่ CI ใช้​ยัง​ไม่มี​มัน​เป็น​ค่า​ตั้ง​ต้น
  • ไม่​แตะ leap second

อ่าน​ห้า​ภาษา​ออก​พอ​ตาม​ตัว​อย่าง​สั้นๆ ได้​ก็​พอ ไม่​ต้อง​รู้​เรื่อง DST มา​ก่อน

  • อ่าน Rust Go และ Java ออก ระดับ​ที่​ตาม​ตัวอย่าง​สิบ​บรรทัด​ได้​ก็​พอ ถ้า​ยัง​ไม่​ถึง​ตรง​นั้น เริ่ม​ที่ Rust จาก​ศูนย์ Go จาก​ศูนย์ หรือ Java จาก​ศูนย์ ก่อน
  • ไม่​ต้อง​รู้จัก DST มา​ก่อน บท​ที่ 1 อธิบาย​มัน​ตอน​ที่​มัน​โผล่​ครั้ง​แรก
  • ควร​เคย​อ่าน​คอร์ส จำนวนเต็ม​ที่​เชื่อ​ไม่​ได้ คอร์ส​นี้​ใช้​วิธี​วัด​แบบ​เดียวกัน กับ​ห้า​ภาษา​เดียวกัน แต่​เดิน​ตาม​เวลา​ขอ​งอ​อเด​อร์​แทน​ยอด​ออ​เด​อร์
  • ต้อง​มี Rust Go JDK 25 Node และ Python 3 เพราะ​ทุก​ข้อ​อ้าง​มา​จาก probe ที่​คุณ​รัน​เอง​ได้ · ไม่​ต้อง​มี Docker

แผนผัง​นี้​เติม​ขึ้น​เอง​เมื่อ​บท​เรียน​ถูก​เพิ่ม​เข้า​มา