Perché un EPG mostra l'ora sbagliata: timestamp XMLTV e offset
Una guida sfasata di un numero intero di ore non è quasi mai un mistero. È un suffisso che nel file c'era e che il lettore non ha rispettato.
<programme start="20260719150000 +0200" stop="20260719160000 +0200" channel="one.example.com">
Quattordici cifre, uno spazio, poi cinque caratteri che decidono se quella riga inizia alle 15:00 o alle 13:00. Le quattordici cifre non sono un istante. Sono la lettura di un orologio senza nessun luogo attaccato, e una guida ai programmi corretta dipende dal fatto che quegli ultimi cinque caratteri vengano letti, oppure che la loro assenza venga interpretata come intendeva il grabber che ha scritto il file.
Prima di cambiare qualsiasi cosa, misura lo scarto. Se ogni programma su ogni canale è sfasato dello stesso numero intero di ore, la cosa che non va è una sola, e quasi sempre è un offset che nel file c'era e che nessuno ha rispettato. Se alcuni canali sono giusti e altri sbagliati, oppure se una riga è fuori posto e quelle subito prima e subito dopo vanno bene, allora il guasto è un altro e niente di quello che segue sugli offset lo toccherà.
Il nostro lettore analizza la guida sul tuo dispositivo, contro l'indirizzo che gli indichi tu. Noi curiamo degli indirizzi di guide, e lo stesso parser gira dalla nostra parte su quelli curati, per il lavoro di catalogo. Un indirizzo di guida che fornisci tu viene scaricato e letto sulla tua macchina, e i dati della tua guida non li teniamo mai noi.
Uno scarto su tutta la guida e un singolo programma sbagliato sono due bug diversi
Uno scarto uniforme ha tre cause plausibili, e si distinguono in un minuto.
La prima è un offset che il file portava e che il lettore ha buttato via. La seconda è un offset che il file non ha mai portato: il grabber ha scritto orari locali da orologio, senza suffisso, e il lettore non aveva modo di saperlo. La terza è una correzione applicata due volte, una dalla sorgente e una da te, ed è quella a cui tutti pensano per prima ed è quella che produce uno scarto esattamente doppio di quello che hanno impostato. Se la tua guida è passata da un'ora di sfasamento a tre dopo che hai impostato una correzione di due ore, sei nel terzo caso, e la risposta è rimettere la correzione a zero prima di diagnosticare qualsiasi altra cosa.
Per misurarlo servono due confronti e un orologio. Prendi un programma in onda adesso e annota di quanto l'inizio dichiarato si scosta dalla realtà, poi ripeti su un secondo canale della stessa sorgente, poi su un canale di una sorgente diversa. Stesso errore su entrambi i canali di una sorgente e nessun errore sull'altra sorgente restringe il campo al file di quella sorgente. Stesso errore dappertutto punta invece all'orologio del dispositivo, che è tutta un'altra riparazione.
Uno scarto non uniforme non è affatto un problema di fusi orari. La causa abituale è che la riga di guida è agganciata al canale sbagliato, il che fa sembrare il palinsesto corretto di un'altra emittente il palinsesto sbagliato del tuo. È un problema di identità, e la sua forma è l'argomento di un altro articolo. Che un id sia presente nella playlist non vuol dire che sia un id che corrisponde alla tua guida: nella più grande delle due playlist che abbiamo misurato, 527 righe su 1.895 finivano senza nessun id utilizzabile. Le altre cause non uniformi riguardano le durate e non gli orari di inizio, e le trattiamo più avanti.
Un suffisso che inizia con un segno ti ha già detto che è un numero
Ecco la regola che applica il nostro lettore di timestamp, e il commento che le sta sopra, alla lettera:
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;
}
Quel commento descrive un bug nostro. In una versione precedente di questo lettore ci siamo sbagliati: trattava un offset illeggibile esattamente come trattava l'assenza totale di offset, cioè tirava dritto, assumeva UTC e andava avanti. Il sintomo era una guida spostata esattamente della dimensione dell'offset che il file dichiarava, su ogni canale, per ogni giorno del feed, con un riepilogo di importazione pulito e niente da guardare nei log. La causa era un solo return anticipato mancante. Il costo è stato che il guasto era impossibile da rilevare dall'interno dell'app: nessun contatore si muoveva, nessuna riga di log veniva scritta, e la guida restava sbagliata finché qualcuno non la confrontava con un orologio.
Rifiutare la riga è peggio per quel singolo programma ed è molto meglio per la guida. Un programma scartato lascia un buco che si vede. Un feed reinterpretato in silenzio è un palinsesto sbagliato in modo uniforme e sicuro di sé, e puoi fissarlo quanto vuoi, non te lo dirà mai.
Quella regola ha un buco, e vale la pena dirlo chiaramente. Il controllo guarda solo il primo carattere del suffisso. Un suffisso scritto GMT+0200, con la sigla prima del segno invece che dopo, non inizia con un segno, quindi prende la strada silenziosa. Passato nel parser che spediamo restituisce le 15:00 UTC per un orario locale delle 15:00, cioè l'offset è lì nella stringa e viene ignorato. Nessun test fissa quella forma, e non abbiamo un corpus XMLTV nostro, quindi il fatto di non averla mai vista non è la prova che sia rara.
Tre forme di offset vengono lette, sette vengono rifiutate
Dopo il segno, il lettore prende tutto fino al primo spazio e misura la sequenza di cifre. Solo tre lunghezze significano qualcosa: due caratteri per un offset con le sole ore, quattro per ore e minuti attaccati, cinque quando il terzo carattere è un due punti. +02, +0200 e +02:00 si risolvono tutti nelle stesse due ore. Qualsiasi cosa sotto i tre caratteri totali viene rifiutata prima di arrivare allo switch, e ogni altra lunghezza esce da un ramo default.
Tutte e tre le forme sono ISO 8601, ed è per questo che le leggiamo tutte e tre. +0200 è la forma base, +02:00 quella estesa, +02 quella con le sole ore, e il DTD di XMLTV scrive il proprio esempio nella forma base, +0300. Benedire una forma sola e prendere le altre due in silenzio per UTC sfaserebbe di un'ora o più ogni feed che ha scelto diversamente.
Intorno a tutto questo ci sono due tolleranze. Lo spazio prima del segno è facoltativo, perché il suffisso è definito come tutto quello che sta dal quindicesimo carattere in poi, quindi 20260719150000+0200 funziona allo stesso modo. E una sigla di fuso dopo un offset vero viene scartata invece di essere trattata come un danno: +0100 BST si tronca allo spazio, si legge +0100 e dà le 14:00 UTC per un orario locale delle 15:00. I fusi a mezz'ora vengono gratis, dato che i minuti si analizzano come due cifre a sé, quindi +05:30 si risolve correttamente.
Quel confine al quindicesimo carattere ha una conseguenza che non ha progettato nessuno. Il nucleo della data è preso come esattamente i primi quattordici caratteri, quindi un timestamp che porta una quindicesima cifra, 202607191500000, analizza i primi quattordici senza problemi e passa lo 0 di troppo al lettore del suffisso, che vede un carattere che non è un segno e prende la strada del nessun offset. Passato nel parser, quel valore restituisce le 15:00 UTC e la cifra in più viene scartata senza dire niente. È l'esito innocuo per una Z finale, che la stessa ricaduta gestisce e che per giunta significa UTC, ed è un troncamento silenzioso per qualsiasi altra cosa. Il caso della Z è innocuo solo perché niente nell'app che spediamo imposta un fuso presunto, e questo ha una sezione tutta sua più avanti.
Queste sette sono fissate dai test come fallimenti, e ognuna dà il nome a una classe di danno reale:
+2, un'ora a una cifra, troppo corta per arrivare allo switch+02:0, una forma con i due punti a cui manca una cifra+0X30, una lettera dentro la sequenza di cifre+2400, ventiquattro ore+99:00, novantanove ore-0270, settanta minuti+, un segno e nient'altro
Due guardie producono la maggior parte di quei rifiuti. I minuti sopra 59 vengono rifiutati senza appello, e la grandezza totale è limitata a 14 ore, perché il tipo data sottostante rifiuta qualsiasi cosa oltre e perché, come dice il commento nel codice, “a guide claiming one is corrupt rather than exotic” (una guida che ne dichiara una è corrotta, non esotica). Il limite è incluso, quindi +1400 si risolve in un'ora dopo la mezzanotte UTC e +1401 fallisce. Il test sulle cifre pretende inoltre cifre ASCII in senso stretto e analizza senza abilitare nessuno stile numerico, così non si intrufola nessun segno iniziale, nessun separatore delle migliaia e nessuna cifra a larghezza piena attraverso un parsing indulgente e sensibile alla cultura.
Due dei tre timestamp di esempio del DTD vengono rifiutati dal nostro lettore
Il DTD non permette solo la forma a quattordici cifre. Dice che una data può essere YYYYMMDDhhmmss o una sua sottostringa iniziale, e stampa tre esempi: 200007281733 BST, 200209 e 19880523083000 +0300. Solo il terzo sopravvive al nostro parser, e si risolve nelle 05:30 UTC. Il secondo cade sulla soglia di lunghezza, che rifiuta qualsiasi cosa più corta di quattordici caratteri prima ancora di tentare l'analisi. Il primo supera quella soglia e poi fallisce lo stesso, perché il lettore taglia i primi quattordici caratteri come nucleo della data e 200007281733 B non è una data.
Quella restrizione è una scelta nostra, e quando scatta non è colpa del file. Un timestamp al minuto non ha secondi da tagliare, un timestamp con solo anno e mese non è l'inizio di un programma in nessun senso utile, e un lettore che accettasse l'uno o l'altro si inventerebbe i campi mancanti. Rifiutarli è difendibile. Presentarli come un danno no, e l'ultima riga del frammento qui sotto è XMLTV conforme che noi scartiamo.
Il primo esempio del DTD porta l'altra metà dell'asimmetria di questo articolo dentro una parentesi. 200007281733 BST è annotato lì come (BST == +0100.), quindi il documento che definisce il formato risolve proprio la sigla che il nostro lettore si rifiuta di risolvere.
Perché a +0100 BST crediamo e a un BST da solo no
Qui c'è un'asimmetria che sembra una svista ed è una decisione. Un suffisso +0100 BST si risolve in più un'ora, perché il numero c'è e la sigla viene buttata via. Un suffisso BST da solo si risolve in UTC, che è sbagliato di un'ora per ogni data estiva britannica su cui compare. Sono fissati dai test tutti e due, il che vuol dire che sono voluti tutti e due.
I fusi con un nome non vengono mai risolti, e il commento ne dà il motivo: “the abbreviations are ambiguous across regions” (le sigle sono ambigue tra una regione e l'altra). BST è il British Summer Time, ed è stato scritto anche per il Bangladesh Standard Time, cinque ore più in là. CST è la collisione che sopravvive dentro il tz database stesso, portata contemporaneamente da Chicago, L'Avana e Shanghai. Risolvere un nome vuol dire sceglierne uno, cioè tirare a indovinare, e un'ipotesi giusta in un mercato e sbagliata in un altro è peggio di una regola che sbaglia allo stesso modo dappertutto.
Abbiamo preferito la risposta sbagliata in modo prevedibile, per regola fissa, al tirare a indovinare, e ci costa un'ora nelle date estive in ogni feed che etichetta i suoi orari così invece di numerarli. Il fatto che il DTD annoti il proprio esempio rende la scelta più difficile da difendere di quanto sembrasse prima che scrivessimo questo articolo: la documentazione del formato una sigla l'ha risolta, e il paragrafo qui sopra è tutto quello che abbiamo da dire sul perché noi continuiamo a non farlo.
È un limite vero, e non sappiamo quanto sia diffusa questa forma, perché un nome di fuso da solo è esattamente l'input che non produce nessun errore, nessun contatore e nessuna lamentela finché qualcuno non confronta la guida con un orologio.
La riga diagnostica conta gli scarti senza dire perché
Un timestamp che non si lascia analizzare non solleva eccezioni. L'attributo non viene mai assegnato, lo start resta a zero, la riga viene rifiutata al momento della costruzione e un contatore si incrementa. Quel contatore riemerge nella finestra Impostazioni della guida della sorgente, sotto “Ultimo download della guida”, formattato così:
{0} channels · {1} programmes · {2} dropped · {3} ms
È l'unica lettura che ottieni, ed è onesta sul totale e muta sul motivo. Tre condizioni senza nessun rapporto tra loro incrementano lo stesso numero: un programma senza attributo channel, un programma il cui timestamp di start è fallito e un programma finito fuori dalla finestra di conservazione.
La finestra è di solito da dove arriva un numero grosso, e non è una finestra sola. Comincia sempre 24 ore prima dell'istante di analisi. Finisce otto giorni dopo per una guida di singola sorgente, quattro giorni dopo quando il download non compresso supera i 200 MB, e due giorni dopo per l'archivio condiviso di casa, che tiene meno perché contiene tutte le sorgenti insieme. Una guida che pubblica tre settimane di palinsesto riporterà quindi un conteggio di scarti altissimo su un'importazione perfettamente sana, e lo stesso identico file ne riporterà uno molto più alto una volta acceso l'archivio condiviso di casa. Un numero alto è un motivo per andare a guardare, non la prova di un problema di orari.
La parte scomoda è che il nostro modo più rumoroso di fallire è un singolo numero intero in una finestra che nessuno apre se non quando qualcosa sembra già andato storto.
Uno stop sbagliato ti costa tre ore, non un programma
Un offset illeggibile sul timestamp di start fa scartare la riga. Un offset illeggibile sul timestamp di stop no.
Quando lo start si analizza e lo stop no, lo stop resta a zero, il che non supera il test “lo stop è dopo lo start”, e alla riga viene data una durata predefinita di tre ore. Un limite separato tiene ogni programma entro le sei ore; qui resta inerte, perché tre sta sotto sei, e scatta solo sulle righe il cui stop si analizza davvero e finisce molto lontano. Né il valore predefinito né il limite incrementano il contatore degli scarti. Quindi un file i cui attributi stop sono malformati in un modo in cui i suoi start non lo sono produce una guida con orari di inizio corretti, blocchi da tre ore dappertutto, righe sovrapposte e un riepilogo di importazione pulito. È una forma frequente dietro le segnalazioni di una guida in cui gli orari sembrano giusti ma non combacia niente.
Entrano sei programmi, ne escono quattro
Ecco un frammento costruito per mettere alla prova tutti i percorsi insieme. L'abbiamo eseguito contro il parser che spediamo, non tracciato a mano. Ogni id di canale usa un dominio di esempio riservato e ogni titolo è inventato, e devi dare per scontato che la finestra di conservazione copra queste date, altrimenti la conservazione smonta il ragionamento prima che tocchi agli offset.
<?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>
La riga uno è l'happy path: le 15:00 con più due ore fanno le 13:00 UTC, e arriva fino alle 14:00 UTC.
La riga due ha +02:0 su entrambi gli attributi. Il suffisso inizia con un segno, quindi deve leggersi come offset; la sequenza di cifre è di quattro caratteri e gli ultimi due non sono entrambi cifre, quindi fallisce. Lo start non viene mai assegnato e la riga viene rifiutata, il che porta il contatore degli scarti a uno.
La riga tre ha uno start pulito e lo stesso stop rotto. Lo start si risolve nelle 15:00 UTC. Lo stop non si risolve, quindi si applica il valore predefinito di tre ore e la riga occupa dalle 15:00 alle 18:00 UTC invece di un'ora. Non viene contato niente. È questa riga il motivo per cui il contatore degli scarti non è una metrica di salute.
La riga quattro è il caso dell'ora di scarto nella sua forma più pura. Il suffisso è BST, che non inizia con un segno, quindi il lettore prende la strada del nessun offset e applica il fuso presunto, cioè UTC. Il grabber intendeva le 17:00 UTC. La guida mostra le 18:00 UTC. Nessun contatore si muove.
La riga cinque porta la stessa sigla dopo un numero vero, e si risolve correttamente nelle 18:00 UTC. Le righe quattro e cinque partono quindi tutte e due alle 18:00 UTC sullo stesso canale e solo una delle due voleva farlo: è così che si presenta dall'interno un file con due grabber alle spalle.
La riga sei è di otto caratteri, sotto la soglia dei quattordici, quindi falliscono entrambi gli attributi e il contatore degli scarti va a due. È anche la riga che il DTD chiamerebbe legittima, una sottostringa iniziale di un timestamp completo, quindi metà del danno contato in questo frammento è la nostra restrizione, non una colpa del file.
Quattro righe sono arrivate all'archivio, e due delle quattro sono sbagliate in un modo che nessuno segnala: una parte con un'ora di ritardo e una dura tre volte la sua lunghezza reale. Il contatore segnala le altre due e per nessuna delle due dà un motivo.
L'offset si risolve istante per istante, non una volta per file
Il nucleo della data viene analizzato senza impostare nessun flag di assunzione, il che restituisce un valore il cui kind è unspecified, ed è voluto. Il lettore poi lo abbina esplicitamente a un offset. L'alternativa, passare un valore unspecified a un costruttore che riempie lui il vuoto, fa applicare a .NET l'offset della macchina host, quindi lo stesso file produrrebbe un palinsesto diverso su un dispositivo a Parigi e su uno a Tunisi, e l'archivio smetterebbe di essere trasportabile tra due dispositivi della stessa casa.
Sul percorso senza offset, l'offset del fuso presunto viene cercato per quel particolare istante, non una volta sola per tutto il file. A cavallo del cambio di primavera in un fuso dell'Europa occidentale, due timestamp distanti due ore e mezza sull'orologio locale escono distanti novanta minuti in UTC: è corretto, ed è quello che un singolo offset a livello di file sbaglierebbe per ogni riga dopo il cambio. I due orari locali che un cambio rende inutilizzabili, l'ora che capita due volte e l'ora che non capita mai, si risolvono entrambi sull'offset standard, e una nuova analisi restituisce lo stesso valore, quindi un file non può diventare due guide diverse su due importazioni consecutive.
In quel meccanismo c'è un buco, e lo diciamo apertamente. Il parametro del fuso presunto esiste, entrambi i percorsi che importano una guida sul dispositivo lo passano, ed entrambi passano null. Niente nell'app che spediamo lo imposta mai, e un test lo fissa. Una guida che scrive orari locali nudi da orologio, senza suffisso, viene letta come UTC, che è il valore predefinito documentato dal DTD, e non c'è nessun controllo per singola sorgente che cambi come viene analizzata. Tutto quello che sta nel paragrafo qui sopra sui cambi d'ora è meccanica che oggi nessun percorso di codice spedito raggiunge.
La correzione oraria è in ore intere, e due punti la spengono
Una correzione c'è, e lavora a un altro livello. Ogni sorgente porta uno scarto orario in ore intere, da meno 48 a più 48, modificabile in quella stessa finestra Impostazioni della guida sotto “Scarto orario (ore intere)”, con il campo etichettato “Ore da aggiungere”. Viene applicato quando i palinsesti si leggono dall'archivio, non quando ci entrano, quindi cambiarlo non richiede di riscaricare niente e non riscrive quello che è stato memorizzato.
Due cose che di proposito non fa. È solo a ore intere, quindi non può correggere un fuso a mezz'ora, e una sorgente in una regione +05:30 che è stata letta come UTC con questo non la recuperi. E viene forzato a zero in due casi. Il primo è l'archivio della guida condiviso di casa, che all'importazione viene normalizzato a UTC e non lascia più niente da correggere a uno scarto. Il secondo è qualsiasi canale che appartenga a una sorgente aggregata, dove il canale è servito da più membri insieme e l'offset giusto dipende da quale membro lo sta servendo. Un numero solo sarebbe sbagliato per tutti tranne uno, quindi il controllo non lo offriamo, invece di offrirlo e mentire.
Quest'ultima decisione è quella che rivedrei per prima se le prove cambiassero. È difendibile, e vuol dire anche che la scappatoia manca proprio dove una casa ha più sorgenti e più probabilità che una di loro sia sbagliata.
Le nostre app per Windows e Xbox fanno tutta questa analisi in locale, su un indirizzo di guida che hai già. La riga diagnostica e il controllo dello scarto stanno nella stessa finestra per un motivo: il primo numero ti dice se il file viene letto o no, e il secondo è quello che vai a toccare una volta che sai che viene letto.
Se ti stai scrivendo un lettore tuo, la regola che ti salva è quella che a noi è costata più tempo imparare. Un suffisso che inizia con un segno ti ha già detto che è un numero. Leggilo oppure fai fallire la riga, ma non ricadere su un valore predefinito, perché un valore predefinito viene applicato in modo uniforme e sicuro di sé a ogni timestamp del file, e sbagliare in modo uniforme e sicuro di sé è l'unico guasto che nessuno segnala.
Cosa ha misurato questo articolo28 affermazioni, ciascuna con le prove a sostegno
| Richiedi | Prove | Contato |
|---|---|---|
| La document type definition di XMLTV dice che un timestamp senza fuso orario esplicito va letto come UTC.xmltv.dtd, date field documentation: you can also append a timezone to the end; if no explicit timezone is given, UTC is assumed. | Specifica | Non applicabile |
| Il DTD di XMLTV scrive il proprio offset di esempio nella forma base a quattro cifre, +0300.xmltv.dtd, date field documentation, example 19880523083000 +0300 | Specifica | Non applicabile |
| Il DTD di XMLTV permette che una data sia YYYYMMDDhhmmss o una sua sottostringa iniziale, e stampa 200007281733 BST, 200209 e 19880523083000 +0300 come suoi tre esempi.xmltv.dtd, date field documentation, including the annotation (BST == +0100.) on the first example | Specifica | Non applicabile |
| ISO 8601 scrive un offset da UTC in tre forme: base (+0200), estesa (+02:00) e con le sole ore (+02).ISO 8601-1, time offset from UTC | Specifica | Non applicabile |
| Un DateTimeOffset di .NET rifiuta un offset la cui grandezza supera le 14 ore, e accetta esattamente 14 ore..NET DateTimeOffset constructor, offset argument range -14:00 to +14:00 inclusive | Specifica | Non applicabile |
| Costruire un DateTimeOffset a partire da un DateTime il cui Kind è Unspecified applica l'offset locale della macchina host..NET DateTimeOffset(DateTime) constructor remarks, which treat an Unspecified Kind as local time | Specifica | Non applicabile |
| Le sigle dei fusi non sono univoche: CST appartiene contemporaneamente ad America/Chicago, America/Havana e Asia/Shanghai, e BST è il British Summer Time ma è stato scritto anche per il Bangladesh Standard Time, cinque ore più in là.IANA time zone database, theory.html on the ambiguity of zone abbreviations, plus the CST abbreviation on America/Chicago, America/Havana and Asia/Shanghai | Specifica | Non applicabile |
| Un suffisso di timestamp che inizia con + o - deve leggersi come offset numerico, altrimenti fallisce l'intero timestamp. Ricadere sul fuso orario presunto è il difetto che avevamo in produzione e che questa regola ha sostituito. | n = 2 | 22 ago 2026 |
| Sono accettate solo sequenze di cifre lunghe 2, 4 o 5 con i due punti in posizione 2, quindi +02, +0200 e +02:00 si risolvono tutte; ogni altra lunghezza esce dal ramo default. | n = 3 | 22 ago 2026 |
| Sette forme di offset sono fissate dai test come fallimenti di analisi: +2, +02:0, +0X30, +2400, +99:00, -0270 e un + da solo. | n = 7 | 22 ago 2026 |
| I minuti sopra 59 vengono rifiutati e la grandezza totale è limitata a 14 ore incluse, quindi +1400 si risolve e +1401 no; il lettore a due cifre pretende cifre ASCII e analizza con NumberStyles.None. | n = 2 | 22 ago 2026 |
| Una sigla di fuso dopo un offset vero viene scartata al primo spazio, quindi +0100 BST si risolve in +01:00 e un orario locale delle 15:00 dà le 14:00 UTC. | n = 1 | 22 ago 2026 |
| Un nome di fuso puramente alfabetico non arriva mai al lettore di offset e prende la strada del fuso presunto, quindi 20260719150000 BST e 20260719150000 UTC si risolvono entrambi nelle 15:00 UTC. | n = 2 | 22 ago 2026 |
| Due dei tre timestamp di esempio stampati dal DTD di XMLTV vengono rifiutati dal nostro lettore: 200007281733 BST e 200209 falliscono entrambi, e solo 19880523083000 +0300 si analizza, dando le 05:30 UTC. | n = 3 | 22 ago 2026 |
| Un timestamp più corto di 14 caratteri viene rifiutato prima ancora di tentare l'analisi, quindi un valore con la sola data fallisce. | n = 1 | 22 ago 2026 |
| Il nucleo della data è esattamente i primi 14 caratteri, quindi una quindicesima cifra di troppo finisce nel suffisso, non supera il test del segno e viene scartata in silenzio. | n = 1 | 22 ago 2026 |
| La regola dell'errore bloccante si basa sul primo carattere del suffisso, quindi una forma con la sigla davanti come GMT+0200 prende la strada del fuso presunto e viene letta come UTC, con l'offset ignorato. | n = 1 | 22 ago 2026 |
| Lo stesso contatore degli scarti si incrementa per un attributo channel mancante, uno start illeggibile e un programme fuori dalla finestra di conservazione. | n = 3 | 22 ago 2026 |
| La finestra di conservazione comincia sempre 24 ore prima dell'istante di analisi e finisce otto giorni dopo sul percorso per singola sorgente, quattro giorni quando il download non compresso supera i 200 MB, e due giorni per l'archivio condiviso di casa. | n = 3 | 22 ago 2026 |
| Un programme con start valido e stop illeggibile viene tenuto con una durata predefinita di tre ore e non viene contato tra gli scarti; il massimo separato di sei ore non entra in gioco. | n = 1 | 22 ago 2026 |
| La riga sull'ultimo download è formattata come canali, programmi, scartati e millisecondi, sotto l'intestazione Ultimo download della guida. | n = 1 | 22 ago 2026 |
| La correzione per singola sorgente è in ore intere da -48 a +48, etichettata Scarto orario (ore intere) con il campo Ore da aggiungere, applicata in lettura e non in importazione. | n = 1 | 22 ago 2026 |
| La correzione viene forzata a zero quando l'archivio condiviso di casa è attivo e quando il canale appartiene a una sorgente aggregata. | n = 2 | 22 ago 2026 |
| Entrambi i percorsi di importazione sul dispositivo costruiscono la pipeline di analisi con assumeTimezone null, quindi un timestamp nudo viene letto come UTC e il fuso della macchina non entra mai in gioco. | n = 2 | 22 ago 2026 |
| Sul percorso del fuso presunto l'offset viene risolto istante per istante, quindi due orari locali distanti due ore e mezza a cavallo del cambio di primavera escono distanti novanta minuti in UTC. | n = 2 | 22 ago 2026 |
| L'ora locale ripetuta e l'ora locale inesistente si risolvono entrambe sull'offset standard, e una nuova analisi restituisce lo stesso valore. | n = 3 | 22 ago 2026 |
| Il frammento costruito a mano in questo articolo produce 1 canale, 4 righe memorizzate e 2 scarti; una riga memorizzata parte con un'ora di ritardo e un'altra dura tre ore invece di una. | n = 6 | 22 ago 2026 |
| Nella più grande delle due playlist del nostro corpus M3U, 527 righe su 1.895 finiscono senza un id di canale utilizzabile, quindi una riga di guida non ha niente di affidabile a cui agganciarsi. | n = 1895 | 22 ago 2026 |