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

Bangkok ไม่​ได้​เป็น +07:00 มา​ตลอด

ร้าน​ของ​คุณ​อยู่​ใน Bangkok ออ​เด​อร์​ที่​สั่ง​เวลา 09:00 ของ​วัน​ที่ 2026-10-02 ออก​จาก Java กับ Go เป็น 2026-10-02T09:00:00+07:00 offset ของ​ร้าน​คือ +07:00 และ​ไม่มี​ใคร​สงสัย​ค่า​นี้

สมมติ​ว่า​ร้าน​กำลัง​ย้าย​ข้อมูล​จาก​ระบบ​เดิม ระบบ​เดิม​เติม 1900-01-01 00:00 ลง​ใน​ช่อง​วัน​ส่ง​ขอ​งอ​อเด​อร์ ที่​ยัง​ไม่มี​วัน​ส่ง เป็น​ค่า​แทน “ยัง​ไม่​กำหนด” แล้ว service ใหม่​อ่าน​ค่า​นั้น​ใน​โซน Asia/Bangkok

คำถาม​ของ​บท​นี้​มี​สอง​ข้อ ข้อ​แรก​คือ offset ของ Bangkok ใน​วัน​นั้น​คือ​อะไร ข้อ​ที่​สอง​คือ ห้า​ภาษา​พิมพ์​มัน​ออก​มา​เหมือน​กัน​ไหม คำ​ตอบ​ของ​ข้อ​แรก​คือ​ไม่ใช่ +07:00 และ​คำ​ตอบ​ของ​ข้อ​ที่​สอง​คือ​ไม่​เหมือน ทั้ง​ที่ instant ที่​วัด​ได้​ตรง​กัน​ทุก​ค่า

📦 orderctl — ของ​ชิ้น​เดียวกัน​กับ​คอร์ส​ภาษา​อื่น​ใน​เสา​นี้

orderctl คือ​เครื่อง​มือ​ประมวล​ผล​ออ​เด​อร์​ที่​คอร์ส​ภาษา​ใน​เสา​นี้​สร้าง​ซ้ำ​ที​ละ​ภาษา repo ตัวอย่าง กำลัง​จัด​ทำ

ระหว่าง​นี้​ทุก​ค่า​ใน​บท​นี้​รัน​เอง​ได้​ด้วย npm run verify:time-semantics ซึ่ง​เรียก python3 scripts/time-semantics/probe.py · ไม่​ต้อง​มี Docker แต่​การ​รัน​ครั้ง​แรก ต้อง​ต่อ​เน็ต​เพื่อ​ดึง chrono กับ chrono-tz จาก crates.io

tzdata ให้ Asia/Bangkok ในปี 1900 ใช้ offset ที่​มี​เศษ​วินาที เพราะ​มัน​เป็น​เวลา​ที่​ตั้ง​ตาม​ดวง​อาทิตย์​ของ​เมือง

ก่อน​จะ​มี​โซน​มาตรฐาน แต่ละ​เมือง​ตั้ง​นาฬิกา​ตาม​ดวง​อาทิตย์​ของ​ตัว​เอง เวลา​แบบ​นี้​เรียก​ว่า local mean timelocal mean time (LMT)เวลา​ท้อง​ถิ่น​ที่​ตั้ง​ตาม​ดวง​อาทิตย์​ของ​เมือง​นั้น ก่อน​ที่​จะ​มี​โซน​มาตรฐาน · offset ของ​มัน​จึง​มี​เศษ​วินาที Bangkok ในปี 1900 ใช้ +06:42:04 และ tzdata ยัง​เก็บ​กฎ​นั้น​ไว้​สำหรับ​วัน​ที่​ใน​ช่วง​นั้น (เวลา​ท้อง​ถิ่น​ที่​ตั้ง​ตาม​ดวง​อาทิตย์ ย่อ​ว่า LMT) offset ของ​มัน​จึง​ไม่​ลงตัว​ที่​นาที

ซอร์ส​ของ tzdata เก็บ​กฎ​ของ Asia/Bangkok ไว้​ใน file ชื่อ asia แถว​ต้นๆ ของ​โซน​นี้​ใช้ offset เดียว​กับ +06:42:04 ช่วง​แรก​ใช้​ชื่อ​ย่อ LMT ช่วง​ถัด​มา​ใช้​ชื่อ​ย่อ BMT และ​มี​คอม​เมน​ต์​ขยาย​ว่า “Bangkok Mean Time” แถว​สุดท้าย​คือ offset +07:00 ที่​ใช้​มา​ถึง​วัน​นี้

