Files
scripts/Perl OTRS/Kernel/System/DateTime.pm
2024-10-14 00:08:40 +02:00

1999 lines
58 KiB
Perl

# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::DateTime;
## nofilter(TidyAll::Plugin::OTRS::Perl::Time)
## nofilter(TidyAll::Plugin::OTRS::Perl::Translatable)
use strict;
use warnings;
use Exporter qw(import);
our %EXPORT_TAGS = ( ## no critic
all => [
'OTRSTimeZoneGet',
'SystemTimeZoneGet',
'TimeZoneList',
'UserDefaultTimeZoneGet',
],
);
Exporter::export_ok_tags('all');
use DateTime;
use DateTime::TimeZone;
use Scalar::Util qw( looks_like_number );
use Kernel::System::VariableCheck qw( IsArrayRefWithData IsHashRefWithData );
our %ObjectManagerFlags = (
NonSingleton => 1,
AllowConstructorFailure => 1,
);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Log',
);
our $Locale = DateTime::Locale->load('en_US');
use overload
'>' => \&_OpIsNewerThan,
'<' => \&_OpIsOlderThan,
'>=' => \&_OpIsNewerThanOrEquals,
'<=' => \&_OpIsOlderThanOrEquals,
'==' => \&_OpEquals,
'!=' => \&_OpNotEquals,
'fallback' => 1;
=head1 NAME
Kernel::System::DateTime - Handles date and time calculations.
=head1 DESCRIPTION
Handles date and time calculations.
=head1 PUBLIC INTERFACE
=head2 new()
Creates a DateTime object. Do not use new() directly, instead use the object manager:
# Create an object with current date and time
# within time zone set in SysConfig OTRSTimeZone:
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime'
);
# Create an object with current date and time
# within a certain time zone:
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
TimeZone => 'Europe/Berlin', # optional, TimeZone name.
}
);
# Create an object with a specific date and time:
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
Year => 2016,
Month => 1,
Day => 22,
Hour => 12, # optional, defaults to 0
Minute => 35, # optional, defaults to 0
Second => 59, # optional, defaults to 0
TimeZone => 'Europe/Berlin', # optional, defaults to setting of SysConfig OTRSTimeZone
}
);
# Create an object from an epoch timestamp. These timestamps are always UTC/GMT,
# hence time zone will automatically be set to UTC.
#
# If parameter Epoch is present, all other parameters will be ignored.
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
Epoch => 1453911685,
}
);
# Create an object from a date/time string.
#
# If parameter String is given, Year, Month, Day, Hour, Minute and Second will be ignored
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
String => '2016-08-14 22:45:00',
TimeZone => 'Europe/Berlin', # optional, defaults to setting of SysConfig OTRSTimeZone
}
);
# Following formats for parameter String are supported:
#
# yyyy-mm-dd hh:mm:ss
# yyyy-mm-dd hh:mm # sets second to 0
# yyyy-mm-dd # sets hour, minute and second to 0
# yyyy-mm-ddThh:mm:ss+tt:zz
# yyyy-mm-ddThh:mm:ss+ttzz
# yyyy-mm-ddThh:mm:ss-tt:zz
# yyyy-mm-ddThh:mm:ss-ttzz
# yyyy-mm-ddThh:mm:ss [timezone] # time zone will be deduced from an optional string
# yyyy-mm-ddThh:mm:ss[timezone] # i.e. 2018-04-20T07:37:10UTC
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# CPAN DateTime: only use English descriptions and abbreviations internally.
# This has nothing to do with the user's locale settings in OTRS.
$Self->{Locale} = $Locale;
# Use private parameter to pass in an already created CPANDateTimeObject (used)
# by the Clone() method).
if ( $Param{_CPANDateTimeObject} ) {
$Self->{CPANDateTimeObject} = $Param{_CPANDateTimeObject};
return $Self;
}
# Create the CPAN/Perl DateTime object.
my $CPANDateTimeObject = $Self->_CPANDateTimeObjectCreate(%Param);
if ( ref $CPANDateTimeObject ne 'DateTime' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => 'Error creating DateTime object.',
);
return;
}
$Self->{CPANDateTimeObject} = $CPANDateTimeObject;
return $Self;
}
=head2 Get()
Returns hash ref with the date, time and time zone values of this object.
my $DateTimeSettings = $DateTimeObject->Get();
Returns:
my $DateTimeSettings = {
Year => 2016,
Month => 1, # starting at 1
Day => 22,
Hour => 16,
Minute => 35,
Second => 59,
DayOfWeek => 5, # starting with 1 for Monday, ending with 7 for Sunday
TimeZone => 'Europe/Berlin',
};
=cut
sub Get {
my ( $Self, %Param ) = @_;
my $Values = {
Year => $Self->{CPANDateTimeObject}->year(),
Month => $Self->{CPANDateTimeObject}->month(),
MonthAbbr => $Self->{CPANDateTimeObject}->month_abbr(),
Day => $Self->{CPANDateTimeObject}->day(),
Hour => $Self->{CPANDateTimeObject}->hour(),
Minute => $Self->{CPANDateTimeObject}->minute(),
Second => $Self->{CPANDateTimeObject}->second(),
DayOfWeek => $Self->{CPANDateTimeObject}->day_of_week(),
DayAbbr => $Self->{CPANDateTimeObject}->day_abbr(),
TimeZone => $Self->{CPANDateTimeObject}->time_zone_long_name(),
};
return $Values;
}
=head2 Set()
Sets date and time values of this object. You have to give at least one parameter. Only given values will be changed.
Note that the resulting date and time have to be valid. On validation error, the current date and time of the object
won't be changed.
Note that in order to change the time zone, you have to use method C<L</ToTimeZone()>>.
# Setting values by hash:
my $Success = $DateTimeObject->Set(
Year => 2016,
Month => 1,
Day => 22,
Hour => 16,
Minute => 35,
Second => 59,
);
# Settings values by date/time string:
my $Success = $DateTimeObject->Set( String => '2016-02-25 20:34:01' );
If parameter C<String> is present, all other parameters will be ignored. Please see C<L</new()>> for the list of
supported string formats.
Returns:
$Success = 1; # On success, or false otherwise.
=cut
sub Set {
my ( $Self, %Param ) = @_;
if ( defined $Param{String} ) {
my $DateTimeHash = $Self->_StringToHash( String => $Param{String} );
return if !$DateTimeHash;
%Param = %{$DateTimeHash};
}
my @DateTimeParams = qw ( Year Month Day Hour Minute Second );
# Check given parameters
my $ParamGiven;
DATETIMEPARAM:
for my $DateTimeParam (@DateTimeParams) {
next DATETIMEPARAM if !defined $Param{$DateTimeParam};
$ParamGiven = 1;
last DATETIMEPARAM;
}
if ( !$ParamGiven ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => 'Missing at least one parameter.',
);
return;
}
# Validate given values by using the current settings + the given ones.
my $CurrentValues = $Self->Get();
DATETIMEPARAM:
for my $DateTimeParam (@DateTimeParams) {
next DATETIMEPARAM if !defined $Param{$DateTimeParam};
$CurrentValues->{$DateTimeParam} = $Param{$DateTimeParam};
}
# Create a new DateTime object with the new/added values
my $CPANDateTimeParams = $Self->_ToCPANDateTimeParamNames( %{$CurrentValues} );
# Delete parameters that are not allowed for set method
delete $CPANDateTimeParams->{time_zone};
my $Result;
eval {
$Result = $Self->{CPANDateTimeObject}->set( %{$CPANDateTimeParams} );
};
return $Result;
}
=head2 Add()
Adds duration or working time to date and time of this object. You have to give at least one of the valid parameters.
On error, the current date and time of this object won't be changed.
my $Success = $DateTimeObject->Add(
Years => 1,
Months => 2,
Weeks => 4,
Days => 34,
Hours => 2,
Minutes => 5,
Seconds => 459,
# Calculate "destination date" by adding given time values as
# working time. Note that for adding working time,
# only parameters Seconds, Minutes, Hours and Days are allowed.
AsWorkingTime => 0, # set to 1 to add given values as working time
# Calendar to use for working time calculations, optional
Calendar => 9,
);
Returns:
$Success = 1; # On success, or false otherwise.
=cut
sub Add {
my ( $Self, %Param ) = @_;
#
# Check parameters
#
my @DateTimeParams = qw ( Years Months Weeks Days Hours Minutes Seconds );
@DateTimeParams = qw( Days Hours Minutes Seconds ) if $Param{AsWorkingTime};
# Check for needed parameters
my $ParamsGiven = 0;
my $ParamsValid = 1;
DATETIMEPARAM:
for my $DateTimeParam (@DateTimeParams) {
next DATETIMEPARAM if !defined $Param{$DateTimeParam};
if ( !looks_like_number( $Param{$DateTimeParam} ) ) {
$ParamsValid = 0;
last DATETIMEPARAM;
}
# negative values are not allowed when calculating working time
if ( int $Param{$DateTimeParam} < 0 && $Param{AsWorkingTime} ) {
$ParamsValid = 0;
last DATETIMEPARAM;
}
$ParamsGiven = 1;
}
if ( !$ParamsGiven || !$ParamsValid ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => 'Missing or invalid date/time parameter(s).',
);
return;
}
# Check for not allowed parameters
my %AllowedParams = map { $_ => 1 } @DateTimeParams;
$AllowedParams{AsWorkingTime} = 1;
if ( $Param{AsWorkingTime} ) {
$AllowedParams{Calendar} = 1;
}
for my $Param ( sort keys %Param ) {
if ( !$AllowedParams{$Param} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Parameter $Param is not allowed.",
);
return;
}
}
# NOTE: For performance reasons, the following code for calculating date and time
# works directly with the CPAN DateTime object instead of methods of Kernel::System::DateTime.
#
# Working time calculation
#
if ( $Param{AsWorkingTime} ) {
# Combine time parameters to seconds
my $RemainingSeconds = 0;
if ( defined $Param{Seconds} ) {
$RemainingSeconds += int $Param{Seconds};
}
if ( defined $Param{Minutes} ) {
$RemainingSeconds += int $Param{Minutes} * 60;
}
if ( defined $Param{Hours} ) {
$RemainingSeconds += int $Param{Hours} * 60 * 60;
}
if ( defined $Param{Days} ) {
$RemainingSeconds += int $Param{Days} * 60 * 60 * 24;
}
return if !$RemainingSeconds;
# Backup current date/time to be able to revert to it in case of failure
my $OriginalDateTimeObject = $Self->{CPANDateTimeObject}->clone();
my $TimeZone = $OriginalDateTimeObject->time_zone();
# Get working and vacation times, use calendar if given
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $TimeWorkingHours = $ConfigObject->Get('TimeWorkingHours');
my $TimeVacationDays = $ConfigObject->Get('TimeVacationDays');
my $TimeVacationDaysOneTime = $ConfigObject->Get('TimeVacationDaysOneTime');
if (
$Param{Calendar}
&& $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} . "Name" )
)
{
$TimeWorkingHours = $ConfigObject->Get( "TimeWorkingHours::Calendar" . $Param{Calendar} );
$TimeVacationDays = $ConfigObject->Get( "TimeVacationDays::Calendar" . $Param{Calendar} );
$TimeVacationDaysOneTime = $ConfigObject->Get(
"TimeVacationDaysOneTime::Calendar" . $Param{Calendar}
);
# Switch to time zone of calendar
$TimeZone = $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} )
|| $Self->OTRSTimeZoneGet();
# Use Kernel::System::DateTime's ToTimeZone() here because of error handling
# and because performance is irrelevant at this point.
if ( !$Self->ToTimeZone( TimeZone => $TimeZone ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Error setting time zone $TimeZone.",
);
return;
}
}
# If there are for some reason no working hours configured, stop here
# to prevent failing via loop protection below.
my $WorkingHoursConfigured;
WORKINGHOURCONFIGDAY:
for my $WorkingHourConfigDay ( sort keys %{$TimeWorkingHours} ) {
if ( IsArrayRefWithData( $TimeWorkingHours->{$WorkingHourConfigDay} ) ) {
$WorkingHoursConfigured = 1;
last WORKINGHOURCONFIGDAY;
}
}
return 1 if !$WorkingHoursConfigured;
# Convert $TimeWorkingHours into Hash
my %TimeWorkingHours;
for my $DayName ( sort keys %{$TimeWorkingHours} ) {
$TimeWorkingHours{$DayName} = { map { $_ => 1 } @{ $TimeWorkingHours->{$DayName} } };
}
# Protection for endless loop
my $LoopStartTime = time();
LOOP:
while ( $RemainingSeconds > 0 ) {
# Fail if this loop takes longer than 5 seconds
if ( time() - $LoopStartTime > 5 ) {
# Reset this object to original date/time.
$Self->{CPANDateTimeObject} = $OriginalDateTimeObject->clone();
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Adding working time took too long, aborting.',
);
return;
}
my $Year = $Self->{CPANDateTimeObject}->year();
my $Month = $Self->{CPANDateTimeObject}->month();
my $Day = $Self->{CPANDateTimeObject}->day();
my $DayName = $Self->{CPANDateTimeObject}->day_abbr();
my $Hour = $Self->{CPANDateTimeObject}->hour();
my $Minute = $Self->{CPANDateTimeObject}->minute();
my $Second = $Self->{CPANDateTimeObject}->second();
# Check working times and vacation days
my $IsWorkingDay = !$TimeVacationDays->{$Month}->{$Day}
&& !$TimeVacationDaysOneTime->{$Year}->{$Month}->{$Day}
&& exists $TimeWorkingHours->{$DayName}
&& keys %{ $TimeWorkingHours{$DayName} };
# On start of day check if whole day can be processed in one chunk
# instead of hour by hour (performance reasons).
if ( !$Hour && !$Minute && !$Second ) {
# The following code is slightly faster than using CPAN DateTime's add(),
# presumably because add() always creates a DateTime::Duration object.
my $Epoch = $Self->{CPANDateTimeObject}->epoch();
$Epoch += 60 * 60 * 24;
my $NextDayDateTimeObject = DateTime->from_epoch(
epoch => $Epoch,
time_zone => $TimeZone,
locale => $Self->{Locale},
);
# Only handle days with exactly 24 hours here
if (
!$NextDayDateTimeObject->hour()
&& !$NextDayDateTimeObject->minute()
&& !$NextDayDateTimeObject->second()
&& $NextDayDateTimeObject->day() != $Day
)
{
my $FullDayProcessed = 1;
if ($IsWorkingDay) {
my $WorkingHours = keys %{ $TimeWorkingHours{$DayName} };
my $WorkingSeconds = $WorkingHours * 60 * 60;
if ( $RemainingSeconds > $WorkingSeconds ) {
$RemainingSeconds -= $WorkingSeconds;
}
else {
$FullDayProcessed = 0;
}
}
# Move forward 24 hours if full day has been processed
if ($FullDayProcessed) {
# Time implicitly set to 0
$Self->{CPANDateTimeObject}->set(
year => $NextDayDateTimeObject->year(),
month => $NextDayDateTimeObject->month(),
day => $NextDayDateTimeObject->day(),
);
next LOOP;
}
}
}
# Calculate remaining seconds of the current hour
my $SecondsOfCurrentHour = ( $Minute * 60 ) + $Second;
my $SecondsToAdd = ( 60 * 60 ) - $SecondsOfCurrentHour;
if ( $IsWorkingDay && $TimeWorkingHours{$DayName}->{$Hour} ) {
$SecondsToAdd = $RemainingSeconds if $SecondsToAdd > $RemainingSeconds;
$RemainingSeconds -= $SecondsToAdd;
}
# The following code is slightly faster than using CPAN DateTime's add(),
# presumably because add() always creates a DateTime::Duration object.
my $Epoch = $Self->{CPANDateTimeObject}->epoch();
$Epoch += $SecondsToAdd;
$Self->{CPANDateTimeObject} = DateTime->from_epoch(
epoch => $Epoch,
time_zone => $TimeZone,
locale => $Self->{Locale},
);
}
# Return to original time zone, might have been changed by calendar
$Self->{CPANDateTimeObject}->set_time_zone( $OriginalDateTimeObject->time_zone() );
return 1;
}
#
# "Normal" date/time calculation
#
# Calculations are only made in UTC/floating time zone to prevent errors with times that
# would not exist in the given time zone (e. g. on/around daylight saving time switch).
# CPAN DateTime fails if adding days, months or years which would result in a non-existing
# time in the given time zone. Converting it to UTC and back has the desired effect.
#
# Also see http://stackoverflow.com/questions/18489927/a-day-without-midnight
my $TimeZone = $Self->{CPANDateTimeObject}->time_zone();
$Self->{CPANDateTimeObject}->set_time_zone('UTC');
# Convert to floating time zone to get rid of leap seconds which can lead to times like 23:59:61
$Self->{CPANDateTimeObject}->set_time_zone('floating');
# Add duration
my $DurationParameters = $Self->_ToCPANDateTimeParamNames(%Param);
eval {
$Self->{CPANDateTimeObject}->add( %{$DurationParameters} );
};
# Store possible error before it might get lost by call to ToTimeZone
my $Error = $@;
# First convert floating time zone back to UTC and from there to the original time zone
$Self->{CPANDateTimeObject}->set_time_zone('UTC');
$Self->{CPANDateTimeObject}->set_time_zone($TimeZone);
return if $Error;
return 1;
}
=head2 Subtract()
Subtracts duration from date and time of this object. You have to give at least one of the valid parameters. On
validation error, the current date and time of this object won't be changed.
my $Success = $DateTimeObject->Subtract(
Years => 1,
Months => 2,
Weeks => 4,
Days => 34,
Hours => 2,
Minutes => 5,
Seconds => 459,
);
Returns:
$Success = 1; # On success, or false otherwise.
=cut
sub Subtract {
my ( $Self, %Param ) = @_;
my @DateTimeParams = qw ( Years Months Weeks Days Hours Minutes Seconds );
# Check for needed parameters
my $ParamsGiven = 0;
my $ParamsValid = 1;
DATETIMEPARAM:
for my $DateTimeParam (@DateTimeParams) {
next DATETIMEPARAM if !defined $Param{$DateTimeParam};
if ( !looks_like_number( $Param{$DateTimeParam} ) ) {
$ParamsValid = 0;
last DATETIMEPARAM;
}
# negative values are not allowed when calculating working time
if ( int $Param{$DateTimeParam} < 0 && $Param{AsWorkingTime} ) {
$ParamsValid = 0;
last DATETIMEPARAM;
}
$ParamsGiven = 1;
}
if ( !$ParamsGiven || !$ParamsValid ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => 'Missing or invalid date/time parameter(s).',
);
return;
}
# Check for not allowed parameters
my %AllowedParams = map { $_ => 1 } @DateTimeParams;
$AllowedParams{AsWorkingTime} = 1;
if ( $Param{AsWorkingTime} ) {
$AllowedParams{Calendar} = 1;
}
for my $Param ( sort keys %Param ) {
if ( !$AllowedParams{$Param} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Parameter $Param is not allowed.",
);
return;
}
}
# Calculations are only made in UTC/floating time zone to prevent errors with times that
# would not exist in the given time zone (e. g. on/around daylight saving time switch).
my $DateTimeValues = $Self->Get();
$Self->ToTimeZone( TimeZone => 'UTC' );
# Convert to floating time zone to get rid of leap seconds which can lead to times like 23:59:61
$Self->{CPANDateTimeObject}->set_time_zone('floating');
# Subtract duration
my $DurationParameters = $Self->_ToCPANDateTimeParamNames(%Param);
eval {
$Self->{CPANDateTimeObject}->subtract( %{$DurationParameters} );
};
# Store possible error before it might get lost by call to ToTimeZone
my $Error = $@;
# First convert floating time zone back to UTC and from there to the original time zone
$Self->{CPANDateTimeObject}->set_time_zone('UTC');
$Self->ToTimeZone( TimeZone => $DateTimeValues->{TimeZone} );
return if $@;
return 1;
}
=head2 Delta()
Calculates delta between this and another DateTime object. Optionally calculates the working time between the two.
my $Delta = $DateTimeObject->Delta( DateTimeObject => $AnotherDateTimeObject );
Note that the returned values are always positive. Use the comparison methods to see if a date is newer/older/equal.
# Calculate "working time"
ForWorkingTime => 0, # set to 1 to calculate working time between the two DateTime objects
# Calendar to use for working time calculations, optional
Calendar => 9,
Returns:
my $Delta = {
Years => 1, # Set to 0 if working time was calculated
Months => 2, # Set to 0 if working time was calculated
Weeks => 4, # Set to 0 if working time was calculated
Days => 34, # Set to 0 if working time was calculated
Hours => 2,
Minutes => 5,
Seconds => 459,
AbsoluteSeconds => 42084759, # complete delta in seconds
};
=cut
sub Delta {
my ( $Self, %Param ) = @_;
if (
!defined $Param{DateTimeObject}
|| ref $Param{DateTimeObject} ne ref $Self
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing or invalid parameter DateTimeObject.",
);
return;
}
my $Delta = {
Years => 0,
Months => 0,
Weeks => 0,
Days => 0,
Hours => 0,
Minutes => 0,
Seconds => 0,
AbsoluteSeconds => 0,
};
#
# Calculate delta for working time
#
if ( $Param{ForWorkingTime} ) {
# NOTE: For performance reasons, the following code for calculating the working time
# works directly with the CPAN DateTime object instead of Kernel::System::DateTime.
# Clone StartDateTime object because it will be changed while calculating
# but the original object must not be changed.
my $StartDateTimeObject = $Self->{CPANDateTimeObject}->clone();
my $TimeZone = $StartDateTimeObject->time_zone();
# Get working and vacation times, use calendar if given
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $TimeWorkingHours = $ConfigObject->Get('TimeWorkingHours');
my $TimeVacationDays = $ConfigObject->Get('TimeVacationDays');
my $TimeVacationDaysOneTime = $ConfigObject->Get('TimeVacationDaysOneTime');
if (
$Param{Calendar}
&& $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} . "Name" )
)
{
$TimeWorkingHours = $ConfigObject->Get( "TimeWorkingHours::Calendar" . $Param{Calendar} );
$TimeVacationDays = $ConfigObject->Get( "TimeVacationDays::Calendar" . $Param{Calendar} );
$TimeVacationDaysOneTime = $ConfigObject->Get(
"TimeVacationDaysOneTime::Calendar" . $Param{Calendar}
);
# switch to time zone of calendar
$TimeZone = $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} )
|| $Self->OTRSTimeZoneGet();
eval {
$StartDateTimeObject->set_time_zone($TimeZone);
};
if ($@) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Error setting time zone $TimeZone for start DateTime object.",
);
return;
}
}
# If there are for some reason no working hours configured, stop here
# to prevent failing via loop protection below.
my $WorkingHoursConfigured;
WORKINGHOURCONFIGDAY:
for my $WorkingHourConfigDay ( sort keys %{$TimeWorkingHours} ) {
if ( IsArrayRefWithData( $TimeWorkingHours->{$WorkingHourConfigDay} ) ) {
$WorkingHoursConfigured = 1;
last WORKINGHOURCONFIGDAY;
}
}
return $Delta if !$WorkingHoursConfigured;
# Convert $TimeWorkingHours into Hash
my %TimeWorkingHours;
for my $DayName ( sort keys %{$TimeWorkingHours} ) {
$TimeWorkingHours{$DayName} = { map { $_ => 1 } @{ $TimeWorkingHours->{$DayName} } };
}
my $StartTime = $StartDateTimeObject->epoch();
my $StopTime = $Param{DateTimeObject}->{CPANDateTimeObject}->epoch();
my $WorkingTime = 0;
# Protection for endless loop
my $LoopStartTime = time();
LOOP:
while ( $StartTime < $StopTime ) {
# Fail if this loop takes longer than 5 seconds
if ( time() - $LoopStartTime > 5 ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Delta calculation of working time took too long, aborting.',
);
return;
}
my $RemainingSeconds = $StopTime - $StartTime;
my $Year = $StartDateTimeObject->year();
my $Month = $StartDateTimeObject->month();
my $Day = $StartDateTimeObject->day();
my $DayName = $StartDateTimeObject->day_abbr();
my $Hour = $StartDateTimeObject->hour();
my $Minute = $StartDateTimeObject->minute();
my $Second = $StartDateTimeObject->second();
# Check working times and vacation days
my $IsWorkingDay = !$TimeVacationDays->{$Month}->{$Day}
&& !$TimeVacationDaysOneTime->{$Year}->{$Month}->{$Day}
&& exists $TimeWorkingHours->{$DayName}
&& keys %{ $TimeWorkingHours{$DayName} };
# On start of day check if whole day can be processed in one chunk
# instead of hour by hour (performance reasons).
if ( !$Hour && !$Minute && !$Second ) {
# The following code is slightly faster than using CPAN DateTime's add(),
# presumably because add() always creates a DateTime::Duration object.
my $Epoch = $StartDateTimeObject->epoch();
$Epoch += 60 * 60 * 24;
my $NextDayDateTimeObject = DateTime->from_epoch(
epoch => $Epoch,
time_zone => $TimeZone,
locale => $Self->{Locale},
);
# Only handle days with exactly 24 hours here
if (
!$NextDayDateTimeObject->hour()
&& !$NextDayDateTimeObject->minute()
&& !$NextDayDateTimeObject->second()
&& $NextDayDateTimeObject->day() != $Day
&& $RemainingSeconds > 60 * 60 * 24
)
{
my $FullDayProcessed = 1;
if ($IsWorkingDay) {
my $WorkingHours = keys %{ $TimeWorkingHours{$DayName} };
my $WorkingSeconds = $WorkingHours * 60 * 60;
if ( $RemainingSeconds > $WorkingSeconds ) {
$WorkingTime += $WorkingSeconds;
}
else {
$FullDayProcessed = 0;
}
}
# Move forward 24 hours if full day has been processed
if ($FullDayProcessed) {
# Time implicitly set to 0
$StartDateTimeObject->set(
year => $NextDayDateTimeObject->year(),
month => $NextDayDateTimeObject->month(),
day => $NextDayDateTimeObject->day(),
);
$StartTime = $Epoch;
next LOOP;
}
}
}
# Calculate remaining seconds of the current hour
my $SecondsOfCurrentHour = ( $Minute * 60 ) + $Second;
my $SecondsToAdd = ( 60 * 60 ) - $SecondsOfCurrentHour;
if ( $IsWorkingDay && $TimeWorkingHours{$DayName}->{$Hour} ) {
$SecondsToAdd = $RemainingSeconds if $SecondsToAdd > $RemainingSeconds;
$WorkingTime += $SecondsToAdd;
}
# The following code is slightly faster than using CPAN DateTime's add(),
# presumably because add() always creates a DateTime::Duration object.
my $Epoch = $StartDateTimeObject->epoch();
$Epoch += $SecondsToAdd;
$StartDateTimeObject = DateTime->from_epoch(
epoch => $Epoch,
time_zone => $TimeZone,
locale => $Self->{Locale},
);
$StartTime = $Epoch;
}
# Set values for delta
my $RemainingWorkingTime = $WorkingTime;
$Delta->{Hours} = int $RemainingWorkingTime / ( 60 * 60 );
$RemainingWorkingTime -= $Delta->{Hours} * 60 * 60;
$Delta->{Minutes} = int $RemainingWorkingTime / 60;
$RemainingWorkingTime -= $Delta->{Minutes} * 60;
$Delta->{Seconds} = $RemainingWorkingTime;
$RemainingWorkingTime = 0;
$Delta->{AbsoluteSeconds} = $WorkingTime;
return $Delta;
}
#
# Calculate delta for "normal" date/time
#
my $DeltaDuration = $Self->{CPANDateTimeObject}->subtract_datetime(
$Param{DateTimeObject}->{CPANDateTimeObject}
);
$Delta->{Years} = $DeltaDuration->years();
$Delta->{Months} = $DeltaDuration->months();
$Delta->{Weeks} = $DeltaDuration->weeks();
$Delta->{Days} = $DeltaDuration->days();
$Delta->{Hours} = $DeltaDuration->hours();
$Delta->{Minutes} = $DeltaDuration->minutes();
$Delta->{Seconds} = $DeltaDuration->seconds();
# Absolute seconds
$DeltaDuration = $Self->{CPANDateTimeObject}->subtract_datetime_absolute(
$Param{DateTimeObject}->{CPANDateTimeObject}
);
$Delta->{AbsoluteSeconds} = $DeltaDuration->seconds();
return $Delta;
}
=head2 Compare()
Compares dates and returns a value suitable for using Perl's sort function (-1, 0, 1).
my $Result = $DateTimeObject->Compare( DateTimeObject => $AnotherDateTimeObject );
You can also use this as a function for Perl's sort:
my @SortedDateTimeObjects = sort { $a->Compare( DateTimeObject => $b ); } @UnsortedDateTimeObjects:
Returns:
$Result = -1; # if date/time of this object < date/time of given object
$Result = 0; # if date/time are equal
$Result = 1: # if date/time of this object > date/time of given object
=cut
sub Compare {
my ( $Self, %Param ) = @_;
if (
!defined $Param{DateTimeObject}
|| ref $Param{DateTimeObject} ne ref $Self
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing or invalid parameter DateTimeObject.",
);
return;
}
my $Result;
eval {
$Result = DateTime->compare(
$Self->{CPANDateTimeObject},
$Param{DateTimeObject}->{CPANDateTimeObject}
);
};
return $Result;
}
=head2 ToTimeZone()
Converts the date and time of this object to the given time zone.
my $Success = $DateTimeObject->ToTimeZone(
TimeZone => 'Europe/Berlin',
);
Returns:
$Success = 1; # success, or false otherwise.
=cut
sub ToTimeZone {
my ( $Self, %Param ) = @_;
for my $RequiredParam (qw( TimeZone )) {
if ( !defined $Param{$RequiredParam} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing parameter $RequiredParam.",
);
return;
}
}
eval {
$Self->{CPANDateTimeObject}->set_time_zone( $Param{TimeZone} );
};
return if $@;
return 1;
}
=head2 ToOTRSTimeZone()
Converts the date and time of this object to the data storage time zone.
my $Success = $DateTimeObject->ToOTRSTimeZone();
Returns:
$Success = 1; # success, or false otherwise.
=cut
sub ToOTRSTimeZone {
my ( $Self, %Param ) = @_;
return $Self->ToTimeZone( TimeZone => $Self->OTRSTimeZoneGet() );
}
=head2 Validate()
Checks if given date, time and time zone would result in a valid date.
my $IsValid = $DateTimeObject->Validate(
Year => 2016,
Month => 1,
Day => 22,
Hour => 16,
Minute => 35,
Second => 59,
TimeZone => 'Europe/Berlin',
);
Returns:
$IsValid = 1; # if date/time is valid, or false otherwise.
=cut
sub Validate {
my ( $Self, %Param ) = @_;
my @DateTimeParams = qw ( Year Month Day Hour Minute Second TimeZone );
for my $RequiredDateTimeParam (@DateTimeParams) {
if ( !defined $Param{$RequiredDateTimeParam} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing parameter $RequiredDateTimeParam.",
);
return;
}
}
my $DateTimeObject = $Self->_CPANDateTimeObjectCreate(%Param);
return if !$DateTimeObject;
return 1;
}
=head2 Format()
Returns the date/time as string formatted according to format given.
See L<http://search.cpan.org/~drolsky/DateTime-1.21/lib/DateTime.pm#strftime_Patterns> for supported formats.
Short overview of essential formatting options:
%Y or %{year}: four digit year
%m: month with leading zero
%{month}: month without leading zero
%d: day of month with leading zero
%{day}: day of month without leading zero
%H: 24 hour with leading zero
%{hour}: 24 hour without leading zero
%l: 12 hour with leading zero
%{hour_12}: 12 hour without leading zero
%M: minute with leading zero
%{minute}: minute without leading zero
%S: second with leading zero
%{second}: second without leading zero
%{time_zone_long_name}: Time zone, e. g. 'Europe/Berlin'
%{epoch}: Seconds since the epoch (OS specific)
%{offset}: Offset in seconds to GMT/UTC
my $DateTimeString = $DateTimeObject->Format( Format => '%Y-%m-%d %H:%M:%S' );
Returns:
my $String = '2016-01-22 18:07:23';
=cut
sub Format {
my ( $Self, %Param ) = @_;
for my $RequiredParam (qw( Format )) {
if ( !defined $Param{$RequiredParam} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing parameter $RequiredParam.",
);
return;
}
}
return $Self->{CPANDateTimeObject}->strftime( $Param{Format} );
}
=head2 ToEpoch()
Returns date/time as seconds since the epoch.
my $Epoch = $DateTimeObject->ToEpoch();
Returns e. g.:
my $Epoch = 1454420017;
=cut
sub ToEpoch {
my ( $Self, %Param ) = @_;
return $Self->{CPANDateTimeObject}->epoch();
}
=head2 ToString()
Returns date/time as string.
my $DateTimeString = $DateTimeObject->ToString();
Returns e. g.:
$DateTimeString = '2016-01-31 14:05:45'
=cut
sub ToString {
my ( $Self, %Param ) = @_;
return $Self->Format( Format => '%Y-%m-%d %H:%M:%S' );
}
=head2 ToEmailTimeStamp()
Returns the date/time of this object as time stamp in RFC 2822 format to be used in email headers.
my $MailTimeStamp = $DateTimeObject->ToEmailTimeStamp();
# Typical usage:
# You want to have the date/time of OTRS + its UTC offset, so:
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $MailTimeStamp = $DateTimeObject->ToEmailTimeStamp();
# If you already have a DateTime object, possibly in another time zone:
$DateTimeObject->ToOTRSTimeZone();
my $MailTimeStamp = $DateTimeObject->ToEmailTimeStamp();
Returns:
my $String = 'Wed, 2 Sep 2014 16:30:57 +0200';
=cut
sub ToEmailTimeStamp {
my ( $Self, %Param ) = @_;
# According to RFC 2822, section 3.3
# The date and time-of-day SHOULD express local time.
#
# The zone specifies the offset from Coordinated Universal Time (UTC,
# formerly referred to as "Greenwich Mean Time") that the date and
# time-of-day represent. The "+" or "-" indicates whether the
# time-of-day is ahead of (i.e., east of) or behind (i.e., west of)
# Universal Time. The first two digits indicate the number of hours
# difference from Universal Time, and the last two digits indicate the
# number of minutes difference from Universal Time. (Hence, +hhmm
# means +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm)
# minutes). The form "+0000" SHOULD be used to indicate a time zone at
# Universal Time. Though "-0000" also indicates Universal Time, it is
# used to indicate that the time was generated on a system that may be
# in a local time zone other than Universal Time and therefore
# indicates that the date-time contains no information about the local
# time zone.
my $EmailTimeStamp = $Self->Format(
Format => '%a, %{day} %b %Y %H:%M:%S %z',
);
return $EmailTimeStamp;
}
=head2 ToCTimeString()
Returns date and time as ctime string, as for example returned by Perl's C<localtime> and C<gmtime> in scalar context.
my $CTimeString = $DateTimeObject->ToCTimeString();
Returns:
my $String = 'Fri Feb 19 16:07:31 2016';
=cut
sub ToCTimeString {
my ( $Self, %Param ) = @_;
my $LocalTimeString = $Self->Format(
Format => '%a %b %{day} %H:%M:%S %Y',
);
return $LocalTimeString;
}
=head2 IsVacationDay()
Checks if date/time of this object is a vacation day.
my $IsVacationDay = $DateTimeObject->IsVacationDay(
Calendar => 9, # optional, OTRS vacation days otherwise
);
Returns:
my $IsVacationDay = 'some vacation day', # description of vacation day or 0 if no vacation day.
=cut
sub IsVacationDay {
my ( $Self, %Param ) = @_;
my $OriginalDateTimeValues = $Self->Get();
# Get configured vacation days
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $TimeVacationDays = $ConfigObject->Get('TimeVacationDays');
my $TimeVacationDaysOneTime = $ConfigObject->Get('TimeVacationDaysOneTime');
if ( $Param{Calendar} ) {
if ( $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} . "Name" ) ) {
$TimeVacationDays = $ConfigObject->Get( "TimeVacationDays::Calendar" . $Param{Calendar} );
$TimeVacationDaysOneTime = $ConfigObject->Get(
"TimeVacationDaysOneTime::Calendar" . $Param{Calendar}
);
# Switch to time zone of calendar
my $TimeZone = $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} )
|| $Self->OTRSTimeZoneGet();
if ( defined $TimeZone ) {
$Self->ToTimeZone( TimeZone => $TimeZone );
}
}
}
my $DateTimeValues = $Self->Get();
my $VacationDay = $TimeVacationDays->{ $DateTimeValues->{Month} }->{ $DateTimeValues->{Day} };
my $VacationDayOneTime = $TimeVacationDaysOneTime->{ $DateTimeValues->{Year} }->{ $DateTimeValues->{Month} }
->{ $DateTimeValues->{Day} };
# Switch back to original time zone
$Self->ToTimeZone( TimeZone => $OriginalDateTimeValues->{TimeZone} );
return $VacationDay if defined $VacationDay;
return $VacationDayOneTime if defined $VacationDayOneTime;
return 0;
}
=head2 LastDayOfMonthGet()
Returns the last day of the month.
$LastDayOfMonth = $DateTimeObject->LastDayOfMonthGet();
Returns:
my $LastDayOfMonth = {
Day => 31,
DayOfWeek => 5,
DayAbbr => 'Fri',
};
=cut
sub LastDayOfMonthGet {
my ( $Self, %Param ) = @_;
my $DateTimeValues = $Self->Get();
my $TempCPANDateTimeObject;
eval {
$TempCPANDateTimeObject = DateTime->last_day_of_month(
year => $DateTimeValues->{Year},
month => $DateTimeValues->{Month},
);
};
return if !$TempCPANDateTimeObject;
my $Result = {
Day => $TempCPANDateTimeObject->day(),
DayOfWeek => $TempCPANDateTimeObject->day_of_week(),
DayAbbr => $TempCPANDateTimeObject->day_abbr(),
};
return $Result;
}
=head2 Clone()
Clones the DateTime object.
my $ClonedDateTimeObject = $DateTimeObject->Clone();
=cut
sub Clone {
my ( $Self, %Param ) = @_;
return __PACKAGE__->new(
_CPANDateTimeObject => $Self->{CPANDateTimeObject}->clone()
);
}
=head2 TimeZoneList()
Returns an array ref of available time zones.
my $TimeZones = $DateTimeObject->TimeZoneList();
You can also call this method without an object:
my $TimeZones = Kernel::System::DateTime->TimeZoneList();
Returns:
my $TimeZoneList = [
# ...
'Europe/Amsterdam',
'Europe/Andorra',
'Europe/Athens',
# ...
];
=cut
sub TimeZoneList {
my @TimeZones = @{ DateTime::TimeZone->all_names() };
# add missing UTC time zone for certain DateTime versions
my %TimeZones = map { $_ => 1 } @TimeZones;
if ( !exists $TimeZones{UTC} ) {
push @TimeZones, 'UTC';
}
return \@TimeZones;
}
=head2 TimeZoneByOffsetList()
Returns a list of time zones by offset in hours. Of course, the resulting list depends on the date/time set within this
DateTime object.
my %TimeZoneByOffset = $DateTimeObject->TimeZoneByOffsetList();
Returns:
my $TimeZoneByOffsetList = {
# ...
-9 => [ 'America/Adak', 'Pacific/Gambier', ],
# ...
2 => [
# ...
'Europe/Berlin',
# ...
],
# ...
8.75 => [ 'Australia/Eucla', ],
# ...
};
=cut
sub TimeZoneByOffsetList {
my ( $Self, %Param ) = @_;
my $DateTimeObject = $Self->Clone();
my $TimeZones = $Self->TimeZoneList();
my %TimeZoneByOffset;
for my $TimeZone ( sort @{$TimeZones} ) {
$DateTimeObject->ToTimeZone( TimeZone => $TimeZone );
my $TimeZoneOffset = $DateTimeObject->Format( Format => '%{offset}' ) / 60 / 60;
if ( exists $TimeZoneByOffset{$TimeZoneOffset} ) {
push @{ $TimeZoneByOffset{$TimeZoneOffset} }, $TimeZone;
}
else {
$TimeZoneByOffset{$TimeZoneOffset} = [ $TimeZone, ];
}
}
return \%TimeZoneByOffset;
}
=head2 IsTimeZoneValid()
Checks if the given time zone is valid.
my $Valid = $DateTimeObject->IsTimeZoneValid( TimeZone => 'Europe/Berlin' );
Returns:
$ValidID = 1; # if given time zone is valid, 0 otherwise.
=cut
my %ValidTimeZones; # Cache for all instances.
sub IsTimeZoneValid {
my ( $Self, %Param ) = @_;
for my $RequiredParam (qw( TimeZone )) {
if ( !defined $Param{$RequiredParam} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing parameter $RequiredParam.",
);
return;
}
}
# allow DateTime internal time zone in 'floating'
return 1 if $Param{TimeZone} eq 'floating';
if ( !%ValidTimeZones ) {
%ValidTimeZones = map { $_ => 1 } @{ $Self->TimeZoneList() };
}
return $ValidTimeZones{ $Param{TimeZone} } ? 1 : 0;
}
=head2 OTRSTimeZoneGet()
Returns the time zone set for OTRS.
my $OTRSTimeZone = $DateTimeObject->OTRSTimeZoneGet();
# You can also call this method without an object:
#my $OTRSTimeZone = Kernel::System::DateTime->OTRSTimeZoneGet();
Returns:
my $OTRSTimeZone = 'Europe/Berlin';
=cut
sub OTRSTimeZoneGet {
return $Kernel::OM->Get('Kernel::Config')->Get('OTRSTimeZone') || 'UTC';
}
=head2 UserDefaultTimeZoneGet()
Returns the time zone set as default in SysConfig UserDefaultTimeZone for newly created users or existing users without
time zone setting.
my $UserDefaultTimeZoneGet = $DateTimeObject->UserDefaultTimeZoneGet();
You can also call this method without an object:
my $UserDefaultTimeZoneGet = Kernel::System::DateTime->UserDefaultTimeZoneGet();
Returns:
my $UserDefaultTimeZone = 'Europe/Berlin';
=cut
sub UserDefaultTimeZoneGet {
return $Kernel::OM->Get('Kernel::Config')->Get('UserDefaultTimeZone') || 'UTC';
}
=head2 SystemTimeZoneGet()
Returns the time zone of the system.
my $SystemTimeZone = $DateTimeObject->SystemTimeZoneGet();
You can also call this method without an object:
my $SystemTimeZone = Kernel::System::DateTime->SystemTimeZoneGet();
Returns:
my $SystemTimeZone = 'Europe/Berlin';
=cut
sub SystemTimeZoneGet {
return DateTime::TimeZone->new( name => 'local' )->name();
}
=begin Internal:
=head2 _ToCPANDateTimeParamNames()
Maps date/time parameter names expected by the methods of this package to the ones expected by CPAN/Perl DateTime
package.
my $DateTimeParams = $DateTimeObject->_ToCPANDateTimeParamNames(
Year => 2016,
Month => 1,
Day => 22,
Hour => 17,
Minute => 20,
Second => 2,
TimeZone => 'Europe/Berlin',
);
Returns:
my $CPANDateTimeParamNames = {
year => 2016,
month => 1,
day => 22,
hour => 17,
minute => 20,
second => 2,
time_zone => 'Europe/Berlin',
};
=cut
sub _ToCPANDateTimeParamNames {
my ( $Self, %Param ) = @_;
my %ParamNameMapping = (
Year => 'year',
Month => 'month',
Day => 'day',
Hour => 'hour',
Minute => 'minute',
Second => 'second',
TimeZone => 'time_zone',
Years => 'years',
Months => 'months',
Weeks => 'weeks',
Days => 'days',
Hours => 'hours',
Minutes => 'minutes',
Seconds => 'seconds',
);
my $DateTimeParams;
PARAMNAME:
for my $ParamName ( sort keys %ParamNameMapping ) {
next PARAMNAME if !exists $Param{$ParamName};
$DateTimeParams->{ $ParamNameMapping{$ParamName} } = $Param{$ParamName};
}
return $DateTimeParams;
}
=head2 _StringToHash()
Parses a date/time string and returns a hash ref.
my $DateTimeHash = $DateTimeObject->_StringToHash( String => '2016-08-14 22:45:00' );
# Sets second to 0:
my $DateTimeHash = $DateTimeObject->_StringToHash( String => '2016-08-14 22:45' );
# Sets hour, minute and second to 0:
my $DateTimeHash = $DateTimeObject->_StringToHash( String => '2016-08-14' );
Please see C<L</new()>> for the list of supported string formats.
Returns:
my $DateTimeHash = {
Year => 2016,
Month => 8,
Day => 14,
Hour => 22,
Minute => 45,
Second => 0,
};
=cut
sub _StringToHash {
my ( $Self, %Param ) = @_;
for my $RequiredParam (qw( String )) {
if ( !defined $Param{$RequiredParam} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing parameter $RequiredParam.",
);
return;
}
}
if ( $Param{String} =~ m{\A(\d{4})-(\d{1,2})-(\d{1,2})(\s(\d{1,2}):(\d{1,2})(:(\d{1,2}))?)?\z} ) {
my $DateTimeHash = {
Year => int $1,
Month => int $2,
Day => int $3,
Hour => defined $5 ? int $5 : 0,
Minute => defined $6 ? int $6 : 0,
Second => defined $8 ? int $8 : 0,
};
return $DateTimeHash;
}
# Match the following formats:
# - yyyy-mm-ddThh:mm:ss+tt:zz
# - yyyy-mm-ddThh:mm:ss+ttzz
# - yyyy-mm-ddThh:mm:ss-tt:zz
# - yyyy-mm-ddThh:mm:ss-ttzz
# - yyyy-mm-ddThh:mm:ss [timezone]
# - yyyy-mm-ddThh:mm:ss[timezone]
if ( $Param{String} =~ /^\d{4}-\d{1,2}-\d{1,2}T\d{1,2}:\d{1,2}:\d{1,2}(.+)$/i ) {
my ( $Year, $Month, $Day, $Hour, $Minute, $Second, $OffsetOrTZ ) =
( $Param{String} =~ m/^(\d{4})-(\d{2})-(\d{2})T(\d{1,2}):(\d{1,2}):(\d{1,2})\s*(.+)$/i );
my $DateTimeHash = {
Year => int $Year,
Month => int $Month,
Day => int $Day,
Hour => int $Hour,
Minute => int $Minute,
Second => int $Second,
};
# Check if the rest 'OffsetOrTZ' is an offset or timezone.
# If isn't an offset consider it a timezone
if ( $OffsetOrTZ !~ m/(\+|\-)\d{2}:?\d{2}/i ) {
# Make sure the time zone is valid. Otherwise, assume UTC.
if ( !$Self->IsTimeZoneValid( TimeZone => $OffsetOrTZ ) ) {
$OffsetOrTZ = 'UTC';
}
return {
%{$DateTimeHash},
TimeZone => $OffsetOrTZ,
};
}
# It's an offset, get the time in GMT/UTC.
$OffsetOrTZ =~ s/://i; # Remove the ':'
my $DT = DateTime->new(
( map { lcfirst $_ => $DateTimeHash->{$_} } keys %{$DateTimeHash} ),
time_zone => $OffsetOrTZ,
);
$DT->set_time_zone('UTC');
$DT->set_time_zone( $Self->OTRSTimeZoneGet() );
return {
( map { ucfirst $_ => $DT->$_() } qw(year month day hour minute second) )
};
}
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Invalid date/time string $Param{String}.",
);
return;
}
=head2 _CPANDateTimeObjectCreate()
Creates a CPAN DateTime object which will be stored within this object and used for date/time calculations.
# Create an object with current date and time
# within time zone set in SysConfig OTRSTimeZone:
my $CPANDateTimeObject = $DateTimeObject->_CPANDateTimeObjectCreate();
# Create an object with current date and time
# within a certain time zone:
my $CPANDateTimeObject = $DateTimeObject->_CPANDateTimeObjectCreate(
TimeZone => 'Europe/Berlin',
);
# Create an object with a specific date and time:
my $CPANDateTimeObject = $DateTimeObject->_CPANDateTimeObjectCreate(
Year => 2016,
Month => 1,
Day => 22,
Hour => 12, # optional, defaults to 0
Minute => 35, # optional, defaults to 0
Second => 59, # optional, defaults to 0
TimeZone => 'Europe/Berlin', # optional, defaults to setting of SysConfig OTRSTimeZone
);
# Create an object from an epoch timestamp. These timestamps are always UTC/GMT,
# hence time zone will automatically be set to UTC.
#
# If parameter Epoch is present, all other parameters except TimeZone will be ignored.
my $CPANDateTimeObject = $DateTimeObject->_CPANDateTimeObjectCreate(
Epoch => 1453911685,
);
# Create an object from a date/time string.
#
# If parameter String is given, Year, Month, Day, Hour, Minute and Second will be ignored. Please see C<L</new()>>
# for the list of supported string formats.
my $CPANDateTimeObject = $DateTimeObject->_CPANDateTimeObjectCreate(
String => '2016-08-14 22:45:00',
TimeZone => 'Europe/Berlin', # optional, defaults to setting of SysConfig OTRSTimeZone
);
=cut
sub _CPANDateTimeObjectCreate {
my ( $Self, %Param ) = @_;
# Create object from string
if ( defined $Param{String} ) {
my $DateTimeHash = $Self->_StringToHash( String => $Param{String} );
if ( !IsHashRefWithData($DateTimeHash) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Invalid value for String: $Param{String}.",
);
return;
}
%Param = (
TimeZone => $Param{TimeZone},
%{$DateTimeHash},
);
}
my $CPANDateTimeObject;
my $TimeZone = $Param{TimeZone} || $Self->OTRSTimeZoneGet();
if ( !$Self->IsTimeZoneValid( TimeZone => $TimeZone ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Invalid value for TimeZone: $TimeZone.",
);
return;
}
# Create object from epoch
if ( defined $Param{Epoch} ) {
if ( $Param{Epoch} !~ m{\A[+-]?\d+\z}sm ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Invalid value for Epoch: $Param{Epoch}.",
);
return;
}
eval {
$CPANDateTimeObject = DateTime->from_epoch(
epoch => $Param{Epoch},
time_zone => $TimeZone,
locale => $Self->{Locale},
);
};
return $CPANDateTimeObject;
}
$Param{TimeZone} = $TimeZone;
# Check if date/time params were given, excluding time zone
my $DateTimeParamsGiven = %Param && ( !defined $Param{TimeZone} || keys %Param > 1 );
# Create object from date/time parameters
if ($DateTimeParamsGiven) {
# Check existence of required params
for my $RequiredParam (qw( Year Month Day )) {
if ( !$Param{$RequiredParam} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
'Priority' => 'Error',
'Message' => "Missing parameter $RequiredParam.",
);
return;
}
}
# Create DateTime object
my $DateTimeParams = $Self->_ToCPANDateTimeParamNames(%Param);
eval {
$CPANDateTimeObject = DateTime->new(
%{$DateTimeParams},
locale => $Self->{Locale},
);
};
return $CPANDateTimeObject;
}
# Create object with current date/time.
eval {
$CPANDateTimeObject = DateTime->now(
time_zone => $TimeZone,
locale => $Self->{Locale},
);
};
return $CPANDateTimeObject;
}
=head2 _OpIsNewerThan()
Operator overloading for >
=cut
sub _OpIsNewerThan {
my ( $Self, $OtherDateTimeObject ) = @_;
my $Result = $Self->Compare( DateTimeObject => $OtherDateTimeObject );
return if !defined $Result;
$Result = $Result == 1 ? 1 : 0;
return $Result;
}
=head2 _OpIsOlderThan()
Operator overloading for <
=cut
sub _OpIsOlderThan {
my ( $Self, $OtherDateTimeObject ) = @_;
my $Result = $Self->Compare( DateTimeObject => $OtherDateTimeObject );
return if !defined $Result;
$Result = $Result == -1 ? 1 : 0;
return $Result;
}
=head2 _OpIsNewerThanOrEquals()
Operator overloading for >=
=cut
sub _OpIsNewerThanOrEquals {
my ( $Self, $OtherDateTimeObject ) = @_;
my $Result = $Self->Compare( DateTimeObject => $OtherDateTimeObject );
return if !defined $Result;
$Result = $Result >= 0 ? 1 : 0;
return $Result;
}
=head2 _OpIsOlderThanOrEquals()
Operator overloading for <=
=cut
sub _OpIsOlderThanOrEquals {
my ( $Self, $OtherDateTimeObject ) = @_;
my $Result = $Self->Compare( DateTimeObject => $OtherDateTimeObject );
return if !defined $Result;
$Result = $Result <= 0 ? 1 : 0;
return $Result;
}
=head2 _OpEquals()
Operator overloading for ==
=cut
sub _OpEquals {
my ( $Self, $OtherDateTimeObject ) = @_;
my $Result = $Self->Compare( DateTimeObject => $OtherDateTimeObject );
return if !defined $Result;
$Result = !$Result ? 1 : 0;
return $Result;
}
=head2 _OpNotEquals()
Operator overloading for !=
=cut
sub _OpNotEquals {
my ( $Self, $OtherDateTimeObject ) = @_;
my $Result = $Self->Compare( DateTimeObject => $OtherDateTimeObject );
return if !defined $Result;
$Result = $Result != 0 ? 1 : 0;
return $Result;
}
1;
=end Internal:
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L<https://otrs.org/>).
This software comes with ABSOLUTELY NO WARRANTY. For details, see
the enclosed file COPYING for license information (GPL). If you
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
=cut