You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
532 lines
19 KiB
532 lines
19 KiB
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 POSIX qw(floor fmod);
|
|
|
|
use constant RFC_1123_FORMAT_PATTERN => '%a, %d %b %Y %T %Z';
|
|
use constant TIMEZONE_MAP => { map { $_ => 1; } DateTime::TimeZone->all_names };
|
|
|
|
my $is_fake_time = 0;
|
|
|
|
sub is_valid_timezone_name {
|
|
my ($tz,$all) = shift;
|
|
return 0 unless $tz;
|
|
if ($all) {
|
|
return DateTime::TimeZone->is_valid_name($tz);
|
|
} else {
|
|
return 0 unless exists TIMEZONE_MAP->{$tz};
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
sub current_local {
|
|
if ($is_fake_time) {
|
|
return DateTime->from_epoch(epoch => Time::Warp::time,
|
|
time_zone => DateTime::TimeZone->new(name => 'local')
|
|
);
|
|
} else {
|
|
return DateTime->now(
|
|
time_zone => DateTime::TimeZone->new(name => 'local')
|
|
);
|
|
}
|
|
}
|
|
|
|
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')
|
|
);
|
|
|
|
}
|
|
|
|
sub set_local_tz {
|
|
my $dt = shift;
|
|
if (defined $dt && ref $dt eq 'DateTime' && !is_infinite($dt)) {
|
|
$dt->set_time_zone('local');
|
|
}
|
|
return $dt;
|
|
}
|
|
|
|
sub infinite_past {
|
|
#mysql 5.5: The supported range is '1000-01-01 00:00:00' ...
|
|
return DateTime->new(year => 1000, month => 1, day => 1, hour => 0, minute => 0, second => 0,
|
|
time_zone => DateTime::TimeZone->new(name => 'UTC')
|
|
);
|
|
#$dt->epoch calls should be okay if perl >= 5.12.0
|
|
}
|
|
|
|
sub convert_tz {
|
|
my ($dt,$from_tz,$to_tz,$c) = @_;
|
|
#use Data::Dumper;
|
|
#$c->log->debug("converting $dt from '". Dumper($from_tz) ."' to $to_tz'") if $c;
|
|
$c->log->debug("converting $dt from '$from_tz' to '$to_tz'") if $c;
|
|
$from_tz = 'local' if (not defined $from_tz or lc($from_tz) eq 'system');
|
|
$to_tz = 'local' if (not defined $to_tz or lc($to_tz) eq 'system');
|
|
$dt = $dt->clone; #do not touch the original
|
|
return $dt if (is_infinite($dt) or $from_tz eq $to_tz);
|
|
my $tz = $dt->time_zone; #save away the original tz the dt was marked with
|
|
$dt->set_time_zone("floating") unless $tz->is_floating;; #unmark
|
|
$dt->set_time_zone($from_tz); #set "from" tz
|
|
$dt->set_time_zone($to_tz); #convert to "to" tz
|
|
$dt->set_time_zone("floating"); #unmark
|
|
$dt->set_time_zone($tz) unless $tz->is_floating; #mark again with the original tz
|
|
return $dt;
|
|
}
|
|
|
|
sub is_infinite_past {
|
|
my $dt = shift;
|
|
return $dt->year <= 1000;
|
|
}
|
|
|
|
sub infinite_future {
|
|
#... to '9999-12-31 23:59:59'
|
|
return DateTime->new(year => 9999, month => 12, day => 31, hour => 23, minute => 59, second => 59,
|
|
#applying the 'local' timezone takes too long -> "The current implementation of DateTime::TimeZone
|
|
#will use a huge amount of memory calculating all the DST changes from now until the future date.
|
|
#Use UTC or the floating time zone and you will be safe."
|
|
time_zone => DateTime::TimeZone->new(name => 'UTC')
|
|
#- with floating timezones, the long conversion takes place when comparing with a 'local' dt
|
|
#- the error due to leap years/seconds is not relevant in comparisons
|
|
);
|
|
}
|
|
|
|
sub is_infinite_future {
|
|
my $dt = shift;
|
|
return $dt->year >= 9999;
|
|
}
|
|
|
|
sub is_infinite {
|
|
my $dt = shift;
|
|
return is_infinite_future($dt) || is_infinite_past($dt);
|
|
}
|
|
|
|
sub set_fake_time {
|
|
my ($o) = @_;
|
|
$is_fake_time = 1;
|
|
if (defined $o) {
|
|
if (ref $o eq 'DateTime') {
|
|
$o = $o->epoch;
|
|
} else {
|
|
my %mult = (
|
|
s => 1,
|
|
m => 60,
|
|
h => 60*60,
|
|
d => 60*60*24,
|
|
M => 60*60*24*30,
|
|
y => 60*60*24*365,
|
|
);
|
|
|
|
if (!$o) {
|
|
$o = time;
|
|
} elsif ($o =~ m/^([+-]\d+)([smhdMy]?)$/) {
|
|
$o = time + $1 * $mult{ $2 || "s" };
|
|
} elsif ($o !~ m/\D/) {
|
|
|
|
} else {
|
|
die("Invalid time offset: '$o'");
|
|
}
|
|
}
|
|
Time::Warp::to($o);
|
|
} else {
|
|
Time::Warp::reset();
|
|
}
|
|
}
|
|
|
|
sub last_day_of_month {
|
|
my $dt = shift;
|
|
return DateTime->last_day_of_month(year => $dt->year, month => $dt->month,
|
|
time_zone => DateTime::TimeZone->new(name => 'local'))->day;
|
|
}
|
|
|
|
sub epoch_local {
|
|
my $epoch = shift;
|
|
return DateTime->from_epoch(
|
|
time_zone => DateTime::TimeZone->new(name => 'local'),
|
|
epoch => $epoch,
|
|
);
|
|
}
|
|
|
|
sub epoch_tz {
|
|
my $epoch = shift;
|
|
my $tz = shift;
|
|
#if(!$tz || !DateTime::TimeZone->is_valid_name($tz)) {
|
|
if(not is_valid_timezone_name($tz,1)) {
|
|
$tz = DateTime::TimeZone->new(name => 'local');
|
|
}
|
|
return DateTime->from_epoch(
|
|
time_zone => $tz,
|
|
epoch => $epoch,
|
|
);
|
|
}
|
|
|
|
sub from_string {
|
|
my $s = shift;
|
|
|
|
# if date is passed like xxxx-xx (as from monthpicker field), add a day
|
|
$s = $s . "-01" if($s =~ /^\d{4}\-\d{2}$/);
|
|
$s = $s . "T00:00:00" if($s =~ /^\d{4}\-\d{2}-\d{2}$/);
|
|
|
|
# just for convenience, if date is passed like xxxx-xx-xx xx:xx:xx,
|
|
# convert it to xxxx-xx-xxTxx:xx:xx
|
|
$s =~ s/^(\d{4}\-\d{2}\-\d{2})\s+(\d.+)$/$1T$2/;
|
|
my $ts = DateTime::Format::ISO8601->parse_datetime($s);
|
|
$ts->set_time_zone( DateTime::TimeZone->new(name => 'local') );
|
|
return $ts;
|
|
}
|
|
|
|
sub from_rfc1123_string {
|
|
|
|
my $s = shift;
|
|
my $strp = DateTime::Format::Strptime->new(pattern => RFC_1123_FORMAT_PATTERN,
|
|
locale => 'en_US',
|
|
on_error => 'undef');
|
|
return $strp->parse_datetime($s);
|
|
}
|
|
|
|
sub new_local {
|
|
my %params;
|
|
@params{qw/year month day hour minute second nanosecond/} = @_;
|
|
foreach(keys %params){
|
|
!defined $params{$_} and delete $params{$_};
|
|
}
|
|
return DateTime->new(
|
|
time_zone => DateTime::TimeZone->new(name => 'local'),
|
|
%params,
|
|
);
|
|
}
|
|
|
|
# convert seconds to 'HH:MM:SS.x' format
|
|
sub sec_to_hms {
|
|
my ($c,$secs,$sec_decimals) = @_;
|
|
$sec_decimals //= 0;
|
|
my ($result,$years,$months,$days,$hours,$minutes,$seconds) = to_duration_string($c,$secs,'hours','seconds',$sec_decimals);
|
|
$result = sprintf("%d", $hours) . ':' . sprintf("%02d", $minutes) . ':' . sprintf("%02d", $seconds);
|
|
my $fractional_secs;
|
|
if ($sec_decimals > 0 && ($fractional_secs = $seconds - int($seconds)) > 0.0) {
|
|
$result .= '.' . substr(sprintf('%.' . $sec_decimals . 'f', $fractional_secs),2);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
sub to_string {
|
|
my ($dt) = @_;
|
|
return unless defined ($dt);
|
|
my $s = $dt->ymd('-') . ' ' . $dt->hms(':');
|
|
$s .= '.'.$dt->millisecond if $dt->millisecond > 0.0;
|
|
return $s;
|
|
}
|
|
|
|
sub to_rfc1123_string {
|
|
my $dt = shift;
|
|
my $strp = DateTime::Format::Strptime->new(pattern => RFC_1123_FORMAT_PATTERN,
|
|
locale => 'en_US',
|
|
on_error => 'undef');
|
|
return $strp->format_datetime($dt);
|
|
}
|
|
|
|
sub get_weekday_names {
|
|
my $c = shift;
|
|
return [
|
|
$c->loc('Monday'),
|
|
$c->loc('Tuesday'),
|
|
$c->loc('Wednesday'),
|
|
$c->loc('Thursday'),
|
|
$c->loc('Friday'),
|
|
$c->loc('Saturday'),
|
|
$c->loc('Sunday')
|
|
];
|
|
}
|
|
|
|
#pretty printing a duration given in seconds according to ISO8601v2000, Section 5.5.3.2:
|
|
sub to_duration_string {
|
|
my ($c,$duration_secs,$most_significant,$least_significant,$least_significant_decimals) = @_;
|
|
my $abs = abs($duration_secs);
|
|
my ($years,$months,$days,$hours,$minutes,$seconds);
|
|
my $result = '';
|
|
if ('seconds' ne $least_significant) {
|
|
$abs = $abs / 60.0; #minutes
|
|
if ('minutes' ne $least_significant) {
|
|
$abs = $abs / 60.0; #hours
|
|
if ('hours' ne $least_significant) {
|
|
$abs = $abs / 24.0; #days
|
|
if ('days' ne $least_significant) {
|
|
$abs = $abs / 30.0; #months
|
|
if ('months' ne $least_significant) {
|
|
$abs = $abs / 12.0; #years
|
|
if ('years' ne $least_significant) {
|
|
die("unknown least significant duration unit-of-time: '$least_significant'");
|
|
} else {
|
|
$seconds = 0.0;
|
|
$minutes = 0.0;
|
|
$hours = 0.0;
|
|
$days = 0.0;
|
|
$months = 0.0;
|
|
if ('years' eq $most_significant) {
|
|
$years = $abs;
|
|
} else {
|
|
die("most significant duration unit-of-time '$most_significant' lower than least significant duration unit-of-time '$least_significant'");
|
|
}
|
|
}
|
|
} else {
|
|
$seconds = 0.0;
|
|
$minutes = 0.0;
|
|
$hours = 0.0;
|
|
$days = 0.0;
|
|
$years = 0.0;
|
|
if ('months' eq $most_significant) {
|
|
$months = $abs;
|
|
} else {
|
|
$months = ($abs >= 12.0) ? fmod($abs,12.0) : $abs;
|
|
$abs = $abs / 12.0;
|
|
if ('years' eq $most_significant) {
|
|
$years = floor($abs);
|
|
} else {
|
|
die("most significant duration unit-of-time '$most_significant' lower than least significant duration unit-of-time '$least_significant'");
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$seconds = 0.0;
|
|
$minutes = 0.0;
|
|
$hours = 0.0;
|
|
$months = 0.0;
|
|
$years = 0.0;
|
|
if ('days' eq $most_significant) {
|
|
$days = $abs;
|
|
} else {
|
|
$days = ($abs >= 30.0) ? fmod($abs,30.0) : $abs;
|
|
$abs = $abs / 30.0;
|
|
if ('months' eq $most_significant) {
|
|
$months = floor($abs);
|
|
} else {
|
|
$months = ($abs >= 12.0) ? fmod($abs,12.0) : $abs;
|
|
$abs = $abs / 12.0;
|
|
if ('years' eq $most_significant) {
|
|
$years = floor($abs);
|
|
} else {
|
|
die("most significant duration unit-of-time '$most_significant' lower than least significant duration unit-of-time '$least_significant'");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$seconds = 0.0;
|
|
$minutes = 0.0;
|
|
$days = 0.0;
|
|
$months = 0.0;
|
|
$years = 0.0;
|
|
if ('hours' eq $most_significant) {
|
|
$hours = $abs;
|
|
} else {
|
|
$hours = ($abs >= 24.0) ? fmod($abs,24.0) : $abs;
|
|
$abs = $abs / 24.0;
|
|
if ('days' eq $most_significant) {
|
|
$days = floor($abs);
|
|
} else {
|
|
$days = ($abs >= 30.0) ? fmod($abs,30) : $abs;
|
|
$abs = $abs / 30.0;
|
|
if ('months' eq $most_significant) {
|
|
$months = floor($abs);
|
|
} else {
|
|
$months = ($abs >= 12.0) ? fmod($abs,12.0) : $abs;
|
|
$abs = $abs / 12.0;
|
|
if ('years' eq $most_significant) {
|
|
$years = floor($abs);
|
|
} else {
|
|
die("most significant duration unit-of-time '$most_significant' lower than least significant duration unit-of-time '$least_significant'");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$seconds = 0.0;
|
|
$hours = 0.0;
|
|
$days = 0.0;
|
|
$months = 0.0;
|
|
$years = 0.0;
|
|
if ('minutes' eq $most_significant) {
|
|
$minutes = $abs;
|
|
} else {
|
|
$minutes = ($abs >= 60.0) ? fmod($abs,60.0) : $abs;
|
|
$abs = $abs / 60.0;
|
|
if ('hours' eq $most_significant) {
|
|
$hours = floor($abs);
|
|
} else {
|
|
$hours = ($abs >= 24.0) ? fmod($abs,24.0) : $abs;
|
|
$abs = $abs / 24.0;
|
|
if ('days' eq $most_significant) {
|
|
$days = floor($abs);
|
|
} else {
|
|
$days = ($abs >= 30.0) ? fmod($abs,30.0) : $abs;
|
|
$abs = $abs / 30.0;
|
|
if ('months' eq $most_significant) {
|
|
$months = floor($abs);
|
|
} else {
|
|
$months = ($abs >= 12.0) ? fmod($abs,12.0) : $abs;
|
|
$abs = $abs / 12.0;
|
|
if ('years' eq $most_significant) {
|
|
$years = floor($abs);
|
|
} else {
|
|
die("most significant duration unit-of-time '$most_significant' lower than least significant duration unit-of-time '$least_significant'");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$minutes = 0.0;
|
|
$hours = 0.0;
|
|
$days = 0.0;
|
|
$months = 0.0;
|
|
$years = 0.0;
|
|
if ('seconds' eq $most_significant) {
|
|
$seconds = $abs;
|
|
} else {
|
|
$seconds = ($abs >= 60.0) ? fmod($abs,60.0) : $abs;
|
|
$abs = $abs / 60.0;
|
|
if ('minutes' eq $most_significant) {
|
|
$minutes = floor($abs);
|
|
} else {
|
|
$minutes = ($abs >= 60.0) ? fmod($abs,60.0) : $abs;
|
|
$abs = $abs / 60.0;
|
|
if ('hours' eq $most_significant) {
|
|
$hours = floor($abs);
|
|
} else {
|
|
$hours = ($abs >= 24.0) ? fmod($abs,24.0) : $abs;
|
|
$abs = $abs / 24.0;
|
|
if ('days' eq $most_significant) {
|
|
$days = floor($abs);
|
|
} else {
|
|
$days = ($abs >= 30.0) ? fmod($abs,30.0) : $abs;
|
|
$abs = $abs / 30.0;
|
|
if ('minutes' eq $most_significant) {
|
|
$months = floor($abs);
|
|
} else {
|
|
$months = ($abs >= 12.0) ? fmod($abs,12.0) : $abs;
|
|
$abs = $abs / 12.0;
|
|
if ('years' eq $most_significant) {
|
|
$years = floor($abs);
|
|
} else {
|
|
die("most significant duration unit-of-time '$most_significant' lower than least significant duration unit-of-time '$least_significant'");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($years > 0.0) {
|
|
if ($months > 0.0 || $days > 0.0 || $hours > 0.0 || $minutes > 0.0 || $seconds > 0.0) {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$years, 0, 'years');
|
|
} else {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$years, $least_significant_decimals, 'years');
|
|
}
|
|
}
|
|
if ($months > 0.0) {
|
|
if ($years > 0.0) {
|
|
$result .= ', ';
|
|
}
|
|
if ($days > 0.0 || $hours > 0.0 || $minutes > 0.0 || $seconds > 0.0) {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$months, 0, 'months');
|
|
} else {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$months, $least_significant_decimals, 'months');
|
|
}
|
|
}
|
|
if ($days > 0.0) {
|
|
if ($years > 0.0 || $months > 0.0) {
|
|
$result .= ', ';
|
|
}
|
|
if ($hours > 0.0 || $minutes > 0.0 || $seconds > 0.0) {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$days, 0, 'days');
|
|
} else {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$days, $least_significant_decimals, 'days');
|
|
}
|
|
}
|
|
if ($hours > 0.0) {
|
|
if ($years > 0.0 || $months > 0.0 || $days > 0.0) {
|
|
$result .= ', ';
|
|
}
|
|
if ($minutes > 0.0 || $seconds > 0.0) {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$hours, 0, 'hours');
|
|
} else {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$hours, $least_significant_decimals, 'hours');
|
|
}
|
|
}
|
|
if ($minutes > 0.0) {
|
|
if ($years > 0.0 || $months > 0.0 || $days > 0.0 || $hours > 0.0) {
|
|
$result .= ', ';
|
|
}
|
|
if ($seconds > 0.0) {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$minutes, 0, 'minutes');
|
|
} else {
|
|
$result .= _duration_unit_of_time_value_to_string($c,$minutes, $least_significant_decimals, 'minutes');
|
|
}
|
|
}
|
|
if ($seconds > 0.0) {
|
|
if ($years > 0.0 || $months > 0.0 || $days > 0.0 || $hours > 0.0 || $minutes > 0.0) {
|
|
$result .= ', ';
|
|
}
|
|
$result .= _duration_unit_of_time_value_to_string($c,$seconds, $least_significant_decimals, 'seconds');
|
|
}
|
|
if (length($result) == 0) {
|
|
$result .= _duration_unit_of_time_value_to_string($c,0.0, $least_significant_decimals, $least_significant);
|
|
}
|
|
return ($result,$years,$months,$days,$hours,$minutes,$seconds);
|
|
}
|
|
|
|
sub _duration_unit_of_time_value_to_string {
|
|
my ($c,$value, $decimals, $unit_of_time) = @_;
|
|
my $result = '';
|
|
my $unit_label_plural = '';
|
|
my $unit_label_singular = '';
|
|
if (defined $c) {
|
|
if ('seconds' eq $unit_of_time) {
|
|
$unit_label_plural = ' ' . $c->loc('seconds');
|
|
$unit_label_singular = ' ' . $c->loc("second");
|
|
} elsif ('minutes' eq $unit_of_time) {
|
|
$unit_label_plural = ' ' . $c->loc('minutes');
|
|
$unit_label_singular = ' ' . $c->loc("minute");
|
|
} elsif ('hours' eq $unit_of_time) {
|
|
$unit_label_plural = ' ' . $c->loc('hours');
|
|
$unit_label_singular = ' ' . $c->loc("hour");
|
|
} elsif ('days' eq $unit_of_time) {
|
|
$unit_label_plural = ' ' . $c->loc('days');
|
|
$unit_label_singular = ' ' . $c->loc("day");
|
|
} elsif ('months' eq $unit_of_time) {
|
|
$unit_label_plural = ' ' . $c->loc('months');
|
|
$unit_label_singular = ' ' . $c->loc("month");
|
|
} elsif ('years' eq $unit_of_time) {
|
|
$unit_label_plural = ' ' . $c->loc('years');
|
|
$unit_label_singular = ' ' . $c->loc("year");
|
|
}
|
|
}
|
|
if ($decimals < 1) {
|
|
if (int($value) == 1) {
|
|
$result .= '1';
|
|
$result .= $unit_label_singular;
|
|
} else {
|
|
$result .= int($value);
|
|
$result .= $unit_label_plural;
|
|
}
|
|
} else {
|
|
$result .= sprintf('%.' . $decimals . 'f', $value);
|
|
$result .= $unit_label_plural;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
1;
|
|
|
|
# vim: set tabstop=4 expandtab:
|