1900-01-01 ตก​อยู่​ใน​แถว​ของ BMT ซึ่ง​เป็น local mean time ของ Bangkok เอง สาม​ภาษา​ที่ probe อ่าน​ชื่อ​ย่อ​ได้ คือ Go, Python และ Rust ตอบ BMT ตรง​กัน​ทั้ง​สาม​ภาษา

ข้อมูล​ชุด​นี้​ไม่​ได้​มา​พร้อม​ระบบ​ปฏิบัติ​การ​เสมอ​ไป แต่ละ​ภาษา​ได้​มัน​มา​คนละ​ทาง และ​แต่ละ​ทาง​อาจ​เป็น​คนละ​รุ่น รุ่น​ของ​ข้อมูล​ที่​ภาษา​นั้น​อ่าน​จริง​ตอน​รัน​เรียก​ว่า tzdata versiontzdata versionรุ่น​ของ IANA tz database ที่​ภาษา​นั้น​ใช้​อยู่​จริง​ตอน​รัน · ภาษา​ต่าง​กัน​บน​เครื่อง​เดียวกัน​อาจ​ใช้​คนละ​รุ่น ตาราง​ที่​เทียบ​ผล​หลาย​ภาษา​จึง​ต้อง​ระบุ​รุ่น​ของ​ทุก​ภาษา ไม่ใช่​รุ่น​เดียว​ของ​เครื่อง (รุ่น​ของ tzdata) บน​เครื่อง​ที่​วัด ห้า​ภาษา​อ่าน​สาม​รุ่น

  • Java openjdk 25.0.4.1 อ่าน​รุ่น 2026b จาก tzdb.dat ของ JDK
  • Node v24.19.0 อ่าน​รุ่น 2026b จาก ICU ที่​ฝัง​ใน node
  • Go go1.27.0 อ่าน​รุ่น 2026c จาก /usr/share/zoneinfo ของ​ระบบ
  • Python 3.14.6 อ่าน​รุ่น 2026c จาก /usr/share/zoneinfo ของ​ระบบ
  • Rust อ่าน​รุ่น 2025b ที่​ฝัง​ใน chrono-tz 0.10.4

ข้อ​อ้าง​ของ​บท​นี้​ยืน​อยู่​ได้​บน​สาม​รุ่น​นั้น เพราะ​สี่​ภาษา​ที่​อ่าน offset เป็น​วินาที​ได้​ค่า​เดียวกัน​ทั้ง​สี่​ภาษา ถ้า​กฎ​ของ Bangkok ในปี 1900 ถูก​แก้​ใน​รุ่น​หน้า probe จะ​แดง ไม่ใช่​บท​เรียน​ที่​ผิด​เงียบๆ

flowchart LR
  A["1900-01-01 00:00 ที่ Asia/Bangkok"] --> B["tzdata ตอบ offset +06:42:04"]
  B --> C["instant 1899-12-31T17:17:56Z"]
  B --> D["ตัวพิมพ์ที่มีช่องวินาทีของ offset"]
  B --> E["ตัวพิมพ์ที่ไม่มีช่องวินาทีของ offset"]
  D --> F["+06:42:04 · Java · Python · layout ที่ Go เขียนเอง"]
  E --> G["+06:42 · time.RFC3339 ของ Go · to_rfc3339 ของ chrono"]
  E --> H["GMT+0642 · toString ของ Node ที่ TZ=Asia/Bangkok"]

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

สี่​ภาษา​ที่​อ่าน offset เป็น​วินาที​ได้ 24124 และ​ทุก instant ที่ probe อ่าน​จาก​เวลา​นี้​ของ Bangkok ชี้​จุด​เดียว​กับ 1899-12-31T17:17:56Z

ทุก​การ​เรียก​ข้าง​ล่าง​ตัด​มา​จาก runner ของ probe ใน scripts/time-semantics/runners/ และ​ค่า​ใน​คอม​เมน​ต์ คือ​ค่าที่ probe วัด​ได้

