# -- # 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>. # 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 is present, all other parameters will be ignored. Please see C> 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 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 and C 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> 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> # 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). 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. =cut