ทำความเข้าใจ Unix Timestamp UTC: Timezone, Offset และการแปลงค่า

แผนที่โลกแสดงเส้นเวลา UTC และ Unix timestamp เชื่อมต่อกับนาฬิกาท้องถิ่นหลายเรือน

ถ้าคุณเคยเก็บข้อมูลวันที่ในฐานข้อมูล สร้าง API หรือดีบักบั๊กเกี่ยวกับการจัดตารางเวลาที่เกิดขึ้นเฉพาะในบางประเทศ คุณคงเคยเจอกับความสัมพันธ์ระหว่าง Unix timestamp กับ UTC มาบ้างแล้ว ความสับสนนี้เป็นเรื่องจริงและทำให้เกิดบั๊กในระบบ production ได้จริงๆ บทความนี้จะอธิบายอย่างละเอียดว่า UTC คืออะไร ทำไม Unix timestamp ถึงถูกกำหนดให้อิงกับ UTC เสมอ UTC offset ทำงานอย่างไร และวิธีแปลง timestamp อย่างถูกต้องใน JavaScript, Python และ PHP รวมถึงข้อผิดพลาดที่พบบ่อยที่สุดของนักพัฒนาและวิธีหลีกเลี่ยงครับ

สรุปประเด็นสำคัญ:

  • Unix timestamp นับจำนวนวินาทีนับตั้งแต่วันที่ 1 มกราคม 1970 เวลา 00:00:00 UTC เสมอ โดยไม่มี timezone แนบมาด้วย
  • UTC คือจุดอ้างอิงสากล ส่วนเวลาท้องถิ่นคือ UTC บวกหรือลบ offset เท่านั้น
  • การแปลง timestamp เป็นเวลาท้องถิ่นอย่างถูกต้องต้องรู้ timezone ปลายทาง ไม่ใช่แค่ตัวเลข offset
  • DST (Daylight Saving Time) ทำให้ offset ของ timezone เปลี่ยนไป นั่นคือสาเหตุที่การ hardcode offset ทำให้เกิดบั๊กได้

UTC คืออะไร และทำไมถึงสำคัญ?

UTC ย่อมาจาก Coordinated Universal Time คือมาตรฐานเวลาหลักที่ทั่วโลกใช้เป็นเกณฑ์ในการกำหนดนาฬิกาและเวลา ต่างจาก timezone ทั่วไปตรงที่ UTC ไม่มี offset ใดๆ มันคือจุดศูนย์กลาง ไม่มีการปรับเวลาตาม Daylight Saving Time และไม่เปลี่ยนแปลงตามประเทศหรือภูมิภาคใดๆ

ก่อนจะมี UTC นั้นมี GMT (Greenwich Mean Time) และหลายคนยังใช้สองคำนี้แทนกันอยู่ ในทางเทคนิคแล้ว UTC กับ GMT มีความแตกต่างกันเล็กน้อย แต่สำหรับงานพัฒนาซอฟต์แวร์ส่วนใหญ่ถือว่าเทียบเท่ากันได้ UTC ถูกกำหนดอย่างเป็นทางการโดย ITU-R TF.460 และดูแลรักษาด้วยนาฬิกาอะตอม

สำหรับนักพัฒนา UTC มีความสำคัญเพราะให้จุดอ้างอิงเดียวที่ชัดเจนและไม่คลุมเครือ ถ้าเซิร์ฟเวอร์ในแฟรงก์เฟิร์ตและผู้ใช้ในลอสแองเจลิสบันทึก event เดียวกันด้วย UTC ทั้งคู่ก็สามารถเปรียบเทียบ timestamp ได้ถูกต้องเสมอ แต่ถ้าแต่ละฝ่ายบันทึกเป็นเวลาท้องถิ่นโดยไม่มี metadata บอก timezone ปัญหาก็จะตามมาทันทีครับ

ทำไม Unix Timestamp ถึงเป็น UTC เสมอ

Unix timestamp (หรือที่เรียกว่า epoch time หรือ POSIX time) ถูกนิยามว่าเป็นจำนวนวินาทีที่ผ่านมาตั้งแต่ วันที่ 1 มกราคม 1970 เวลา 00:00:00 UTC จุดอ้างอิงนั้น คือเที่ยงคืนของวันที่ 1 มกราคม 1970 ตาม UTC ถูกฝังอยู่ในนิยามนี้ตั้งแต่ต้น ไม่มี Unix time เวอร์ชันที่เริ่มนับจากเวลาท้องถิ่นของนิวยอร์กหรือโตเกียว

