ตัวห่างไกลแต่ใจอยู่ timezone เดียวกัน
หนึ่งในปัญหาปวดหัวของเหล่ามนุษย์สายเดต้า คือ เวลา
ก็ไม่ได้จะสื่อว่าเวลาน้อย งานเยอะ หรืออะไรทำนองนั้นหรอกนะฮะ (แม้จะจริงก็เถอะ) แต่นี่เป็นบล็อกเล่าถึงการจัดการข้อมูลที่เป็นประเภทวันที่และเวลา เช่น ข้อมูลวันที่ขึ้นเครื่อง-ลงเครื่องในตารางการบิน ข้อมูลวันเวลาเข้าใช้งานโปรแกรมของตารางเก็บข้อมูลลูกค้า และอื่นๆ
ปัญหาของเวลาในเชิงโปรแกรมมิ่ง
คือว่านะฮะ การเก็บข้อมูลเวลาเนี่ย ปัญหาอันดับแรกที่มักจะมองข้ามกันคือเขตเวลา หรือ Timezone นั่นแหละ มันจะน่าเบื่อมากกกกก ถ้าเราต้องมาจัดการเวลาในหลายๆ ประเทศ เอ๊ะ ประเทศนี้ต้องบวกกี่ชั่วโมง ประเทศนั้นต้องลบไปอีกกี่ชั่วโมง ตรงนี้คือจุดแรกที่ต้องคำนึงถึง
อีกจุดนึง คือ format เพราะ database แต่ละยี่ห้อจะรองรับค่าเวลาไม่เหมือนกัน บางตัวก็จะเป็นปี-เดือน-วัน อีกเจ้ากลับเป็นวัน-เดือน-ปี ตรงนี้ก็ต้องคิดเหมือนกันนะฮะ แต่จะไม่พูดถึงในตอนนี้นะ
ชีวิตเราจะต้องวนเวียนกับการแปลงค่านั่นนี่ไปมาตลอดนั่นแหละฮะ
Coordinated Universal Time
Coordinated Universal Time หรือย่อว่า UTC เป็นมาตรฐานค่าเวลาของโลกนี้ฮะ (มีแค่โลกเดียวแหละ) เป็นการกำหนดค่าเวลาที่ละติจูดที่ศูนย์
ทีนี้ มันจะมีคำว่า GMT หรือ Greenwich Mean Time ก็เป็นชื่อ timezone ฮะ คำว่า Greenwich เป็นชื่อเมืองในลอนดอน ประเทศอังกฤษ ตั้งอยู่ที่ตำแหน่งละติจูดที่ศูนย์ ทำให้ GMT มีค่าตรงกันกับ UTC ฮะ หรือบางทีก็จะเรียกว่า UCT + 0 (อ้างอิง: GMT versus UTC (timeanddate.com))
ในตอนนี้ผมอยู่ในประเทศไทย ก็เป็น timezone ที่ชื่อ ICT ย่อมาจาก Indochina Time เพราะอยู่ในช่วงประเทศ Indonesia และ China เป็นที่มาของชื่อน่ะแหละ มีค่าเวลาเป็น UTC + 7 นั่นคือที่นี่เวลาอยู่หลัง UCT ไป 7 ชั่วโมง
แต่ไม่ได้มีแค่ ICT timezone เท่านั้นนะฮะที่มีค่าเวลาเป็น UTC + 7 ยังมี CXT หรือ Christmas Island Time ที่อยู่แถวๆ ออสเตรเลีย รายชื่อ timezone พวกนี้สามารถเข้าไปดูได้ที่ Time Zone Abbreviations – Worldwide List (timeanddate.com) ฮะ
Epoch time
ถึงประเด็นหลักของบล็อกนี้ล่ะ Epoch time เป็นตัวเลขยาวๆ ที่อ้างอิงจำนวนวินาทีโดยนับตั้งแต่ปีใหม่ของปี 1970 ในระบบ UTC (1970-01-01 00:00:00 UTC) มีหลายชื่อเลยล่ะ ไม่ว่าจะเป็น Unix time, Unix epoch, Unix timestamp หรือ POSIX time
โดยทั่วไป Epoch time จะเป็นตัวเลขอ้างอิงจำนวนวินาที แต่ในบางระบบก็รองรับเป็นจำนวนมิลลิวินาที, ไมโครวินาที, รวมไปถึงนาโนวินาทีด้วยนะฮะ ดังนั้น ถ้า Epoch time ของเราเป็น 3600 ก็จะเป็น 3600 วินาทีของเวลาอ้างอิง หรือก็คือ 1970-01-01 01:00:00 UTC นั่นเองฮะ
Epoch time จึงเป็นค่าเวลาใน GMT timezone เสมอนะฮะ หรือเรียกอีกอย่างว่า Epoch time เป็น UTC time เสมอ
เครื่องมือตัวนึงที่ผมชอบใช้มากๆ เวลาต้องคำนวณ Epoch time ก็จะเป็นเว็บ Epoch Converter – Unix Timestamp Converter ตัวนี้ฮะ สามารถแปลงเป็น local timezone ของเราเองได้ด้วยล่ะ
ว่าด้วยการเขียนโปรแกรม
เอาล่ะ ถึงคิวเราชาวโปรแกรมเมอร์ต้องใช้งาน epoch time แล้วล่ะฮะ มาดูกันว่าจะทำยังไงได้บ้าง
Bash script
# generate epoch time of current time
date +%s
# convert epoch time to UTC date time
date -d @1609459200
# convert epoch time to UTC date time in format YYYY-mm-dd HH:MM:SS
date -d @1609459200 +"%Y-%m-%d %H:%M:%S"
คราวนี้เราจะลองใช้ timezone กันฮะ โดยใช้ command ตามข้างล่างนี้ ส่วนค่า timezone
สามารถหาได้จากไฟล์ในพาธ /usr/share/zoneinfo
(อ้างอิง: How can I have `date` output the time from a different timezone? – Unix & Linux Stack Exchange)
TZ=":timezone" date
Python
กลับมาที่ Python ที่เราคุ้นเคย สามารถใช้ library datetime
เพื่อหาค่าได้ตามนี้ฮะ
from datetime import datetime
# print current timestamp
print(datetime.now())
# print epoch time of current timestamp
print(datetime.now().timestamp())
# convert epoch time to LOCAL date time
print(datetime.fromtimestamp(1609459200))
# convert epoch time to LOCAL date time in format YYYY-mm-dd HH:MM:SS
datetime.fromtimestamp(1609459200).strftime("%Y-%m-%d %H:%M:%S")
แล้วถ้าจะย้าย timezone ล่ะ เราก็ใช้ library pytz
เข้ามาช่วยด้วยกันฮะ
import pytz
from datetime import datetime
# convert current time to specific timezone: Asia/Bangkok
target_timezone = "Asia/Bangkok"
datetime.now().astimezone(pytz.timezone(target_timezone))
# convert current time to specific timezone: Australia/Melbourne
target_timezone = "Australia/Melbourne"
datetime.now().astimezone(pytz.timezone(target_timezone))
# convert time across timezones
source_timezone = "Asia/Bangkok"
target_timezone = "Australia/Melbourne"
quest_datetime = datetime(2021, 3, 1, 0, 0, 0)
pytz.timezone(source_timezone) \
.localize(quest_datetime) \
.astimezone(pytz.timezone(target_timezone))
น่าจะช่วยลดปัญหาเวลาต้องมาแก้ timezone ไม่มากก็น้อยนะฮะ ไม่ต้องมานั่งหานั่งจำว่าต้องบวกลบไปอีกกี่นาทีกี่ชั่วโมง จะได้ไม่ต้อง hard-coded กันอีกต่อไป