TT#34800 inflate/deflate DateTime for timestamps

inflate/deflate DateTime for simple (complete) timestamps
considering the correct timezone at the latest possible point
in the action chains: on form-level as well as in the DataTables json output.

Change-Id: Icfe94d6d5a9ac02d9fca0f4b8d048d86cf66cffa
changes/04/19504/4
Gerhard Jungwirth 8 years ago
parent 99c856fd31
commit 001474fd7f

@ -21,7 +21,7 @@ sub api_description {
sub get_list{
my ($self, $c) = @_;
return NGCP::Panel::Utils::Security::list_banned_users($c, data_for_json => 1);
return NGCP::Panel::Utils::Security::list_banned_users($c);
}
1;

@ -313,12 +313,20 @@ sub number_edit :Chained('number_base') :PathPart('edit') {
my $schema = $c->model('DB');
$schema->txn_do(sub {
if(length $form->values->{start}) {
$form->values->{start} .= 'T00:00:00';
$form->values->{start} .= ' 00:00:00';
my $start_dt = NGCP::Panel::Utils::DateTime::from_forminput_string(
$form->values->{start},
$c->session->{user_tz});
$form->values->{start} = NGCP::Panel::Utils::DateTime::to_local_string($start_dt);
} else {
$form->values->{start} = undef;
}
if(length $form->values->{end}) {
$form->values->{end} .= 'T23:59:59';
$form->values->{end} .= ' 23:59:59';
my $end_dt = NGCP::Panel::Utils::DateTime::from_forminput_string(
$form->values->{end},
$c->session->{user_tz});
$form->values->{end} = NGCP::Panel::Utils::DateTime::to_local_string($end_dt);
} else {
$form->values->{end} = undef;
}

@ -63,6 +63,8 @@ sub index :Path Form {
if($res) {
# auth ok
$c->session->{user_tz} = undef; # reset to reload from db
$c->session->{user_tz_name} = undef; # reset to reload from db
my $target = $c->session->{'target'} || '/';
delete $c->session->{target};
$target =~ s!^https?://[^/]+/!/!;

@ -15,6 +15,8 @@ use Time::HiRes qw();
use DateTime::Format::RFC3339 qw();
use HTTP::Status qw(:constants);
use NGCP::Schema qw//;
#
# Sets the actions in this controller to be registered with no prefix
# so they function identically to actions created in MyApp.pm
@ -53,6 +55,36 @@ sub auto :Private {
$c->log->debug("lang set by browser or config: " . $c->language);
}
################################################### timezone retrieval
if ($c->user_exists) {
if ($c->session->{user_tz}) {
# nothing to do
} elsif ($c->user->roles eq 'admin') {
my $reseller_id = $c->user->reseller_id;
my $tz_row = $c->model('DB')->resultset('reseller_timezone')->find({reseller_id => $reseller_id});
_set_session_tz_from_row($c, $tz_row, 'admin', $reseller_id);
} elsif($c->user->roles eq 'reseller') {
my $reseller_id = $c->user->reseller_id;
my $tz_row = $c->model('DB')->resultset('reseller_timezone')->find({reseller_id => $reseller_id});
_set_session_tz_from_row($c, $tz_row, 'reseller', $reseller_id);
} elsif($c->user->roles eq 'subscriberadmin') {
my $contract_id = $c->user->account_id;
my $tz_row = $c->model('DB')->resultset('contract_timezone')->find({contract_id => $contract_id});
_set_session_tz_from_row($c, $tz_row, 'subscriberadmin', $contract_id);
} elsif($c->user->roles eq 'subscriber') {
my $uuid = $c->user->uuid;
my $tz_row = $c->model('DB')->resultset('voip_subscriber_timezone')->find({uuid => $uuid});
_set_session_tz_from_row($c, $tz_row, 'subscriber', $uuid);
} else {
# this shouldnt happen
}
$NGCP::Schema::CURRENT_USER_TZ = $c->session->{user_tz};
} else {
$NGCP::Schema::CURRENT_USER_TZ = undef;
}
###################################################
if (
__PACKAGE__ eq $c->controller->catalyst_component_name
or 'NGCP::Panel::Controller::Login' eq $c->controller->catalyst_component_name
@ -314,18 +346,6 @@ sub end :Private {
}
}
sub _prune_row {
my ($columns, %row) = @_;
while (my ($k,$v) = each %row) {
unless (grep { $k eq $_ } @$columns) {
delete $row{$k};
next;
}
$row{$k} = $v->datetime if blessed($v) && $v->isa('DateTime');
}
return { %row };
}
sub error_page :Private {
my ($self,$c) = @_;
$c->log->error( 'Failed to find path ' . $c->request->path );
@ -472,6 +492,20 @@ sub api_apply_fake_time :Private {
}
}
sub _set_session_tz_from_row {
my ($c, $tz_row, $role, $identifier) = @_;
my $tz_name = $tz_row ? $tz_row->name : undef;
$tz_name =~ s/^localtime$/local/ if $tz_name;
eval { $c->session->{user_tz} = DateTime::TimeZone->new( name => $tz_name ); };
if ($@) {
$c->log->warning("couldnt set timezone. error in creation probably caused by invalid timezone name. role $role ($identifier) to $tz_name");
} else {
$c->session->{user_tz_name} = $tz_name;
$c->log->debug("timezone set for $role ($identifier) to $tz_name");
}
}
1;

@ -1,17 +1,43 @@
package NGCP::Panel::Field::DateTime;
use HTML::FormHandler::Moose;
use Sipwise::Base;
use NGCP::Panel::Utils::DateTime qw//;
extends 'HTML::FormHandler::Field::Text';
has '+deflate_method' => ( default => sub { \&datetime_deflate } );
has '+inflate_method' => ( default => sub { \&datetime_inflate } );
sub datetime_deflate { # deflate: DateTime (in any tz) -> User representation (with correct tz)
my ( $self, $value ) = @_;
my $c = $self->form->ctx;
if(blessed($value) && $value->isa('DateTime')) {
if($c && $c->session->{user_tz}) {
$value->set_time_zone('local'); # starting point for conversion
$value->set_time_zone($c->session->{user_tz}); # desired time zone
}
return $value->ymd('-') . ' ' . $value->hms(':');
} else {
return $value;
}
}
sub datetime_inflate { # inflate: User entry -> DateTime -> Plaintext but converted
my ( $self, $value ) = @_;
my $c = $self->form->ctx;
my $tz;
if($c && $c->session->{user_tz}) {
$tz = $c->session->{user_tz};
}
my $date = NGCP::Panel::Utils::DateTime::from_forminput_string($value, $tz);
$date->set_time_zone('local'); # convert to local
sub datetime_deflate {
my ( $self, $value ) = @_;
if(blessed($value) && $value->isa('DateTime')) {
return $value->ymd('-') . ' ' . $value->hms(':');
} else {
return $value;
}
return $date->ymd('-') . ' ' . $date->hms(':');
}
no Moose;

@ -3,10 +3,10 @@ use strict;
use warnings;
use Sipwise::Base;
use NGCP::Panel::Utils::DateTime qw();
use NGCP::Panel::Utils::Generic qw(:all);
use List::Util qw/first/;
use Scalar::Util qw/blessed/;
use DateTime::Format::Strptime;
sub process {
my ($c, $rs, $cols, $row_func, $params) = @_;
@ -20,6 +20,8 @@ sub process {
my $aggregate_cols = [];
my $aggregations = {};
my $user_tz = $c->session->{user_tz};
# check if we need to join more tables
# TODO: can we nest it deeper than once level?
@ -116,15 +118,11 @@ sub process {
my $from_date_in = $c->request->params->{sSearch_0} // "";
my $to_date_in = $c->request->params->{sSearch_1} // "";
my($from_date,$to_date);
my $parser = DateTime::Format::Strptime->new(
#pattern => '%Y-%m-%d %H:%M',
pattern => '%Y-%m-%d',
);
if($from_date_in) {
$from_date = $parser->parse_datetime($from_date_in);
$from_date = NGCP::Panel::Utils::DateTime::from_forminput_string($from_date_in, $c->session->{user_tz});
}
if($to_date_in) {
$to_date = $parser->parse_datetime($to_date_in);
$to_date = NGCP::Panel::Utils::DateTime::from_forminput_string($to_date_in, $c->session->{user_tz});
}
foreach my $col(@{ $cols }) {
# avoid amigious column names if we have the same column in different joined tables
@ -192,7 +190,7 @@ sub process {
my $topId = $c->request->params->{iIdOnTop};
if(defined $topId) {
if(defined(my $row = $rs->find($topId))) {
push @{ $aaData }, _prune_row($cols, $row->get_inflated_columns);
push @{ $aaData }, _prune_row($user_tz, $cols, $row->get_inflated_columns);
if (defined $row_func) {
$aaData->[-1] = {%{$aaData->[-1]}, $row_func->($row)};
}
@ -247,7 +245,7 @@ sub process {
}
for my $row ($rs->all) {
push @{ $aaData }, _prune_row($cols, $row->get_inflated_columns);
push @{ $aaData }, _prune_row($user_tz, $cols, $row->get_inflated_columns);
if (defined $row_func) {
$aaData->[-1] = {%{$aaData->[-1]}, $row_func->($row)} ;
}
@ -277,13 +275,17 @@ sub set_columns {
}
sub _prune_row {
my ($columns, %row) = @_;
my ($user_tz, $columns, %row) = @_;
while (my ($k,$v) = each %row) {
unless (first { $_->{accessor} eq $k && $_->{title} } @{ $columns }) {
delete $row{$k};
next;
}
if(blessed($v) && $v->isa('DateTime')) {
if($user_tz) {
$v->set_time_zone('local'); # starting point for conversion
$v->set_time_zone($user_tz); # desired time zone
}
$row{$k} = $v->ymd('-') . ' ' . $v->hms(':');
$row{$k} .= '.'.$v->millisecond if $v->millisecond > 0.0;
}

@ -1,18 +1,19 @@
package NGCP::Panel::Utils::DateTime;
#use Sipwise::Base; seg fault when creating threads in test scripts
use strict;
use warnings;
use Time::HiRes; #prevent warning from Time::Warp
use Time::Warp qw();
#use Time::Fake; #load this before any use DateTime
use DateTime;
use DateTime::Format::ISO8601;
use DateTime::Format::Strptime;
use DateTime;
use POSIX qw(floor fmod);
use Readonly qw();
use Time::HiRes; #prevent warning from Time::Warp
use Time::Warp qw();
use constant RFC_1123_FORMAT_PATTERN => '%a, %d %b %Y %T %Z';
use constant TIMEZONE_MAP => { map { $_ => 1; } DateTime::TimeZone->all_names };
my $RFC_1123_FORMAT_PATTERN = '%a, %d %b %Y %T %Z';
my $TIMEZONE_MAP = { map { $_ => 1; } DateTime::TimeZone->all_names };
my $LOCAL_TZ = DateTime::TimeZone->new(name => 'local');
my $is_fake_time = 0;
@ -22,7 +23,7 @@ sub is_valid_timezone_name {
if ($all) {
return DateTime::TimeZone->is_valid_name($tz);
} else {
return 0 unless exists TIMEZONE_MAP->{$tz};
return 0 unless exists $TIMEZONE_MAP->{$tz};
return 1;
}
}
@ -30,11 +31,11 @@ sub is_valid_timezone_name {
sub current_local {
if ($is_fake_time) {
return DateTime->from_epoch(epoch => Time::Warp::time,
time_zone => DateTime::TimeZone->new(name => 'local')
time_zone => $LOCAL_TZ,
);
} else {
return DateTime->now(
time_zone => DateTime::TimeZone->new(name => 'local')
time_zone => $LOCAL_TZ,
);
}
}
@ -43,7 +44,7 @@ sub current_local_hires {
#If the epoch value is a floating-point value, it will be rounded to nearest microsecond.
return DateTime->from_epoch( epoch => Time::HiRes::time,
time_zone => DateTime::TimeZone->new(name => 'local')
time_zone => $LOCAL_TZ,
);
}
@ -51,7 +52,7 @@ sub current_local_hires {
sub set_local_tz {
my $dt = shift;
if (defined $dt && ref $dt eq 'DateTime' && !is_infinite($dt)) {
$dt->set_time_zone('local');
$dt->set_time_zone($LOCAL_TZ);
}
return $dt;
}
@ -150,17 +151,16 @@ sub last_day_of_month {
sub epoch_local {
my $epoch = shift;
return DateTime->from_epoch(
time_zone => DateTime::TimeZone->new(name => 'local'),
time_zone => $LOCAL_TZ,
epoch => $epoch,
);
}
sub epoch_tz {
my $epoch = shift;
my $tz = shift;
my ($epoch, $tz) = @_;
#if(!$tz || !DateTime::TimeZone->is_valid_name($tz)) {
if(not is_valid_timezone_name($tz,1)) {
$tz = DateTime::TimeZone->new(name => 'local');
$tz = $LOCAL_TZ;
}
return DateTime->from_epoch(
time_zone => $tz,
@ -186,12 +186,37 @@ sub from_string {
sub from_rfc1123_string {
my $s = shift;
my $strp = DateTime::Format::Strptime->new(pattern => RFC_1123_FORMAT_PATTERN,
my $strp = DateTime::Format::Strptime->new(pattern => $RFC_1123_FORMAT_PATTERN,
locale => 'en_US',
on_error => 'undef');
return $strp->parse_datetime($s);
}
# this shall give a little freedom in how datetime is entered
# this shall be allowed: Y-m-d H:M:S, Y-m-d H:M, Y-m-d
# it returns a DateTime object in floating (meaning local) timezone or the specified timezone
sub from_forminput_string {
my($string, $tz) = @_;
my $parser1 = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %H:%M:%S', $tz ? (time_zone => $tz) : (),
);
my $parser2 = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %H:%M', $tz ? (time_zone => $tz) : (),
);
my $parser3 = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d', $tz ? (time_zone => $tz) : (),
);
$string =~ s/^\s*(.*)\s*$/$1/; # remove whitespace around
my $dt = $parser1->parse_datetime($string);
unless ($dt) {
$dt = $parser2->parse_datetime($string);
}
unless ($dt) {
$dt = $parser3->parse_datetime($string);
}
return $dt;
}
sub new_local {
my %params;
@params{qw/year month day hour minute second nanosecond/} = @_;
@ -199,7 +224,7 @@ sub new_local {
!defined $params{$_} and delete $params{$_};
}
return DateTime->new(
time_zone => DateTime::TimeZone->new(name => 'local'),
time_zone => $LOCAL_TZ,
%params,
);
}
@ -227,12 +252,23 @@ sub to_string {
sub to_rfc1123_string {
my $dt = shift;
my $strp = DateTime::Format::Strptime->new(pattern => RFC_1123_FORMAT_PATTERN,
my $strp = DateTime::Format::Strptime->new(pattern => $RFC_1123_FORMAT_PATTERN,
locale => 'en_US',
on_error => 'undef');
return $strp->format_datetime($dt);
}
sub to_local_string {
my ($dt) = @_;
unless ('DateTime' eq ref $dt) {
die 'needs a DateTime object to be converted';
}
$dt->set_time_zone($LOCAL_TZ);
return to_string($dt);
}
sub get_weekday_names {
my $c = shift;
return [

@ -76,7 +76,8 @@ EOF
my $config_failed_auth_attempts = $c->config->{security}->{failed_auth_attempts} // 3;
for my $key (keys %{ $usr }) {
my $last_auth = $usr->{$key}->{last_auth} ? NGCP::Panel::Utils::DateTime::epoch_local($usr->{$key}->{last_auth}) : undef;
if($last_auth && $params{data_for_json}){
if ($last_auth) {
$last_auth->set_time_zone($c->session->{user_tz}) if $c->session->{user_tz};
$last_auth = $last_auth->ymd.' '. $last_auth->hms;
}
if( defined $usr->{$key}->{auth_count}

@ -22,6 +22,9 @@
<i class="icon-user"></i> [% c.loc("Not logged in") %]
[%- END -%]
</li>
<li>
[% IF c.user && c.session.user_tz_name; '(' _ c.session.user_tz_name _ ')'; END; %]
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-globe"></i> [% c.loc('Language') %]

Loading…
Cancel
Save