আপনি যদি কখনও কোনো পোস্ট লাইভ করার পর দেখেন যে পাবলিক সাইটে সেটি "৫৭ বছর আগে" হিসেবে দেখাচ্ছে, অথচ আপনার ড্যাশবোর্ড শান্তভাবে বলছে যে এটি মাত্র দশ মিনিট আগে আপলোড করা হয়েছে, তবে আপনি এই বাগের বিশেষ যন্ত্রণাটি বুঝতে পারবেন। এটি লেআউট নষ্ট করে না। এটি কোনো ফ্যাটাল এরর (fatal error) তৈরি করে না। এটি কেবল আপনার কন্টেন্টকে অর্ধশতাব্দী পুরনো করে দেয়, আর আপনার থিম ফাইলগুলো খুঁটিয়ে দেখলেও এই সংখ্যাটি পরিবর্তন হবে না।
এই সমস্যাটি সাধারণত সাধারণ পরীক্ষা-নিরীক্ষাতে ধরা পড়ে না। আপনি single.php, archive.php, এবং functions.php-এ কোনো ত্রুটিপূর্ণ get_the_date() কল আছে কি না তা খুঁজতে grep করতে পারেন। সবকিছুই স্বাভাবিক মনে হবে। আপনি লাইভ পেজটি রিলোড করলেন। ১৯৬৮ সালের সেই ভূতটি এখনও আপনার বাইলাইনে (byline) ঘুরে বেড়াচ্ছে। সেই মুহূর্তে, সমস্যাটি আপনার টেমপ্লেটের সাথে প্রায় কোনো সম্পর্ক রাখে না, বরং এটি সম্পূর্ণভাবে নির্ভর করে WordPress স্ট্যাকের মাধ্যমে সময় কীভাবে প্রবাহিত হচ্ছে তার ওপর।
কেন এই নির্দিষ্ট সংখ্যাটি বারবার দেখাচ্ছে
"৫৭ বছর আগে" সংখ্যাটি এলোমেলোভাবে আসে না। WordPress থিমগুলো প্রায়শই human_time_diff() ফাংশনের মাধ্যমে আপেক্ষিক সময় (relative time) প্রদর্শন করে, যা একটি পোস্টের টাইমস্ট্যাম্পের সাথে বর্তমান সময়ের তুলনা করে এবং "২ ঘণ্টা আগে"-এর মতো একটি সহজবোধ্য বাক্য প্রিন্ট করে। এই গাণিতিক হিসাবটি কাজ করার জন্য PHP-এর একটি বৈধ Unix টাইমস্ট্যাম্প প্রয়োজন—যা ১ জানুয়ারি, ১৯৭০ (Unix epoch) থেকে অতিবাহিত সেকেন্ডের একটি গণনা।
যখন কোনো কারণে সেই হিসাব করার আগেই টাইমস্ট্যাম্পটি বাদ পড়ে যায় বা নষ্ট হয়ে যায়, তখন ফাংশনটি প্রায়শই বর্তমান সময়ের সাথে শূন্য (বা অনুরূপ কোনো অবৈধ শুরুর বিন্দু) তুলনা করে ফেলে। এর ফলে প্রায় সাড়ে পাঁচ দশকের একটি বিশাল সময়সীমা দেখা দেয়, যা প্রতি বছর করে সামনের দিকে এগোতে থাকে। এটি একটি অনুপস্থিত বা ভুলভাবে পড়া সময়ের মানের লক্ষণ মাত্র, কোনো ডেটাবেস যা রেট্রো ব্লগ পোস্টে পূর্ণ—এমনটি নয়।
ড্যাশবোর্ডের বিভ্রান্তি
এই বাগের অন্যতম হতাশাজনক দিক হলো আপনার অ্যাডমিন প্যানেলের দ্বৈত আচরণ। wp-admin-এর ভেতরে, Posts লিস্টে সঠিক প্রকাশের তারিখ দেখায় কারণ WordPress প্রায়শই সরাসরি MySQL-এর wp_posts টেবিল থেকে সেটি সংগ্রহ করে এবং আপেক্ষিক-সময়ের ফিল্টারের মধ্য দিয়ে না পাঠিয়ে সার্ভার-সাইডেই ফরম্যাট করে ফেলে। তবে, ফ্রন্টএন্ড কোনো থিম বা প্লাগইনের ওপর নির্ভর করতে পারে যা human_time_diff() কল করে, অথবা এটি সরাসরি ডেটাবেসের মানটিকে PHP টাইমজোন কনভার্সনের একটি চেইনের মধ্য দিয়ে পাঠাতে পারে যা ব্যাকএন্ড সম্পূর্ণ এড়িয়ে যায়।
সুতরাং ডেটা সাধারণত অক্ষত থাকে। সমস্যাটি মূলত ডেটাবেস এবং রেন্ডার করা HTML-এর মধ্যবর্তী পাইপলাইনে তৈরি হওয়া একটি ত্রুটির কারণে ঘটে।
PHP ভার্সন এবং টাইমজোন সেটিংস
PHP দিয়ে শুরু করা যাক। হোস্টিং প্যানেলগুলো ভার্সন পরিবর্তন করাকে খুব সহজ ও নিরীহ মনে করতে পারে, কিন্তু PHP তার প্রধান রিলিজগুলোর মধ্যে টাইম অবজেক্টগুলোকে ভিন্নভাবে হ্যান্ডেল করে। একটি সাইট যদি হঠাৎ PHP 8.1 এনভায়রনমেন্টে পুরনো PHP 7.4 কোড চালায়, তবে DateTime অবজেক্টগুলো কীভাবে খালি বা ত্রুটিপূর্ণ স্ট্রিং ইনিশিয়ালাইজ করবে তার ক্ষেত্রে সূক্ষ্ম পরিবর্তন দেখা দিতে পারে। যদি আপনার php.ini ফাইলে date.timezone অসংজ্ঞায়িত (undefined) থাকে, তবে PHP ডিফল্টভাবে UTC ব্যবহার করে এবং ধরে নেয় যে সার্ভারই সবথেকে ভালো জানে। WordPress এর সাথে এর অমিল হতে পারে।
চেক করে দেখুন কেউ wp-config.php-এর ভেতরে টাইমজোন ডিক্লারেশন হার্ডকোড করে রেখেছে কি না। date_default_timezone_set( 'Asia/Kolkata' );-এর মতো একটি লাইন সহায়ক মনে হতে পারে, কিন্তু WordPress প্রত্যাশা করে যে সে Settings > General-এর অধীনে সেট করা মানের মাধ্যমে তার নিজস্ব ঘড়ি পরিচালনা করবে। PHP-স্তরে একটি অফসেট জোরপূর্বক প্রয়োগ করলে একটি 'রেস কন্ডিশন' (race condition) তৈরি হতে পারে, যেখানে WordPress কোর মনে করে সময়টি লোকাল এবং PHP মনে করে এটি ইউনিভার্সাল। যখন টাইমস্ট্যাম্প কনভার্সনের সময় এই অফসেটগুলো সংঘর্ষে লিপ্ত হয়, তখন আপনার টেমপ্লেটে ফিরে আসা ফলাফলটি শূন্যে নেমে আসতে পারে।
সার্ভার টাইম জোন কনফিগারেশন
আপনার MySQL সার্ভারের নিজস্ব লোকাল টাইম বা স্থানীয় সময়ের ধারণা রয়েছে। WordPress প্রতিটি পোস্টের জন্য দুটি তারিখ লেখে: সাইটের লোকাল সময়ে post_date, এবং ইউনিভার্সাল টাইমে post_date_gmt। যদি ডেটাবেসের গ্লোবাল time_zone পরিবর্তিত হয়—ধরা যাক, মাইগ্রেশনের পরে SYSTEM থেকে +00:00-এ পরিবর্তিত হয়—তবে PHP-এর strtotime() স্টোর করা স্ট্রিংটির সাথে তার প্রত্যাশিত মানের সামঞ্জস্য করতে ব্যর্থ হতে পারে। ডেটাবেস এখনও 2024-03-15 14:30:00 সংরক্ষণ করে, কিন্তু এর চারপাশের প্রেক্ষাপট বদলে যায় এবং PHP-তে পার্স করা আউটপুটটি false বা null হয়ে যায়।
বাস্তব উদাহরণ: একটি ম্যানেজড হোস্ট আপনার ডেটাবেসকে SYSTEM টাইমসহ MySQL 5.7 চালিত একটি নোড থেকে স্ট্রিক্ট UTC-তে সেট করা MariaDB 10.11 চালিত একটি ক্লাস্টারে স্থানান্তর করল। আপনার থিম পরিবর্তন হয়নি। আপনার WordPress সেটিংসও পরিবর্তন হয়নি। তবুও get_the_time( 'U' )—অর্থাৎ Unix টাইমস্ট্যাম্প ফরম্যাট—হঠাৎ কিছু পোস্টের জন্য একটি খালি মান রিটার্ন করে, এবং রিলেটিভ-টাইম ফাংশনটি সেই 'এপক-জিরো' (epoch-zero) ক্যালকুলেশনে ফিরে যায়। এর সমাধান হলো অ্যাপ্লিকেশন টাইমজোন, ডেটাবেস টাইমজোন এবং অপারেটিং সিস্টেমের ঘড়িকে একই সাথে সারিবদ্ধ (align) করা, যাতে WordPress-কে অনুমান করতে না হয় কোন রেফারেন্স ফ্রেম ব্যবহার করতে হবে।
ডেটাবেস টাইমস্ট্যাম্প ফরম্যাট
WordPress কোর পোস্টের তারিখগুলো MySQL-এর DATETIME কলামে সংরক্ষণ করে, TIMESTAMP ফিল্ডে নয়। এই পার্থক্যটি গুরুত্বপূর্ণ কারণ পুরনো MySQL ভার্সনগুলোতে DATETIME টাইমজোন সচেতনতা ছাড়াই একটি ক্যালেন্ডার ভ্যালু ধারণ করে, যেখানে TIMESTAMP পর্দার আড়ালে সবকিছুকে UTC-তে রূপান্তর করে। যদি কোনো প্লাগইন বা মাইগ্রেশন টুল আপনার wp_posts স্কিমা পরিবর্তন করে থাকে, অথবা যদি কোনো পুরনো ব্যাকআপ টেবিলের মধ্যে ভুল 'zero dates' ফিরিয়ে আনে, তবে WordPress 0000-00-00 00:00:00-এর মতো একটি ভ্যালু পড়তে পারে এবং নিঃশব্দে তা আপনার থিমের কাছে পাঠিয়ে দিতে পারে।
আধুনিক MySQL মোড, বিশেষ করে NO_ZERO_DATE এবং STRICT_TRANS_TABLES, এই জিরো প্লেসহোল্ডারগুলোকে প্রত্যাখ্যান করে। যদি মাইগ্রেশনের সময় কোনো ব্যাকআপ স্ক্রিপ্ট এগুলো ইনসার্ট করে থাকে, তবে ইমপোর্ট করার সময় ডাটাবেস এগুলোকে ট্রাঙ্কেট (truncate) বা নাল (null) করে দিতে পারে। আপনি একটি সরাসরি SQL কুয়েরির মাধ্যমে এটি দ্রুত যাচাই করতে পারেন:
SELECT ID, post_title, post_date, post_date_gmt
FROM wp_posts
WHERE post_status = 'publish'
ORDER BY post_date DESC
LIMIT 10;
যদি র (raw) কলামগুলো সঠিক দেখায় কিন্তু সাইটটি এখনও অনেক পুরনো তারিখ প্রদর্শন করে, তবে বুঝতে হবে কুয়েরির পরে ডেটাটি নষ্ট হচ্ছে। আর যদি কলামগুলোর মধ্যেই জিরো থাকে, তবে আপনি সমস্যার মূল উৎসটি খুঁজে পেয়েছেন।
ডেট ফাংশনের সাথে প্লাগইন সংঘাত (Plugin Conflicts)
ডেট আউটপুট ফিল্টার করা প্লাগইনগুলোই সাধারণত এর জন্য দায়ী থাকে। WPML বা Polylang-এর মতো মাল্টিলিঙ্গুয়াল টুলগুলো মাসের নাম অনুবাদ করার জন্য get_the_date-এর সাথে যুক্ত হয়। ক্যাশিং প্লাগইনগুলো চূড়ান্ত HTML ইন্টারসেপ্ট করে। এই দুই স্তরের যেকোনো একটি ভুলবশত একটি আনফরম্যাটেড স্ট্রিংকে এমন একটি ফাংশনে পাঠিয়ে দিতে পারে যা একটি Unix integer আশা করে।
একটি ট্রান্সলেশন প্লাগইন হয়তো “March 15, 2024”-কে তার স্থানীয় সমতুল্য শব্দ দিয়ে প্রতিস্থাপন করতে পারে, কিন্তু থিমটি যদি সেই স্থানীয় স্ট্রিংটিকে কোনো কাস্টম ফাংশনের ভেতরে strtotime()-এ পাঠায়, তবে নন-ইংলিশ ক্যারেক্টারের কারণে পার্সারটি ব্যর্থ হয় এবং false রিটার্ন করে। সেই false ভ্যালুটি যখন human_time_diff()-এ পৌঁছায়, তখন এটি বর্তমান সময়ের সাথে শূন্যের তুলনা করে, যার ফলে সেই কুখ্যাত অর্ধ-শতাব্দীর ব্যবধান তৈরি হয়।
অবজেক্ট ক্যাশ লেয়ারগুলো বিষয়টিকে আরও জটিল করে তোলে। Redis বা Memcached এমন একটি রিকোয়েস্ট থেকে একটি সিরিয়ালাইজড WP_Post অবজেক্ট সংরক্ষণ করতে পারে যেখানে তারিখটি পার্স করতে সাময়িকভাবে ব্যর্থ হয়েছিল। এরপর প্রতিটি ভিজিটর সরাসরি RAM থেকে সেই ত্রুটিপূর্ণ অবজেক্টটি পায়, যা ডাটাবেসকে পুরোপুরি বাইপাস করে। লাইভ সাইটে সমস্যাটি বজায় থাকে কারণ আপনার প্রোডাকশন স্ট্যাকে এমন ক্যাশ ইনফ্রাস্ট্রাকচার রয়েছে যা আপনার লোকাল ইন্সটলেশনে নেই।
একটি ব্যবহারিক ডিবাগিং চেকলিস্ট
যেহেতু এই লক্ষণটি স্ট্যাকের গভীরে লুকিয়ে থাকে, তাই তারিখগুলো ঠিক হওয়া পর্যন্ত আপনাকে পদ্ধতিগতভাবে প্রতিটি স্তর পরীক্ষা করতে হবে।
- সরাসরি ডাটাবেস কুয়েরি করুন। phpMyAdmin ওপেন করুন বা WP-CLI চালান এবং র (raw)
post_dateভ্যালুগুলো পরীক্ষা করুন। যদি সেগুলো সঠিক থাকে, তবে ডাটাবেস সমস্যা নয়। - ডিফল্ট থিমে পরিবর্তন করুন। Twenty Twenty-Four বা Twenty Twenty-Three অ্যাক্টিভেট করুন। যদি বাগটি চলে যায়, তবে আপনার অ্যাক্টিভ থিম এবং চাইল্ড থিমে টাইম আউটপুট র্যাপ (wrap) করা কোনো কাস্টম ফাংশন আছে কি না তা পরীক্ষা করুন।
- প্লাগইনগুলো নিষ্ক্রিয় করুন। সাময়িকভাবে
/wp-content/pluginsফোল্ডারটির নাম পরিবর্তন করুন অথবা ড্যাশবোর্ড থেকে সব প্লাগইন ডিজেবল করুন। এরপর একটি একটি করে প্লাগইন পুনরায় সক্রিয় করুন এবং প্রতিবার অ্যাক্টিভেশনের পর ফ্রন্টএন্ড চেক করুন। - এরর লগ (error log) পড়ুন। টাইমজোন ওয়ার্নিং,
DateTimeএরর বাstrtotime()সংক্রান্ত অভিযোগ খুঁজুন। এগুলো প্রায়ই সঠিক