1900-01-01 00:00 ของ Asia/Bangkok ไม่​ได้​ตก​อยู่​ใน​ช่วง​ที่​นาฬิกา​ของ​โซน​นั้น​กระโดด​หรือ​ถอย probe จึง​ผูก​ค่า​ของ Go ใน​ทรง​นี้​เป็น​ข้อ​อ้าง​ได้ ต่าง​จาก​ค่า​ของ Go ใน บท​ที่ 1

scripts/time-semantics/runners/Shapes.java (เฉพาะ​ทรง F)
ZoneId bkk = ZoneId.of("Asia/Bangkok");
ZonedDateTime b = ZonedDateTime.of(1900, 1, 1, 0, 0, 0, 0, bkk);
b.getOffset().toString(); // +06:42:04
b.getOffset().getTotalSeconds(); // 24124
b.toString(); // 1900-01-01T00:00+06:42:04[Asia/Bangkok]
DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(b); // 1900-01-01T00:00:00+06:42:04
b.toInstant().toString(); // 1899-12-31T17:17:56Z

24124 วินาที​คือ 6 ชั่วโมง 42 นาที กับ​อีก 4 วินาที และ instant ที่​ได้​คือ 00:00:00 ลบ​ด้วย offset นั้น จึง​เป็น 17:17:56 ของ​วัน​ก่อน​หน้า​ใน UTC วินาที​ที่ 56 คือ​ร่อง​รอย​ของ​สี่​วินาที​นั้น

scripts/time-semantics/runners/shapes.py (เฉพาะ​ทรง F)
from datetime import datetime
from zoneinfo import ZoneInfo
bkk = ZoneInfo("Asia/Bangkok")
b = datetime(1900, 1, 1, tzinfo=bkk)
b.isoformat() # 1900-01-01T00:00:00+06:42:04
int(b.utcoffset().total_seconds()) # 24124
b.tzname() # BMT

เอกสาร​ของ isoformat() เขียน​รูป​ของ offset ไว้​เป็น +HH:MM[:SS[.ffffff]] คือ​มี​ช่อง​วินาที​ที่​ใส่​เมื่อ​จำเป็น offset ของ Bangkok ปี 1900 มี​วินาที Python จึง​พิมพ์​มัน​ออก​มา probe ไม่​ได้​อ่าน instant ของ Python ใน​ทรง​นี้

scripts/time-semantics/runners/go/main.go (เฉพาะ​ทรง F)
bkk, err := time.LoadLocation("Asia/Bangkok")
// ...
t := time.Date(1900, 1, 1, 0, 0, 0, 0, bkk)
name, off := t.Zone()
t.Format("2006-01-02T15:04:05-07:00:00") // 1900-01-01T00:00:00+06:42:04
t.Format(time.RFC3339) // 1900-01-01T00:00:00+06:42
fmt.Sprint(off) // 24124
name // BMT
t.UTC().Format(time.RFC3339) // 1899-12-31T17:17:56Z

ค่า t ตัว​เดียวกัน​ให้​สอง​สตริง layout ที่​เขียน​เอง​มี -07:00:00 ซึ่ง​เอกสาร​ของ package time ระบุ​ว่า​เป็น​รูป ±hh:mm:ss ส่วน time.RFC3339 คือ layout "2006-01-02T15:04:05Z07:00" ซึ่ง​มี​แค่​ชั่วโมง​กับ​นาที

off ยัง​เป็น 24124 และ instant ใน UTC ยัง​เป็น 1899-12-31T17:17:56Z สี่​วินาที​จึง​ไม่​ได้​หาย​จาก​ค่าที่ Go เก็บ มัน​หาย​ตอน​พิมพ์​ด้วย time.RFC3339 เท่านั้น

scripts/time-semantics/runners/rs/src/main.rs (เฉพาะ​ทรง F)
use chrono::{Offset, TimeZone, Utc};
use chrono_tz::Asia::Bangkok;
let b = Bangkok.with_ymd_and_hms(1900, 1, 1, 0, 0, 0).unwrap();
b.offset().fix().local_minus_utc(); // 24124
b.to_rfc3339(); // 1900-01-01T00:00:00+06:42
b.with_timezone(&Utc).to_rfc3339(); // 1899-12-31T17:17:56+00:00
format!("{}", b.format("%Z")); // BMT