นั่นหมายความว่าคำตอบของคำถาม "Unix timestamp เป็น UTC เสมอไหม?" คือ ใช่ โดยนิยาม ตัวเลข 1700000000 แทนช่วงเวลาเดียวกันทุกประการสำหรับทุกคนบนโลก สิ่งที่ต่างกันในแต่ละ timezone คือวิธีที่ช่วงเวลานั้น ถูกแสดงผล เท่านั้น

หากต้องการทำความเข้าใจพื้นฐานของแนวคิดนี้อย่างลึกซึ้งยิ่งขึ้น อ่านบทความของเราเรื่อง Epoch Time: พื้นฐานของ Unix Timestamp ได้เลยครับ

การออกแบบที่ยึด UTC เป็นหลักนี้คือหนึ่งในคุณสมบัติที่มีประโยชน์ที่สุดของ Unix time เพราะตัวเลขไม่ได้พกพาข้อมูล timezone ไปด้วย ระบบสองระบบในต่างประเทศจึงสามารถแลกเปลี่ยน timestamp และรู้ได้ทันทีว่าหมายถึงช่วงเวลาใด โดยไม่ต้องตกลงกันเพิ่มเติม

UTC กับเวลาท้องถิ่นและ Timezone

UTC คือจุดอ้างอิง ส่วนเวลาท้องถิ่นคือสิ่งที่ได้เมื่อนำกฎของ timezone มาใช้กับ UTC Timezone ไม่ใช่แค่ค่า offset ที่ตายตัว แต่เป็นชื่อภูมิภาคที่มีชุดกฎซึ่งอาจเปลี่ยนแปลงได้ตามเวลา (ส่วนใหญ่เพราะ Daylight Saving Time)

ตัวอย่างเช่น "Eastern Time" ในสหรัฐอเมริกาคือ UTC-5 ในช่วงฤดูหนาว และ UTC-4 ในช่วงฤดูร้อน ชื่อ timezone ในรูปแบบมาตรฐานคือ "America/New_York" ใน ฐานข้อมูล timezone ของ IANA ซึ่งเป็นแหล่งข้อมูลอ้างอิงหลักที่ภาษาโปรแกรมและระบบปฏิบัติการส่วนใหญ่ใช้

ข้อสรุปในทางปฏิบัติ: เมื่อเก็บ Unix timestamp ในฐานข้อมูล คุณกำลังเก็บค่า UTC เมื่อจะแสดงผลให้ผู้ใช้ ค่อยแปลงเป็น timezone ท้องถิ่นของเขา อย่าเก็บเวลาท้องถิ่นเป็นตัวเลขดิบแล้วสมมติว่าเป็น UTC เพราะนั่นคือจุดเริ่มต้นของบั๊กครับ

UTC Offset ทำงานอย่างไร

UTC offset บอกว่า timezone นั้นห่างจาก UTC เท่าไหร่ เขียนในรูปแบบ +HH:MM หรือ -HH:MM ตัวอย่างเช่น:

  • UTC+02:00 - Central European Summer Time (CEST) ใช้ในเยอรมนีและฝรั่งเศสช่วงฤดูร้อน
  • UTC-05:00 - Eastern Standard Time (EST) ใช้บนชายฝั่งตะวันออกของสหรัฐฯ ในช่วงฤดูหนาว
  • UTC+05:30 - India Standard Time (IST) ซึ่งเป็น offset แบบครึ่งชั่วโมง
  • UTC+07:00 - เวลามาตรฐานของประเทศไทย (ICT) ใช้ตลอดทั้งปี
  • UTC+00:00 - UTC เอง ซึ่งสหราชอาณาจักรก็ใช้ในช่วงฤดูหนาว (GMT)

ในการแปลง UTC epoch time เป็นเวลาท้องถิ่นด้วยตนเอง ให้บวกหรือลบ offset เป็นวินาที สำหรับ UTC+02:00 คือ 2 * 3600 = 7200 วินาที ดังนั้นถ้า Unix timestamp คือ 1700000000 เวลาท้องถิ่นในโซน UTC+02:00 จะสอดคล้องกับ 1700000000 + 7200 ก่อนจะ format ต่อไป

อย่างไรก็ตาม การคำนวณเองแบบนี้มีความเสี่ยงเพราะ offset เปลี่ยนได้ตาม DST ควรใช้ไลบรารี timezone ที่เหมาะสมแทนการ hardcode offset เสมอ ดูคู่มือเพิ่มเติมได้ที่ วิธีแปลง Unix Timestamp เป็นวันที่

การแปลง Unix Timestamp เป็นเวลาท้องถิ่น

ลองดูตัวอย่างจริงโดยใช้ Unix timestamp 1700000000 ซึ่งตรงกับ 14 พฤศจิกายน 2023 เวลา 22:13:20 UTC เราจะแปลงเป็น timezone "America/New_York" (UTC-5 ในเดือนพฤศจิกายน) ใน 3 ภาษาครับ

