لماذا يعرض دليل البرامج وقتًا خاطئًا: طوابع XMLTV الزمنية والإزاحات

الدليل الذي يتقدم أو يتأخر بعدد صحيح من الساعات نادرًا ما يكون لغزًا. إنها لاحقة كانت موجودة في الملف وتجاهلها القارئ.

<programme start="20260719150000 +0200" stop="20260719160000 +0200" channel="one.example.com">

أربعة عشر رقمًا، ثم مسافة، ثم خمسة محارف تقرر إن كان هذا الصف يبدأ الساعة 15:00 أم 13:00. الأرقام الأربعة عشر ليست لحظة زمنية. إنها قراءة ساعة حائط بلا موقع، ودليل البرامج الصحيح يتوقف على قراءة تلك المحارف الخمسة الأخيرة، أو على تفسير غيابها بالمعنى الذي قصدته أداة الجلب (grabber) التي كتبت الملف.

قبل أن تغيّر أي شيء، قِس الانزياح. إذا كان كل برنامج على كل قناة منزاحًا بالعدد الصحيح نفسه من الساعات، فالعطل واحد، وهو في الغالب الأعم إزاحة كانت موجودة في الملف ولم يعمل بها القارئ. أما إذا كانت بعض القنوات صحيحة وبعضها خاطئًا، أو كان صف واحد منزاحًا والصفان حوله سليمين، فتلك مشكلة أخرى ولن يمسّها أي مما يلي عن الإزاحات.

مشغّلنا يحلّل الدليل على جهازك، مقابل العنوان الذي توجّهه إليه. نحن ننتقي عناوين أدلة، والمحلِّل نفسه يعمل عندنا على العناوين المنتقاة لأغراض الكتالوج. أما عنوان الدليل الذي تقدّمه أنت فيُجلب ويُقرأ على جهازك، ونحن لا نحتفظ ببيانات دليلك أبدًا.

انزياح الدليل كله وبرنامج واحد خاطئ عطلان مختلفان

للانزياح المنتظم ثلاثة أسباب محتملة، وتمييزها يستغرق دقيقة.

الأول إزاحة حملها الملف ورماها القارئ. والثاني إزاحة لم يحملها الملف أصلًا، حيث كتبت أداة الجلب أوقات ساعة الحائط المحلية بلا لاحقة ولم يكن أمام القارئ ما يدلّه على ذلك. والثالث تصحيح طُبِّق مرتين، مرة من المصدر ومرة منك، وهو أول ما يلجأ إليه الناس وهو الذي ينتج انزياحًا يساوي ضعف ما ضبطوه بالضبط. إذا انتقل دليلك من ساعة واحدة خطأً إلى ثلاث ساعات بعد أن ضبطت تصحيحًا بساعتين، فهذه هي الحالة الثالثة، والحل أن تعيد التصحيح إلى الصفر قبل أن تشخّص أي شيء آخر.

القياس يحتاج مقارنتين وساعة. اختر برنامجًا يُبَث الآن ولاحظ كم يبعد وقت بدايته المعلن عن الحقيقة، ثم كرّر ذلك على قناة ثانية من المصدر نفسه، ثم على قناة من مصدر آخر. الخطأ نفسه على قناتي مصدر واحد وانعدامه على المصدر الآخر يحصر المشكلة في ملف ذلك المصدر. أما الخطأ نفسه في كل مكان فيشير إلى ساعة الجهاز، وذلك إصلاح مختلف تمامًا.

الانزياح غير المنتظم ليس مشكلة منطقة زمنية من الأساس. السبب المعتاد أن صف الدليل مرتبط بالقناة الخطأ، فيبدو جدول محطة أخرى صحيح تمامًا وكأنه جدول خاطئ لقناتك. هذا عطل هوية، وشكله موضوع مقالة منفصلة. ووجود معرّف في قائمة التشغيل شيء، ومطابقة ذلك المعرّف لدليلك شيء آخر: في الأكبر من قائمتي التشغيل اللتين قِسناهما، انتهى 527 صفًا من 1,895 دون أي معرّف صالح للاستخدام. أما بقية الأسباب غير المنتظمة فهي مدد لا أوقات بداية، وسيأتي ذكرها لاحقًا.

اللاحقة التي تبدأ بإشارة أخبرتك أنها رقم

هذه هي القاعدة التي يطبّقها قارئ الطوابع الزمنية عندنا، ومعها التعليق الذي يعلوها، حرفيًا:

