diff options
Diffstat (limited to 'bin/hafas-m')
-rwxr-xr-x | bin/hafas-m | 731 |
1 files changed, 632 insertions, 99 deletions
diff --git a/bin/hafas-m b/bin/hafas-m index b2efd2b..ea8471f 100755 --- a/bin/hafas-m +++ b/bin/hafas-m @@ -1,23 +1,27 @@ -#!/usr/bin/env perl +#!perl use strict; use warnings; use 5.014; -our $VERSION = '2.03'; +our $VERSION = '6.03'; +use utf8; +use DateTime; use Encode qw(decode); -use Getopt::Long qw(:config no_ignore_case); +use JSON; +use Getopt::Long qw(:config no_ignore_case); use List::MoreUtils qw(uniq); -use List::Util qw(first max); +use List::Util qw(first max); use Travel::Status::DE::HAFAS; -my ( $date, $time ); -my $arrivals = 0; -my $ignore_late = 0; -my $types = q{}; -my $language; +my ( $date, $time, $language ); +my $arrivals; +my $show_jid; +my $types = q{}; my $developer_mode; -my ( $list_services, $service, $hafas_url ); +my $via; +my ( $json_output, $raw_json_output ); +my ( $list_services, $service ); my ( @excluded_mots, @exclusive_mots ); my @output; @@ -27,45 +31,116 @@ for my $arg (@ARGV) { $arg = decode( 'UTF-8', $arg ); } +my $output_bold = -t STDOUT ? "\033[1m" : q{}; +my $output_reset = -t STDOUT ? "\033[0m" : q{}; + GetOptions( - 'a|arrivals' => \$arrivals, - 'd|date=s' => \$date, - 'h|help' => sub { show_help(0) }, - 'l|lang=s' => \$language, - 'L|ignore-late' => \$ignore_late, - 'm|mot=s' => \$types, - 's|service=s' => \$service, - 't|time=s' => \$time, - 'u|url=s' => \$hafas_url, - 'V|version' => \&show_version, - 'devmode' => \$developer_mode, - 'list' => \$list_services, + 'a|arrivals' => \$arrivals, + 'd|date=s' => \$date, + 'h|help' => sub { show_help(0) }, + 'j|with-jid' => \$show_jid, + 'l|language=s' => \$language, + 'm|mot=s' => \$types, + 's|service=s' => \$service, + 't|time=s' => \$time, + 'v|via=s' => \$via, + 'V|version' => \&show_version, + 'devmode' => \$developer_mode, + 'json' => \$json_output, + 'raw-json' => \$raw_json_output, + 'list' => \$list_services, ) or show_help(1); if ($list_services) { - printf( "%-40s %-14s %s\n\n", 'operator', 'abbr. (-s)', 'url (-u)' ); + printf( + "%-40s %-14s %-15s %s\n\n", + 'operator', 'abbr. (-s)', 'languages (-l)', + 'time zone' + ); for my $service ( Travel::Status::DE::HAFAS::get_services() ) { - printf( "%-40s %-14s %s\n", @{$service}{qw(name shortname url)} ); + printf( + "%-40s %-14s %-15s %s\n", + @{$service}{qw(name shortname)}, + join( q{ }, @{ $service->{languages} // [] } ), + $service->{time_zone} // q{}, + ); } exit 0; } parse_mot_options(); -my $status = Travel::Status::DE::HAFAS->new( - date => $date, - language => $language, +my %opt = ( excluded_mots => \@excluded_mots, exclusive_mots => \@exclusive_mots, station => shift || show_help(1), - time => $time, - mode => $arrivals ? 'arr' : 'dep', + arrivals => $arrivals, developer_mode => $developer_mode, service => $service, - url => $hafas_url, + language => $language, ); +if ( $opt{station} =~ m{ ^ (?<lat> [0-9.]+ ) : (?<lon> [0-9].+ ) $ }x ) { + $opt{geoSearch} = { + lat => $+{lat}, + lon => $+{lon}, + }; + delete $opt{station}; +} +elsif ( $opt{station} =~ m{ ^ [?] (?<query> .*) $ }x ) { + $opt{locationSearch} = $+{query}; + delete $opt{station}; +} +elsif ( $opt{station} =~ m{[|]} ) { + $opt{journey} = { id => $opt{station} }; + delete $opt{station}; +} +elsif ( $opt{station} =~ m{ ^ [!] (?<query> .*) $ }x ) { + $opt{journeyMatch} = $+{query}; + delete $opt{station}; +} + +if ( $date or $time ) { + my $desc = Travel::Status::DE::HAFAS::get_service($service) // {}; + my $dt + = DateTime->now( time_zone => $desc->{time_zone} // 'Europe/Berlin' ); + if ($date) { + if ( $date + =~ m{ ^ (?<day> \d{1,2} ) [.] (?<month> \d{1,2} ) [.] (?<year> \d{4})? $ }x + ) + { + $dt->set( + day => $+{day}, + month => $+{month} + ); + if ( $+{year} ) { + $dt->set( year => $+{year} ); + } + } + else { + say "--date must be specified as DD.MM.[YYYY]"; + exit 1; + } + } + if ($time) { + if ( $time =~ m{ ^ (?<hour> \d{1,2} ) : (?<minute> \d{1,2} ) $ }x ) { + $dt->set( + hour => $+{hour}, + minute => $+{minute}, + second => 0, + ); + } + else { + say "--time must be specified as HH:MM"; + exit 1; + } + } + $opt{datetime} = $dt; +} + +my $status = Travel::Status::DE::HAFAS->new(%opt); + sub show_help { my ($code) = @_; @@ -82,27 +157,29 @@ sub show_version { exit 0; } -sub parse_mot_options { +sub spacer { + my ($len) = @_; + return ( $len % 2 ? q { } : q{} ) . ( q{ ·} x ( $len / 2 ) ); +} +sub parse_mot_options { my $default_yes = 1; - if ( $types and $hafas_url ) { - say STDERR 'The options -u and -m cannot be combined. Discarding -m'; - return; - } - for my $type ( split( qr{,}, $types ) ) { if ( $type eq 'help' or $type eq 'list' or $type eq q{?} ) { - if ( not $hafas_url ) { - $service //= 'DB'; - } + $service //= 'DB'; my $desc = Travel::Status::DE::HAFAS::get_service($service); if ($desc) { - my @mots = @{ $desc->{productbits} }; - @mots = grep { $_ ne 'x' } @mots; - @mots = uniq @mots; - @mots = sort @mots; - say join( "\n", @mots ); + for my $mot ( @{ $desc->{productbits} } ) { + if ( ref($mot) eq 'ARRAY' ) { + if ( $mot->[0] ne '_' ) { + printf( "%-10s %s\n", @{$mot} ); + } + } + elsif ( $mot ne '_' ) { + say $mot; + } + } exit 0; } else { @@ -127,6 +204,37 @@ sub show_similar_stops { for my $c (@candidates) { printf( "%s (%s)\n", $c->{name}, $c->{id} ); } + return; + } + my $hafas = Travel::Status::DE::HAFAS->new( + locationSearch => $opt{station}, + developer_mode => $developer_mode, + service => $service, + language => $language, + ); + if ( $hafas->results ) { + say 'You might want to try one of the following stops:'; + for my $r ( $hafas->results ) { + printf( "%s (%s)\n", $r->name, $r->eva ); + } + } + return; +} + +sub journey_has_via { + my ( $journey, $via ) = @_; + + if ( $via =~ m{ ^ [0-9,]+ $ }x ) { + for my $eva ( split( qr{,}, $via ) ) { + if ( my $stop = first { $_->loc->eva == $eva } $journey->route ) { + return $stop; + } + } + return; + } + + if ( my $stop = first { $_->loc->name =~ m{$via}io } $journey->route ) { + return $stop; } return; } @@ -146,11 +254,18 @@ sub display_result { for my $line (@lines) { - my $d = $line->[6]; - if ( $d->messages ) { - print "\n"; - for my $msg ( $d->messages ) { - printf( "# %s\n", $msg ); + my $d = $line->[6]; + my $first_message = 1; + for my $msg ( $d->messages ) { + if ( $msg->ref_count == 0 ) { + if ($first_message) { + print "\n"; + $first_message = 0; + } + if ( $msg->short ) { + printf( "# %s\n", $msg->short ); + } + printf( "# %s\n", $msg->text ); } } @@ -167,38 +282,388 @@ sub display_result { return; } +sub display_occupancy { + my ($occupancy) = @_; + + if ( not $occupancy ) { + return q{ }; + } + if ( $occupancy == 1 ) { + return q{.}; + } + if ( $occupancy == 2 ) { + return q{o}; + } + if ( $occupancy == 3 ) { + return q{*}; + } + if ( $occupancy == 4 ) { + return q{!}; + } + return q{?}; +} + +sub format_delay { + my ( $delay, $len ) = @_; + if ( $delay and $len ) { + return sprintf( "(%+${len}d)", $delay ); + } + return q{}; +} + if ( my $err = $status->errstr ) { say STDERR "Request error: ${err}"; - if ( $status->errcode and $status->errcode eq 'H730' ) { + if ( $status->errcode + and ( $status->errcode eq 'H730' or $status->errcode eq 'LOCATION' ) + and not $raw_json_output ) + { show_similar_stops(); } exit 2; } -for my $d ( $status->results() ) { - - if ( $ignore_late and $d->delay ) { - next; - } - - push( - @output, - [ - $d->time, - $d->is_cancelled - ? 'CANCELED' - : ( $d->delay ? sprintf( '%+d', $d->delay ) : q{} ), - $d->train, - $d->route_end, - ( $d->platform // q{} ) . ( $d->is_changed_platform ? ' !' : q{} ), - $d->info, - $d - ] - ); +if ($raw_json_output) { + say JSON->new->convert_blessed->encode( $status->{raw_json} ); + exit 0; +} + +if ($json_output) { + if ( $opt{journey} ) { + say JSON->new->convert_blessed->encode( $status->result ); + } + else { + say JSON->new->convert_blessed->encode( [ $status->results ] ); + } + exit 0; +} + +if ( $opt{journeyMatch} ) { + if ( scalar $status->results == 1 ) { + my ($journey) = $status->results; + $opt{journey} = { id => $journey->id }; + delete $opt{journeyMatch}; + $status = Travel::Status::DE::HAFAS->new(%opt); + if ( my $err = $status->errstr ) { + say STDERR "Request error: ${err}"; + if ( + $status->errcode + and + ( $status->errcode eq 'H730' or $status->errcode eq 'LOCATION' ) + and not $raw_json_output + ) + { + show_similar_stops(); + } + exit 2; + } + } + else { + for my $result ( $status->results ) { + my $start = ( $result->route )[0]; + my $end = ( $result->route )[-1]; + say $result->id; + print $result->name; + if ( $result->number ) { + printf( " | Nr %s", $result->number ); + } + if ( $result->line_no ) { + printf( " | Linie %s", $result->line_no ); + } + say q{}; + printf( "%s ab %s\n", + $start->dep->strftime('%H:%M'), + $start->loc->name ); + printf( "%s an %s\n\n", + $end->arr->strftime('%H:%M'), + $end->loc->name ); + } + exit 0; + } +} + +if ( $opt{geoSearch} ) { + for my $result ( $status->results ) { + printf( + "%5.1f km %8d %s\n", + $result->distance_m * 1e-3, + $result->eva, $result->name + ); + } + exit 0; +} +elsif ( $opt{locationSearch} ) { + for my $result ( $status->results ) { + printf( "%8d %s\n", $result->eva, $result->name ); + } + exit 0; +} +elsif ( $opt{journey} ) { + my $result = $status->result; + my @prods; + my @directions; + my $prev_prod = 0; + + printf( "%s → %s", $result->name, $result->route_end ); + if ( $result->number ) { + printf( " / Nr %s", $result->number ); + } + if ( $result->line_no ) { + printf( " / Linie %s", $result->line_no ); + } + printf( "\nFahrt %s am %s\n", + $result->id, ( $result->route )[0]->sched_dep->strftime('%d.%m.%Y') ); + + my $delay_len = 0; + my $delay_fmt = 0; + my $occupancy_len = 0; + my $stop_len = 0; + for my $stop ( $result->route ) { + if ( $stop->delay ) { + $delay_len = max( $delay_len, length( $stop->delay ) + 1 ); + } + if ( $stop->load and ( $stop->load->{FIRST} or $stop->load->{SECOND} ) ) + { + $occupancy_len = 2; + } + if ( length( $stop->loc->name ) > $stop_len ) { + $stop_len = length( $stop->loc->name ); + } + my $prod = $stop->prod_dep // $stop->prod_arr; + if ( $prod and $prod != $prev_prod ) { + push( @prods, $prod ); + $prev_prod = $prod; + } + if ( $stop->direction ) { + push( @directions, $stop->direction ); + } + } + if ($delay_len) { + $delay_fmt = $delay_len + 3; + } + + if ( $result->operators ) { + printf( "Betrieb: %s\n", join( q{, }, $result->operators ) ); + } + + $prev_prod = 0; + + my $desc = Travel::Status::DE::HAFAS::get_service($service) // {}; + my $now + = DateTime->now( time_zone => $desc->{time_zone} // 'Europe/Berlin' ); + my $mark_stop = 0; + for my $i ( reverse 1 .. scalar $result->route ) { + my $stop = ( $result->route )[ $i - 1 ]; + if ( not $stop->dep_cancelled and $stop->dep and $now <= $stop->dep ) { + $mark_stop = $stop; + } + elsif ( not $stop->arr_cancelled and $stop->arr and $now <= $stop->arr ) + { + $mark_stop = $stop; + } + } + + my $message_id = 1; + + print "\n"; + for my $stop ( $result->route ) { + my $msg_line = q{}; + for my $message ( $stop->messages ) { + if ( $message->ref_count > 0 + and $message->code ne + 'text.journeystop.product.or.direction.changes.stop.message' + and $message->text ne 'Halt entfällt' ) + { + if ( not $message->{id} ) { + $message->{id} = $message_id++; + } + $msg_line .= sprintf( ' (%d)', $message->{id} ); + } + } + + my $prod_line = q{}; + if ( @prods > 1 ) { + my $prod = $stop->prod_dep // $stop->prod_arr; + if ( $prod and $prod != $prev_prod ) { + $prod_line + = sprintf( " %s (%s)", $prod->name, $prod->operator ); + $prev_prod = $prod; + } + } + + my $dir_line = q{}; + if ( @directions > 1 and $stop->direction ) { + $dir_line = ' → ' . $stop->direction; + } + + my $tz_line = q{}; + if ( $stop->tz_offset and ( $stop->arr or $stop->dep ) ) { + $tz_line = ( $prod_line or $dir_line ) ? q{ · } : q{ }; + $tz_line .= 'local '; + if ( $stop->arr ) { + $tz_line + .= $stop->arr->clone->add( minutes => $stop->tz_offset ) + ->strftime('%H:%M'); + } + if ( $stop->arr and $stop->dep ) { + $tz_line .= ' → '; + } + if ( $stop->dep ) { + $tz_line + .= $stop->dep->clone->add( minutes => $stop->tz_offset ) + ->strftime('%H:%M'); + } + $tz_line .= q{ }; + } + + printf( +"%s%5s %s %5s %-${delay_fmt}s%${occupancy_len}s%-${occupancy_len}s %s%s%s%s%s%s%s\n", + $stop == $mark_stop ? $output_bold : q{}, + $stop->arr_cancelled ? '--:--' + : ( $stop->arr ? $stop->arr->strftime('%H:%M') : q{} ), + ( $stop->arr and $stop->dep ) ? '→' : q{ }, + $stop->dep_cancelled ? '--:--' + : ( $stop->dep ? $stop->dep->strftime('%H:%M') : q{} ), + format_delay( $stop->delay, $delay_len ), + $stop->load->{FIRST} ? display_occupancy( $stop->load->{FIRST} ) + : q{}, + $stop->load->{SECOND} ? display_occupancy( $stop->load->{SECOND} ) + : q{}, + $stop->loc->name, + $stop == $mark_stop ? $output_reset : q{}, + ( $tz_line or $prod_line or $dir_line or $msg_line ) + ? spacer( $stop_len + 1 - length( $stop->loc->name ) ) + : q{}, + $prod_line, + $dir_line, + $tz_line, + $msg_line, + ); + } + + for my $msg ( $result->messages ) { + if ( $msg->code eq + 'text.journeystop.product.or.direction.changes.journey.message' ) + { + next; + } + say ''; + if ( $msg->short ) { + printf( "%s\n", $msg->short ); + } + printf( "%s\n", $msg->text ); + } + + for my $msg ( $status->messages ) { + if ( $msg->{id} ) { + say ''; + if ( $msg->short ) { + printf( "(%d) %s\n", $msg->{id}, $msg->short ); + } + printf( "(%d) %s\n", $msg->{id}, $msg->text ); + } + } + exit 0; +} + +my @results = map { $_->[1] } + sort { $a->[0] <=> $b->[0] } + map { [ $_->datetime->epoch, $_ ] } $status->results; + +if ($via) { + @results = grep { journey_has_via( $_, $via ) } @results; +} + +my $delay_len = 0; +my $occupancy_len = 0; +my $offset_len = 0; +for my $d (@results) { + if ( $d->delay ) { + $delay_len = max( $delay_len, length( $d->delay ) + 1 ); + } + if ( $d->load and ( $d->load->{FIRST} or $d->load->{SECOND} ) ) { + $occupancy_len = 2; + } + if ( $d->tz_offset ) { + $offset_len = 1; + } +} + +my $message_id = 1; +for my $m ( $status->messages ) { + if ( $m->ref_count > 0 ) { + $m->{id} = $message_id++; + } +} + +for my $d (@results) { + + my $info_line = q{}; + + for my $message ( $d->messages ) { + if ( $message->ref_count > 0 ) { + $message->{show} = 1; + $info_line = sprintf( '(%d) %s', $message->{id}, $info_line ); + } + } + + if ( $d->load ) { + $info_line + = display_occupancy( $d->load->{FIRST} ) + . display_occupancy( $d->load->{SECOND} ) . ' ' + . $info_line; + } + + if ($show_jid) { + $info_line = $d->id . ' ' . $info_line; + } + + my $entry = [ + ( $d->is_cancelled ? '--:--' : $d->datetime->strftime('%H:%M') ) + . ( $d->tz_offset ? q{*} : ( q{ } x $offset_len ) ), + $d->is_cancelled + ? q{} + : format_delay( $d->delay, $delay_len ), + $d->name, + $d->route_end, + ( $d->platform // q{} ) . ( $d->is_changed_platform ? ' !' : q{} ), + $info_line, + $d + ]; + + if ($via) { + my $stop = journey_has_via( $d, $via ); + + # HAFAS does not provide real-time data for route entries, so we have to guesstimate the arrival time + $entry->[0] .= ' → ' + . ( + $stop->arr_cancelled + ? '--:--' + : $stop->arr->clone->add( minutes => $d->delay // 0 ) + ->strftime('%H:%M') + ); + } + + push( @output, $entry, ); } display_result(@output); +if ($offset_len) { + printf( "\n* reported for %s; local time differs\n", + $status->get_active_service->{time_zone} // 'Europe/Berlin' ); +} + +for my $m ( $status->messages ) { + if ( $m->ref_count > 0 and $m->{show} ) { + if ( $m->short ) { + printf( "\n# (%d) %s\n# %s\n", $m->{id}, $m->short, $m->text ); + } + else { + printf( "\n# (%d) %s\n", $m->{id}, $m->text ); + } + } +} + __END__ =head1 NAME @@ -207,21 +672,70 @@ hafas-m - Interface to the DeutscheBahn/HAFAS online departure monitor =head1 SYNOPSIS -B<hafas-m> [B<-d> I<date>] [B<-t> I<time>] [B<-m> I<motlist>] -[B<-s> I<service> | B<-u> I<url>] I<station> +B<hafas-m> [B<-a>] [B<-d> I<date>] [B<-t> I<time>] [B<-m> I<motlist>] +[B<-v> I<via>] [B<-s> I<service>] [B<-l> I<language>] I<station> + +B<hafas-m> [B<-s> I<service>] B<?>I<query>|I<lat>B<:>I<lon> + +B<hafas-m> [B<-s> I<service>] [B<-l> I<language>] B<!>I<query>|I<journeyID> =head1 VERSION -version 2.03 +version 6.03 =head1 DESCRIPTION -hafas-m is an interface to HAFAS-based departure monitors, for instance the -one available at L<http://reiseauskunft.bahn.de/bin/bhftafel.exe/dn>. +hafas-m is an interface to HAFAS public transport services such as the one +operated by Deutsche Bahn. + +It has four operating modes that depend on the contents of its argument. + +=head2 Arrival/Departure Monitor (I<station>) + +Show departures (or arrivals) at I<station>, optionally filtered by mode of +transport. I<station> may be given as a station name or HAFAS station ID. +For each train, B<hafas-m> shows + +=over + +=item * estimated departure (or arrival) time, + +=item * delay, if known, + +=item * trip name, number, or line, -It requests all departures at I<station> (optionally filtered by date, time, -route and means of transport) and lists them on stdout, similar to the big -departure screens installed at most main stations. +=item * direction / destination, + +=item * platform, if known (B<!> indicates a platform change), and + +=item * expected occupancy of first and second class, if known. + +=back + +Times are always given in the selected service's default time zone (see +B<--list>). Times that differ from the queried stop's local time are marked +with C<< * >>. Occupancy indicators are, from least occupied to fully booked: +B<.> B<o> B<*> B<!>. + +=head2 Location Search (B<?>I<query>|I<lat>B<:>I<lon>) + +List stations that match I<query> or that are located in the vicinity of +I<lat>B<:>I<lon> geocoordinates with EVA ID and name. + +=head2 Trip Search (B<!>I<query>) + +Show trip details (see below) for the train number provided in I<query> +(e.g. "ICE 205" or "S 31111") if it resolves into a single journey ID. +Otherwise, list all journey IDs that match I<query>. + +=head2 Trip Details (I<journeyID>) + +List intermediate stops of I<journeyID> with arrival/departure time, delay (if +available), occupancy (if available), and stop name. Also includes +line/journey, operator, and heading information. + +Times are reported in the selected HAFAS service's default time zone (typically +Europe/Berlin). If a stop's local time differs, it is also provided. =head1 OPTIONS @@ -233,22 +747,31 @@ Show arrivals instead of departures, including trains ending at the specified station. Note that this causes the output to display the start instead of the end station. -=item B<-d>, B<--date> I<dd>.I<mm>.I<yyyy> +=item B<-d>, B<--date> I<dd>.I<mm>.[I<yyyy>] Date to list departures for. Default: today. -=item B<-l>, B<--lang> B<d>|B<e>|B<i>|B<n> +=item B<-j>, B<--with-jid> -Set language used for additional information. Supports B<d>eutsch (default), -B<e>nglish, B<i>talian and dutch (B<n>), depending on the used service. +Show journey IDs in arrival/departure board. These can be used to obtain +details on individual journeys with subsequent B<hafas-m> invocations. -=item B<-L>, B<--ignore-late> +=item B<--json> -Do not display delayed trains. +Print result(s) as JSON and exit. This is a dump of internal data structures +and not guaranteed to remain stable between minor versions. Please use the +Travel::Status::DE::HAFAS(3pm) module if you need a proper API. + +=item B<-l>, B<--language> I<language> + +Request free-text messages to be provided in I<language>. +See B<--list> for a list of languages supported by individual HAFAS instances. +Note that requesting an invalid/unsupported language may lead to garbage output. =item B<--list> -List known HAFAS installations. See also B<--service> and B<--url>. +List known HAFAS instances and exit. Use B<-s>|B<--service> to select a +service from this list for a HAFAS request. =item B<-m>, B<--mot> I<motlist> @@ -262,6 +785,11 @@ To show them exclusively, set I<motlist> to I<mot1>,I<mot2>,... The I<mot> types depend on the used service. Use C<< -m help >> to list them. +=item B<--raw-json> + +Print unprocessed HAFAS response as JSON and exit. +Useful for debugging and development purposes. + =item B<-s>, B<--service> I<service> Request arrivals/departures using the API provided by I<service>, defaults @@ -269,26 +797,25 @@ to DB (Deutsche Bahn). See B<--list> for a list of known services. =item B<-t>, B<--time> I<hh>:I<mm> -Time to list departures for. Default: now. - -=item B<-u>, B<--url> I<url> +Time to list departures for. Must be specified in the selected HAFAS +service's default time zone, see B<--list>. Default: now. -Request arrivals/departures using the API entry point at I<url>, defaults to -C<< http://reiseauskunft.bahn.de/bin/bhftafel.exe >>. Note that the language -and output selection suffix (e.g. "/dn") must not be included here. +=item B<-v>, B<--via> I<stopname>|I<eva1>,I<eva2>,... -Again, see B<--list> for a list of known URLs. Unknown URLs are also -supported, though note that B<--mot> will not work when using this opton. +Only show departures that pass by I<stopname> (or arivals that have passed by +I<stopname>). If I<stopname> is given as a list of numeric EVA IDs, only +arrivals/departures with an exact EVA ID match are shown. Otherwise I<stopname> +is treated as a regular expression and matched against stop names. =item B<-V>, B<--version> -Show version information. +Show version information and exit. =back =head1 EXIT STATUS -Zero unless things went wrong. +0 upon success, 1 upon internal error, 2 upon backend error. =head1 CONFIGURATION @@ -302,17 +829,23 @@ None. =item * LWP::UserAgent(3pm) -=item * XML::LibXML(3pm) - =back =head1 BUGS AND LIMITATIONS -The non-default services (anything other than DB) are not well tested. +=over + +=item * The non-default services (anything other than DB) are not well-tested. + +=item * HAFAS does not provide real-time data for routes of stationboard +entries. Hence, B<--via> estimates the arrival time from scheduled +departure and departure delay + +=back =head1 AUTHOR -Copyright (C) 2015-2017 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> +Copyright (C) 2015-2023 by Birte Kristina Friesel E<lt>derf@finalrewind.orgE<gt> =head1 LICENSE |