JavaScript

const ts = 1700000000;

// JavaScript Date รับค่าเป็น milliseconds
const date = new Date(ts * 1000);

// แสดงผลเป็นเวลาท้องถิ่นของนิวยอร์ก
const options = {
  timeZone: 'America/New_York',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  timeZoneName: 'short'
};

console.log(date.toLocaleString('en-US', options));
// Output: November 14, 2023 at 05:13:20 PM EST

สังเกตว่า object Date ของ JavaScript เก็บเวลาภายในเป็น UTC milliseconds method toLocaleString ที่ระบุ option timeZone จะจัดการ offset และกฎ DST ให้อัตโนมัติครับ

Python

from datetime import datetime, timezone
import zoneinfo  # Python 3.9+

ts = 1700000000

# สร้าง datetime ที่ระบุ timezone เป็น UTC จาก timestamp
utc_dt = datetime.fromtimestamp(ts, tz=timezone.utc)

# แปลงเป็นเวลาท้องถิ่นของนิวยอร์ก
ny_tz = zoneinfo.ZoneInfo('America/New_York')
local_dt = utc_dt.astimezone(ny_tz)

print(local_dt.strftime('%Y-%m-%d %H:%M:%S %Z'))
# Output: 2023-11-14 17:13:20 EST

จุดสำคัญคือการใช้ datetime.fromtimestamp(ts, tz=timezone.utc) ถ้าละเว้น argument tz Python จะใช้ timezone ท้องถิ่นของเซิร์ฟเวอร์ในการตีความ timestamp ซึ่งเป็นแหล่งที่มาของบั๊กที่พบบ่อยมากครับ

PHP

$ts = 1700000000;

// สร้าง object DateTime จาก Unix timestamp (เป็น UTC เสมอ)
$dt = new DateTime('@' . $ts);

// กำหนด timezone ปลายทาง
$dt->setTimezone(new DateTimeZone('America/New_York'));

echo $dt->format('Y-m-d H:i:s T');
// Output: 2023-11-14 17:13:20 EST

ใน PHP prefix @ ตอนสร้าง object DateTime บอกให้ PHP ตีความค่านั้นเป็น Unix timestamp (UTC) ถ้าไม่ใส่ PHP อาจตีความ string โดยใช้ค่า timezone เริ่มต้นของเซิร์ฟเวอร์แทนครับ

ข้อผิดพลาดที่นักพัฒนามักเจอ

แม้แต่นักพัฒนาที่มีประสบการณ์ก็ยังสะดุดกับการจัดการ timezone ได้ ต่อไปนี้คือปัญหาที่พบบ่อยที่สุดครับ

1. สมมติว่าเวลาท้องถิ่นของเซิร์ฟเวอร์คือ UTC

ถ้าเซิร์ฟเวอร์ตั้งค่าเป็น "Europe/Berlin" แล้วคุณเรียก time() ใน PHP หรือ datetime.now() ใน Python โดยไม่ระบุ argument timezone คุณจะได้เวลาท้องถิ่น ไม่ใช่ UTC ควรระบุ UTC อย่างชัดเจนเสมอเมื่อบันทึก timestamp

2. Hardcode UTC offset แทนการใช้ชื่อ timezone

การใช้ +02:00 เป็น offset คงที่สำหรับเยอรมนีจะพังในช่วงฤดูหนาวเมื่อเยอรมนีเปลี่ยนเป็น +01:00 ควรใช้ชื่อ timezone เสมอ เช่น Europe/Berlin เพื่อให้ไลบรารีใช้กฎ DST ที่ถูกต้องโดยอัตโนมัติ

3. เก็บ timestamp เป็น string เวลาท้องถิ่นโดยไม่มีข้อมูล timezone

String อย่าง 2023-11-14 17:13:20 ในฐานข้อมูลนั้นคลุมเครือมาก มันคือ UTC, EST หรือ ICT กันแน่? ถ้าเก็บ timestamp เป็น integer แบบ Unix หรือ string แบบ ISO 8601 ที่มี UTC offset (เช่น 2023-11-14T22:13:20Z) ความหมายจะชัดเจนไม่คลุมเครือ ดูการเปรียบเทียบได้ที่ Unix Timestamp Format กับ ISO 8601

4. ไม่คำนึงถึง DST ในการจัดตารางงาน

ถ้าคุณตั้งงานให้รันทุกวันเวลา "09:00 น." โดยบวก 86400 วินาทีต่อรอบ งานจะเหลื่อมไปหนึ่งชั่วโมงในวันที่ DST เปลี่ยน ควรใช้ไลบรารี cron หรือ scheduler ที่เข้าใจกฎ timezone