รูป​แบบ​เดียว​กับ time.RFC3339 ของ Go offset ที่​อ่าน​เป็น​วินาที​ยัง​เป็น 24124 instant ยัง​ตรง​กับ Java แต่ to_rfc3339() พิมพ์ +06:42 chrono ไม่​ได้​ตัด​วินาที​อยู่​ตัว​เดียว time.RFC3339 ของ Go ให้​สตริง​เดียวกัน​ทุก​ตัว​อักษร

new Date(1900, 0, 1) อ่าน​วัน​ที่​และ​ชั่วโมง​เป็น​เวลา​ท้อง​ถิ่น คือ​เวลา​ใน​โซน​ของ process ซึ่ง​มา​จาก TZ probe จึง​รัน file เดียวกัน​สอง​ครั้ง

scripts/time-semantics/runners/shapes.mjs (เฉพาะ​ทรง F)
// 1900-01-01 00:00 ในโซนของ process · เดือนของ Date เริ่มที่ 0
const b = new Date(1900, 0, 1);
b.toString();
b.toISOString();
b.getTimezoneOffset();
TZtoString()toISOString()getTimezoneOffset()
Asia/BangkokMon Jan 01 1900 00:00:00 GMT+0642 (Indochina Time)1899-12-31T17:17:56.000Z-402
America/New_YorkMon Jan 01 1900 00:00:00 GMT-0500 (Eastern Standard Time)1900-01-01T05:00:00.000Z300

แถว​แรก​คือ​แถว​ที่​ตอบ​คำถาม​ของ​บท​นี้ toISOString() ได้ 17:17:56 ตรง​กับ Java ทุก​วินาที สี่​วินาที​จึง​ยัง​อยู่​ใน instant ที่ Node เก็บ

แต่​อีก​สอง​การ​เรียก​ไม่มี​มัน toString() พิมพ์ GMT+0642 และ getTimezoneOffset() คืน -402 ซึ่ง​เป็น​นาที คือ 6 ชั่วโมง 42 นาที​พอดี ชื่อ​ใน​วงเล็บ​ก็​ไม่ใช่ BMT แต่​เป็น Indochina Time

แถว​ที่​สอง​ไม่​ได้​ตอบ​คำถาม​ของ​บท​นี้​เลย บน server ที่​ตั้ง TZ=America/New_York การ​เรียก new Date(1900, 0, 1) ได้ 00:00 ของ New York คือ 1900-01-01T05:00:00.000Z ไม่ใช่ 00:00 ของ Bangkok

ค่าที่​ทุก​ภาษา​เก็บ​มี​สี่​วินาที​ครบ แต่ RFC 3339 ไม่มี​ช่อง​ให้​มัน และ RFC เขียน​ข้อ​นี้​ไว้​เอง

grammar ของ RFC 3339 นิยาม​ช่อง offset ไว้​บรรทัด​เดียว

RFC 3339 §5.6
time-numoffset = ("+" / "-") time-hour ":" time-minute

ไม่มี time-second ใน​บรรทัด​นั้น สตริง​ที่​ผ่าน grammar นี้​จึง​เขียน offset +06:42:04 ไม่​ได้ time.RFC3339 ของ Go กับ to_rfc3339() ของ chrono ทำ​ตาม grammar นี้ จึง​ได้ +06:42 ทั้ง​สอง​ตัว

RFC 3339 รู้​ปัญหา​นี้ ใน​หัวข้อ​ตัวอย่าง​มัน​ยก​เวลา​ของ​เนเธอร์แลนด์​ที่ offset มี​เศษ​วินาที แล้ว​เขียน​ว่า “This time zone cannot be represented exactly using the HH:MM format, and this timestamp uses the closest representable UTC offset.”

ตัวอย่าง​ของ RFC เลื่อน​เวลา​บน​หน้า​ปัด​ตาม​ไป​ด้วย เพื่อ​ให้​สตริง​ยัง​ชี้ instant เดิม ส่วน Go กับ chrono พิมพ์ 00:00:00 ตาม​เดิม แล้ว​พิมพ์ offset โดย​ไม่มี​สี่​วินาที

