आपका टाइमस्टैम्प एक झूठ है
एक ट्रेन टिकट ने मुझे डेटाबेस में समय स्टोर करने के बारे में क्या सिखाया
मैं न्यूयॉर्क से शिकागो के लिए ट्रेन बुक कर रहा था जब मुझे अचानक समझ आया कि Postgres में टाइमस्टैम्प टाइप्स इतने भ्रामक क्यों हैं। टिकट पर दिखाया गया था:
- प्रस्थान: 8:00 AM EST
- आगमन: 7:30 PM CST
- अवधि: 11 घंटे 30 मिनट
एक ही टिकट पर समय के बारे में बात करने के तीन अलग-अलग तरीके। और हर एक को डेटाबेस में अलग तरह से स्टोर करने की जरूरत है।
वह सवाल जो कोई पहले नहीं पूछता
Postgres में TIMESTAMP और TIMESTAMPTZ दोनों में ठीक 8 बाइट लगते हैं और माइक्रोसेकंड सटीकता भी समान होती है। तो फिर दो टाइप्स क्यों हैं?
क्योंकि “समय क्या हुआ है?” यह पूरी तरह इस बात पर निर्भर करता है कि आप क्या बताना चाहते हैं।
जब मैं न्यूयॉर्क में उस ट्रेन में चढ़ता हूं, मुझे यह जानने की जरूरत है कि यह 8:00 AM ईस्टर्न समय पर निकलती है। यह वही नंबर है जिसे मुझे स्टेशन की घड़ी पर मिलाना है। जब मेरी दोस्त मुझे शिकागो में लेने आती है, उसे यह जानने की जरूरत है कि मैं 7:30 PM सेंट्रल समय पर पहुंचता हूं—यह वही नंबर है जो उसकी घड़ी पर दिखेगा। और अगर मैं यह पता लगाने की कोशिश कर रहा हूं कि क्या मेरे पास अपनी किताब पढ़ने का समय होगा, तो मुझे यह जानने की जरूरत है कि यह 11 घंटे और आधे घंटे की यात्रा है।
एक ही ट्रेन। एक ही यात्रा। समय के तीन पूरी तरह से अलग प्रतिनिधित्व।
TIMESTAMPTZ वास्तव में क्या करता है
TIMESTAMPTZ के साथ ट्रिक यह है—और यह वह नहीं है जो ज्यादातर लोग सोचते हैं। यह टाइमज़ोन स्टोर नहीं करता है। नाम भ्रामक है।
यह जो करता है वह यह है कि जो भी समय आप देते हैं उसे स्टोर करने से पहले UTC में कन्वर्ट कर देता है, और फिर जब आप उसे पढ़ते हैं तो आपके सेशन के टाइमज़ोन में वापस कन्वर्ट कर देता है। “TZ” हिस्सा स्टोरेज के बारे में नहीं है, यह कन्वर्जन सपोर्ट के बारे में है।
मान लीजिए आप उस ट्रेन के प्रस्थान को स्टोर कर रहे हैं। टोक्यो में बैठा कोई व्यक्ति आपके डेटाबेस को क्वेरी करता है और प्रस्थान को JST में देखता है। लंदन में बैठा कोई व्यक्ति इसे GMT में देखता है। हर कोई एक ही निरपेक्ष क्षण को देख रहा है, बस अपने कॉन्फ़िगर किए गए टाइमज़ोन में व्यक्त किया गया। यह इवेंट्स को रिकॉर्ड करने के लिए बिल्कुल सही है: “यह भुगतान कब प्रोसेस हुआ?” या “यह API रिक्वेस्ट कब आई?”
लेकिन उस ट्रेन टिकट के बारे में क्या? आप नहीं चाहते कि प्रस्थान समय बदल जाए सिर्फ इसलिए कि किसी ने इसे अलग टाइमज़ोन से क्वेरी किया। ट्रेन 8:00 AM ईस्टर्न समय पर निकलती है, बस इतना ही। यह एक निरपेक्ष क्षण नहीं है—यह इस बात का वादा है कि ग्रैंड सेंट्रल की घड़ी क्या कहेगी।
वास्तव में जो मतलब है उसे स्टोर करना
उस ट्रेन यात्रा के लिए, आपको अलग-अलग उद्देश्यों के लिए अलग-अलग चीजें स्टोर करने की जरूरत है:
- निरपेक्ष क्षण (
departs_atऔरarrives_atकोTIMESTAMPTZके रूप में) - डिस्प्ले कॉन्टेक्स्ट (
origin_timezoneऔरdestination_timezoneको टेक्स्ट के रूप में) - अवधि (दोनों क्षणों के बीच एक
INTERVAL)
अब आपका एप्लिकेशन वही कर सकता है जो ट्रेन टिकट करता है: ऑरिजिन टाइमज़ोन में कन्वर्ट करके “प्रस्थान 8:00 AM EST” दिखाएं, डेस्टिनेशन टाइमज़ोन में कन्वर्ट करके “आगमन 7:30 PM CST” दिखाएं, और अवधि को सीधे इंटरवल से “अवधि: 11घं 30मि” दिखाएं।
टोक्यो से टिकट बुक करने वाला व्यक्ति भी हर स्टेशन पर वही लोकल टाइम देखता है। वही उन्हें जानने की जरूरत है।
आपकी फ्लाइट ट्रैकिंग ऐप ने इसे गलत क्यों किया
क्या आपने कभी noticed किया है कि कुछ फ्लाइट ट्रैकिंग ऐप्स फ्लाइट के दौरान आपका टाइमज़ोन दिखाते हैं? जैसे आप अटलांटिक के ऊपर हैं और यह कहता है “वर्तमान समय: 4:32 PM GMT।” किसको फर्क पड़ता है? आप ग्रीनविच में नहीं हैं, आप महासागर के ऊपर कहीं 38,000 फीट पर हैं।
आप वास्तव में क्या देखना चाहते हैं:
- टेकऑफ के बाद से बीता हुआ समय
- डेस्टिनेशन तक शेष समय
- जब आप लैंड करेंगे तो वहां क्या समय होगा
इनमें से कोई भी टाइमज़ोन कन्वर्जन नहीं है। पहले दो इंटरवल हैं—अवधियां, क्षण नहीं। आखिरी वाला एक विशिष्ट जगह पर टाइमज़ोन कन्वर्जन है, “आपके वर्तमान टाइमज़ोन” पर नहीं।
यह देखा? दो इंटरवल कैलकुलेशन (NOW() - actual_departure और estimated_arrival - NOW()), एक विशिष्ट जगह पर टाइमज़ोन कन्वर्जन (AT TIME ZONE destination_timezone)। आपका वर्तमान टाइमज़ोन इसमें शामिल नहीं होता है।
जब वॉल-क्लॉक टाइम वास्तव में चाहिए होता है
होटलों को निरपेक्ष क्षणों से कोई मतलब नहीं है। उन्हें अपने लोकेशन पर घड़ी के रीडिंग से मतलब है।
“चेक-इन दोपहर 3:00 बजे के बाद है” का मतलब “चेक-इन मध्यरात्रि UTC के 15 घंटे बाद है” नहीं है। इसका मतलब है “जब भी हमारी लॉबी की घड़ी दोपहर 3:00 बजे कहेगी, आप चेक-इन कर सकते हैं।” अगर आपके सर्वर वर्जीनिया में हैं लेकिन होटल पेरिस में है, तो भी आप चाहेंगे कि यह नियम दोपहर 3:00 बजे पेरिस समय पर ट्रिगर हो।
TIME टाइप (बिना डेट या टाइमज़ोन के) बिल्कुल यही दर्शाता है: “घड़ी पर एक रीडिंग।” इसे एक टाइमज़ोन टेक्स्ट फील्ड (“Europe/Paris”) के साथ पेयर करें, और आप अपने सर्वर कहीं भी हों, वॉल-क्लॉक पॉलिसी लागू कर सकते हैं। लेकिन आपको विशिष्ट अतिथियों के वास्तविक चेक-इन और चेक-आउट के लिए TIMESTAMPTZ कॉलम की भी जरूरत होगी—ये निरपेक्ष क्षण हैं जिन्हें आपके बैकएंड को ट्रैक करने की जरूरत है।
कैलेंडर समस्या
मेरे पास 9:00 AM के लिए एक रिकरिंग रिमाइंडर सेट है: “दैनिक प्राथमिकताओं की समीक्षा करें।” मैं चाहता हूं कि यह रिमाइंडर 9:00 AM पर आए चाहे मैं कहीं भी रहूं। अगर मैं यात्रा कर रहा हूं, तो भी यह मेरे लोकल समय के अनुसार 9:00 AM पर फायर होना चाहिए।
लेकिन मेरे पास एक कैलेंडर इवेंट भी है: “टीम स्टैंडअप 10:00 AM EST पर।” बर्लिन में मेरे टीममेट को उसी इवेंट के लिए “4:00 PM CET” दिखना चाहिए। एक ही मीटिंग, अलग-अलग डिस्प्ले टाइम, क्योंकि यह एक निरपेक्ष क्षण है जिसमें हम सभी शामिल हो रहे हैं।
दो अलग-अलग प्रकार के इवेंट्स, दो अलग-अलग स्टोरेज स्ट्रैटेजी। मीटिंग को TIMESTAMPTZ मिलता है। रिमाइंडर को TIME प्लस मेरी वर्तमान टाइमज़ोन सेटिंग मिलती है। दोनों को एक ही फील्ड में फोर्स करने की कोशिश न करें।
प्रोडक्शन में टूटने वाली चीजें
सही टाइप्स के साथ भी, प्रिसिजन आपको मुसीबत में डाल सकती है। Postgres माइक्रोसेकंड स्टोर करता है: 10:00:00.123456। JavaScript का Date ऑब्जेक्ट मिलीसेकंड का उपयोग करता है: 10:00:00.123।
तो यह क्वेरी रहस्यमय तरीके से कोई रिटर्न नहीं दे सकती है:
SELECT * FROM orders WHERE created_at = '2026-01-15 10:00:00.123';डेटाबेस में 10:00:00.123456 है और आपका कोड 10:00:00.123 पास करता है। आपके ड्राइवर इसे कैसे हैंडल करता है, इस पर निर्भर करते हुए, वे मैच नहीं कर सकते हैं।
टाइमस्टैम्प्स के लिए exact equality का उपयोग न करें। रेंज क्वेरी का उपयोग करें, या—बेहतर है—अपने क्रिएशन टाइमस्टैम्प से रिकॉर्ड्स को लुकअप न करें। प्रॉपर यूनिक कंस्ट्रेंट या आइडेम्पोटेंसी की का उपयोग करें।
व्यावहारिक नियम
TIMESTAMPTZ को डिफॉल्ट बनाएं। अगर संदेह है, TIMESTAMPTZ का उपयोग करें। यह मल्टी-रीजन डिप्लॉयमेंट, डेलाइट सेविंग टाइम, और भविष्य के टाइमज़ोन बदलावों को ऑटोमैटिकली हैंडल करता है। यह TIMESTAMP के समान स्टोरेज साइज है, तो कोई पेनाल्टी नहीं है।
कॉन्टेक्स्ट को अलग से स्टोर करें। अगर आपको “प्रस्थान 8:00 AM EST” को वास्तविक क्षण के साथ दिखाने की जरूरत है, तो TIMESTAMPTZ और origin_timezone को अलग-अलग कॉलम के रूप में स्टोर करें। सब कुछ को एक फील्ड में एन्कोड करने की कोशिश न करें।
इंटरवल्स के बारे में सोचें। बहुत सारे समय-संबंधित रिक्वायरमेंट वास्तव में क्षणों के बारे में नहीं, अवधि के बारे में हैं। “यह कब से पेंडिंग है?” “यह कब एक्सपायर होगा?” टाइमज़ोन कन्वर्जन नहीं, INTERVAL ऑपरेशन्स का उपयोग करें।
सब कुछ UTC में चलाएं। आपके सर्वर UTC पर सेट होने चाहिए। आपके डेटाबेस सेशन्स डिफॉल्ट रूप से UTC होने चाहिए। लोकल टाइमज़ोन में केवल तभी कन्वर्ट करें जब यूजर्स को डिस्प्ले कर रहे हों, और केवल तभी जब आपको पता हो कि कौन सा टाइमज़ोन मायने रखता है।
क्लाइंट्स से टाइमज़ोन जानकारी की आवश्यकता रखें। अगर कोई क्लाइंट 2026-01-15T10:00:00 बिना ऑफसेट के भेजता है, तो उसे रिजेक्ट करें। Z या -05:00 जैसे स्पष्ट ऑफसेट के साथ ISO-8601 फॉर्मेट की आवश्यकता रखें। अनुमान न लगाएं।
अच्छे डिफॉल्ट्स को लागू करना
अगर TIMESTAMPTZ आपका डिफॉल्ट है (और होना चाहिए), तो इसे डेटाबेस लेवल पर लागू करने पर विचार करें। एक ट्रिगर जो TIMESTAMP WITHOUT TIME ZONE कॉलम को रिजेक्ट करता है, यह चरम लग सकता है, लेकिन स्कीमा क्रिएशन टाइम पर “TZ जोड़ना भूल गए” को पकड़ना इससे बेहतर है कि छह महीने बाद किसी को डीबग करना पड़े जब कोई नई टेबल जोड़ता है और भूल जाता है।
उस ट्रेन टिकट ने मुझे क्या सिखाया
डेटाबेस में समय मुश्किल इसलिए नहीं है क्योंकि टाइमस्टैम्प कॉम्प्लिकेटेड हैं। यह मुश्किल इसलिए है क्योंकि हम आमतौर पर कई चिंताओं को एक फील्ड में स्टोर कर रहे होते हैं, या यह नहीं सोच रहे होते कि हम वास्तव में यूजर्स को क्या दिखाना चाहते हैं।
उस ट्रेन टिकट ने इसे सही किया था: ऑरिजिन टाइमज़ोन में प्रस्थान समय, डेस्टिनेशन टाइमज़ोन में आगमन समय, और अवधि को पूरी तरह से अलग चीज के रूप में। तीन अलग-अलग जानकारी के टुकड़े, हर एक अपने तरीके से अर्थपूर्ण।
आपका डेटाबेस भी वही कर सकता है। निरपेक्ष क्षणों को TIMESTAMPTZ के रूप में स्टोर करें। डिस्प्ले कॉन्टेक्स्ट (टाइमज़ोन, लोकेशन्स) को अलग कॉलम के रूप में स्टोर करें। अवधियों के लिए INTERVAL टाइप्स का उपयोग करें। जब आपको जरूरत हो तो Postgres को कन्वर्जन करने दें, लेकिन स्पष्ट रहें कि किस उद्देश्य के लिए कौन सा टाइमज़ोन मायने रखता है।
ज्यादातर समय, इसका मतलब हर जगह TIMESTAMPTZ और UTC है, और केवल डिस्प्ले टाइम पर टाइमज़ोन कन्वर्जन है। लेकिन जब आपको वॉल-क्लॉक टाइम या रिकरिंग शेड्यूल चाहिए, तो TIMESTAMP या TIME टाइप्स बिल्कुल उसी कारण के लिए मौजूद हैं।
कुंजी यह जानना है कि आप कौन सा सवाल पूछने की कोशिश कर रहे हैं: “यह कब हुआ?” बनाम “मुझे वहां कितने बजे होना चाहिए?” बनाम “इसमें कितना समय लगेगा?” ये समय के बारे में सभी अलग-अलग सवाल हैं, और उन्हें अक्सर अलग-अलग स्टोरेज स्ट्रैटेजी की जरूरत होती है।
इस बारे में सोचें कि आपके यूजर्स को क्या देखने की जरूरत है। फिर वह डेटा स्टोर करें जो उन्हें बिल्कुल वही दिखाने दे।
संसाधन
- PostgreSQL Date/Time Types Documentation
- PostgreSQL Timestamp Best Practices
- ISO 8601 Date and Time Format
- Time Zone Database (IANA)
- Dealing with Timestamps in Distributed Systems