var suffix = value[14..].Trim();
if (suffix.Length > 0 && (suffix[0] == '+' || suffix[0] == '-'))
{
    // A suffix that looks like a numeric offset must read as one. Falling
    // through to the assumed timezone here is what used to shift a whole
    // feed by hours with nothing in the logs: the offset was present, said
    // +02:00, and was read as UTC.
    if (!TryParseOffset(suffix, out var offset))
    {
        return false;
    }

ذلك التعليق يصف عطلًا من صنعنا. أخطأنا في هذا في نسخة سابقة من هذا القارئ: كانت تعامل الإزاحة التي لا تُقرأ معاملة انعدام الإزاحة تمامًا، فتسقط إلى الحالة الافتراضية وتفترض UTC وتمضي. والعَرَض كان دليلًا منزاحًا بمقدار الإزاحة التي أعلنها الملف بالضبط، على كل قناة، ولكل يوم في الموجز، مع ملخص استيراد نظيف ولا شيء في السجل تنظر إليه. والسبب كان خروجًا مبكرًا واحدًا ناقصًا من الدالة. والثمن أن العطل كان غير قابل للاكتشاف من داخل التطبيق: لم يتحرك عدّاد، ولم يُكتب سطر سجل، وظل الدليل خاطئًا حتى قارنه أحدهم بساعة.

رفض الصف أسوأ لذلك البرنامج الواحد وأفضل بكثير للدليل. البرنامج المُسقط يترك فجوة تراها بعينك. أما الموجز الذي أُعيد تفسيره بصمت فهو جدول خاطئ خطأً منتظمًا وواثقًا، ولن يخبرك بذلك مهما حدّقت فيه.

في تلك القاعدة ثغرة، ويستحق الأمر قولها صراحة. الفحص ينظر إلى أول محرف من اللاحقة فقط. لاحقة مكتوبة هكذا GMT+0200، بالتسمية قبل الإشارة لا بعدها، لا تبدأ بإشارة، فتسلك المسار الصامت. مررناها على المحلِّل الذي نشحنه فأعادت 15:00 UTC لتوقيت محلي 15:00، أي أن الإزاحة موجودة في السلسلة ومُتجاهَلة. لا اختبار يثبّت هذا الشكل، ولا نملك مجموعة XMLTV خاصة بنا، فغياب الشاهد ليس دليلًا على ندرته.

ثلاثة أشكال للإزاحة تُقرأ وسبعة تُرفض

بعد الإشارة يأخذ القارئ كل شيء حتى أول مسافة ويقيس سلسلة الأرقام. ثلاثة أطوال فقط لها معنى: محرفان لإزاحة بالساعات وحدها، وأربعة للساعات والدقائق ملتصقة، وخمسة حين يكون المحرف الثالث نقطتين رأسيتين. تُحَل +02 و+0200 و+02:00 جميعها إلى الساعتين نفسيهما. وأي شيء أقل من ثلاثة محارف في المجموع يُرفض قبل بلوغ جملة switch، وكل طول آخر يخرج من فرع افتراضي.

الأشكال الثلاثة كلها من ISO 8601، ولهذا تُقرأ ثلاثتها. +0200 هو الشكل الأساسي، و+02:00 الممتد، و+02 الساعات وحدها، وXMLTV DTD يكتب مثاله بالشكل الأساسي، هكذا +0300. ولو باركنا شكلًا واحدًا وأخذنا الآخرين بصمت على أنهما UTC لأزحنا كل موجز اختار غير ذلك ساعةً أو أكثر.

وحول ذلك تسامحان. المسافة قبل الإشارة اختيارية، لأن اللاحقة معرَّفة بأنها كل شيء من المحرف الخامس عشر فصاعدًا، فتعمل 20260719150000+0200 تمامًا كسابقتها. وتسمية المنطقة بعد إزاحة حقيقية تُهمَل بدل أن تُعامَل كتلف: +0100 BST تُقتطع عند المسافة، فتُقرأ +0100، وتعطي 14:00 UTC لتوقيت محلي 15:00. ومناطق النصف ساعة تأتي مجانًا، لأن الدقائق تُحلَّل كرقمين مستقلين، فتُحَل +05:30 كما ينبغي.

ولحدّ المحرف الخامس عشر هذا نتيجة لم يصممها أحد. نواة التاريخ تُؤخذ على أنها أول أربعة عشر محرفًا بالضبط، فالطابع الزمني الذي يحمل رقمًا خامس عشر، 202607191500000، تُحلَّل محارفه الأربعة عشر الأولى بنظافة ويُسلَّم الرقم 0 الشارد إلى قارئ اللاحقة، فيرى محرفًا ليس إشارة ويسلك مسار انعدام الإزاحة. مررنا هذه القيمة على المحلِّل فأعادت 15:00 UTC وأُهمل الرقم الزائد بلا تعليق. هذه هي النتيجة غير المؤذية لحرف Z في النهاية، فالسقوط نفسه يتولاه ومعناه UTC على أي حال، وهي اقتطاع صامت لأي شيء غيره. وحالة Z غير مؤذية فقط لأن لا شيء في التطبيق الذي نشحنه يضبط منطقة زمنية مفترضة، وهذا قسم قائم بذاته أدناه.

هذه السبعة مثبَّتة بالاختبارات كحالات فشل، وكل واحد منها يسمّي صنفًا حقيقيًا من التلف:

  • +2، ساعة برقم واحد، أقصر من أن تبلغ جملة switch
  • +02:0، شكل بنقطتين ينقصه رقم
  • +0X30، حرف داخل سلسلة الأرقام
  • +2400، أربع وعشرون ساعة
  • +99:00، تسع وتسعون ساعة
  • -0270، سبعون دقيقة
  • +، إشارة ولا شيء بعدها

حارسان ينتجان معظم تلك الرفوض. الدقائق فوق 59 مرفوضة رفضًا مباشرًا، والمقدار الكلي محدود بـ 14 ساعة، لأن نوع التاريخ الأساسي يرفض ما بعدها، ولأن الدليل الذي يدّعي إزاحة أكبر، كما يقول تعليق الشيفرة، «a guide claiming one is corrupt rather than exotic» (أي أنه تالف لا غريب). والحد شامل، فتُحَل +1400 إلى الساعة الواحدة بعد منتصف الليل بتوقيت UTC وتفشل +1401. واختبار الأرقام يشترط أرقام ASCII تحديدًا ويحلّل بلا تفعيل أي أنماط أرقام، فلا تتسلل إشارة في المقدمة ولا فاصل آلاف ولا رقم بعرض كامل عبر تحليل متساهل يراعي الثقافة.

قارئنا يرفض اثنين من الطوابع الزمنية الثلاثة في أمثلة الـ DTD

الـ DTD لا يسمح بالشكل ذي الأربعة عشر رقمًا وحده. يقول إن التاريخ يمكن أن يكون YYYYMMDDhhmmss أو أي مقطع بادئ منه، ويطبع ثلاثة أمثلة: 200007281733 BST و200209 و19880523083000 +0300. لا ينجو من محلِّلنا إلا الثالث، فيُحَل إلى 05:30 UTC. والثاني يسقط عند الحد الأدنى للطول، الذي يرفض كل ما هو أقصر من أربعة عشر محرفًا قبل أي محاولة تحليل. والأول يتجاوز ذلك الحد ثم يفشل رغم ذلك، لأن القارئ يقتطع أول أربعة عشر محرفًا كنواة تاريخ، و200007281733 B ليس تاريخًا.

هذا التضييق اختيار اخترناه نحن، وليس الملف مذنبًا حين ينطبق. الطابع الزمني بدقة الدقيقة لا ثواني فيه لتُقتطع، والطابع الذي يحمل سنة وشهرًا ليس بداية برنامج بأي معنى مفيد، والقارئ الذي يقبل أيًّا منهما سيكون مخترعًا للحقول الناقصة. رفضهما موقف يمكن الدفاع عنه. أما تقديمهما على أنهما تلف فلا، والصف الأخير في الجولة أدناه هو XMLTV مطابق للمواصفة ونحن نُسقطه.

المثال الأول في الـ DTD يحمل النصف الآخر من عدم التماثل في هذه المقالة داخل قوسين. 200007281733 BST مشروح هناك بـ (BST == +0100.)، فالمستند الذي يعرّف الصيغة يحسم الاختصار نفسه الذي يرفض قارئنا حسمه.

لماذا نصدّق +0100 BST ولا نصدّق BST وحدها

هنا عدم تماثل يبدو سهوًا وهو قرار. اللاحقة +0100 BST تُحَل إلى زائد ساعة، لأن الرقم موجود والتسمية تُرمى. واللاحقة BST وحدها تُحَل إلى UTC، وهذا خطأ بمقدار ساعة في كل تاريخ صيفي بريطاني تظهر فيه. وكلتاهما مثبَّتة بالاختبارات، أي أن كلتيهما مقصودة.

المناطق المسماة لا تُحسم أبدًا، والتعليق يعطي السبب: «the abbreviations are ambiguous across regions» (الاختصارات ملتبسة عبر المناطق). فـ BST هو التوقيت الصيفي البريطاني، وقد كُتب أيضًا للتوقيت القياسي في بنغلاديش، والفارق خمس ساعات. وCST هو التصادم الباقي في قاعدة بيانات tz نفسها، تحمله شيكاغو وهافانا وشنغهاي في آن واحد. وحسم الاسم يعني اختيار واحد منها، أي التخمين، والتخمين الذي يصيب في سوق ويخطئ في أخرى أسوأ من قاعدة تخطئ الخطأ نفسه في كل مكان.

اخترنا الجواب الخاطئ خطأً معلومًا بقاعدة ثابتة على التخمين، وهذا يكلّفنا ساعة في التواريخ الصيفية في أي موجز يسمّي أوقاته بهذه الطريقة بدل أن يرقّمها. وشرح الـ DTD لمثاله بنفسه يجعل الدفاع عن هذا أصعب مما بدا قبل كتابة هذه المقالة: وثائق الصيغة ذاتها حسمت اختصارًا واحدًا، والفقرة أعلاه هي كل حجّتنا في أننا ما زلنا لا نفعل.

هذا قيد حقيقي، ولا نعرف كم هو شائع هذا الشكل، لأن اسم المنطقة المجرد هو بالضبط المُدخل الذي لا ينتج عنه خطأ ولا عدّاد ولا شكوى، إلى أن يقارن أحدهم الدليل بساعة.

سطر التشخيص يعدّ الإسقاطات ولا يقول لماذا

الطابع الزمني الذي يفشل تحليله لا يرمي استثناءً. السمة لا تُسنَد أصلًا، والبداية تبقى صفرًا، والصف يُرفض عند البناء، ويزيد عدّاد. ويظهر ذلك العدّاد في مربع إعدادات الدليل الخاص بالمصدر تحت «آخر جلب للدليل»، بهذه الصيغة:

{0} channels · {1} programmes · {2} dropped · {3} ms

هذه هي القراءة الوحيدة التي تحصل عليها، وهي صادقة في المجموع وصامتة عن السبب. ثلاثة أحوال لا رابط بينها تزيد الرقم نفسه: برنامج بلا سمة قناة، وبرنامج فشل طابع بدايته، وبرنامج وقع خارج نافذة الاحتفاظ.

النافذة هي مصدر الرقم الكبير عادةً، وهي ليست نافذة واحدة. تبدأ دائمًا قبل لحظة التحليل بـ 24 ساعة. وتنتهي بعد ثمانية أيام لدليل مصدر واحد، وبعد أربعة أيام حين يتجاوز التنزيل غير المضغوط 200 MB، وبعد يومين لمخزن المنزل الموحّد، الذي يحتفظ بأقل لأنه يحمل كل المصادر دفعة واحدة. لذا فالدليل الذي ينشر ثلاثة أسابيع من القوائم سيبلّغ عن عدد إسقاطات كبير جدًا في استيراد سليم تمامًا، والملف نفسه سيبلّغ عن عدد أكبر بكثير بمجرد تشغيل مخزن المنزل الموحّد. الرقم المرتفع سبب لأن تنظر، لا دليل على مشكلة وقت.

والجزء المزعج أن أعلى إنذار عندنا هو عدد صحيح واحد في مربع حوار لا يفتحه أحد ما لم يكن شيء ما قد بدا خاطئًا سلفًا.

stop خاطئ يكلّفك ثلاث ساعات لا برنامجًا واحدًا

الإزاحة التي لا تُقرأ في طابع البداية تُسقط الصف. أما في طابع النهاية فلا.

حين تُحلَّل البداية ولا تُحلَّل النهاية، تبقى النهاية صفرًا، فتسقط في اختبار «النهاية بعد البداية»، ويُمنح الصف مدة افتراضية ثلاث ساعات. وثمة حدّ منفصل يقصر أي برنامج على ست ساعات؛ يبقى خاملًا هنا لأن الثلاثة أقل من الستة، ولا يطال إلا الصفوف التي تُحلَّل نهايتها فعلًا وتقع بعيدًا. ولا الافتراضي ولا الحدّ يزيدان عدّاد الإسقاط. فالملف الذي تتشوّه فيه سمات النهاية بطريقة لا تتشوّه بها سمات البداية ينتج دليلًا بأوقات بداية صحيحة، وكتلًا من ثلاث ساعات في كل مكان، وصفوفًا متداخلة، وملخص استيراد نظيف. وهذا شكل شائع خلف البلاغات عن دليل تبدو أوقاته صحيحة ولا يستقيم فيه شيء.

ستة برامج تدخل وأربعة تخرج

هذا مقطع بُني ليمرّ على كل المسارات دفعة واحدة. نفّذناه على المحلِّل الذي نشحنه بدل تتبّعه يدويًا. كل معرّف قناة يستخدم نطاق أمثلة محجوزًا وكل عنوان مخترع، ويجدر بك أن تفترض أن نافذة الاحتفاظ تغطي هذه التواريخ، وإلا حذف الاحتفاظُ الحجةَ قبل أن يأتي دور الإزاحات.

<?xml version="1.0" encoding="UTF-8"?>
<tv>
  <channel id="one.example.com"><display-name>Example One</display-name></channel>
  <programme start="20260719150000 +0200" stop="20260719160000 +0200" channel="one.example.com">
    <title>Clean offset</title>
  </programme>
  <programme start="20260719160000 +02:0" stop="20260719170000 +02:0" channel="one.example.com">
    <title>Broken on both ends</title>
  </programme>
  <programme start="20260719170000 +02:00" stop="20260719180000 +02:0" channel="one.example.com">
    <title>Broken on the stop only</title>
  </programme>
  <programme start="20260719180000 BST" stop="20260719190000 BST" channel="one.example.com">
    <title>Zone name, no number</title>
  </programme>
  <programme start="20260719190000 +0100 BST" stop="20260719200000 +0100 BST" channel="one.example.com">
    <title>Zone name after a number</title>
  </programme>
  <programme start="20260719" stop="20260720" channel="one.example.com">
    <title>Date only</title>
  </programme>
</tv>

الصف الأول هو المسار السعيد: 15:00 عند زائد ساعتين تساوي 13:00 UTC، ويمتد إلى 14:00 UTC.

الصف الثاني يحمل +02:0 في السمتين. اللاحقة تبدأ بإشارة، فيجب أن تُقرأ كإزاحة؛ وسلسلة الأرقام أربعة محارف وآخر اثنين فيها ليسا رقمين معًا، فتفشل. البداية لا تُسنَد والصف يُرفض، فيصير عدّاد الإسقاط واحدًا.

الصف الثالث بدايته نظيفة ونهايته مكسورة كسابقتها. تُحَل البداية إلى 15:00 UTC. ولا تُحَل النهاية، فيُطبَّق الافتراضي بثلاث ساعات ويشغل الصف من 15:00 إلى 18:00 UTC بدل ساعة واحدة. ولا يُحسب شيء. هذا الصف هو السبب في أن عدّاد الإسقاط ليس مؤشر صحة.

الصف الرابع هو حالة الساعة الواحدة في أنقى صورها. اللاحقة BST، وهي لا تبدأ بإشارة، فيسلك القارئ مسار انعدام الإزاحة ويطبّق المنطقة الزمنية المفترضة، وهي UTC. أداة الجلب قصدت 17:00 UTC. والدليل يعرض 18:00 UTC. ولا يتحرك عدّاد.

الصف الخامس يحمل التسمية نفسها بعد رقم حقيقي، ويُحَل صحيحًا إلى 18:00 UTC. فالصفان الرابع والخامس يبدآن كلاهما عند 18:00 UTC على قناة واحدة، وواحد منهما فقط قصد ذلك، وهكذا يبدو من الداخل ملفٌّ خلفه أداتا جلب.

الصف السادس ثمانية محارف، دون الحد الأدنى البالغ أربعة عشر محرفًا، فتفشل السمتان ويصير عدّاد الإسقاط اثنين. وهو أيضًا الصف الذي يعدّه الـ DTD سليمًا، مقطعًا بادئًا من طابع زمني كامل، فنصف التلف المحسوب في هذا المقطع هو تضييقنا نحن لا خطأ الملف.

أربعة صفوف بلغت المخزن، واثنان منها خاطئان بطريقة لا يبلّغ عنها شيء: واحد يبدأ متأخرًا ساعة، وواحد يستمر ثلاثة أضعاف طوله الحقيقي. والعدّاد يبلّغ عن الاثنين الآخرين ولا يعطي سببًا لأي منهما.

الإزاحة تُحسم لكل لحظة، لا مرة واحدة لكل ملف

نواة التاريخ تُحلَّل بلا ضبط أي رايات افتراض إطلاقًا، فتعود بقيمة نوعها غير محدد، وهذا مقصود. ثم يقرنها القارئ بإزاحة صراحةً. أما البديل، أي تسليم قيمة غير محددة إلى بانٍ يملأ الفراغ، فيجعل .NET يطبّق إزاحة جهاز المضيف نفسه، فينتج الملف الواحد جدولًا مختلفًا على جهاز في باريس وجهاز في تونس، ويكفّ المخزن عن كونه قابلًا للنقل بين جهازين في منزل واحد.

وفي مسار انعدام الإزاحة تُستخرج إزاحة المنطقة المفترضة لتلك اللحظة بعينها لا مرة واحدة للملف كله. فعبر انتقال ربيعي في منطقة أوروبية غربية، يخرج طابعان زمنيان بينهما ساعتان ونصف على الساعة المحلية وبينهما تسعون دقيقة بتوقيت UTC، وهذا هو الصحيح، وهو ما كانت إزاحة واحدة على مستوى الملف ستخطئ فيه في كل صف بعد التغيير. والتوقيتان المحليان اللذان يجعلهما الانتقال غير صالحين، الساعة التي تحدث مرتين والساعة التي لا تحدث أبدًا، يُحَلّان كلاهما إلى الإزاحة القياسية، وإعادة التحليل تعيد القيمة نفسها، فلا يمكن لملف واحد أن يظهر كدليلين مختلفين في استيرادين متتاليين.

وفي هذه الآلية فجوة نقولها بصراحة. معامل المنطقة الزمنية المفترضة موجود، والمساران اللذان يُدخلان دليلًا على الجهاز يمرّرانه كلاهما، وكلاهما يمرّر null. لا شيء في التطبيق الذي نشحنه يضبطه إطلاقًا، وهناك اختبار يثبّت ذلك. والدليل الذي يكتب أوقات ساعة حائط محلية مجردة بلا لاحقة يُقرأ على أنه UTC، وهو الافتراضي الموثّق في الـ DTD، ولا يوجد أي عنصر تحكم لكل مصدر يغيّر طريقة تحليله. وكل ما في فقرة الانتقال أعلاه آلية لا يبلغها أي مسار شيفرة نشحنه اليوم.

تصحيح الساعات بساعات كاملة، وموضعان يعطّلانه

يوجد تصحيح، وهو يعمل في طبقة أخرى. كل مصدر يحمل إزاحة وقت بساعات كاملة، من ناقص 48 إلى زائد 48، تُحرَّر في مربع إعدادات الدليل نفسه تحت «إزاحة الوقت (ساعات كاملة)» في الحقل المعنون «الساعات المراد إضافتها». وتُطبَّق حين تُقرأ القوائم من المخزن، لا حين تدخل إليه، فتغييرها لا يستلزم إعادة تنزيل أي شيء ولا يعيد كتابة ما خُزِّن.

وشيئان لن يفعلهما عمدًا. هو بساعات كاملة فقط، فلا يصحّح منطقة نصف ساعة، والمصدر في منطقة +05:30 الذي قُرئ على أنه UTC لا يمكن إرجاعه به. وهو مُجبَر على الصفر في حالتين. الأولى مخزن دليل المنزل الموحّد، الذي يُطبَّع إلى UTC عند الإدخال ولم يبق فيه ما يصححه تصحيح. والثانية أي قناة تنتمي إلى مصدر مجمَّع، حيث تسندها عدة أعضاء في آن واحد وتتوقف الإزاحة الصحيحة على العضو الذي يخدمها. رقم واحد سيكون خاطئًا لكل الأعضاء إلا واحدًا، فلا نعرض عنصر التحكم أصلًا بدل أن نعرضه ويكذب.

ذلك القرار الأخير هو أول ما سأعيد النظر فيه إن تغيّرت الأدلة. له وجاهته، وهو في الوقت نفسه يعني أن مخرج الطوارئ غائب في المكان الذي يملك فيه المنزل أكبر عدد من المصادر وأكبر فرصة لأن يكون أحدها خاطئًا.

تطبيقانا على Windows وXbox ينفّذان كل هذا التحليل محليًا، على عنوان دليل تملكه أنت أصلًا. وسطر التشخيص وعنصر التحكم بالإزاحة في المربع نفسه لسبب: الرقم الأول يخبرك إن كان الملف يُقرأ أصلًا، والثاني هو ما تمدّ يدك إليه بعد أن تعرف أنه يُقرأ.

إن كنت تكتب قارئك الخاص، فالقاعدة التي ستنقذك هي التي استغرق تعلّمها منا أطول وقت. اللاحقة التي تبدأ بإشارة أخبرتك أنها رقم. اقرأها أو أفشِل الصف، ولا تسقط إلى قيمة افتراضية، لأن القيمة الافتراضية تُطبَّق بانتظام وثقة على كل طابع زمني في الملف، والخطأ المنتظم الواثق هو العطل الوحيد الذي لا يبلّغ عنه أحد.

ما قياس هذه المادة28 ادعاءات، كل منها مزود بالأدلة التي تقف خلفه
المطالبةالأدلةعد
يقول تعريف نوع مستند XMLTV إن الطابع الزمني الذي لا يحمل منطقة زمنية صريحة يُقرأ على أنه UTC.xmltv.dtd, date field documentation: you can also append a timezone to the end; if no explicit timezone is given, UTC is assumed.المواصفاتلا ينطبق
يكتب XMLTV DTD مثاله على الإزاحة بالشكل الأساسي المكوّن من أربعة أرقام، هكذا: +0300.xmltv.dtd, date field documentation, example 19880523083000 +0300المواصفاتلا ينطبق
يسمح XMLTV DTD بأن يكون التاريخ YYYYMMDDhhmmss أو أي مقطع بادئ منه، ويطبع 200007281733 BST و200209 و19880523083000 +0300 كأمثلته الثلاثة.xmltv.dtd, date field documentation, including the annotation (BST == +0100.) on the first exampleالمواصفاتلا ينطبق
يكتب ISO 8601 الإزاحة عن UTC بثلاثة أشكال: الأساسي (+0200)، والممتد (+02:00)، والساعات وحدها (+02).ISO 8601-1, time offset from UTCالمواصفاتلا ينطبق
يرفض DateTimeOffset في .NET أي إزاحة يتجاوز مقدارها 14 ساعة، ويقبل 14 ساعة بالضبط..NET DateTimeOffset constructor, offset argument range -14:00 to +14:00 inclusiveالمواصفاتلا ينطبق
بناء DateTimeOffset من DateTime قيمة Kind فيه هي Unspecified يطبّق الإزاحة المحلية لجهاز المضيف..NET DateTimeOffset(DateTime) constructor remarks, which treat an Unspecified Kind as local timeالمواصفاتلا ينطبق
اختصارات المناطق ليست فريدة: CST تحمله America/Chicago وAmerica/Havana وAsia/Shanghai في آنٍ واحد، وBST هو التوقيت الصيفي البريطاني وقد كُتب أيضًا للتوقيت القياسي في بنغلاديش، والفارق بينهما خمس ساعات.IANA time zone database, theory.html on the ambiguity of zone abbreviations, plus the CST abbreviation on America/Chicago, America/Havana and Asia/Shanghaiالمواصفاتلا ينطبق
اللاحقة التي تبدأ بـ + أو - يجب أن تُحلَّل كإزاحة رقمية، وإلا فشل الطابع الزمني كله. والسقوط إلى المنطقة الزمنية المفترضة هو الخلل الذي شحنّاه واستبدلته هذه القاعدة.n = 222‏/08‏/2026
لا تُقبل إلا سلاسل الأرقام بطول 2 أو 4 أو 5 مع نقطتين رأسيتين في الموضع 2، لذا تُحَل +02 و+0200 و+02:00 جميعها؛ وكل طول آخر يخرج من الفرع الافتراضي.n = 322‏/08‏/2026
سبعة أشكال إزاحة مثبَّتة بالاختبارات كحالات فشل تحليل: +2 و+02:0 و+0X30 و+2400 و+99:00 و-0270 و+ وحدها.n = 722‏/08‏/2026
الدقائق فوق 59 مرفوضة، والمقدار الكلي محدود بـ 14 ساعة شاملةً، فتُحَل +1400 ولا تُحَل +1401؛ وقارئ الرقمين يشترط أرقام ASCII ويحلّل بـ NumberStyles.None.n = 222‏/08‏/2026
تسمية المنطقة بعد إزاحة حقيقية تُقتطع عند أول مسافة، فتُحَل +0100 BST إلى +01:00 ويعطي توقيت محلي 15:00 قيمة 14:00 UTC.n = 122‏/08‏/2026
اسم المنطقة الأبجدي المجرد لا يصل إلى قارئ الإزاحة أبدًا، بل يسلك مسار المنطقة الزمنية المفترضة، فتُحَل 20260719150000 BST و20260719150000 UTC كلتاهما إلى 15:00 UTC.n = 222‏/08‏/2026
قارئنا يرفض اثنين من الطوابع الزمنية الثلاثة التي يطبعها XMLTV DTD: 200007281733 BST و200209 كلاهما يفشل، ولا يُحلَّل إلا 19880523083000 +0300، فيعطي 05:30 UTC.n = 322‏/08‏/2026
الطابع الزمني الأقصر من 14 محرفًا مرفوض قبل أي محاولة تحليل، فتفشل القيمة التي تحمل تاريخًا فقط.n = 122‏/08‏/2026
نواة التاريخ هي أول 14 محرفًا بالضبط، فيقع الرقم الخامس عشر الشارد في اللاحقة، ويفشل في اختبار الإشارة، ويُهمَل بصمت.n = 122‏/08‏/2026
قاعدة الخطأ القاطع تعتمد على أول محرف في اللاحقة، فيسلك شكلٌ تسبق فيه التسمية الإشارةَ مثل GMT+0200 مسارَ المنطقة الزمنية المفترضة ويُقرأ على أنه UTC مع تجاهل الإزاحة.n = 122‏/08‏/2026
عدّاد الإسقاط نفسه يزيد عند غياب سمة القناة، وعند بداية يتعذّر تحليلها، وعند برنامج خارج نافذة الاحتفاظ.n = 322‏/08‏/2026
تبدأ نافذة الاحتفاظ دائمًا قبل لحظة التحليل بـ 24 ساعة، وتنتهي بعدها بثمانية أيام في مسار المصدر الواحد، وبأربعة أيام حين يتجاوز التنزيل غير المضغوط 200 MB، وبيومين لمخزن المنزل الموحّد.n = 322‏/08‏/2026
البرنامج الذي تصح بدايته ويتعذّر تحليل نهايته يُحفظ بمدة افتراضية ثلاث ساعات ولا يُحسب مُسقطًا؛ والحد الأقصى المنفصل البالغ ست ساعات لا يطاله.n = 122‏/08‏/2026
قراءة آخر جلب مصاغة على هيئة قنوات وبرامج ومُسقطة ومللي ثانية، تحت العنوان «آخر جلب للدليل».n = 122‏/08‏/2026
التصحيح لكل مصدر بساعات كاملة من -48 إلى +48، وعنوانه «إزاحة الوقت (ساعات كاملة)» وحقله «الساعات المراد إضافتها»، ويُطبَّق عند القراءة لا عند الإدخال.n = 122‏/08‏/2026
يُجبَر التصحيح على الصفر عندما يكون مخزن المنزل الموحّد نشطًا وعندما تنتمي القناة إلى مصدر مجمَّع.n = 222‏/08‏/2026
مسارا الإدخال على الجهاز كلاهما يبنيان خط التحليل بقيمة assumeTimezone تساوي null، فيُقرأ الطابع الزمني المجرد على أنه UTC ولا تدخل المنطقة الزمنية للمضيف أبدًا.n = 222‏/08‏/2026
في مسار المنطقة الزمنية المفترضة تُحسم الإزاحة لكل لحظة على حدة، فيخرج توقيتان محليان بينهما ساعتان ونصف عبر انتقال ربيعي وبينهما تسعون دقيقة بتوقيت UTC.n = 222‏/08‏/2026
الساعة المحلية المكرَّرة والساعة المحلية غير الموجودة كلتاهما تُحَلّان إلى الإزاحة القياسية، وإعادة التحليل تعيد القيمة نفسها.n = 322‏/08‏/2026
المقطع المبني يدويًا في هذه المقالة يعطي قناة واحدة و4 صفوف مخزَّنة وإسقاطين؛ أحد الصفوف المخزَّنة يبدأ متأخرًا ساعة وآخر يستمر ثلاث ساعات بدل ساعة.n = 622‏/08‏/2026
في الأكبر من قائمتي التشغيل في مجموعة M3U لدينا، ينتهي 527 صفًا من 1,895 دون معرّف قناة صالح للاستخدام، فلا يجد صف الدليل ما يتعلق به بثقة.n = 189522‏/08‏/2026