ผล​คือ​สตริง 1900-01-01T00:00:00+06:42 อ่าน​ตาม grammar แล้ว​ชี้​จุด​ที่​ห่าง​จาก 1899-12-31T17:17:56Z อยู่​สี่​วินาที นี่​เป็นการ​คิด​เลข​จาก offset ที่​วัด​ได้ probe ไม่​ได้ parse สตริง​นี้​กลับ บท​นี้​จึง​ไม่​พิมพ์​ค่าที่ parse ได้

Javadoc ของ ZoneOffset.getId() แยก​รูป​ของ offset ไว้​สาม​แบบ และ​เขียน​กำกับ​แบบ​ที่​มี​วินาที​ไว้​ว่า “+hh:mm:ss or -hh:mm:ss - if the seconds are non-zero (not ISO-8601)”

สตริง 1900-01-01T00:00:00+06:42:04 ของ Java และ Python จึง​ตรง​กับ instant แต่​ไม่​ผ่าน grammar ของ RFC 3339 probe ไม่​ได้​วัด​ว่า parser ตัว​ไหน​รับ​หรือ​ปฏิเสธ​สตริง​นี้ สิ่ง​ที่​พูด​ได้​คือ​มัน​อยู่​นอก grammar

สรุป​เป็น​สอง​ทาง​ที่​มี​ราคา​ทั้ง​คู่

  • สตริง​ที่​ผ่าน RFC 3339 อย่าง +06:42 ของ Go กับ chrono ไม่มี​สี่​วินาที
  • สตริง​ที่​มี​สี่​วินาที​ครบ อย่าง +06:42:04 ของ Java กับ Python ไม่​ผ่าน grammar ของ RFC 3339

ใน​ค่าที่ probe วัด รูป​ที่​ทั้ง​ผ่าน grammar และ​ชี้ instant ตรง คือ instant ที่​เขียน​เป็น UTC เช่น 1899-12-31T17:17:56Z ของ Java กับ Go เพราะ offset ของ UTC ไม่มี​เศษ​วินาที​ให้​หาย

รวม​ทุก​ตัว​พิมพ์​ไว้​ใน​ตาราง​เดียว พร้อม​รุ่น​ของ tzdata ที่​แต่ละ​ภาษา​อ่าน​จริง

ภาษาการ​เรียกค่าที่​วัด​ได้วินาที​ของ offsettzdata ที่​ใช้
Java openjdk 25.0.4.1ISO_OFFSET_DATE_TIME.format(b)1900-01-01T00:00:00+06:42:04ครบ2026b · tzdb.dat ของ JDK
Python 3.14.6b.isoformat()1900-01-01T00:00:00+06:42:04ครบ2026c · /usr/share/zoneinfo ของ​ระบบ
Go go1.27.0t.Format("2006-01-02T15:04:05-07:00:00")1900-01-01T00:00:00+06:42:04ครบ2026c · /usr/share/zoneinfo ของ​ระบบ
Go go1.27.0t.Format(time.RFC3339)1900-01-01T00:00:00+06:42หาย2026c · /usr/share/zoneinfo ของ​ระบบ
Rust chrono 0.4.45b.to_rfc3339()1900-01-01T00:00:00+06:42หาย2025b · ฝัง​ใน chrono-tz 0.10.4
Node v24.19.0 ที่ TZ=Asia/Bangkokb.toString()Mon Jan 01 1900 00:00:00 GMT+0642 (Indochina Time)หาย2026b · ICU ที่​ฝัง​ใน node
Node v24.19.0 ที่ TZ=Asia/Bangkokb.toISOString()1899-12-31T17:17:56.000Zอยู่​ใน instant2026b · ICU ที่​ฝัง​ใน node

offset ที่​อ่าน​เป็น​วินาที​ได้ 24124 ทั้ง​สี่​ภาษา​ที่ probe อ่าน คือ Java, Go, Python และ Rust ทั้ง​ที่​สี่​ภาษา​นั้น​อ่าน tzdata สาม​รุ่น

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

ค่า​แทน​วัน​ที่​ว่าง​ที่​สอง​ระบบ​พิมพ์​ไม่​ตรง​กัน

หัวข้อ​ที่​มีชื่อ​ว่า “ค่า​แทน​วัน​ที่​ว่าง​ที่​สอง​ระบบ​พิมพ์​ไม่​ตรง​กัน”

ค่า 1900-01-01 00:00 ค่า​เดียว ออก​จาก​สอง service เป็น​สอง​สตริง และ​ไม่มี​สตริง​ไหน​มี +07:00

