Nếu bạn đã từng xuất bản một bài viết nhưng khi xem trên trang web công khai, nó lại hiển thị là “57 năm trước” trong khi bảng điều khiển vẫn hiển thị bình thường là vừa đăng cách đây mười phút, bạn sẽ hiểu sự khó chịu đặc trưng của lỗi này. Nó không làm hỏng bố cục. Nó không gây ra lỗi nghiêm trọng. Nó chỉ đơn giản là khiến nội dung của bạn trông như đã từ nửa thế kỷ trước, và việc kiểm tra các tệp theme cũng sẽ không làm con số đó thay đổi.

Vấn đề này thường vượt qua các bước kiểm tra thông thường. Bạn dùng grep để tìm kiếm trong single.php, archive.php, và functions.php để tìm một lệnh gọi get_the_date() bị lỗi. Mọi thứ trông đều bình thường. Bạn tải lại trang web. Bóng ma của năm 1968 vẫn ám ảnh dòng ghi chú tác giả (byline) của bạn. Tại thời điểm đó, vấn đề hầu như không liên quan đến các template của bạn mà liên quan đến cách thời gian truyền qua ngăn xếp (stack) của WordPress.

Tại sao con số cụ thể đó cứ xuất hiện liên tục

Con số “57 năm trước” không phải là ngẫu nhiên. Các theme WordPress thường hiển thị thời gian tương đối thông qua hàm human_time_diff(), hàm này so sánh timestamp của bài viết với thời điểm hiện tại và in ra một cụm từ thân thiện như “2 giờ trước”. Để phép toán đó hoạt động, PHP cần một Unix timestamp hợp lệ—một số giây tính từ ngày 1 tháng 1 năm 1970, kỷ nguyên Unix (Unix epoch).

Khi có thứ gì đó làm mất hoặc làm hỏng timestamp trước khi nó đến bước tính toán, hàm này thường kết thúc bằng việc so sánh giá trị 0 (hoặc một điểm bắt đầu không hợp lệ tương tự) với thời điểm hiện tại. Kết quả là một khoảng thời gian kéo dài khoảng năm thập kỷ rưỡi, tăng dần từng năm một theo lịch. Đây là triệu chứng của một giá trị thời gian bị thiếu hoặc bị đọc sai, chứ không phải do cơ sở dữ liệu chứa đầy các bài viết blog từ thời xưa.

Sự đánh lừa từ Bảng điều khiển

Một trong những khía cạnh gây nản lòng nhất của lỗi này là "tính cách phân liệt" của bảng quản trị. Trong wp-admin, danh sách Bài viết (Posts) hiển thị đúng ngày xuất bản vì WordPress thường lấy dữ liệu đó trực tiếp từ bảng MySQL wp_posts và định dạng nó ở phía máy chủ mà không chạy qua bộ lọc thời gian tương đối. Tuy nhiên, ở phía frontend, nó có thể phụ thuộc vào một theme hoặc plugin gọi hàm human_time_diff(), hoặc nó có thể truyền giá trị thô từ cơ sở dữ liệu qua một chuỗi chuyển đổi múi giờ của PHP mà phía backend hoàn toàn bỏ qua.

Vì vậy, dữ liệu thường vẫn còn nguyên vẹn. Vấn đề nằm ở quy trình truyền dữ liệu giữa cơ sở dữ liệu và HTML được hiển thị đã gặp trục trặc.

Phiên bản PHP và Cài đặt Múi giờ

Hãy bắt đầu với PHP. Các bảng điều khiển hosting làm cho việc thay đổi phiên bản trông có vẻ vô hại, nhưng PHP xử lý các đối tượng thời gian khác nhau giữa các phiên bản lớn. Một trang web đang chạy mã PHP 7.4 cũ trên môi trường PHP 8.1 đột ngột có thể gặp phải những thay đổi tinh vi trong cách các đối tượng DateTime khởi tạo các chuỗi trống hoặc bị lỗi định dạng. Nếu tệp php.ini của bạn để date.timezone ở trạng thái chưa xác định, PHP sẽ mặc định là UTC và giả định rằng máy chủ biết điều gì là tốt nhất. WordPress có thể không đồng ý với điều đó.

