show current/next stop while checked in
This commit is contained in:
parent
155f9f39cc
commit
b1591eed54
4 changed files with 360 additions and 4 deletions
262
lib/Travelynx.pm
262
lib/Travelynx.pm
|
@ -2,10 +2,12 @@ package Travelynx;
|
||||||
use Mojo::Base 'Mojolicious';
|
use Mojo::Base 'Mojolicious';
|
||||||
|
|
||||||
use Mojo::Pg;
|
use Mojo::Pg;
|
||||||
|
use Mojo::Promise;
|
||||||
use Mojolicious::Plugin::Authentication;
|
use Mojolicious::Plugin::Authentication;
|
||||||
use Cache::File;
|
use Cache::File;
|
||||||
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
|
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use DateTime::Format::Strptime;
|
||||||
use Encode qw(decode encode);
|
use Encode qw(decode encode);
|
||||||
use Geo::Distance;
|
use Geo::Distance;
|
||||||
use JSON;
|
use JSON;
|
||||||
|
@ -14,6 +16,7 @@ use List::MoreUtils qw(after_incl before_incl);
|
||||||
use Travel::Status::DE::IRIS;
|
use Travel::Status::DE::IRIS;
|
||||||
use Travel::Status::DE::IRIS::Stations;
|
use Travel::Status::DE::IRIS::Stations;
|
||||||
use Travelynx::Helper::Sendmail;
|
use Travelynx::Helper::Sendmail;
|
||||||
|
use XML::LibXML;
|
||||||
|
|
||||||
sub check_password {
|
sub check_password {
|
||||||
my ( $password, $hash ) = @_;
|
my ( $password, $hash ) = @_;
|
||||||
|
@ -395,6 +398,8 @@ sub startup {
|
||||||
"Checkin($uid): INSERT failed: $@");
|
"Checkin($uid): INSERT failed: $@");
|
||||||
return ( undef, 'INSERT failed: ' . $@ );
|
return ( undef, 'INSERT failed: ' . $@ );
|
||||||
}
|
}
|
||||||
|
$self->add_route_timestamps( $self->current_user->{id},
|
||||||
|
$train );
|
||||||
$self->run_hook( $self->current_user->{id}, 'checkin' );
|
$self->run_hook( $self->current_user->{id}, 'checkin' );
|
||||||
return ( $train, undef );
|
return ( $train, undef );
|
||||||
}
|
}
|
||||||
|
@ -630,6 +635,7 @@ sub startup {
|
||||||
return ( 0, undef );
|
return ( 0, undef );
|
||||||
}
|
}
|
||||||
$self->run_hook( $uid, 'update' );
|
$self->run_hook( $uid, 'update' );
|
||||||
|
$self->add_route_timestamps( $self->current_user->{id}, $train );
|
||||||
return ( 1, undef );
|
return ( 1, undef );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1489,6 +1495,223 @@ sub startup {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$self->helper(
|
||||||
|
'get_hafas_json_p' => sub {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
|
||||||
|
my $cache = $self->app->cache_iris_main;
|
||||||
|
my $promise = Mojo::Promise->new;
|
||||||
|
|
||||||
|
if ( my $content = $cache->thaw($url) ) {
|
||||||
|
$promise->resolve($content);
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->ua->request_timeout(5)->get_p($url)->then(
|
||||||
|
sub {
|
||||||
|
my ($tx) = @_;
|
||||||
|
my $body = decode( 'ISO-8859-15', $tx->res->body );
|
||||||
|
|
||||||
|
$body =~ s{^TSLs[.]sls = }{};
|
||||||
|
$body =~ s{;$}{};
|
||||||
|
$body =~ s{(}{(}g;
|
||||||
|
$body =~ s{)}{)}g;
|
||||||
|
my $json = JSON->new->decode($body);
|
||||||
|
$cache->freeze( $url, $json );
|
||||||
|
$promise->resolve($json);
|
||||||
|
}
|
||||||
|
)->catch(
|
||||||
|
sub {
|
||||||
|
my ($err) = @_;
|
||||||
|
$self->app->log->warning("get($url): $err");
|
||||||
|
$promise->reject($err);
|
||||||
|
}
|
||||||
|
)->wait;
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$self->helper(
|
||||||
|
'get_hafas_xml_p' => sub {
|
||||||
|
my ( $self, $url ) = @_;
|
||||||
|
|
||||||
|
my $cache = $self->app->cache_iris_rt;
|
||||||
|
my $promise = Mojo::Promise->new;
|
||||||
|
|
||||||
|
if ( my $content = $cache->thaw($url) ) {
|
||||||
|
$promise->resolve($content);
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->ua->request_timeout(5)->get_p($url)->then(
|
||||||
|
sub {
|
||||||
|
my ($tx) = @_;
|
||||||
|
my $body = decode( 'ISO-8859-15', $tx->res->body );
|
||||||
|
my $tree;
|
||||||
|
|
||||||
|
my $traininfo = {
|
||||||
|
station => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
# <SDay text="... > ..."> is invalid HTML, but present in
|
||||||
|
# regardless. As it is the last tag, we just throw it away.
|
||||||
|
$body =~ s{<SDay .*}{</Journey>}s;
|
||||||
|
eval { $tree = XML::LibXML->load_xml( string => $body ) };
|
||||||
|
if ($@) {
|
||||||
|
$self->app->log->warning("load_xml($url): $@");
|
||||||
|
$cache->freeze( $url, $traininfo );
|
||||||
|
$promise->resolve($traininfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $station ( $tree->findnodes('/Journey/St') ) {
|
||||||
|
my $name = $station->getAttribute('name');
|
||||||
|
my $adelay = $station->getAttribute('adelay');
|
||||||
|
my $ddelay = $station->getAttribute('ddelay');
|
||||||
|
$traininfo->{station}{$name} = {
|
||||||
|
adelay => $adelay,
|
||||||
|
ddelay => $ddelay,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache->freeze( $url, $traininfo );
|
||||||
|
$promise->resolve($traininfo);
|
||||||
|
}
|
||||||
|
)->catch(
|
||||||
|
sub {
|
||||||
|
my ($err) = @_;
|
||||||
|
$self->app->log->warning("get($url): $err");
|
||||||
|
$promise->reject($err);
|
||||||
|
}
|
||||||
|
)->wait;
|
||||||
|
return $promise;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$self->helper(
|
||||||
|
'add_route_timestamps' => sub {
|
||||||
|
my ( $self, $uid, $train ) = @_;
|
||||||
|
|
||||||
|
$uid //= $self->current_user->{id};
|
||||||
|
|
||||||
|
my $db = $self->pg->db;
|
||||||
|
|
||||||
|
my $journey
|
||||||
|
= $db->select( 'in_transit', ['route'], { user_id => $uid } )
|
||||||
|
->expand->hash;
|
||||||
|
|
||||||
|
if ( not $journey ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $route = $journey->{route};
|
||||||
|
|
||||||
|
my $base
|
||||||
|
= 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json.vs_hap&start=yes&rt=1';
|
||||||
|
my $date_yy = $train->start->strftime('%d.%m.%y');
|
||||||
|
my $date_yyyy = $train->start->strftime('%d.%m.%Y');
|
||||||
|
my $train_no = $train->type . ' ' . $train->train_no;
|
||||||
|
|
||||||
|
$self->app->log->debug("add_route_timestamps");
|
||||||
|
|
||||||
|
my ( $trainlink, $route_data );
|
||||||
|
|
||||||
|
$self->get_hafas_json_p(
|
||||||
|
"${base}&date=${date_yy}&trainname=${train_no}")->then(
|
||||||
|
sub {
|
||||||
|
my ($trainsearch) = @_;
|
||||||
|
|
||||||
|
# Fallback: Take first result
|
||||||
|
$trainlink = $trainsearch->{suggestions}[0]{trainLink};
|
||||||
|
|
||||||
|
# Try finding a result for the current date
|
||||||
|
for
|
||||||
|
my $suggestion ( @{ $trainsearch->{suggestions} // [] } )
|
||||||
|
{
|
||||||
|
|
||||||
|
# Drunken API, sail with care. Both date formats are used interchangeably
|
||||||
|
if ( $suggestion->{depDate} eq $date_yy
|
||||||
|
or $suggestion->{depDate} eq $date_yyyy )
|
||||||
|
{
|
||||||
|
$trainlink = $suggestion->{trainLink};
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( not $trainlink ) {
|
||||||
|
$self->app->log->debug("trainlink not found");
|
||||||
|
return Mojo::Promise->reject("trainlink not found");
|
||||||
|
}
|
||||||
|
my $base2
|
||||||
|
= 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
|
||||||
|
return $self->get_hafas_json_p(
|
||||||
|
"${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_json.vs_hap"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)->then(
|
||||||
|
sub {
|
||||||
|
my ($traininfo) = @_;
|
||||||
|
|
||||||
|
if ( not $traininfo or $traininfo->{error} ) {
|
||||||
|
$self->app->log->debug("traininfo error");
|
||||||
|
return Mojo::Promise->reject("traininfo error");
|
||||||
|
}
|
||||||
|
my $routeinfo
|
||||||
|
= $traininfo->{suggestions}[0]{locations};
|
||||||
|
|
||||||
|
my $strp = DateTime::Format::Strptime->new(
|
||||||
|
pattern => '%d.%m.%y %H:%M',
|
||||||
|
time_zone => 'Europe/Berlin',
|
||||||
|
);
|
||||||
|
|
||||||
|
$route_data = {};
|
||||||
|
|
||||||
|
for my $station ( @{$routeinfo} ) {
|
||||||
|
my $arr
|
||||||
|
= $strp->parse_datetime(
|
||||||
|
$station->{arrDate} . ' ' . $station->{arrTime} );
|
||||||
|
my $dep
|
||||||
|
= $strp->parse_datetime(
|
||||||
|
$station->{depDate} . ' ' . $station->{depTime} );
|
||||||
|
$route_data->{ $station->{name} } = {
|
||||||
|
sched_arr => $arr ? $arr->epoch : 0,
|
||||||
|
sched_dep => $dep ? $dep->epoch : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $base2
|
||||||
|
= 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
|
||||||
|
return $self->get_hafas_xml_p(
|
||||||
|
"${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)->then(
|
||||||
|
sub {
|
||||||
|
my ($traininfo2) = @_;
|
||||||
|
|
||||||
|
for my $station ( keys %{$route_data} ) {
|
||||||
|
for my $key (
|
||||||
|
keys %{ $traininfo2->{station}{$station} // {} } )
|
||||||
|
{
|
||||||
|
$route_data->{$station}{$key}
|
||||||
|
= $traininfo2->{station}{$station}{$key};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $station ( @{$route} ) {
|
||||||
|
$station->[1]
|
||||||
|
= $route_data->{ $station->[0] };
|
||||||
|
}
|
||||||
|
$db->update(
|
||||||
|
'in_transit',
|
||||||
|
{ route => JSON->new->encode($route) },
|
||||||
|
{ user_id => $uid }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)->wait;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$self->helper(
|
$self->helper(
|
||||||
'get_oldest_journey_ts' => sub {
|
'get_oldest_journey_ts' => sub {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
@ -1828,6 +2051,7 @@ sub startup {
|
||||||
|
|
||||||
my $db = $self->pg->db;
|
my $db = $self->pg->db;
|
||||||
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
|
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
|
||||||
|
my $epoch = $now->epoch;
|
||||||
|
|
||||||
my $in_transit
|
my $in_transit
|
||||||
= $db->select( 'in_transit_str', '*', { user_id => $uid } )
|
= $db->select( 'in_transit_str', '*', { user_id => $uid } )
|
||||||
|
@ -1883,12 +2107,45 @@ sub startup {
|
||||||
}
|
}
|
||||||
$ret->{messages} = [ reverse @parsed_messages ];
|
$ret->{messages} = [ reverse @parsed_messages ];
|
||||||
|
|
||||||
|
for my $station (@route_after) {
|
||||||
|
if ( @{$station} > 1 ) {
|
||||||
|
my $times = $station->[1];
|
||||||
|
if ( $times->{sched_arr} ) {
|
||||||
|
$times->{sched_arr}
|
||||||
|
= epoch_to_dt( $times->{sched_arr} );
|
||||||
|
$times->{rt_arr} = $times->{sched_arr}->clone;
|
||||||
|
if ( $times->{adelay}
|
||||||
|
and $times->{adelay} =~ m{^\d+$} )
|
||||||
|
{
|
||||||
|
$times->{rt_arr}
|
||||||
|
->add( minutes => $times->{adelay} );
|
||||||
|
}
|
||||||
|
$times->{rt_arr_countdown}
|
||||||
|
= $times->{rt_arr}->epoch - $epoch;
|
||||||
|
}
|
||||||
|
if ( $times->{sched_dep} ) {
|
||||||
|
$times->{sched_dep}
|
||||||
|
= epoch_to_dt( $times->{sched_dep} );
|
||||||
|
$times->{rt_dep} = $times->{sched_dep}->clone;
|
||||||
|
if ( $times->{ddelay}
|
||||||
|
and $times->{ddelay} =~ m{^\d+$} )
|
||||||
|
{
|
||||||
|
$times->{rt_dep}
|
||||||
|
->add( minutes => $times->{ddelay} );
|
||||||
|
}
|
||||||
|
$times->{rt_dep_countdown}
|
||||||
|
= $times->{rt_dep}->epoch - $epoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$ret->{departure_countdown}
|
$ret->{departure_countdown}
|
||||||
= $ret->{real_departure}->epoch - $now->epoch;
|
= $ret->{real_departure}->epoch - $now->epoch;
|
||||||
if ( $in_transit->{real_arr_ts} ) {
|
if ( $in_transit->{real_arr_ts} ) {
|
||||||
$ret->{arrival_countdown}
|
$ret->{arrival_countdown}
|
||||||
= $ret->{real_arrival}->epoch - $now->epoch;
|
= $ret->{real_arrival}->epoch - $now->epoch;
|
||||||
$ret->{journey_duration} = $ret->{real_arrival}->epoch
|
$ret->{journey_duration}
|
||||||
|
= $ret->{real_arrival}->epoch
|
||||||
- $ret->{real_departure}->epoch;
|
- $ret->{real_departure}->epoch;
|
||||||
$ret->{journey_completion}
|
$ret->{journey_completion}
|
||||||
= $ret->{journey_duration}
|
= $ret->{journey_duration}
|
||||||
|
@ -2116,7 +2373,8 @@ sub startup {
|
||||||
and $next_departure->epoch - $journey->{rt_arrival}->epoch
|
and $next_departure->epoch - $journey->{rt_arrival}->epoch
|
||||||
< ( 60 * 60 ) )
|
< ( 60 * 60 ) )
|
||||||
{
|
{
|
||||||
if ( $next_departure->epoch - $journey->{rt_arrival}->epoch
|
if (
|
||||||
|
$next_departure->epoch - $journey->{rt_arrival}->epoch
|
||||||
< 0 )
|
< 0 )
|
||||||
{
|
{
|
||||||
push( @inconsistencies,
|
push( @inconsistencies,
|
||||||
|
|
|
@ -60,6 +60,7 @@ sub run {
|
||||||
},
|
},
|
||||||
{ user_id => $uid }
|
{ user_id => $uid }
|
||||||
);
|
);
|
||||||
|
$self->app->add_route_timestamps( $uid, $train );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if ($@) {
|
if ($@) {
|
||||||
|
@ -112,6 +113,7 @@ sub run {
|
||||||
},
|
},
|
||||||
{ user_id => $uid }
|
{ user_id => $uid }
|
||||||
);
|
);
|
||||||
|
$self->app->add_route_timestamps( $uid, $train );
|
||||||
}
|
}
|
||||||
elsif ( $entry->{real_arr_ts} ) {
|
elsif ( $entry->{real_arr_ts} ) {
|
||||||
$self->app->log->debug(" - checking out");
|
$self->app->log->debug(" - checking out");
|
||||||
|
|
|
@ -62,8 +62,56 @@
|
||||||
noch nicht bekannt
|
noch nicht bekannt
|
||||||
% }
|
% }
|
||||||
</div>
|
</div>
|
||||||
|
<div class="center-align hide-on-small-only">
|
||||||
|
% for my $station (@{$journey->{route_after}}) {
|
||||||
|
% if ($station->[0] eq $journey->{arr_name}) {
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||||
|
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||||
|
<%= $station->[0] %><br/>
|
||||||
|
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||||
|
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
<div style="clear: both;">
|
<div style="clear: both;">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="hide-on-med-and-up" style="margin-top: 2ex;">
|
||||||
|
% for my $station (@{$journey->{route_after}}) {
|
||||||
|
% if ($station->[0] eq $journey->{arr_name}) {
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||||
|
Nächster Halt:<br/>
|
||||||
|
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||||
|
Aktueller Halt:<br/>
|
||||||
|
<%= $station->[0] %><br/>
|
||||||
|
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||||
|
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
</p>
|
</p>
|
||||||
% }
|
% }
|
||||||
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {
|
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {
|
||||||
|
|
|
@ -70,8 +70,56 @@
|
||||||
noch nicht bekannt
|
noch nicht bekannt
|
||||||
% }
|
% }
|
||||||
</div>
|
</div>
|
||||||
|
<div class="center-align hide-on-small-only">
|
||||||
|
% for my $station (@{$journey->{route_after}}) {
|
||||||
|
% if ($station->[0] eq $journey->{arr_name}) {
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||||
|
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||||
|
<%= $station->[0] %><br/>
|
||||||
|
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||||
|
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
<div style="clear: both;">
|
<div style="clear: both;">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="hide-on-med-and-up" style="margin-top: 2ex;">
|
||||||
|
% for my $station (@{$journey->{route_after}}) {
|
||||||
|
% if ($station->[0] eq $journey->{arr_name}) {
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||||
|
Nächster Halt:<br/>
|
||||||
|
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||||
|
Aktueller Halt:<br/>
|
||||||
|
<%= $station->[0] %><br/>
|
||||||
|
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||||
|
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||||
|
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||||
|
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||||
|
% }
|
||||||
|
% last;
|
||||||
|
% }
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
</p>
|
</p>
|
||||||
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {
|
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {
|
||||||
<p style="margin-bottom: 2ex;">
|
<p style="margin-bottom: 2ex;">
|
||||||
|
|
Loading…
Reference in a new issue