5. สับสนระหว่าง milliseconds กับ seconds

JavaScript ใช้ milliseconds สำหรับ object Date แต่ Unix timestamp ส่วนใหญ่เป็นหน่วย seconds การส่ง timestamp ที่เป็น seconds ให้ new Date() โดยไม่คูณด้วย 1000 จะได้วันที่ในเดือนมกราคม 1970 อ่านรายละเอียดเพิ่มเติมได้ที่บทความ Seconds, Milliseconds และ Microseconds

สรุป

Unix timestamp และ UTC ถูกออกแบบมาให้แยกกันไม่ออก timestamp คือค่า UTC เสมอ นับวินาทีจากจุดอ้างอิง UTC ที่ตายตัว ส่วนเวลาท้องถิ่นเป็นแค่รูปแบบการแสดงผล ไม่ใช่ timestamp ประเภทอื่น วิธีที่ปลอดภัยที่สุดคือเก็บทุกอย่างเป็น UTC แปลงเป็นเวลาท้องถิ่นเฉพาะตอนแสดงผลเท่านั้น และใช้ชื่อ timezone เสมอแทนการ hardcode offset ถ้าทำตามหลักการเหล่านี้ บั๊กส่วนใหญ่ที่เกี่ยวกับ timezone ก็จะหายไปเองครับ สำหรับแนวทางการเก็บค่าเหล่านี้ในฐานข้อมูล ดูได้ที่คู่มือ Unix Timestamp ในฐานข้อมูล

แปลง Unix timestamp เป็น UTC หรือเวลาท้องถิ่นด้วย unixtimestamp.app

แปลง Unix Timestamp เป็น UTC หรือเวลาท้องถิ่น - ทันที

วาง Unix timestamp ใดก็ได้แล้วดูผลลัพธ์ที่แปลงเป็น UTC และ timezone ท้องถิ่นของคุณในคลิกเดียว ฟรี ไม่ต้องสมัครสมาชิก รองรับทั้ง seconds และ milliseconds

ลองใช้เครื่องมือฟรีของเรา →

ใช่ครับ โดยนิยาม Unix epoch timestamp นับวินาทีตั้งแต่วันที่ 1 มกราคม 1970 เวลา 00:00:00 UTC ตัวเลขนั้นไม่มี timezone แนบอยู่ด้วย มันคือค่า UTC Timezone จะมีความสำคัญก็ต่อเมื่อคุณแปลง timestamp เป็นวันและเวลาที่มนุษย์อ่านได้เพื่อแสดงผลเท่านั้น

ใช้ไลบรารี timezone ในตัวของภาษาที่คุณใช้ครับ ใน JavaScript ใช้ toLocaleString พร้อม option timeZone ใน Python ใช้ datetime.fromtimestamp(ts, tz=timezone.utc).astimezone() พร้อมชื่อ timezone ใน PHP สร้าง DateTime จาก timestamp แล้วเรียก setTimezone() และควรใช้ชื่อ timezone เสมอ ไม่ใช่ค่า offset ดิบ

ปัญหานี้มักเกิดเพราะฟังก์ชันใช้ timezone เริ่มต้นของเซิร์ฟเวอร์แทน UTC ในการตีความ timestamp ครับ ใน Python การละเว้น argument tz ใน datetime.fromtimestamp() จะใช้เวลาท้องถิ่นของระบบ ใน PHP การไม่ใช้ prefix @ ก็มีผลเช่นเดียวกัน ควรระบุ UTC อย่างชัดเจนเสมอตั้งแต่ตอนสร้าง

UTC offset คือจำนวนชั่วโมง (และบางครั้งนาที) ที่ timezone นั้นอยู่ก่อนหน้าหรือหลัง UTC ครับ ตัวอย่างเช่น UTC-05:00 คือช้ากว่า UTC ห้าชั่วโมง เมื่อแปลง Unix timestamp เป็นเวลาท้องถิ่น offset จะถูกนำไปใช้กับค่า UTC เพราะ offset เปลี่ยนได้ตาม DST จึงควรใช้ชื่อ timezone แทนการ hardcode offset

ได้เลยครับ และนี่คือหนึ่งในข้อดีหลักของ Unix timestamp เพราะทุก timestamp คือค่า UTC คุณจึงเปรียบเทียบ timestamp ใดๆ ก็ได้โดยตรงในฐานะ integer ตัวเลขที่มากกว่าหมายถึงช่วงเวลาที่หลังกว่าเสมอ ไม่ว่า timestamp เหล่านั้นจะถูกสร้างจากที่ไหน ไม่จำเป็นต้องแปลง timezone ก่อนเปรียบเทียบ