กลับ​ไป​ที่​การ​ย้าย​ข้อมูล​ของ​ร้าน สมมติ​ว่า service ที่​เขียน​ด้วย Java ส่ง​ช่อง​วัน​ส่ง​ที่​ว่าง​ออก​ไป​เป็น 1900-01-01T00:00:00+06:42:04 ส่วน service ที่​เขียน​ด้วย Go และ​ใช้ time.RFC3339 ส่ง​ออก​ไป​เป็น 1900-01-01T00:00:00+06:42

ทั้ง​สอง​สตริง​มา​จาก instant เดียวกัน คือ 1899-12-31T17:17:56Z ซึ่ง probe วัด​ได้​ตรง​กัน​ทั้ง​ใน Java และ Go แต่​ระบบ​ปลาย​ทาง​ที่​ถาม​ว่า “ช่อง​นี้​เป็น​ค่า​แทน​วัน​ที่​ว่าง​หรือ​เปล่า” ด้วย​การ​เทียบ​สตริง จะ​ตอบ​สอง​คำ​ตอบ

ระบบ​ที่​สาม​ซึ่ง​เชื่อ​ว่า Bangkok เป็น +07:00 เสมอ จะ​ไม่​เจอ​ทั้ง​สอง​สตริง เพราะ​ไม่มี​การ​เรียก​ไหน​ที่ probe วัด พิมพ์ +07:00 ให้​วัน​ที่​นั้น

ราคา​ของ​การ​เชื่อ​ว่า​โซน​มี offset เดียว

ทางออก​ไม่ใช่​การ​เลือก​ภาษา​ที่​พิมพ์​ครบ แต่​คือ​การ​เลิก​ส่ง offset แทน​โซน ส่ง instant เป็น UTC แล้ว​ส่ง​ชื่อ​โซน​แยก​ไป​อีก​ช่อง ปลาย​ทาง​จะ​ได้ offset ของ​วัน​นั้น​จาก tzdata ของ​มัน​เอง

ราคา​คือ​ปลาย​ทาง​ต้อง​มี tzdata และ​รุ่น​ที่​มัน​อ่าน​อาจ​ไม่​ตรง​กับ​ต้นทาง ข้อ​อ้าง​ของ​บท​นี้​รอด​สาม​รุ่น เพราะ​กฎ​ของ Bangkok ปี 1900 ไม่​ได้​เปลี่ยน​ใน​สาม​รุ่น​นั้น กฎ​ที่​เพิ่ง​ถูก​แก้​ไม่มี​การ​รับปาก​แบบ​นี้

🔁 เส้น​แบ่ง​กับ​บทความ​เรื่อง​ภาษา​ไทย

บทความ ภาษา​ไทย​ทำให้ code พัง​คนละ​ชั้น​กัน​สี่​ชั้น เป็น​เจ้าของ​เรื่อง การ​แสดง​ปี คือ locale th-TH ที่​ผูก​กับ​ปฏิทิน​พุทธ บท​นี้​เป็น​เจ้าของ จุด​บน​เส้น​เวลา คือ offset ที่​โซน​ใช้​ใน​วัน​นั้น

สอง​เรื่อง​นี้​แยก​กัน​ได้ ปี​ที่​แสดง​บน​หน้า​จอ​เปลี่ยน​ตาม locale ของ​ผู้​อ่าน แต่​ไม่​ได้​ย้าย instant ไป​ไหน ส่วน offset +06:42:04 ย้าย instant จริง และ​ไม่​ได้​เปลี่ยน​ตาม locale

โซน​เดียวกัน​ใช้​คนละ offset ใน​คนละ​ปี และ​บท​นี้​ปิด​จุด​สุดท้าย​ใน​หก​จุด​ของ​คอร์ส

Asia/Bangkok ปี 2026 ใช้ +07:00 และ Asia/Bangkok ปี 1900 ใช้ +06:42:04 ทั้ง​สอง​ค่า​มา​จาก​โซน​เดียวกัน offset จึง​เป็น​คำ​ตอบ​ของ​คำถาม​ว่า “โซน​นี้ ณ จุด​นี้” ไม่ใช่​คุณสมบัติ​ของ​โซน

