หากคุณเคยเผยแพร่โพสต์ออกไปแล้วพบว่าหน้าเว็บแสดงผลว่า “57 ปีที่แล้ว” ในขณะที่แดชบอร์ดของคุณยังคงยืนยันอย่างใจเย็นว่าเพิ่งลงไปเมื่อสิบนาทีที่ผ่านมา คุณย่อมรู้ดีถึงความเจ็บปวดเฉพาะตัวของบั๊กตัวนี้ มันไม่ได้ทำให้เลย์เอาต์พัง มันไม่ได้ทำให้เกิดข้อผิดพลาดร้ายแรง (fatal error) แต่มันแค่ทำให้เนื้อหาของคุณดูเก่าลงไปครึ่งศตวรรษ และการนั่งจ้องไฟล์ธีมของคุณก็ไม่ช่วยให้ตัวเลขนั้นขยับเลย
ปัญหานี้มักจะรอดพ้นจากการตรวจสอบแบบปกติ คุณอาจจะลอง grep หาใน single.php, archive.php, และ functions.php เพื่อมองหาการเรียกใช้ get_the_date() ที่ผิดเพี้ยน แต่ทุกอย่างก็ดูเป็นปกติ คุณลองรีโหลดหน้าเว็บจริง แต่เงาหลอนจากปี 1968 ก็ยังคงตามหลอกหลอนอยู่ที่บรรทัดชื่อผู้เขียน (byline) ของคุณ ณ จุดนั้น ปัญหามักจะไม่เกี่ยวกับเทมเพลตของคุณเลย แต่เกี่ยวข้องกับวิธีที่เวลาไหลผ่าน WordPress stack มากกว่า
ทำไมตัวเลขนี้ถึงปรากฏขึ้นมาซ้ำๆ
ตัวเลข “57 ปีที่แล้ว” ไม่ใช่เรื่องบังเอิญ ธีมของ WordPress มักจะแสดงเวลาแบบสัมพัทธ์ (relative time) ผ่านฟังก์ชัน human_time_diff() ซึ่งจะเปรียบเทียบ timestamp ของโพสต์กับเวลาปัจจุบัน และแสดงข้อความที่เข้าใจง่ายอย่าง “2 ชั่วโมงที่แล้ว” เพื่อให้การคำนวณนี้ทำงานได้ PHP จำเป็นต้องมี Unix timestamp ที่ถูกต้อง ซึ่งก็คือจำนวนวินาทีที่นับตั้งแต่ 1 มกราคม 1970 หรือที่เรียกว่า Unix epoch
เมื่อมีบางอย่างไปลบหรือทำให้ timestamp เสียหายก่อนที่จะถึงขั้นตอนการคำนวณ ฟังก์ชันนี้มักจะจบลงด้วยการเปรียบเทียบค่าศูนย์ (หรือจุดเริ่มต้นที่ไม่ถูกต้องในลักษณะเดียวกัน) กับเวลาปัจจุบัน ผลลัพธ์ที่ได้คือช่วงเวลาที่ย้อนกลับไปประมาณห้าทศวรรษครึ่ง โดยค่อยๆ เพิ่มขึ้นทีละหนึ่งปีตามปฏิทิน มันคืออาการของค่าเวลาที่หายไปหรือถูกอ่านผิด ไม่ใช่เพราะฐานข้อมูลของคุณเต็มไปด้วยโพสต์ย้อนยุค
ความหลอกลวงของแดชบอร์ด
แง่มุมหนึ่งที่น่าหงุดหงิดที่สุดของบั๊กนี้คือ "บุคลิกที่แตกแยก" ของแผงควบคุม (admin panel) ภายใน wp-admin รายการโพสต์จะแสดงวันที่เผยแพร่ที่ถูกต้อง เพราะ WordPress มักจะดึงข้อมูลนั้นโดยตรงจากตาราง wp_posts ใน MySQL และจัดรูปแบบที่ฝั่งเซิร์ฟเวอร์โดยไม่ผ่านตัวกรองเวลาแบบสัมพัทธ์ อย่างไรก็ตาม หน้าบ้าน (frontend) อาจต้องพึ่งพาธีมหรือปลั๊กอินที่เรียกใช้ human_time_diff() หรืออาจเป็นการส่งค่าดิบจากฐานข้อมูลผ่านลำดับการแปลงเขตเวลา (timezone conversion) ของ PHP ที่ฝั่งหลังบ้านไม่ได้ใช้งาน
ดังนั้น ข้อมูลมักจะยังคงอยู่ครบถ้วน แต่มันคือช่องทางการส่งข้อมูล (pipeline) ระหว่างฐานข้อมูลและ HTML ที่ถูกแสดงผลต่างหากที่มีปัญหา
เวอร์ชัน PHP และการตั้งค่า Timezone
เริ่มที่ PHP กันก่อน แผงควบคุมโฮสติ้งมักทำให้การสลับเวอร์ชันดูเหมือนไม่มีอะไรเสียหาย แต่ PHP จัดการกับออบเจกต์เวลา (time objects) ต่างกันในแต่ละเวอร์ชันหลัก เว็บไซต์ที่รันโค้ด PHP 7.4 รุ่นเก่าบนสภาพแวดล้อม PHP 8.1 ที่เปลี่ยนไปอย่างกะทันหัน อาจพบความเปลี่ยนแปลงเล็กน้อยในวิธีที่ออบเจกต์ DateTime เริ่มต้นการทำงาน (initialize) เมื่อเจอสตริงที่ว่างเปล่าหรือผิดรูปแบบ หากไฟล์ php.ini ของคุณไม่ได้กำหนด date.timezone ไว้ PHP จะใช้ค่าเริ่มต้นเป็น UTC และสมมติว่าเซิร์ฟเวอร์รู้ดีที่สุด ซึ่ง WordPress อาจจะไม่เห็นด้วย
ลองตรวจสอบดูว่ามีใครเขียนการประกาศเขตเวลาแบบ hardcoded ไว้ใน wp-config.php หรือไม่ บรรทัดอย่าง date_default_timezone_set( 'Asia/Kolkata' ); อาจดูเหมือนมีประโยชน์ แต่ WordPress คาดหวังที่จะจัดการนาฬิกาของตัวเองผ่านค่าที่ตั้งไว้ใน Settings > General การบังคับค่า offset ในระดับ PHP อาจทำให้เกิด race condition ที่ตัว core เชื่อว่าเวลาเป็นเวลาท้องถิ่น แต่ PHP เชื่อว่าเป็นเวลาสากล เมื่อค่า offset เหล่านี้ชนกันระหว่างการแปลง timestamp ผลลัพธ์ที่ส่งกลับไปยังเทมเพลตของคุณอาจลดลงเหลือศูนย์
การกำหนดค่า Timezone ของเซิร์ฟเวอร์
เซิร์ฟเวอร์ MySQL ของคุณก็มีความเข้าใจเรื่องเวลาท้องถิ่นในแบบของตัวเอง WordPress จะเขียนวันที่สองค่าสำหรับทุกโพสต์ ได้แก่ post_date ในเวลาท้องถิ่นของไซต์ และ post_date_gmt ในเวลาสากล (Universal Time) หากค่า time_zone ระดับ global ของฐานข้อมูลเปลี่ยนไป เช่น จาก SYSTEM เป็น +00:00 หลังจากการย้ายข้อมูล (migration) ฟังก์ชัน strtotime() ของ PHP อาจล้มเหลวในการประสานสตริงที่จัดเก็บไว้กับสิ่งที่มันคาดหวัง ฐานข้อมูลยังคงเก็บค่า 2024-03-15 14:30:00 แต่บริบทโดยรอบเปลี่ยนไป และผลลัพธ์ที่ถูก parse ออกมาใน PHP กลายเป็น false หรือ null
ตัวอย่างในโลกความเป็นจริง: โฮสต์แบบ managed ย้ายฐานข้อมูลของคุณจากโหนดที่รัน MySQL 5.7 ที่ใช้เวลาแบบ SYSTEM ไปยังคลัสเตอร์ที่รัน MariaDB 10.11 ที่ตั้งค่าเป็น UTC แบบเคร่งครัด ธีมของคุณไม่เคยเปลี่ยน การตั้งค่า WordPress ของคุณไม่เคยเปลี่ยน แต่ get_the_time( 'U' ) ซึ่งเป็นรูปแบบ Unix timestamp กลับคืนค่าว่างสำหรับบางโพสต์อย่างกะทันหัน และฟังก์ชันเวลาแบบสัมพัทธ์ก็ถอยกลับไปใช้การคำนวณแบบ epoch-zero วิธีแก้ไขคือการปรับเขตเวลาของแอปพลิเคชัน, เขตเวลาของฐานข้อมูล และนาฬิกาของระบบปฏิบัติการให้ตรงกัน เพื่อที่ WordPress จะได้ไม่ต้องเดาว่าจะใช้จุดอ้างอิงใด
รูปแบบ Timestamp ของฐานข้อมูล
WordPress core จัดเก็บวันที่ของโพสต์ในคอลัมน์ DATETIME ของ MySQL ไม่ใช่ฟิลด์ TIMESTAMP ความแตกต่างนี้มีความสำคัญเพราะ DATETIME จะเก็บค่าตามปฏิทินโดยไม่คำนึงถึงโซนเวลา (without_zone awareness) ใน MySQL เวอร์ชันเก่า ในขณะที่ TIMESTAMP จะแปลงทุกอย่างเป็น UTC อยู่เบื้องหลัง หากปลั๊กอินหรือเครื่องมือย้ายข้อมูล (migration tool) ได้เปลี่ยนโครงสร้าง (schema) ของ wp_posts ของคุณ หรือหากการกู้คืนข้อมูลสำรอง (backup) เก่าได้นำวันที่ที่เป็นศูนย์ (zero dates) ที่ไม่ถูกต้องกลับเข้าสู่ตาราง WordPress อาจอ่านค่าอย่างเช่น 0000-00-00 00:00:00 และส่งค่านั้นไปยังธีมของคุณโดยไม่แจ้งเตือน
โหมด MySQL สมัยใหม่ โดยเฉพาะอย่างยิ่ง NO_ZERO_DATE และ STRICT_TRANS_TABLES จะปฏิเสธค่า placeholder ที่เป็นศูนย์เหล่านั้น หากสคริปต์สำรองข้อมูลได้ใส่ค่าเหล่านี้ลงไประหว่างการย้ายข้อมูล ฐานข้อมูลอาจทำการตัดทอน (truncated) หรือทำให้เป็นค่าว่าง (nulled) ในระหว่างการนำเข้า คุณสามารถตรวจสอบเรื่องนี้ได้อย่างรวดเร็วด้วยการใช้ SQL query โดยตรง:
SELECT ID, post_title, post_date, post_date_gmt
FROM wp_posts
WHERE post_status = 'publish'
ORDER BY post_date DESC
LIMIT 10;
หากคอลัมน์ข้อมูลดิบดูถูกต้องแต่เว็บไซต์ยังคงแสดงข้อมูลที่เก่าแก่ผิดปกติ แสดงว่าความผิดพลาดเกิดขึ้นหลังจากขั้นตอนการ query หากตัวคอลัมน์เองมีค่าเป็นศูนย์ แสดงว่าคุณได้พบจุดที่ข้อมูลรั่วไหลแล้ว
ความขัดแย้งของปลั๊กอินกับฟังก์ชันวันที่
ปลั๊กอินที่ทำการกรอง (filter) ผลลัพธ์ของวันที่มักเป็นตัวการสำคัญ เครื่องมือหลายภาษาอย่าง WPML หรือ Polylang จะเข้าไปเชื่อมต่อ (hook) กับ get_the_date เพื่อแปลชื่อเดือน ส่วนปลั๊กอินแคช (caching plugins) จะดักจับ HTML ขั้นสุดท้าย ทั้งสองเลเยอร์นี้อาจส่งสตริงที่ไม่ได้จัดรูปแบบ (unformatted string) เข้าไปยังฟังก์ชันที่คาดหวังค่าตัวเลข Unix integer โดยไม่ตั้งใจ
ปลั๊กอินแปลภาษาอาจเปลี่ยน “March 15, 2024” เป็นรูปแบบภาษาท้องถิ่น แต่หากธีมนำสตริงที่แปลแล้วนั้นไปใส่ใน strtotime() ภายในฟังก์ชันที่เขียนขึ้นเอง ตัว parser จะทำงานผิดพลาดเมื่อเจอตัวอักษรที่ไม่ใช่ภาษาอังกฤษและส่งค่า false กลับมา ค่า false นั้นจะถูกส่งต่อไปยัง human_time_diff() ซึ่งจะนำค่าศูนย์ไปเปรียบเทียบกับเวลาปัจจุบัน ส่งผลให้เกิดช่องว่างเวลาที่ผิดพลาดไปครึ่งศตวรรษอย่างที่เห็นกันบ่อยๆ
เลเยอร์ของ Object cache ยิ่งทำให้เรื่องนี้ซับซ้อนขึ้น Redis หรือ Memcached สามารถจัดเก็บ WP_Post object ที่ถูก serialize จากคำขอ (request) ที่ล้มเหลวในการ parse วันที่เพียงชั่วคราว ผู้เข้าชมทุกคนหลังจากนั้นจะได้รับ object ที่เสียหายนั้นโดยตรงจาก RAM โดยข้ามขั้นตอนการดึงข้อมูลจากฐานข้อมูลไปเลย ปัญหานี้ยังคงค้างอยู่ในไซต์ที่ใช้งานจริง (live site) เพราะ stack ในระบบ production ของคุณมีโครงสร้างพื้นฐานด้านแคชที่การติดตั้งในเครื่อง local ของคุณไม่มี
รายการตรวจสอบเพื่อการแก้ปัญหา (Debugging Checklist) ที่ใช้งานได้จริง
เนื่องจากอาการของปัญหานี้ซ่อนอยู่ลึกใน stack คุณจึงต้องค่อยๆ ไล่ตรวจสอบทีละเลเยอร์อย่างเป็นระบบจนกว่าวันที่กลับมาแสดงผลได้อย่างถูกต้อง
- Query ฐานข้อมูลโดยตรง เปิด phpMyAdmin หรือรัน WP-CLI และตรวจสอบค่า
post_dateดิบ หากค่าเหล่านี้ถูกต้อง แสดงว่าฐานข้อมูลไม่ใช่ปัญหา - เปลี่ยนไปใช้ธีมเริ่มต้น เปิดใช้งาน Twenty Twenty-Four หรือ Twenty Twenty-Three หากบั๊กหายไป ให้ตรวจสอบธีมที่คุณใช้งานอยู่และ child theme ว่ามีฟังก์ชันที่เขียนขึ้นเองซึ่งครอบการแสดงผลเวลาไว้หรือไม่
- ปิดการทำงานของปลั๊กอิน เปลี่ยนชื่อโฟลเดอร์
/wp-content/pluginsชั่วคราว หรือปิดการใช้งานปลั๊กอินทั้งหมดจากหน้า dashboard จากนั้นค่อยเปิดใช้งานทีละปลั๊กอิน และตรวจสอบหน้าเว็บไซต์ (frontend) หลังจากการเปิดใช้งานแต่ละครั้ง - อ่าน error log มองหาคำเตือนเกี่ยวกับ timezone, ข้อผิดพลาดของ
DateTimeหรือข้อร้องเรียนจากstrtotime()ซึ่งมักจะชี้ไปยังจุดที่...
