File Coverage

blib/lib/Travel/Status/DE/IRIS/Result.pm
Criterion Covered Total %
statement 312 398 78.3
branch 66 98 67.3
condition 59 119 49.5
subroutine 47 57 82.4
pod 25 43 58.1
total 509 715 71.1


line stmt bran cond sub pod time code
1             package Travel::Status::DE::IRIS::Result;
2              
3 7     7   53 use strict;
  7         21  
  7         326  
4 7     7   45 use warnings;
  7         17  
  7         533  
5 7     7   189 use 5.014;
  7         36  
6 7     7   51 use utf8;
  7         12  
  7         82  
7              
8 7     7   381 use parent 'Class::Accessor';
  7         17  
  7         66  
9 7     7   27650 use Carp qw(cluck);
  7         46  
  7         545  
10 7     7   49 use DateTime;
  7         14  
  7         191  
11 7     7   35 use DateTime::Format::Strptime;
  7         14  
  7         77  
12 7     7   7160 use List::Compare;
  7         223941  
  7         453  
13 7     7   80 use List::Util qw(any);
  7         21  
  7         703  
14 7     7   57 use List::MoreUtils qw(uniq lastval);
  7         16  
  7         111  
15 7     7   7219 use Scalar::Util qw(weaken);
  7         21  
  7         53298  