Hãy kiểm tra xem có ai đã viết cứng một khai báo múi giờ bên trong wp-config.php hay không. Một dòng như date_default_timezone_set( 'Asia/Kolkata' ); có vẻ hữu ích, nhưng WordPress mong muốn tự quản lý đồng hồ của chính nó thông qua giá trị được thiết lập trong Settings > General. Việc ép buộc một độ lệch (offset) ở cấp độ PHP có thể tạo ra tình trạng tranh chấp (race condition), nơi nhân (core) tin rằng thời gian là giờ địa phương còn PHP lại tin rằng đó là giờ quốc tế. Khi các độ lệch này xung đột trong quá trình chuyển đổi timestamp, kết quả trả về cho template của bạn có thể bị sụp đổ về giá trị 0.

Cấu hình Múi giờ của Máy chủ

Máy chủ MySQL của bạn có khái niệm riêng về giờ địa phương. WordPress ghi hai ngày cho mỗi bài viết: post_date theo giờ địa phương của trang web, và post_date_gmt theo Giờ quốc tế (Universal Time). Nếu múi giờ toàn cục (time_zone) của cơ sở dữ liệu thay đổi—chẳng hạn, từ SYSTEM sang +00:00 sau khi di chuyển (migration)—hàm strtotime() của PHP có thể thất bại trong việc đối chiếu chuỗi đã lưu với những gì nó mong đợi. Cơ sở dữ liệu vẫn lưu 2024-03-15 14:30:00, nhưng ngữ cảnh xung quanh nó thay đổi, và kết quả phân tích (parsed output) trở thành false hoặc null trong PHP.

Ví dụ thực tế: một nhà cung cấp hosting quản lý di chuyển cơ sở dữ liệu của bạn từ một node đang chạy MySQL 5.7 với múi giờ SYSTEM sang một cụm (cluster) chạy MariaDB 10.11 được thiết lập theo chuẩn UTC nghiêm ngặt. Theme của bạn không đổi. Cài đặt WordPress của bạn không đổi. Tuy nhiên, get_the_time( 'U' )—định dạng Unix timestamp—đột nhiên trả về giá trị trống cho một số bài viết, và hàm thời gian tương đối sẽ quay về phép tính dựa trên mốc epoch-zero đó. Cách khắc phục là đồng bộ hóa múi giờ ứng dụng, múi giờ cơ sở dữ liệu và đồng hồ hệ điều hành để WordPress không phải đoán xem nên sử dụng hệ quy chiếu nào.

Định dạng Timestamp của Cơ sở dữ liệu

WordPress core stores post dates in MySQL DATETIME columns, not TIMESTAMP fields. That distinction matters because DATETIME holds a calendar value without_zone awareness in older MySQL versions, whereas TIMESTAMP converts everything to UTC behind the scenes. If a plugin or migration tool has altered your wp_posts schema, or if an old backup restored invalid zero dates into the table, WordPress may read a value like 0000-00-00 00:00:00 and quietly hand it off to your theme.

Modern MySQL modes, particularly NO_ZERO_DATE and STRICT_TRANS_TABLES, reject those zero placeholders. If a backup script inserted them during a migration, the database might have truncated or nulled them on import. You can verify this quickly with a direct 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;

If the raw columns look correct but the site still displays ancient history, the corruption is happening after the query. If the columns themselves contain zeroes, you have found the leak in the plumbing.

Plugin Conflicts with Date Functions

Plugins that filter date output are common culprits. Multilingual tools such as WPML or Polylang hook into get_the_date to translate month names. Caching plugins intercept the final HTML. Either layer can accidentally pass an unformatted string into a function expecting a Unix integer.

A translation plugin might replace “March 15, 2024” with its localized equivalent, but if a theme then pipes that localized string into strtotime() inside a custom function, the parser fails on non-English characters and returns false. That false value hits human_time_diff(), which compares zero against now, producing the infamous half-century gap.

Object cache layers complicate this further. Redis or Memcached can store a serialized WP_Post object from a request that briefly failed to parse the date. Every subsequent visitor receives that corrupted object straight from RAM, bypassing the database entirely. The issue persists on the live site because your production stack has cache infrastructure that your local install lacks.

A Practical Debugging Checklist

Because the symptom hides deep in the stack, you need to peel layers systematically until the dates snap back into place.

  1. Query the database directly. Open phpMyAdmin or run WP-CLI and inspect the raw post_date values. If they are correct, the database is not the problem.
  2. Switch to a default theme. Activate Twenty Twenty-Four or Twenty Twenty-Three. If the bug disappears, audit your active theme and child theme for custom functions wrapping time output.
  3. Silence the plugins. Rename the /wp-content/plugins folder temporarily, or disable all plugins from the dashboard. Re-enable them one by one, checking the frontend after each activation.
  4. Read the error log. Look for timezone warnings, DateTime errors, or strtotime() complaints. They often point to the exact