PostgreSQL में पुनरावर्ती शेड्यूल और समय क्षेत्र: पैटर्न
PostgreSQL में पुनरावर्ती शेड्यूल और समय क्षेत्रों को व्यवहारिक भंडारण, recurrence नियम, एक्सेप्शन्स और क्वेरी पैटर्न के साथ समझें ताकि कैलेंडर सही रहे।

क्यों समय क्षेत्र और पुनरावर्ती इवेंट्स गलत होते हैं
अधिकांश कैलेंडर बग गणित की गलतियों से नहीं होते — वे अर्थ की गलतियाँ हैं। आप एक चीज़ स्टोर करते हैं (एक पल यानी instant), लेकिन उपयोगकर्ता कुछ और उम्मीद करते हैं (एक विशेष स्थान में स्थानीय घड़ी का समय)। यही अंतर है कि क्यों पुनरावर्ती शेड्यूल और समय क्षेत्र टेस्ट में ठीक लग सकते हैं और असली उपयोगकर्ताओं के आने पर टूट जाते हैं।
डे लाइट सेविंग टाइम (DST) क्लासिक ट्रिगर है। "हर रविवार 09:00" जैसा नियम "शुरुआती टाइम्पस्टैम्प से हर 7 दिन" जैसा नहीं होता। जब ऑफसेट बदलता है तो ये दोनों विचार एक घंटे से दूरी पर चले जाते हैं और आपका कैलेंडर चुपचाप गलत हो जाता है।
यात्रा और मिश्रित समय क्षेत्रों से और भी परतें जुड़ जाती हैं। एक बुकिंग भौतिक स्थान (जैसे Chicago में एक सैलून की कुर्सी) से जुड़ी हो सकती है, जबकि उसे देखने वाला व्यक्ति London में हो। अगर आप स्थान-आधारित शेड्यूल को व्यक्ति-आधारित समझते हैं, तो कम से कम एक पक्ष को गलत स्थानीय समय दिखेगा।
सामान्य विफलता के तरीके:
- आप एक स्टोर किए गए टाइम्पस्टैम्प में अंतर जोड़कर पुनरावृत्तियाँ बनाते हैं, फिर DST बदल जाता है।
- आप "स्थानीय समय" बिना ज़ोन नियमों के स्टोर करते हैं, इसलिए बाद में इरादे किए गए instants को पुनर्निर्माण नहीं कर सकते।
- आप केवल ऐसे तारीखों को टेस्ट करते हैं जो कभी DST सीमा को पार नहीं करते।
- आप एक ही क्वेरी में “event time zone”, “user time zone”, और “server time zone” मिला देते हैं।
स्कीमा चुनने से पहले तय करें कि आपके प्रोडक्ट के लिए "सही" का क्या मतलब है।
बुकिंग के लिए, "सही" आमतौर पर मतलब होता है: अपॉइंटमेंट उस वॉल-क्लॉक समय पर होता है जो वेन्यू के टाइम जोन में इरादा था, और जो भी देख रहा है उसे सही रूपांतरण मिलता है।
शिफ्ट के लिए, "सही" अक्सर यह होता है: शिफ्ट स्टोर के लिए एक निश्चित स्थानीय समय पर शुरू होती है, भले ही कर्मचारी यात्रा कर रहा हो।
यह एक निर्णय (स्थान से जुड़ा शेड्यूल बनाम व्यक्ति से जुड़ा) सब कुछ प्रभावित करता है: आप क्या स्टोर करते हैं, कैसे आप पुनरावृत्तियाँ जनरेट करते हैं, और कैसे आप बिना एक घंटे के आश्चर्यों के कैलेंडर व्यू क्वेरी करते हैं।
सही मानसिक मॉडल चुनें: instant बनाम local time
कई बग दो अलग-अलग समय की अवधारणाओं को मिलाने से आते हैं:
- एक instant: एक बिल्कुल निश्चित क्षण जो एक बार होता है।
- एक local time rule: एक वॉल-क्लॉक समय जैसे "पेरिस में हर सोमवार 9:00 AM"।
एक instant हर जगह समान होता है। "2026-03-10 14:00 UTC" एक instant है। वीडियो कॉल, उड़ान की प्रस्थानियाँ, और "ठीक इस क्षण पर यह नोटिफिकेशन भेजो" सामान्यतः instants होते हैं।
लोकल समय वह है जो लोग किसी स्थान की घड़ी पर पढ़ते हैं। "Europe/Paris में हर वर्कडे 9:00 AM" लोकल समय है। स्टोर के घंटे, नियमित कक्षाएँ, और स्टाफ शिफ्ट्स आमतौर पर किसी स्थान के टाइम ज़ोन से जुड़े होते हैं। टाइम ज़ोन अर्थ का हिस्सा होता है, सिर्फ़ डिस्प्ले पसंद नहीं।
एक सरल नियम:
- जब इवेंट को एक वास्तविक वैश्विक क्षण पर होना जरूरी हो तो start/end को instants के रूप में स्टोर करें।
- जब इवेंट स्थानीय घड़ी के अनुसार चलना है तो local date और local time के साथ एक zone ID स्टोर करें।
- अगर उपयोगकर्ता यात्रा कर रहे हैं, तो उन्हें उनके viewer zone में समय दिखाएँ, लेकिन शेड्यूल को उसके स्रोत zone पर एंकर रखें।
- ऑफसेट जैसे "+02:00" से ज़ोन का अनुमान न लगाएँ। ऑफसेट्स में DST नियम नहीं होते।
उदाहरण: एक अस्पताल शिफ्ट "Mon-Fri 09:00-17:00 America/New_York" है। DST परिवर्तन वाले हफ्ते में शिफ्ट स्थानीय रूप से फिर भी 9 से 5 होगी, भले ही UTC instants एक घंटे खिसक जाएँ।
PostgreSQL प्रकार जो मायने रखते हैं (और किनसे बचें)
अधिकतर कैलेंडर बग एक गलत कॉलम प्रकार से शुरू होते हैं। मुख्य बात है कि एक वास्तविक पल को दीवार-घड़ी की उम्मीद से अलग रखना।
वास्तविक instants के लिए timestamptz का उपयोग करें: बुकिंग्स, क्लॉक-इन्स, नोटिफिकेशन, और ऐसी कोई भी चीज़ जिसे आप उपयोगकर्ताओं या क्षेत्रों के बीच तुलना करते हैं। PostgreSQL इसे एक absolute instant के रूप में स्टोर करता है और डिस्प्ले के लिए कन्वर्ट करता है, इसलिए ordering और overlap चेक सही तरीके से काम करते हैं।
स्थानीय वॉल-क्लॉक मानों के लिए timestamp without time zone का उपयोग करें, जो अपने आप में instants नहीं होते — जैसे "हर सोमवार 09:00" या "दुकान 10:00 पर खुलती है"। इसे एक time zone identifier के साथ पेयर करें और occurrences जनरेट करते समय ही वास्तविक instant में बदलें।
पुनरावृत्ति पैटर्न के लिए बेसिक टाइप मदद करते हैं:
dateदिन-आधारित exceptions (छुट्टियाँ) के लिएtimeदैनिक प्रारंभ समय के लिएintervalअवधि के लिए (जैसे 6-घंटे की शिफ्ट)
टाइम ज़ोन को IANA नाम के रूप में स्टोर करें (उदाहरण के लिए America/New_York) text कॉलम में (या छोटे लुकअप टेबल में)। -0500 जैसे ऑफसेट पर्याप्त नहीं हैं क्योंकि उनमें DST नियम शामिल नहीं होते।
कई ऐप्स के लिए एक व्यावहारिक सेट:
- बुक की गई अपॉइंटमेंट्स के start/end instants के लिए
timestamptz - exception दिनों के लिए
date - नियमित स्थानीय start time के लिए
time - अवधि के लिए
interval - IANA time zone ID के लिए
text
बुकिंग और शिफ्ट ऐप्स के लिए डेटा मॉडल विकल्प
सर्वोत्तम स्कीमा इस पर निर्भर करता है कि शेड्यूल कितनी बार बदलते हैं और लोग कितना आगे ब्राउज़ करते हैं। आप आमतौर पर कई पंक्तियाँ पहले से लिखने और पढ़ते समय उन्हें जनरेट करने के बीच चुन रहे होते हैं।
विकल्प A: हर occurrence स्टोर करें
हर शिफ्ट या बुकिंग के लिए एक पंक्ति डालें (पहले से एक्सपैंड)। यह क्वेरी करने में आसान है और समझने में सरल है। ट्रेड-ऑफ़ भारी लिखने और किसी नियम के बदलने पर बहुत सारे अपडेट हैं।
यह तब अच्छा है जब इवेंट्स अधिकतर एक-बार के हों, या जब आप केवल थोड़ा आगे (उदाहरण के लिए अगले 30 दिन) के लिए occurrences बनाते हैं।
विकल्प B: एक नियम स्टोर करें और पढ़ते समय एक्सपैंड करें
एक शेड्यूल नियम स्टोर करें (जैसे "सप्ताह में Mon और Wed को 09:00 America/New_York में") और मांग पर अनुरोधित रेंज के लिए occurrences जनरेट करें।
यह लचीला और स्टोरेज-लाईट है, परन्तु क्वेरीज अधिक जटिल हो जाती हैं। महीने के व्यू भी धीमे हो सकते हैं जब तक आप कैशिंग न करें।
विकल्प C: नियम प्लस कैश्ड occurrences (हाइब्रिड)
रूल को सोर्स ऑफ ट्रुथ के रूप में रखें, और साथ ही एक रोलिंग विंडो के लिए जनरेटेड occurrences स्टोर करें (उदाहरण: 60–90 दिन)। जब नियम बदलता है, आप कैश को रीजनरेट कर देते हैं।
यह शिफ्ट ऐप्स के लिए अच्छा डिफ़ॉल्ट है: महीने के व्यू तेज़ रहते हैं, पर पैटर्न को बदलने के लिए एक ही जगह रहती है।
एक व्यावहारिक टेबल सेट:
- schedule: owner/resource, time zone, local start time, duration, recurrence rule
- occurrence:
start_at timestamptz,end_at timestamptzके साथ विस्तारित इंस्टेन्सेस, और status - exception: "इस तारीख को छोड़ो" या "इस तारीख अलग है" मार्कर
- override: प्रति-इवेंट संपादन जैसे बदला हुआ start time, स्टाफ बदलना, canceled flag
- (वैकल्पिक) schedule_cache_state: अंतिम जनरेट की गई रेंज ताकि पता चले आगे क्या भरना है
कैलेंडर रेंज क्वेरीज के लिए, "मुझे इस विंडो में सब दिखाओ" के लिए इंडेक्स करें:
- occurrence पर:
btree (resource_id, start_at)और अक्सरbtree (resource_id, end_at) - यदि आप अक्सर "overlaps range" क्वेरी करते हैं: एक जनरेटेड
tstzrange(start_at, end_at)प्लसgistइंडेक्स
recurrence rules को नाज़ुक बनाए बिना प्रतिनिधित्व करना
पुनरावर्ती शेड्यूल तब टूटते हैं जब नियम बहुत चालाक, बहुत लचीला, या एक अनक्वेरीबल ब्लॉब के रूप में स्टोर किया जाता है। एक अच्छा नियम फॉर्मेट ऐसा हो जो आपकी ऐप वेलिडेट कर सके और आपकी टीम आसानी से समझा सके।
दो सामान्य दृष्टियाँ:
- साधारण कस्टम फील्ड्स उन पैटर्न्स के लिए जिन्हें आप वास्तव में सपोर्ट करते हैं (साप्ताहिक शिफ्ट, मासिक बिलिंग तारीखें)।
- iCalendar-जैसे नियम (RRULE-स्टाइल) जब आपको कैलेंडर आयात/निर्यात करना हो या कई संयोजनों का समर्थन करना हो।
एक व्यावहारिक बीच का रास्ता: सीमित विकल्पों की अनुमति दें, उन्हें कॉलम में स्टोर करें, और किसी भी RRULE स्ट्रिंग को केवल इंटरचेंज के रूप में मानें।
उदाहरण के लिए, एक साप्ताहिक शिफ्ट नियम फ़ील्ड्स से व्यक्त किया जा सकता है:
freq(daily/weekly/monthly) औरinterval(हर N)byweekday(0-6 की array या bitmask)- वैकल्पिक
bymonthday(1-31) मासिक नियमों के लिए starts_at_local(यूज़र द्वारा चुनी गई स्थानीय date+time) औरtzid- वैकल्पिक
until_dateयाcount(दोनों का समर्थन केवल तब करें जब वास्तव में जरूरी हो)
सीमाओं के लिए, हमेशा duration (जैसे 8 घंटे) स्टोर करना पसंद करें बजाय हर occurrence के लिए एक end timestamp स्टोर करने के। अवधि क्लॉक शिफ्ट होने पर स्थिर रहती है। आप अभी भी occurrence के लिए end time गणना कर सकते हैं: occurrence start + duration।
जब आप नियम को एक्सपैंड कर रहे हों, इसे सुरक्षित और सीमित रखें:
- केवल
window_startऔरwindow_endके भीतर एक्सपैंड करें। - ओवरनाइट इवेंट्स के लिए छोटा बफ़र जोड़ें (उदाहरण: 1 दिन)।
- अधिकतम instance संख्या के बाद रोकें (जैसे 500)।
- जनरेट करने से पहले पहले से उम्मीदवारों को फ़िल्टर करें (जैसे
tzid,freq, और start date)।
स्टेप बाय स्टेप: एक DST-सुरक्षित पुनरावर्ती शेड्यूल बनाना
एक भरोसेमंद पैटर्न यह है: प्रत्येक occurrence को पहले एक स्थानीय कैलेंडर विचार के रूप में ट्रीट करें (date + local time + location time zone), फिर केवल जब आपको सॉर्ट, कॉन्फ्लिक्ट चेक, या डिस्प्ले करने की ज़रूरत हो तब उसे एक instant में कन्वर्ट करें।
1) UTC अनुमान नहीं, स्थानीय इरादा स्टोर करें
शेड्यूल का location time zone (IANA नाम जैसे America/New_York) और एक स्थानीय start time (उदाहरण 09:00) सेव करें। वह स्थानीय समय वही है जो बिज़नेस चाहता है, भले ही DST शिफ्ट हो।
एक अवधि और नियम के स्पष्ट सीमाएँ भी स्टोर करें: एक start date, और या तो एक end date या repeat count। सीमाएँ "अनंत विस्तार" बग्स को रोकेगी।
2) exceptions और overrides को अलग रखें
दो छोटी टेबल्स रखें: एक skipped dates के लिए, और एक changed occurrences के लिए। उन्हें schedule_id + local_date से की करें ताकि आप मौलिक पुनरावृत्ति से साफ़ मेल खा सकें।
एक व्यावहारिक रूप इस तरह दिखता है:
-- core schedule
-- tz is the location time zone
-- start_time is local wall-clock time
schedule(id, tz text, start_date date, end_date date, start_time time, duration_mins int, by_dow int[])
schedule_skip(schedule_id, local_date date)
schedule_override(schedule_id, local_date date, new_start_time time, new_duration_mins int)
3) केवल अनुरोधित विंडो के अंदर एक्सपैंड करें
आप जिन रेंज का रेंडर कर रहे हैं (सप्ताह, महीना) उसके लिए उम्मीदवार स्थानीय तिथियाँ जनरेट करें। दिन-ऑफ-वीक द्वारा फ़िल्टर करें, फिर skips और overrides लागू करें。
WITH days AS (
SELECT d::date AS local_date
FROM generate_series($1::date, $2::date, interval '1 day') d
), base AS (
SELECT s.id, s.tz, days.local_date,
make_timestamp(extract(year from days.local_date)::int,
extract(month from days.local_date)::int,
extract(day from days.local_date)::int,
extract(hour from s.start_time)::int,
extract(minute from s.start_time)::int, 0) AS local_start
FROM schedule s
JOIN days ON days.local_date BETWEEN s.start_date AND s.end_date
WHERE extract(dow from days.local_date)::int = ANY (s.by_dow)
)
SELECT b.id,
(b.local_start AT TIME ZONE b.tz) AS start_utc
FROM base b
LEFT JOIN schedule_skip sk
ON sk.schedule_id = b.id AND sk.local_date = b.local_date
WHERE sk.schedule_id IS NULL;
4) दर्शक के लिए अंतिम समय पर कन्वर्ट करें
s start_utc को timestamptz के रूप में रखें ताकि सॉर्टिंग, कॉन्फ्लिक्ट चेक, और बुकिंग सही रहें। केवल डिस्प्ले करते समय viewer के time zone में कन्वर्ट करें। इससे DST के चौंकाने वाले परिणाम टलते हैं और कैलेंडर व्यू कंसिस्टेंट रहते हैं।
सही कैलेंडर व्यू बनाने के लिए क्वेरी पैटर्न
एक कैलेंडर स्क्रीन आम तौर पर एक रेंज क्वेरी होती है: "मुझे from_ts और to_ts के बीच की सब चीज़ें दिखाओ।" एक सुरक्षित पैटर्न है:
- केवल उस विंडो के भीतर उम्मीदवारों का विस्तार करें।
- exceptions/overrides लागू करें।
- अंतिम पंक्तियाँ
start_atऔरend_atके साथtimestamptzके रूप में आउटपुट करें।
दैनिक या साप्ताहिक विस्तार generate_series के साथ
सरल साप्ताहिक नियमों के लिए (जैसे "हर Mon-Fri को 09:00 स्थानीय"), शेड्यूल के टाइम ज़ोन में स्थानीय तिथियाँ जनरेट करें, फिर प्रत्येक स्थानीय date + local time को एक instant में बदल दें।
-- Inputs: :from_ts, :to_ts are timestamptz
-- rule.tz is an IANA zone like 'America/New_York'
WITH bounds AS (
SELECT
(:from_ts AT TIME ZONE rule.tz)::date AS from_local_date,
(:to_ts AT TIME ZONE rule.tz)::date AS to_local_date
FROM rule
WHERE rule.id = :rule_id
), days AS (
SELECT d::date AS local_date
FROM bounds, generate_series(from_local_date, to_local_date, interval '1 day') AS g(d)
)
SELECT
(local_date + rule.start_local_time) AT TIME ZONE rule.tz AS start_at,
(local_date + rule.end_local_time) AT TIME ZONE rule.tz AS end_at
FROM rule
JOIN days ON true
WHERE EXTRACT(ISODOW FROM local_date) = ANY(rule.by_isodow);
यह इसलिए अच्छा काम करता है क्योंकि timestamptz में कन्वर्ज़न हर occurrence पर होता है, इसलिए DST शिफ्ट सही दिन पर लागू होते हैं।
"nth weekday" जैसे जटिल नियमों के साथ recursive CTE
जब नियम "nth weekday", गैप्स, या कस्टम इंटरवल पर निर्भर हों, तो एक recursive CTE अगले occurrence को बार-बार जनरेट कर सकता है जब तक कि वह to_ts को पार न कर दे। यह सुनिश्चित करें कि recursion विंडो पर एंकर हो ताकि यह अनंत न चले।
उम्मीदवार पंक्तियों के बाद, exception टेबल्स को (rule_id, start_at) या लोकल की (rule_id, local_date) पर join करके overrides और cancellations लागू करें। अगर cancel रिकॉर्ड है तो पंक्ति हटा दें। अगर override है तो start_at/end_at को override मानों से बदल दें।
प्रदर्शन पैटर्न जो सबसे ज़्यादा मायने रखते हैं:
- जल्दी ही रेंज को सीमित करें: पहले नियमों को फ़िल्टर करें, फिर केवल
[from_ts, to_ts)के भीतर एक्सपैंड करें। - exception/override तालिकाओं पर
(rule_id, start_at)या(rule_id, local_date)पर इंडेक्स करें। - किसी महीने के व्यू के लिए वर्षों का डेटा एक्सपैंड करने से बचें।
- केवल तभी कैश्ड occurrences रखें जब आप उन्हें नियम बदलने पर साफ़ तरीके से नष्ट कर सकें।
exceptions और overrides को साफ़ तरीके से हैंडल करना
पुनरावर्ती शेड्यूल तभी उपयोगी होते हैं जब आप उन्हें सुरक्षित रूप से तोड़ सकें। बुकिंग और शिफ्ट ऐप्स में "सामान्य" सप्ताह बेस नियम है, और सब कुछ अन्यथा एक exception है: छुट्टियाँ, रद्द, मूव्ड अपॉइंटमेंट्स, या स्टाफ स्वैप्स। अगर exceptions बाद में जोड़ दिए जाएँ तो कैलेंडर व्यू ड्रीफ्ट करते हैं और डुप्लिकेट दिखते हैं।
तीन अवधारणाओं को अलग रखें:
- एक base schedule (recurrent rule और उसका time zone)
- स्किप्स (तिथियाँ या instances जिन्हें नहीं होना)
- overrides (एक ऐसा occurrence जो मौजूद है, पर बदल विवरण के साथ)
एक तय precedence order रखें
एक ऑर्डर चुनें और उसे हमेशा बनाए रखें। एक सामान्य विकल्प:
- बेस recurrence से उम्मीदवार जनरेट करें।
- overrides लागू करें (जनरेटेड को बदल दें)।
- स्किप्स लागू करें (छिपाएँ)।
यह सुनिश्चित करें कि नियम उपयोगकर्ताओं को एक वाक्य में समझाया जा सके।
जब एक override एक instance बदल दे तो duplicates से बचें
डुप्लिकेट आमतौर पर तब होते हैं जब क्वेरी जनरेटेड occurrence और override दोनों वापस कर देती है। इसे एक स्थिर की के साथ रोकें:
- प्रत्येक जनरेटेड इंस्टेन्स को एक स्थिर की दें, जैसे
(schedule_id, local_date, start_time, tzid)। - उस की को override पंक्ति पर "original occurrence key" के रूप में स्टोर करें।
- एक unique constraint जोड़ें ताकि केवल एक override किसी बेस occurrence पर हो सके।
फिर, क्वेरीज में, उन जनरेटेड occurrences को exclude करें जिनका मिलान करने वाला override है और override पंक्तियों को union में लाएँ।
ऑडिटेबिलिटी रखें पर बिना घर्षण के
Exceptions वही जगह हैं जहाँ विवाद होते हैं ("किसने मेरी शिफ्ट बदली?")। skips और overrides पर बुनियादी ऑडिट फ़ील्ड्स जोड़ें: created_by, created_at, updated_by, updated_at, और एक वैकल्पिक कारण।
ऑफ-बाय-वन-आवर बग्स का कारण बनने वाली सामान्य गलतियाँ
ज़्यादातर एक-घंटे की समस्याएँ दो अर्थों के समय को मिलाने से आती हैं: एक instant (UTC टाइमलाइन पर एक बिंदु) और एक स्थानीय घड़ी पढ़ना (जैसे न्यूयॉर्क में हर सोमवार 09:00)।
एक क्लासिक गलती है स्थानीय वॉल-क्लॉक नियम को timestamptz के रूप में स्टोर कर देना। अगर आप "Mondays at 09:00 America/New_York" को एक single timestamptz के रूप में सेव करते हैं, तो आपने पहले से ही एक विशेष तारीख (और DST स्थिति) चुन ली है। बाद में जब आप भविष्य के Mondays जनरेट करेंगे, मूल इरादा ("हमेशा 09:00 स्थानीय") गायब हो जाएगा।
एक और बार-बार कारण है स्थिर UTC ऑफसेट जैसे -05:00 पर निर्भर रहना बजाय IANA zone नाम के। ऑफसेट्स में DST नियम नहीं होते। ज़ोन ID (उदाहरण: America/New_York) स्टोर करें और PostgreSQL को प्रत्येक तारीख के लिए सही नियम लागू करने दें।
ध्यान दें कि आप कब कन्वर्ट करते हैं। अगर आप recurrence जनरेशन के दौरान बहुत जल्दी UTC में कन्वर्ट कर देते हैं, तो आप एक DST ऑफसेट को फ्रीज़ कर सकते हैं और उसे हर occurrence पर लागू कर देंगे। एक सुरक्षित पैटर्न है: occurrences को स्थानीय शब्दों (date + local time + zone) में जनरेट करें, फिर हर occurrence को अलग-अलग instant में कन्वर्ट करें।
बार-बार दिखने वाली गलतियाँ:
timestamptzका उपयोग एक पुनरावर्ती स्थानीय समय-ऑफ़-डे के लिए करना (आपकोtime+tzid+ नियम चाहिए था)।- केवल एक ऑफसेट स्टोर करना, IANA zone नहीं।
- recurrence जनरेशन के दौरान कन्वर्ट करना, बजाय अंत में।
- "हमेशा के लिए" रेकार्रेंस को बिना हार्ड विंडो के एक्सपैंड करना।
- DST स्टार्ट वीक और DST एंड वीक की टेस्टिंग न करना।
एक साधारण टेस्ट जो अधिकांश समस्याएँ पकड़ता है: एक DST ज़ोन चुनें, एक साप्ताहिक 09:00 शिफ्ट बनाएं, और DST परिवर्तन के पार एक दो-महीने का कैलेंडर रेंडर करें। सुनिश्चित करें कि हर इंस्टेंस स्थानीय रूप से 09:00 दिखे, भले ही underlying UTC instants अलग हों।
शिप करने से पहले त्वरित चेकलिस्ट
रिलीज़ से पहले बेसिक्स चेक करें:
- हर शेड्यूल किसी स्थान (या बिज़नेस यूनिट) से जुड़ा है और उस पर एक नामित time zone स्टोर है।
- आप IANA zone IDs (जैसे
America/New_York) स्टोर करते हैं, कच्चे offsets नहीं। - recurrence expansion केवल अनुरोधित रेंज के भीतर होता है।
- exceptions और overrides की एक ही, डॉक्यूमेंटेड precedence order है।
- आप DST परिवर्तन सप्ताह और उस शेड्यूल से अलग time zone में एक viewer का परीक्षण करते हैं।
एक वास्तविक ड्राय रन करें: एक स्टोर Europe/Berlin में साप्ताहिक 09:00 लोकल शिफ्ट रखता है। एक मैनेजर America/Los_Angeles से देखता है। पक्का करें कि शिफ्ट हर हफ्ते Berlin समय में 09:00 ही रहे, भले ही दोनों क्षेत्रों के DST बदलने की तारीखें अलग हों।
उदाहरण: साप्ताहिक स्टाफ शिफ्ट एक छुट्टी और DST परिवर्तन के साथ
एक छोटी क्लिनिक एक नियमित शिफ्ट चलाती है: हर सोमवार, 09:00 से 17:00 तक क्लिनिक के स्थानीय टाइम ज़ोन (America/New_York) में। क्लिनिक एक विशेष सोमवार को छुट्टी के कारण बंद है। एक स्टाफ सदस्य दो हफ्तों के लिए यूरोप में यात्रा कर रहा है, पर क्लिनिक शेड्यूल क्लिनिक की वॉल क्लॉक टाइम से जुड़ा रहना चाहिए, न कि कर्मचारी के वर्तमान स्थान से।
इसे सही तरीके से काम करने के लिए:
- लोकल तिथियों पर एंकर किए गए recurrence नियम स्टोर करें (weekday = Monday, local times = 09:00 से 17:00)।
- शेड्यूल टाइम ज़ोन (
America/New_York) स्टोर करें। - नियम का एक प्रभावी start date रखें ताकि स्पष्ट एंकर मिल सके।
- छुट्टी वाले सोमवार को कैंसल करने के लिए एक exception स्टोर करें (और एक-ऑफ बदलावों के लिए overrides)।
अब दो-सप्ताह का कैलेंडर रेंज रेंडर करें जिसमें New York में DST परिवर्तन शामिल है। क्वेरी उस स्थानीय तारीख रेंज में Mondays जनरेट करती है, क्लिनिक के लोकल टाइम जोड़ती है, फिर प्रत्येक occurrence को एक absolute instant (timestamptz) में कन्वर्ट करती है। चूंकि कन्वर्ज़न हर occurrence पर होता है, DST सही दिन पर हैंडल हो जाता है।
विभिन्न दर्शक उसी instant के लिए अलग लोकल घड़ी समय देखते हैं:
- Los Angeles का मैनेजर इसे क्लॉक पर पहले देखेगा।
- Europe में यात्रा कर रहा स्टाफ इसे क्लॉक पर बाद में देखेगा।
क्लिनिक को फिर भी वही मिलता है जो उसने चाहा: हर सोमवार 09:00 से 17:00 New York समय, जब तक कि वह रद्द न हो।
अगले कदम: लागू करें, टेस्ट करें, और इसे मेंटेनेबल रखें
समय के प्रति आपका एप्रोच जल्दी लॉक करें: क्या आप केवल नियम स्टोर करेंगे, केवल occurrences स्टोर करेंगे, या हाइब्रिड? कई बुकिंग और शिफ्ट प्रोडक्ट्स के लिए हाइब्रिड अच्छा काम करता है: नियम सोर्स ऑफ ट्रुथ के रूप में रखें, रोलिंग कैश स्टोर करें यदि ज़रूरी हो, और exceptions व overrides को कंक्रीट पंक्तियों के रूप में स्टोर करें।
अपना "time contract" एक जगह लिखें: क्या instant माना जाएगा, क्या स्थानीय वॉल टाइम माना जाएगा, और कौन से कॉलम किसे स्टोर करते हैं। इससे यह रोका जा सकता है कि एक एंडपॉइंट लोकल समय लौटाए और दूसरा UTC।
recurrence जनरेशन को एक मॉड्यूल में रखें, न कि बिखरे SQL फ़्रैगमेंट्स में। अगर आप कभी यह बदलते हैं कि "लोकल 9:00 AM" कैसे इंटरप्रेट किया जाता है, तो आप एक ही जगह अपडेट करना चाहेंगे।
अगर आप एक शेड्यूलिंग टूल बना रहे हैं बिना हर चीज़ को हाथ से कोड किए, तो AppMaster (appmaster.io) ऐसी सलाह के लिए एक व्यावहारिक फिट है: आप Data Designer में डेटाबेस मॉडल कर सकते हैं, बिज़नेस प्रोसेसेस में recurrence और exception लॉजिक बना सकते हैं, और फिर भी वास्तविक जनरेटेड बैकएंड और ऐप कोड हासिल कर सकते हैं।