16              
17             our $VERSION = '2.04';
18              
19             Travel::Status::DE::IRIS::Result->mk_ro_accessors(
20             qw(arrival arrival_delay arrival_has_realtime arrival_is_additional arrival_is_cancelled arrival_hidden
21             date datetime delay
22             departure departure_delay departure_has_realtime departure_is_additional departure_is_cancelled departure_hidden
23             ds100 has_realtime is_transfer is_unscheduled is_wing
24             line_no old_train_id old_train_no operator platform raw_id
25             realtime_xml route_start route_end
26             sched_arrival sched_departure sched_platform sched_route_start
27             sched_route_end start
28             station station_eva station_uic
29             stop_no time train_id train_no transfer type
30             unknown_t unknown_o wing_id wing_of)
31             );
32              
33             # {{{ Data (message codes, station fixups)
34              
35             my %translation = (
36             1 => 'Nähere Informationen in Kürze',
37             2 => 'Polizeieinsatz',
38             3 => 'Feuerwehreinsatz auf der Strecke',
39             4 => 'Kurzfristiger Personalausfall', # xlsx: missing
40             5 => 'Ärztliche Versorgung eines Fahrgastes',
41             6 => 'Betätigen der Notbremse', # xlsx: "Unbefugtes Ziehen der Notbremse"
42             7 => 'Unbefugte Personen auf der Strecke',
43             8 => 'Notarzteinsatz auf der Strecke',
44             9 => 'Streikauswirkungen',
45             10 => 'Tiere auf der Strecke',
46             11 => 'Unwetter',
47             12 => 'Warten auf ein verspätetes Schiff',
48             13 => 'Pass- und Zollkontrolle',
49             14 => 'Defekt am Bahnhof', # xlsx: "Technischer Defekt am Bahnhof"
50             15 => 'Beeinträchtigung durch Vandalismus',
51             16 => 'Entschärfung einer Fliegerbombe',
52             17 => 'Beschädigung einer Brücke',
53             18 => 'Umgestürzter Baum auf der Strecke',
54             19 => 'Unfall an einem Bahnübergang',
55             20 => 'Tiere im Gleis', # xlsx: missing
56             21 => 'Warten auf Anschlussreisende',
57             22 => 'Witterungsbedingte Beeinträchtigungen',
58             23 => 'Betriebsstabilisierung', # deleted as of 2025-12-14
59             24 => 'Verspätung im Ausland',
60             25 => 'Bereitstellung weiterer Wagen',
61             26 => 'Abhängen von Wagen',
62             27 => 'Technische Störung am Bus',
63             28 => 'Gegenstände auf der Strecke',
64             29 => 'Ersatzverkehr mit Bus ist eingerichtet',
65             30 => 'Personalausfall im Stellwerk',
66             31 => 'Bauarbeiten',
67             32 => 'Längere Haltezeit am Bahnhof',
68             33 => 'Defekt an der Oberleitung', # xlsx: "Reparatur an der Oberleitung"
69             34 => 'Defekt an einem Signal', # xlsx: "Reparatur an einem Signal"
70             35 => 'Streckensperrung',
71             36 => 'Technische Störung am Zug',
72             37 => 'Kurzfristiger Fahrzeugausfall',
73             38 => 'Defekt an der Strecke', # xlsx: "Reparatur an der Strecke"
74             39 => 'Stau / Hohes Verkehrsaufkommen',
75             40 => 'Defektes Stellwerk',
76             41 => 'Defekt an einem Bahnübergang'
77             , # xlsx: "Technischer Defekt an einem Bahnüburgang"
78             42 => 'Außerplanmäßige Geschwindigkeitsbeschränkung'
79             , # xlsx: "Vorübergehend verminderte Geschwindigkeit auf der Strecke"
80             43 => 'Verspätung eines vorausfahrenden Zuges',
81             44 => 'Warten auf einen entgegenkommenden Zug',
82             45 => 'Vorfahrt eines anderen Zuges',
83             46 => 'Vorfahrt eines anderen Zuges', # xlsx: missing
84             47 => 'Verspätete Bereitstellung',
85             48 => 'Verspätung aus vorheriger Fahrt',
86             49 => 'Kurzfristiger Personalausfall',
87             50 => 'Kurzfristige Erkrankung von Personal',
88             51 => 'Verspätetes Personal aus vorheriger Fahrt',
89             52 => 'Streik',
90             53 => 'Unwetterauswirkungen',
91             54 => 'Verfügbarkeit der Gleise derzeit eingeschränkt',
92             55 => 'Technischer Defekt an einem anderen Zug',
93             56 => 'Laden der Antriebsbatterie',
94             57 => 'Zusätzlicher Halt', # xslx: "Zusätzlicher Halt zum Ein- und Ausstieg"
95             58 => 'Umleitung', # xlsx: "Umleitung des Zuges"
96             59 => 'Schnee und Eis',
97             60 => 'Witterungsbedingt verminderte Geschwindigkeit',
98             61 => 'Defekte Tür',
99             62 => 'Behobener Defekt am Zug',
100             63 => 'Technische Untersuchung am Zug',
101             64 => 'Defekt an einer Weiche',
102             65 => 'Erdrutsch',
103             66 => 'Hochwasser',
104             67 => 'Behördliche Maßnahme',
105             68 => 'Hohes Fahrgastaufkommen'
106             , # xlsx: "Hohes Fahrgastaufkommen verlängert Ein- und Ausstieg"
107             69 => 'Zug verkehrt mit verminderter Geschwindigeit',
108             70 => 'WLAN nicht verfügbar',
109             71 => 'Eingeschränktes WLAN',
110             72 => 'Info/Entertainment nicht verfügbar',
111             73 => 'Heute: Mehrzweckabteil vorne',
112             74 => 'Heute: Mehrzweckabteil hinten',
113             75 => 'Heute: 1. Klasse vorne',
114             76 => 'Heute: 1. Klasse hinten',
115             77 => '1. Klasse fehlt',
116             78 => 'Ersatzverkehr mit Bus ist eingerichtet',
117             79 => 'Mehrzweckabteil fehlt',
118             80 => 'Abweichende Wagenreihung',
119             81 => 'Fahrzeugtausch', # deleted as of 2025-12-14
120             82 => 'Mehrere Wagen fehlen',
121             83 => 'Heute ohne fahrzeuggebundene Einstiegshilfe',
122             84 => 'Zug verkehrt richtig gereiht',
123             85 => 'Ein Wagen fehlt',
124             86 => 'Gesamter Zug ohne Reservierung',
125             87 => 'Einzelne Wagen ohne Reservierung',
126             88 => 'Keine Qualitätsmängel',
127             89 => 'Reservierungen sind wieder vorhanden',
128             90 => 'Kein gastronomisches Angebot',
129             91 => 'Fahrradmitnahme nicht möglich',
130             92 => 'Fahrradmitnahme kann nicht garantiert werden',
131             93 => 'Behindertengerechte Einrichtung fehlt',
132             94 => 'Ersatzbewirtschaftung',
133             95 => 'Universaltoilette fehlt',
134             96 => 'Zustieg kann nicht garantiert werden',
135             97 => 'Hohe Auslastung',
136             98 => 'Sonstige Qualitätsmängel',
137             99 => 'Verzögerungen im Betriebsablauf',
138              
139             # Occasionally, there's a message with ID 900. In all cases observed so far,
140             # it was used for "Anschlussbus wartet". However, as we don't know which bus
141             # it refers to, we don't show it to users.
142             );
143              
144             # IRIS may return "Betriebsstelle nicht bekannt" for some recently added
145             # stations. Fix those manually.
146             my %fixup = (
147             8002795 => 'Herten(Westf)',
148             8003983 => 'Merklingen - Schwäbische Alb',
149             8005493 => 'Schwetzingen-Hirschacker',
150             8070678 => 'Metzingen-Neuhausen',
151             );
152              
153             # }}}
154             # {{{ Constructor
155              
156             sub new {
157 1140     1140 1 13962 my ( $obj, %opt ) = @_;
158              
159 1140         2558 my $ref = \%opt;
160              
161 1140         10623 my ( $train_id, $start_ts, $stop_no ) = split( /.\K-/, $opt{raw_id} );
162              
163 1140         2904 bless( $ref, $obj );
164              
165 1140   33     3495 $ref->{strptime_obj} //= DateTime::Format::Strptime->new(
166             pattern => '%y%m%d%H%M',
167             time_zone => 'Europe/Berlin',
168             );
169              
170 1140         3371 $ref->{wing_id} = "${train_id}-${start_ts}";
171 1140         2630 $ref->{is_wing} = 0;
172 1140         4039 $train_id =~ s{^-}{};
173              
174 1140         3169 $ref->{start} = $ref->parse_ts($start_ts);
175              
176 1140         1318902 $ref->{train_id} = $train_id;
177 1140         4039 $ref->{stop_no} = $stop_no;
178              
179 1140 100       3650 if ( $opt{transfer} ) {
180 24         232 my ($transfer) = split( /.\K-/, $opt{transfer} );
181 24         118 $transfer =~ s{^-}{};
182 24         98 $ref->{transfer} = $transfer;
183             }
184              
185             my $ar = $ref->{arrival} = $ref->{sched_arrival}
186 1140         3596 = $ref->parse_ts( $opt{arrival_ts} );
187             my $dp = $ref->{departure} = $ref->{sched_departure}
188 1140         1011258 = $ref->parse_ts( $opt{departure_ts} );
189              
190 1140 50 66     999283 if ( not( defined $ar or defined $dp ) ) {
191             cluck(
192             sprintf(
193             "Neither arrival '%s' nor departure '%s' are valid "
194             . "timestamps - can't handle this train",
195             $opt{arrival_ts}, $opt{departure_ts}
196             )
197 0         0 );
198             }
199              
200 1140   66     4201 my $dt = $ref->{datetime} = $dp // $ar;
201              
202 1140         4767 $ref->{date} = $dt->strftime('%d.%m.%Y');
203 1140         77153 $ref->{time} = $dt->strftime('%H:%M');
204 1140         53608 $ref->{epoch} = $dt->epoch;
205              
206             $ref->{route_pre} = $ref->{sched_route_pre}
207 1140   100     34468 = [ split( qr{[|]}, $ref->{route_pre} // q{} ) ];
208             $ref->{route_post} = $ref->{sched_route_post}
209 1140   100     16170 = [ split( qr{[|]}, $ref->{route_post} // q{} ) ];
210              
211 1140         5375 $ref->fixup_route( $ref->{route_pre} );
212 1140         3602 $ref->fixup_route( $ref->{route_post} );
213              
214 1140 100       3675 $ref->{route_pre_incomplete} = $ref->{route_end} ? 1 : 0;
215 1140 50       3005 $ref->{route_post_incomplete} = $ref->{route_post} ? 1 : 0;
216              
217 1140         2814 $ref->{sched_platform} = $ref->{platform};
218             $ref->{route_end}
219             = $ref->{sched_route_end}
220             = $ref->{route_end}
221             || $ref->{route_post}[-1]
222 1140   66     7064 || $ref->{station};
223             $ref->{route_start}
224             = $ref->{sched_route_start}
225             = $ref->{route_start}
226             || $ref->{route_pre}[0]
227 1140   66     5841 || $ref->{station};
228              
229 1140         5153 return $ref;
230             }
231              
232             # }}}
233             # {{{ Internal Helpers
234              
235             sub fixup_route {
236 3012     3012 0 6305 my ( $self, $route ) = @_;
237 3012         4376 for my $stop ( @{$route} ) {
  3012         6931  
238 27180 50       52858 if ( $stop =~ m{^Betriebsstelle nicht bekannt (\d+)$} ) {
239 0 0       0 if ( $fixup{$1} ) {
240 0         0 $stop = $fixup{$1};
241             }
242             }
243             }
244             }
245              
246             sub parse_ts {
247 5011     5011 0 12357 my ( $self, $string ) = @_;
248              
249 5011 100       12522 if ( defined $string ) {
250 4655         18146 return $self->{strptime_obj}->parse_datetime($string);
251             }
252 356         1612 return;
253             }
254              
255             # List::Compare does not keep the order of its arguments (even with unsorted).
256             # So we need to re-sort all stops to maintain their original order.
257             sub sorted_sublist {
258 0     0 0 0 my ( $self, $list, $sublist ) = @_;
259 0         0 my %pos;
260              
261 0 0 0     0 if ( not $sublist or not @{$sublist} ) {
  0         0  
262 0         0 return;
263             }
264              
265 0         0 for my $i ( 0 .. $#{$list} ) {
  0         0  
266 0         0 $pos{ $list->[$i] } = $i;
267             }
268              
269 0         0 my @sorted = sort { $pos{$a} <=> $pos{$b} } @{$sublist};
  0         0  
  0         0  
270              
271 0         0 return @sorted;
272             }
273              
274             sub superseded_messages {
275 3     3 0 10 my ( $self, $msg ) = @_;
276 3         32 my %superseded = (
277             73 => [74],
278             74 => [73],
279             75 => [76],
280             76 => [75],
281             84 => [ 73, 74, 75, 76, 80 ],
282             88 => [
283             70, 71, 72, 77, 78, 79, 82, 83, 85, 90,
284             91, 92, 93, 94, 95, 96, 97, 98
285             ],
286             89 => [ 86, 87 ],
287             );
288              
289 3   50     5 return @{ $superseded{$msg} // [] };
  3         29  
290             }
291              
292             # }}}
293             # {{{ Internal Setters for IRIS.pm
294              
295             sub set_ar {
296 760     760 0 44585 my ( $self, %attrib ) = @_;
297              
298 760 100 100     4095 if ( $attrib{status} and $attrib{status} eq 'c' ) {
    50 66        
299 54         194 $self->{has_realtime} = $self->{arrival_has_realtime} = 1;
300 54         134 $self->{arrival_is_cancelled} = 1;
301             }
302             elsif ( $attrib{status} and $attrib{status} eq 'a' ) {
303 0         0 $self->{arrival_is_additional} = 1;
304             }
305             else {
306 706         1689 $self->{arrival_is_additional} = 0;
307 706         2156 $self->{arrival_is_cancelled} = 0;
308             }
309              
310 760 50       1878 if ( $attrib{arrival_hidden} ) {
311 0         0 $self->{arrival_hidden} = $attrib{arrival_hidden};
312             }
313              
314             # unscheduled arrivals may not appear in the plan, but we do need to
315             # know their planned arrival time
316 760 100       1780 if ( $attrib{plan_arrival_ts} ) {
317             $self->{sched_arrival}
318 184         715 = $self->parse_ts( $attrib{plan_arrival_ts} );
319             }
320              
321 760 100       201413 if ( $attrib{arrival_ts} ) {
322 648         1737 $self->{has_realtime} = $self->{arrival_has_realtime} = 1;
323 648         2010 $self->{arrival} = $self->parse_ts( $attrib{arrival_ts} );
324 648 100       696510 if ( not $self->{arrival_is_cancelled} ) {
325             $self->{delay} = $self->{arrival_delay}
326 640         2956 = $self->arrival->subtract_datetime( $self->sched_arrival )
327             ->in_units('minutes');
328             }
329             }
330             else {
331 112         400 $self->{arrival} = $self->{sched_arrival};
332 112   50     821 $self->{arrival_delay} //= 0;
333 112   50     516 $self->{delay} //= 0;
334             }
335              
336 760 100       362066 if ( $attrib{platform} ) {
337 14         81 $self->{platform} = $attrib{platform};
338             }
339             else {
340 746         2330 $self->{platform} = $self->{sched_platform};
341             }
342              
343 760 100       2038 if ( defined $attrib{route_pre} ) {
344 190   50     3611 $self->{route_pre} = [ split( qr{[|]}, $attrib{route_pre} // q{} ) ];
345 190         1118 $self->fixup_route( $self->{route_pre} );
346 190 100       373 if ( @{ $self->{route_pre} } ) {
  190         677  
347 144         441 $self->{route_start} = $self->{route_pre}[0];
348             }
349             }
350             else {
351 570         1276 $self->{route_pre} = $self->{sched_route_pre};
352 570         1269 $self->{route_start} = $self->{sched_route_start};
353             }
354              
355             # also only for unscheduled arrivals
356 760 100       2278 if ( $attrib{sched_route_pre} ) {
357             $self->{sched_route_pre}
358 184   50     3356 = [ split( qr{[|]}, $attrib{sched_route_pre} // q{} ) ];
359 184         1081 $self->fixup_route( $self->{sched_route_pre} );
360 184         846 $self->{sched_route_start} = $self->{sched_route_pre}[0];
361             }
362              
363 760         3586 return $self;
364             }
365              
366             sub set_dp {
367 686     686 0 38902 my ( $self, %attrib ) = @_;
368              
369 686 100 100     3756 if ( $attrib{status} and $attrib{status} eq 'c' ) {
    50 66        
370 53         155 $self->{has_realtime} = $self->{arrival_has_realtime} = 1;
371 53         142 $self->{departure_is_cancelled} = 1;
372             }
373             elsif ( $attrib{status} and $attrib{status} eq 'a' ) {
374 0         0 $self->{departure_is_additional} = 1;
375             }
376             else {
377 633         1524 $self->{departure_is_additional} = 0;
378 633         1905 $self->{departure_is_cancelled} = 0;
379             }
380              
381 686 50       1732 if ( $attrib{departure_hidden} ) {
382 0         0 $self->{departure_hidden} = $attrib{departure_hidden};
383             }
384              
385             # unscheduled arrivals may not appear in the plan, but we do need to
386             # know their planned arrival time
387 686 100       1797 if ( $attrib{plan_departure_ts} ) {
388             $self->{sched_departure}
389 176         598 = $self->parse_ts( $attrib{plan_departure_ts} );
390             }
391              
392 686 100       196236 if ( $attrib{departure_ts} ) {
393 572         1575 $self->{has_realtime} = $self->{departure_has_realtime} = 1;
394 572         1786 $self->{departure} = $self->parse_ts( $attrib{departure_ts} );
395 572 100       608403 if ( not $self->{departure_is_cancelled} ) {
396             $self->{delay} = $self->{departure_delay}
397 564         2488 = $self->departure->subtract_datetime( $self->sched_departure )
398             ->in_units('minutes');
399             }
400             }
401             else {
402 114         343 $self->{departure} = $self->{sched_departure};
403 114   100     432 $self->{delay} //= 0;
404 114   50     593 $self->{departure_delay} //= 0;
405             }
406              
407 686 100       327175 if ( $attrib{platform} ) {
408 7         25 $self->{platform} = $attrib{platform};
409             }
410             else {
411 679         2028 $self->{platform} = $self->{sched_platform};
412             }
413              
414 686 100       1704 if ( defined $attrib{route_post} ) {
415 182   50     3307 $self->{route_post} = [ split( qr{[|]}, $attrib{route_post} // q{} ) ];
416 182         1171 $self->fixup_route( $self->{route_post} );
417 182 100       343 if ( @{ $self->{route_post} } ) {
  182         664  
418 137         394 $self->{route_end} = $self->{route_post}[-1];
419             }
420             }
421             else {
422 504         1257 $self->{route_post} = $self->{sched_route_post};
423 504         1256 $self->{route_end} = $self->{sched_route_end};
424             }
425              
426             # also only for unscheduled departures
427 686 100       1991 if ( $attrib{sched_route_post} ) {
428             $self->{sched_route_post}
429 176   50     3262 = [ split( qr{[|]}, $attrib{sched_route_post} // q{} ) ];
430 176         985 $self->fixup_route( $self->{sched_route_post} );
431 176         508 $self->{sched_route_end} = $self->{sched_route_post}[-1];
432             }
433              
434 686         6197 return $self;
435             }
436              
437             sub set_messages {
438 827     827 0 3088 my ( $self, %messages ) = @_;
439              
440 827         2501 $self->{messages} = \%messages;
441              
442 827         1901 return $self;
443             }
444              
445             sub set_realtime {
446 827     827 0 1846 my ( $self, $xmlobj ) = @_;
447              
448 827         7764 $self->{realtime_xml} = $xmlobj;
449              
450 827         2058 return $self;
451             }
452              
453             sub add_raw_ref {
454 1     1 0 34 my ( $self, %attrib ) = @_;
455              
456 1         3 push( @{ $self->{refs} }, \%attrib );
  1         4  
457              
458 1         100 return $self;
459             }
460              
461             sub set_unscheduled {
462 121     121 0 368 my ( $self, $unscheduled ) = @_;
463              
464 121         542 $self->{is_unscheduled} = $unscheduled;
465              
466 121         298 return $self;
467             }
468              
469             sub add_arrival_wingref {
470 14     14 0 46 my ( $self, $ref ) = @_;
471              
472 14         27 my $backref = $self;
473              
474 14         41 weaken($ref);
475 14         24 weaken($backref);
476 14         36 $ref->{is_wing} = 1;
477 14         29 $ref->{wing_of} = $backref;
478 14         41 push( @{ $self->{arrival_wings} }, $ref );
  14         51  
479 14         87 return $self;
480             }
481              
482             sub add_departure_wingref {
483 8     8 0 49 my ( $self, $ref ) = @_;
484              
485 8         17 my $backref = $self;
486              
487 8         20 weaken($ref);
488 8         17 weaken($backref);
489 8         19 $ref->{is_wing} = 1;
490 8         19 $ref->{wing_of} = $backref;
491 8         15 push( @{ $self->{departure_wings} }, $ref );
  8         44  
492 8         29 return $self;
493             }
494              
495             sub add_reference {
496 0     0 0 0 my ( $self, $ref ) = @_;
497              
498 0         0 $ref->add_inverse_reference($self);
499 0         0 weaken($ref);
500 0         0 push( @{ $self->{replacement_for} }, $ref );
  0         0  
501 0         0 return $self;
502             }
503              
504             sub merge_with_departure {
505 10     10 0 23 my ( $self, $result ) = @_;
506              
507             # result must be departure-only
508              
509 10         13 $self->{is_transfer} = 1;
510              
511 10         20 $self->{old_train_id} = $self->{train_id};
512 10         17 $self->{old_train_no} = $self->{train_no};
513              
514             # departure is preferred over arrival, so overwrite default values
515 10         18 $self->{date} = $result->{date};
516 10         18 $self->{time} = $result->{time};
517 10         12 $self->{epoch} = $result->{epoch};
518 10         14 $self->{datetime} = $result->{datetime};
519 10         14 $self->{train_id} = $result->{train_id};
520 10         13 $self->{train_no} = $result->{train_no};
521              
522 10         43 $self->{departure} = $result->{departure};
523 10         15 $self->{departure_wings} = $result->{departure_wings};
524 10         14 $self->{route_end} = $result->{route_end};
525 10         14 $self->{route_post} = $result->{route_post};
526 10         14 $self->{sched_departure} = $result->{sched_departure};
527 10         16 $self->{sched_route_post} = $result->{sched_route_post};
528              
529             # update realtime info only if applicable
530 10   33     467 $self->{is_cancelled} ||= $result->{is_cancelled};
531              
532 10         21 return $self;
533             }
534              
535             sub add_inverse_reference {
536 0     0 0 0 my ( $self, $ref ) = @_;
537              
538 0         0 weaken($ref);
539 0         0 push( @{ $self->{replaced_by} }, $ref );
  0         0  
540 0         0 return $self;
541             }
542              
543             # }}}
544             # {{{ Public Accessors
545              
546             sub is_additional {
547 0     0 1 0 my ($self) = @_;
548              
549 0 0 0     0 if ( $self->{arrival_is_additional} and $self->{departure_is_additional} ) {
550 0         0 return 1;
551             }
552 0 0 0     0 if ( $self->{arrival_is_additional}
553             and not defined $self->{departure_is_additional} )
554             {
555 0         0 return 1;
556             }
557 0 0 0     0 if ( not defined $self->{arrival_is_additional}
558             and $self->{departure_is_additional} )
559             {
560 0         0 return 1;
561             }
562 0         0 return 0;
563             }
564              
565             sub is_cancelled {
566 2     2 1 2867 my ($self) = @_;
567              
568 2 50 66     17 if ( $self->{arrival_is_cancelled} and $self->{departure_is_cancelled} ) {
569 1         4 return 1;
570             }
571 1 50 33     8 if ( $self->{arrival_is_cancelled}
572             and not defined $self->{departure_is_cancelled} )
573             {
574 0         0 return 1;
575             }
576 1 0 33     5 if ( not defined $self->{arrival_is_cancelled}
577             and $self->{departure_is_cancelled} )
578             {
579 0         0 return 1;
580             }
581 1         8 return 0;
582             }
583              
584             sub additional_stops {
585 0     0 1 0 my ($self) = @_;
586              
587             $self->{comparator} //= List::Compare->new(
588             {
589 0   0     0 lists => [ $self->{sched_route_post}, $self->{route_post} ],
590             unsorted => 1,
591             }
592             );
593              
594             return $self->sorted_sublist( $self->{route_post},
595 0         0 [ $self->{comparator}->get_complement ] );
596             }
597              
598             sub canceled_stops {
599 0     0 1 0 my ($self) = @_;
600              
601             $self->{comparator} //= List::Compare->new(
602             {
603 0   0     0 lists => [ $self->{sched_route_post}, $self->{route_post} ],
604             unsorted => 1,
605             }
606             );
607              
608             return $self->sorted_sublist( $self->{sched_route_post},
609 0         0 [ $self->{comparator}->get_unique ] );
610             }
611              
612             sub classes {
613 1     1 1 3925 my ($self) = @_;
614              
615 1   50     6 my @classes = split( //, $self->{classes} // q{} );
616              
617 1         7 return @classes;
618             }
619              
620             sub origin {
621 134     134 1 127049 my ($self) = @_;
622              
623 134         556 return $self->route_start;
624             }
625              
626             sub destination {
627 134     134 1 132009 my ($self) = @_;
628              
629 134         631 return $self->route_end;
630             }
631              
632             sub delay_messages {
633 1     1 1 5 my ($self) = @_;
634              
635 1         3 my @keys = sort keys %{ $self->{messages} };
  1         14  
636 1         5 my @msgs = grep { $_->[1] eq 'd' } map { $self->{messages}{$_} } @keys;
  8         19  
  8         20  
637 1         4 my @msgids = uniq( map { $_->[2] } @msgs );
  7         25  
638 1         4 my @ret;
639              
640 1         4 for my $id (@msgids) {
641 2         7 for my $superseded ( $self->superseded_messages($id) ) {
642 0         0 @ret = grep { not( $_->[2] == $superseded ) } @ret;
  0         0  
643             }
644 2     3   36 my $msg = lastval { $_->[2] == $id } @msgs;
  3         8  
645 2         12 push( @ret, $msg );
646             }
647              
648             @ret = reverse
649 1         3 map { [ $self->parse_ts( $_->[0] ), $self->translate_msg( $_->[2] ) ] }
  2         9  
650             @ret;
651              
652 1         13 return @ret;
653             }
654              
655             sub arrival_wings {
656 4     4 1 11676 my ($self) = @_;
657              
658 4 100       19 if ( $self->{arrival_wings} ) {
659 2         5 return @{ $self->{arrival_wings} };
  2         13  
660             }
661 2         13 return;
662             }
663              
664             sub departure_wings {
665 6     6 1 20 my ($self) = @_;
666              
667 6 100       24 if ( $self->{departure_wings} ) {
668 4         8 return @{ $self->{departure_wings} };
  4         28  
669             }
670 2         11 return;
671             }
672              
673             sub replaced_by {
674 0     0 1 0 my ($self) = @_;
675              
676 0 0       0 if ( $self->{replaced_by} ) {
677 0         0 return @{ $self->{replaced_by} };
  0         0  
678             }
679 0         0 return;
680             }
681              
682             sub replacement_for {
683 0     0 1 0 my ($self) = @_;
684              
685 0 0       0 if ( $self->{replacement_for} ) {
686 0         0 return @{ $self->{replacement_for} };
  0         0  
687             }
688 0         0 return;
689             }
690              
691             sub qos_messages {
692 1     1 1 4 my ($self) = @_;
693              
694 1         3 my @keys = sort keys %{ $self->{messages} };
  1         14  
695             my @msgs
696 1         5 = grep { $_->[1] =~ m{^[fq]$} } map { $self->{messages}{$_} } @keys;
  8         26  
  8         20  
697 1         4 my @ret;
698              
699 1         4 for my $msg (@msgs) {
700 1         6 for my $superseded ( $self->superseded_messages( $msg->[2] ) ) {
701 0         0 @ret = grep { not( $_->[2] == $superseded ) } @ret;
  0         0  
702             }
703 1         4 @ret = grep { $_->[2] != $msg->[2] } @ret;
  0         0  
704              
705             # 88 is "no qos shortcomings" and only required to cancel previous qos
706             # messages. Same for 84 ("correct wagon order") and 89 ("reservations
707             # display is working again").
708 1 50 33     11 if ( $msg->[2] != 84 and $msg->[2] != 88 and $msg->[2] != 89 ) {
      33        
709 1         4 push( @ret, $msg );
710             }
711             }
712              
713             @ret
714 1         4 = map { [ $self->parse_ts( $_->[0] ), $self->translate_msg( $_->[2] ) ] }
  1         4  
715             reverse @ret;
716              
717 1         11 return @ret;
718             }
719              
720             sub raw_messages {
721 0     0 0 0 my ($self) = @_;
722              
723 0         0 my @messages = reverse sort keys %{ $self->{messages} };
  0         0  
724             my @ret = map {
725 0         0 [
726             $self->parse_ts( $self->{messages}->{$_}->[0] ),
727 0         0 $self->{messages}->{$_}->[2]
728             ]
729             } @messages;
730              
731 0         0 return @ret;
732             }
733              
734             sub messages {
735 1     1 1 4 my ($self) = @_;
736              
737 1         3 my @messages = reverse sort keys %{ $self->{messages} };
  1         14  
738             my @ret = map {
739 1         4 [
740             $self->parse_ts( $self->{messages}->{$_}->[0] ),
741 8         28 $self->translate_msg( $self->{messages}->{$_}->[2] )
742             ]
743             } @messages;
744              
745 1         22 return @ret;
746             }
747              
748             sub info {
749 1     1 1 17 my ($self) = @_;
750              
751 1         3 my @messages = sort keys %{ $self->{messages} };
  1         18  
752 1         4 my @ids = uniq( map { $self->{messages}{$_}->[2] } @messages );
  8         58  
753              
754 1         24 my @info = map { $self->translate_msg($_) } @ids;
  3         11  
755              
756 1         33 return @info;
757             }
758              
759             sub line {
760 268     268 1 535 my ($self) = @_;
761              
762             return sprintf( '%s %s',
763             $self->{type} // 'Zug',
764 268   50     2685 $self->{line_no} // $self->{train_no} // '-' );
      33        
      50        
765             }
766              
767             sub route_pre {
768 4     4 1 11 my ($self) = @_;
769              
770 4         7 return @{ $self->{route_pre} };
  4         26  
771             }
772              
773             sub route_post {
774 8     8 1 17 my ($self) = @_;
775              
776 8         14 return @{ $self->{route_post} };
  8         56  
777             }
778              
779             sub route {
780 2     2 1 13 my ($self) = @_;
781              
782 2         7 return ( $self->route_pre, $self->{station}, $self->route_post );
783             }
784              
785             sub train {
786 134     134 1 135426 my ($self) = @_;
787              
788 134         494 return $self->line;
789             }
790              
791             sub route_interesting {
792 4     4 1 13 my ( $self, $max_parts ) = @_;
793              
794 4         13 my @via = $self->route_post;
795 4         9 my ( @via_main, @via_show, $last_stop );
796 4   50     28 $max_parts //= 3;
797              
798             # Centraal: dutch main station (Hbf in .nl)
799             # HB: swiss main station (Hbf in .ch)
800             # hl.n.: czech main station (Hbf in .cz)
801 4         9 for my $stop (@via) {
802 16 100       73 if ( $stop =~ m{ HB $ | hl\.n\. $ | Hbf | Centraal | Flughafen }x ) {
803 9         23 push( @via_main, $stop );
804             }
805             }
806             $last_stop
807 4 50       17 = $self->{route_post_incomplete} ? $self->{route_end} : pop(@via);
808              
809 4 100 100     20 if ( @via_main and $via_main[-1] eq $last_stop ) {
810 2         6 pop(@via_main);
811             }
812 4 100 66     19 if ( @via and $via[-1] eq $last_stop ) {
813 3         7 pop(@via);
814             }
815              
816 4 100 66     20 if ( @via_main and @via and $via[0] eq $via_main[0] ) {
      100        
817 1         3 shift(@via_main);
818             }
819              
820 4 100       12 if ( @via < $max_parts ) {
821 2         6 @via_show = @via;
822             }
823             else {
824 2 100       8 if ( @via_main >= $max_parts ) {
825 1         4 @via_show = ( $via[0] );
826             }
827             else {
828 1         6 @via_show = splice( @via, 0, $max_parts - @via_main );
829             }
830              
831 2   66     12 while ( @via_show < $max_parts and @via_main ) {
832 4         8 my $stop = shift(@via_main);
833 4 50 33 6   23 if ( any { $stop eq $_ } @via_show or $stop eq $last_stop ) {
  6         24  
834 0         0 next;
835             }
836 4         23 push( @via_show, $stop );
837             }
838             }
839              
840 4         25 for (@via_show) {
841 6         59 s{ \s? Hbf .* }{}x;
842             }
843              
844 4         36 return @via_show;
845              
846             }
847              
848             sub sched_route_pre {
849 2     2 1 3 my ($self) = @_;
850              
851 2         3 return @{ $self->{sched_route_pre} };
  2         11  
852             }
853              
854             sub sched_route_post {
855 2     2 1 2 my ($self) = @_;
856              
857 2         31 return @{ $self->{sched_route_post} };
  2         17  
858             }
859              
860             sub sched_route {
861 1     1 1 2 my ($self) = @_;
862              
863             return ( $self->sched_route_pre, $self->{station},
864 1         4 $self->sched_route_post );
865             }
866              
867             sub translate_msg {
868 14     14 0 12712 my ( $self, $msg ) = @_;
869              
870 14   33     92 return $translation{$msg} // "?($msg)";
871             }
872              
873             sub TO_JSON {
874 0     0 0   my ($self) = @_;
875              
876 0           my %copy = %{$self};
  0            
877 0           delete $copy{realtime_xml};
878 0           delete $copy{strptime_obj};
879              
880 0           for my $ref_key (
881             qw(arrival_wings departure_wings replaced_by replacement_for))
882             {
883 0           delete $copy{$ref_key};
884 0   0       for my $train_ref ( @{ $self->{$ref_key} // [] } ) {
  0            
885             push(
886 0           @{ $copy{$ref_key} },
  0            
887             {
888             raw_id => $train_ref->raw_id,
889             train => $train_ref->train,
890             train_no => $train_ref->train_no,
891             type => $train_ref->type,
892             }
893             );
894             }
895             }
896              
897 0           delete $copy{wing_of};
898 0 0         if ( my $train_ref = $self->wing_of ) {
899             $copy{wing_of} = {
900 0           raw_id => $train_ref->raw_id,
901             train => $train_ref->train,
902             train_no => $train_ref->train_no,
903             type => $train_ref->type,
904             };
905             }
906              
907 0           for my $datetime_key (
908             qw(arrival departure sched_arrival sched_departure start datetime))
909             {
910 0 0         if ( defined $copy{$datetime_key} ) {
911 0           $copy{$datetime_key} = $copy{$datetime_key}->epoch;
912             }
913             }
914              
915 0           return {%copy};
916             }
917              
918             # }}}
919              
920             1;
921              
922             __END__
923              
924             =head1 NAME
925              
926             Travel::Status::DE::IRIS::Result - Information about a single
927             arrival/departure received by Travel::Status::DE::IRIS
928              
929             =head1 SYNOPSIS
930              
931             for my $result ($status->results) {
932             printf(
933             "At %s: %s to %s from platform %s\n",
934             $result->time,
935             $result->line,
936             $result->destination,
937             $result->platform,
938             );
939             }
940              
941             =head1 VERSION
942              
943             version 2.04
944              
945             =head1 DESCRIPTION
946              
947             Travel::Status::DE::IRIs::Result describes a single arrival/departure
948             as obtained by Travel::Status::DE::IRIS. It contains information about
949             the platform, time, route and more.
950              
951             =head1 METHODS
952              
953             =head2 ACCESSORS
954              
955             =over
956              
957             =item $result->additional_stops
958              
959             Returns served stops which are not part of the schedule. I.e., this is the
960             set of actual stops (B<route_post>) minus the set of scheduled stops
961             (B<sched_route_post>).
962              
963             =item $result->arrival
964              
965             DateTime(3pm) object for the arrival date and time. undef if the
966             train starts here. Contains realtime data if available.
967              
968             =item $result->arrival_delay
969              
970             Estimated arrival delay in minutes (integer number). undef if no realtime
971             data is available, the train starts at the specified station, or there is
972             no scheduled arrival time (e.g. due to diversions). May be negative.
973              
974             =item $result->arrival_has_realtime
975              
976             True if "arrival" is based on real-time data.
977              
978             =item $result->arrival_hidden
979              
980             True if arrival should not be displayed to customers.
981             This often indicates an entry-only stop near the beginning of a train's journey.
982              
983             =item $result->arrival_is_additional
984              
985             True if the arrival at this stop is an additional (unscheduled) event, i.e.,
986             if the train started its journey earlier than planned.
987              
988             =item $result->arrival_is_cancelled
989              
990             True if the arrival at this stop has been cancelled.
991              
992             =item $result->arrival_wings
993              
994             Returns a list of weakened references to Travel::Status::DE::IRIS::Result(3pm)
995             objects which are coupled to this train on arrival. Returns nothing (false /
996             empty list) otherwise.
997              
998             =item $result->canceled_stops
999              
1000             Returns stops which are scheduled, but will not be served by this train.
1001             I.e., this is the set of scheduled stops (B<sched_route_post>) minus the set of
1002             actual stops (B<route_post>).
1003              
1004             =item $result->classes
1005              
1006             List of characters indicating the class(es) of this train, may be empty. This
1007             is slighty related to B<type>, but more generic. At this time, the following
1008             classes are known:
1009              
1010             D Non-DB train. Usually local transport
1011             D,F Non-DB train, long distance transport
1012             F "Fernverkehr", long-distance transport
1013             N "Nahverkehr", local and regional transport
1014             S S-Bahn, rather slow local/regional transport
1015              
1016             =item $result->date
1017              
1018             Scheduled departure date if available, arrival date otherwise (e.g. if the
1019             train ends here). String in dd.mm.YYYY format. Does not contain realtime data.
1020              
1021             =item $result->datetime
1022              
1023             DateTime(3pm) object for departure if available, arrival otherwise. Does not
1024             contain realtime data.
1025              
1026             =item $result->delay
1027              
1028             Estimated delay in minutes (integer number). Defaults to the departure delay,
1029             except for trains which terminate at the specifed station. Similar to
1030             C<< $result->departure_delay // $result->arrival_delay >>. undef if
1031             no realtime data is available. May be negative.
1032              
1033             =item $result->delay_messages
1034              
1035             Get all delay messages entered for this train. Returns a list of [datetime,
1036             string] listrefs sorted by newest first. The datetime part is a DateTime(3pm)
1037             object corresponding to the point in time when the message was entered, the
1038             string is the message. If a delay reason was entered more than once, only its
1039             most recent record will be returned.
1040              
1041             =item $result->departure
1042              
1043             DateTime(3pm) object for the departure date and time. undef if the train ends
1044             here. Contains realtime data if available.
1045              
1046             =item $result->departure_delay
1047              
1048             Estimated departure delay in minutes (integer number). undef if no realtime
1049             data is available, the train terminates at the specified station, or there is
1050             no scheduled departure time (e.g. due to diversions). May be negative.
1051              
1052             =item $result->departure_has_realtime
1053              
1054             True if "departure" is based on real-time data.
1055              
1056             =item $result->departure_hidden
1057              
1058             True if departure should not be displayed to customers.
1059             This often indicates an exit-only stop near the end of a train's journey.
1060              
1061             =item $result->departure_is_additional
1062              
1063             True if the train's departure at this stop is unscheduled (additional), i.e.,
1064             the route has been extended past its scheduled terminal stop.
1065              
1066             =item $result->departure_is_cancelled
1067              
1068             True if the train's departure at this stop has been cancelled, i.e., the train
1069             terminates here and does not continue its scheduled journey.
1070              
1071             =item $result->departure_wings
1072              
1073             Returns a list of weakened references to Travel::Status::DE::IRIS::Result(3pm)
1074             objects which are coupled to this train on departure. Returns nothing (false /
1075             empty list) otherwise.
1076              
1077             =item $result->destination
1078              
1079             Alias for route_end.
1080              
1081             =item $result->has_realtime
1082              
1083             True if arrival or departure time are based on real-time data. Note that this
1084             is different from C<< defined($esult->delay) >>. If delay is defined, some kind
1085             of realtime information for the train is available, but not necessarily its
1086             arrival/departure time. If has_realtime is true, arrival/departure time are
1087             available. This behaviour may change in the future.
1088              
1089             =item $result->info
1090              
1091             List of information strings. Contains both reasons for delays (which may or
1092             may not be up-to-date) and generic information such as missing carriages or
1093             broken toilets.
1094              
1095             =item $result->is_additional
1096              
1097             True if the train's arrival and departure at the stop are unscheduled
1098             additional stops, false otherwise.
1099              
1100             =item $result->is_cancelled
1101              
1102             True if the train was cancelled, false otherwise. Note that this does not
1103             contain information about replacement trains or route diversions.
1104              
1105             =item $result->is_transfer
1106              
1107             True if the train changes its ID at the current station, false otherwise.
1108              
1109             An ID change means: There are two results in the system (e.g. RE 10228
1110             ME<uuml>nster -> Duisburg, RE 30028 Duisburg -> DE<uuml>sseldorf), but they are
1111             the same train (RE line 2 from ME<uuml>nster to DE<uuml>sseldorf in this case)
1112             and should be treated as such. In this case, Travel::Status::DE::IRIS merges
1113             the results and indicates it by setting B<is_transfer> to a true value.
1114              
1115             In case of a transfer, B<train_id> and B<train_no> are set to the "new"
1116             value, the old ones are available in B<old_train_id> and B<old_train_no>.
1117              
1118             =item $result->is_unscheduled
1119              
1120             True if the train does not appear in the requested plans. This can happen
1121             because of two reasons: Either the scheduled time and the actual time are so
1122             far apart that it should've arrived/departed long ago, or it really is an
1123             unscheduled train. In that case, it can be a replacement or an additional
1124             train. There is no logic to distinguish these cases yet.
1125              
1126             =item $result->is_wing
1127              
1128             Returns true if this result is a wing, false otherwise.
1129             A wing is a train which has its own ID and destination, but is currently
1130             coupled to another train and shares all or some of its route.
1131              
1132             =item $result->line
1133              
1134             Train type with line (such as C<< S 1 >>) if available, type with number
1135             (suc as C<< RE 10126 >>) otherwise.
1136              
1137             =item $result->line_no
1138              
1139             Number of the line, undef if unknown. Seems to be set only for S-Bahn and
1140             regional trains. Note that some regional and most long-distance trains do
1141             not have this field set, even if they have a common line number.
1142              
1143             Example: For the line C<< S 1 >>, line_no will return C<< 1 >>.
1144              
1145             =item $result->messages
1146              
1147             Get all qos and delay messages ever entered for this train. Returns a list of
1148             [datetime, string] listrefs sorted by newest first. The datetime part is a
1149             DateTime(3pm) object corresponding to the point in time when the message was
1150             entered, the string is the message. Note that neither duplicates nor superseded
1151             messages are filtered from this list.
1152              
1153             =item $result->old_train_id
1154              
1155             Numeric ID of the pre-transfer train. Seems to be unique for a year and
1156             trackable across stations. Only defined if a transfer took place,
1157             see also B<is_transfer>.
1158              
1159             =item $result->old_train_no
1160              
1161             Number of the pre-tarnsfer train, unique per day. E.g. C<< 2225 >> for
1162             C<< IC 2225 >>. Only defined if a transfer took
1163             place, see also B<is_transfer>.
1164              
1165             =item $result->origin
1166              
1167             Alias for route_start.
1168              
1169             =item $result->qos_messages
1170              
1171             Get all current qos messages for this train. Returns a list of [datetime,
1172             string] listrefs sorted by newest first. The datetime part is a DateTime(3pm)
1173             object corresponding to the point in time when the message was entered, the
1174             string is the message. Contains neither superseded messages nor duplicates (in
1175             case of a duplicate, only the most recent message is present)
1176              
1177             =item $result->platform
1178              
1179             Arrival/departure platform as string, undef if unknown. Note that this is
1180             not neccessarily a number, platform sections may be included (e.g.
1181             C<< 3a/b >>).
1182              
1183             =item $result->raw_id
1184              
1185             Raw ID of the departure, e.g. C<< -4642102742373784975-1401031322-6 >>.
1186             The first part appears to be this train's UUID (can be tracked across
1187             multiple stations), the second the YYmmddHHMM departure timestamp at its
1188             start station, and the third the count of this station in the train's schedule
1189             (in this case, it's the sixth from thestart station).
1190              
1191             About half of all departure IDs do not contain the leading minus (C<< - >>)
1192             seen in this example. The reason for this is unknown.
1193              
1194             This is a developer option. It may be removed without prior warning.
1195              
1196             =item $result->realtime_xml
1197              
1198             XML::LibXML::Node(3pm) object containing all realtime data. undef if none is
1199             available.
1200              
1201             This is a developer option. It may be removed without prior warning.
1202              
1203             =item $result->replaced_by
1204              
1205             Returns a list of weakened references to Travel::Status::DE::IRIS::Result(3pm)
1206             objects which replace the (usually cancelled) arrival/departure of this train.
1207             Returns nothing (false / empty list) otherwise.
1208              
1209             =item $result->replacement_for
1210              
1211             Returns a list of weakened references to Travel::Status::DE::IRIS::Result(3pm)
1212             objects which this (usually unplanned) train is meant to replace. Returns
1213             nothing (false / empty list) otherwise.
1214              
1215             =item $result->route
1216              
1217             List of all stations served by this train, according to its schedule. Does
1218             not contain realtime data.
1219              
1220             =item $result->route_end
1221              
1222             Name of the last station served by this train.
1223              
1224             =item $result->route_interesting
1225              
1226             List of up to three "interesting" stations served by this train, subset of
1227             route_post. Usually contains the next stop and one or two major stations after
1228             that. Does not contain realtime data.
1229              
1230             =item $result->route_pre
1231              
1232             List of station names the train passed (or will have passed) before this stop.
1233              
1234             =item $result->route_post
1235              
1236             List of station names the train will pass after this stop.
1237              
1238             =item $result->route_start
1239              
1240             Name of the first station served by this train.
1241              
1242             =item $result->sched_arrival
1243              
1244             DateTime(3pm) object for the scheduled arrival date and time. undef if the
1245             train starts here.
1246              
1247             =item $result->sched_departure
1248              
1249             DateTime(3pm) object for the scheduled departure date and time. undef if the
1250             train ends here.
1251              
1252             =item $result->sched_platform
1253              
1254             Scheduled Arrival/departure platform as string, undef if unknown. Note that
1255             this is not neccessarily a number, platform sections may be included (e.g. C<<
1256             3a/b >>).
1257              
1258             =item $result->sched_route
1259              
1260             List of all stations served by this train, according to its schedule. Does
1261             not contain realtime data.
1262              
1263             =item $result->sched_route_end
1264              
1265             Name of the last station served by this train according to its schedule.
1266              
1267             =item $result->sched_route_pre
1268              
1269             List of station names the train is scheduled to pass before this stop.
1270              
1271             =item $result->sched_route_post
1272              
1273             List of station names the train is scheduled to pass after this stop.
1274              
1275             =item $result->sched_route_start
1276              
1277             Name of the first station served by this train according to its schedule.
1278              
1279             =item $result->start
1280              
1281             DateTime(3pm) object for the scheduled start of the train on its route
1282             (i.e. the departure time at its first station).
1283              
1284             =item $result->station
1285              
1286             Name of the station this train result belongs to.
1287              
1288             =item $result->station_eva
1289              
1290             EVA number of the station this train result belongs to.
1291             This is often, but not always, identical with the UIC station number.
1292              
1293             =item $result->stop_no
1294              
1295             Number of this stop on the train's route. 1 if it's the start station, 2
1296             for the stop after that, and so on.
1297              
1298             =item $result->time
1299              
1300             Scheduled departure time if available, arrival time otherwise (e.g. if the
1301             train ends here). String in HH:MM format. Does not contain realtime data.
1302              
1303             =item $result->train
1304              
1305             Alias for line.
1306              
1307             =item $result->train_id
1308              
1309             Numeric ID of this train, trackable across stations and days. For instance, the
1310             S 31128 (S1) to Solingen, starting in Dortmund on 19:23, has the ID
1311             2404170432985554630 on each station it passes and (usually) on every day of the
1312             year. Note that it may change during the yearly itinerary update in december.
1313              
1314             =item $result->train_no
1315              
1316             Number of this train, unique per day. E.g. C<< 2225 >> for C<< IC 2225 >>.
1317              
1318             =item $result->type
1319              
1320             Type of this train, e.g. C<< S >> for S-Bahn, C<< RE >> for Regional-Express,
1321             C<< ICE >> for InterCity-Express.
1322              
1323             =item $result->wing_of
1324              
1325             If B<is_wing> is true, returns a weakened reference to the
1326             Travel::Status::DE::IRIS::Result(3pm) object which this train is a wing of. So
1327             far, it seems that a train is either not a wing or a wing of exactly one other
1328             train. Returns undef if B<is_wing> is false.
1329              
1330             =back
1331              
1332             =head2 INTERNAL
1333              
1334             =over
1335              
1336             =item $result = Travel::Status::DE::IRIS::Result->new(I<%data>)
1337              
1338             Returns a new Travel::Status::DE::IRIS::Result object.
1339             You usually do not need to call this.
1340              
1341             =back
1342              
1343             =head1 DIAGNOSTICS
1344              
1345             None.
1346              
1347             =head1 DEPENDENCIES
1348              
1349             =over
1350              
1351             =item Class::Accessor(3pm)
1352              
1353             =back
1354              
1355             =head1 BUGS AND LIMITATIONS
1356              
1357             Unknown.
1358              
1359             =head1 SEE ALSO
1360              
1361             Travel::Status::DE::IRIS(3pm).
1362              
1363             =head1 AUTHOR
1364              
1365             Copyright (C) 2013-2026 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt>
1366              
1367             =head1 LICENSE
1368              
1369             This module is licensed under the same terms as Perl itself.