จุด​ที่​หก​คือ​ข้อ​นี้ กฎ​ของ​โซน​เปลี่ยน​ตาม​ปี ใน​ทรง​ที่ probe วัด สี่​ภาษา​ที่​อ่าน offset เป็น​วินาที​ได้​ค่า​เดียวกัน จาก tzdata สาม​รุ่น แต่​ตัว​พิมพ์​ที่​ใช้​รูป​แบบ​ของ RFC 3339 พิมพ์​มัน​ได้​ไม่​ครบ และ​ไม่มี​การ​เรียก​ไหน​แดง

ใน​ค่าที่​บท​นี้​วัด รูป​ที่​ไม่​เสีย​สี่​วินาที​และ​ผ่าน grammar คือ instant ที่​เขียน​เป็น UTC ซึ่ง​ไม่​พึ่ง offset บน​สาย​เลย บท​ที่ 8 รวม​ทุก​จุด​ของ​คอร์ส​เข้า​เป็น​ท่อ​เดียว แล้ว​วัด​ว่า slot ส่ง​ของ​รอด​ถึง​ปลาย​ทาง​ได้​อย่างไร


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • IANA tz database — file asia (เข้า​ถึง 2026-10-03) — ที่มา​ของ​แถว​ของ Asia/Bangkok ที่​ใช้ offset เดียว​กับ +06:42:04 ชื่อ​ย่อ LMT กับ BMT และ​คอม​เมน​ต์ “Bangkok Mean Time”

  • RFC 3339 — Date and Time on the Internet: Timestamps (เข้า​ถึง 2026-10-03) — ที่มา​ของ​บรรทัด time-numoffset ใน §5.6 และ​ของ​ประโยค “This time zone cannot be represented exactly using the HH:MM format, and this timestamp uses the closest representable UTC offset.” ใน §5.8

  • Go — package time, constants (เข้า​ถึง 2026-10-03) — ที่มา​ของ RFC3339 = "2006-01-02T15:04:05Z07:00" และ​ของ​รูป "-07:00:00" ที่​เอกสาร​ระบุ​ว่า​เป็น ±hh:mm:ss

  • chrono 0.4.45 — DateTime::to_rfc3339 (เข้า​ถึง 2026-10-03) — ที่มา​ของ to_rfc3339() ที่​บท​นี้​วัด

  • Java SE 25 — ZoneOffset.getId() (เข้า​ถึง 2026-10-03) — ที่มา​ของ​ประโยค “+hh:mm:ss or -hh:mm:ss - if the seconds are non-zero (not ISO-8601)”

  • Python — datetime.isoformat (เข้า​ถึง 2026-10-03) — ที่มา​ของ​รูป offset +HH:MM[:SS[.ffffff]]

  • ECMAScript — TimeZoneString ( tv ) (เข้า​ถึง 2026-10-03) — ขั้น​สุดท้าย​ของ​มัน​เขียน​ว่า “Return the string-concatenation of offsetSign, offsetHour, offsetMinute, and timeZoneName.” คือ toString() ของ Date ไม่มี​ช่อง​วินาที​ของ offset และ​ชื่อ​ใน​วงเล็บ​เป็น​สิ่ง​ที่ implementation เลือก​เอง

  • ECMAScript — Date.UTC (เข้า​ถึง 2026-10-03) — หมายเหตุ​ของ​มัน​เขียน​ว่า Date.UTC ต่าง​จาก constructor ของ Date ตรง​ที่ “it interprets the arguments in UTC rather than as local time.” คือ new Date(1900, 0, 1) อ่าน​วัน​ที่​และ​ชั่วโมง​เป็น​เวลา​ท้อง​ถิ่น​ของ process

  • ค่า​ทุก​ค่า​ใน​บท​นี้​มา​จาก scripts/time-semantics/measured.json ซึ่ง scripts/time-semantics/probe.py เขียน​เมื่อ 2026-10-02 บน darwin/arm64 · probe วัด​ทั้ง​ชุด​สาม​รอบ​ต่อ​การ​เรียก​หนึ่ง​ครั้ง และ Node รัน​ด้วย TZ ที่ probe ตั้ง​เอง​ทุก​ครั้ง

เช็กความเข้าใจ — บทที่ 7

ข้อ 1 / 3

tzdata ให้ `Asia/Bangkok` วันที่ `1900-01-01 00:00` ใช้ offset อะไร