package time; use strict; use warnings; no warnings 'redefine'; use utf8; use Time::Local(); use DateTime(); use Date::Calc(); use Date::Manip(); use POSIX qw(strftime); use Data::Dumper; use config(); our @EXPORT_OK = qw( format_datetime format_time date_format time_format datetime_to_time time_to_datetime time_to_date datetime_to_date add_days_to_datetime add_hours_to_datetime add_minutes_to_datetime add_days_to_date datetime_to_array date_to_array array_to_date array_to_datetime array_to_time array_to_time_hm date_cond time_cond check_date check_time check_datetime check_year_month datetime_to_rfc822 get_datetime datetime_to_utc datetime_to_utc_datetime get_duration get_duration_seconds getDurations getWeekdayIndex getWeekdayNames getWeekdayNamesShort getMonthNames getMonthNamesShort ); my $NAMES = { 'de' => { months => [ 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember' ], months_abbr => [ 'Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez' ], weekdays => [ 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag' ], weekdays_abbr => [ 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So' ], }, 'en' => { months => [ 'January', 'February', 'March', 'April', 'May', 'June', 'Jule', 'August', 'September', 'October', 'November', 'December' ], months_abbr => [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], weekdays => [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ], weekdays_abbr => [ 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su' ], }, }; # map starting with monday=0 my $WEEKDAY_INDEX = { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, 'Mo' => 0, 'Tu' => 1, 'Di' => 1, 'We' => 2, 'Mi' => 2, 'Th' => 3, 'Do' => 3, 'Fr' => 4, 'Sa' => 5, 'Su' => 6, 'So' => 6 }; my $DURATIONS = [ 0, 5, 10, 15, 20, 30, 40, 45, 50, 60, 70, 75, 80, 90, 100, 105, 110, 115, 120, 135, 150, 165, 180, 195, 210, 225, 240, 300, 360, 420, 480, 540, 600, 660, 720, 1440 ]; sub getDurations() { return $DURATIONS; } sub getWeekdayNames(;$) { my ($language) = @_; $language ||= 'en'; return $NAMES->{$language}->{weekdays}; } sub getWeekdayNamesShort(;$) { my ($language) = @_; $language ||= 'en'; return $NAMES->{$language}->{weekdays_abbr}; } sub getMonthNames(;$) { my ($language) = @_; $language ||= 'en'; return $NAMES->{$language}->{months}; } sub getMonthNamesShort(;$) { my ($language) = @_; $language ||= 'en'; return $NAMES->{$language}->{months_abbr}; } sub getWeekdayIndex(;$) { my ($weekday) = @_; $weekday ||= ''; return $WEEKDAY_INDEX->{$weekday}; } # map starting with monday=1 sub getWeekdays { return { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 'Mo' => 1, 'Tu' => 2, 'Di' => 2, 'We' => 3, 'Mi' => 3, 'Th' => 4, 'Do' => 4, 'Fr' => 5, 'Sa' => 6, 'Su' => 7, 'So' => 7 }; } #deprecated, for wordpress sync sub format_datetime(;$) { my ($datetime) = @_; return $datetime if ( $datetime eq '' ); return add_hours_to_datetime( $datetime, 0 ); } #deprecated sub format_time($) { my $t = $_[0]; my $year = $t->[5] + 1900; my $month = $t->[4] + 1; $month = '0' . $month if ( length($month) == 1 ); my $day = $t->[3]; $day = '0' . $day if ( length($day) == 1 ); my $hour = $t->[2]; $hour = '0' . $hour if ( length($hour) == 1 ); my $minute = $t->[1]; $minute = '0' . $minute if ( length($minute) == 1 ); return [ $day, $month, $year, $hour, $minute ]; } # convert datetime to unix time sub datetime_to_time ($){ my $datetime = $_[0]; if ( $datetime =~ /(\d\d\d\d)\-(\d+)\-(\d+)[T\s](\d+)\:(\d+)(\:(\d+))?/ ) { my $year = $1; my $month = $2 - 1; my $day = $3; my $hour = $4; my $minute = $5; my $second = $8 || 0; return Time::Local::timelocal( $second, $minute, $hour, $day, $month, $year ) || print STDERR "datetime_to_time: no valid date time found! ($datetime)\n"; } else { print STDERR "datetime_to_time: no valid date time found! ($datetime)\n"; return -1; } } #get rfc822 datetime string from datetime string sub datetime_to_rfc822($) { my $datetime = $_[0]; my $time = datetime_to_time($datetime); return POSIX::strftime( "%a, %d %b %Y %H:%M:%S %z", localtime($time) ); } #get seconds from epoch sub datetime_to_utc($$) { my ($datetime, $time_zone) = @_; $datetime = get_datetime( $datetime, $time_zone ); return $datetime->epoch(); } # get full utc datetime including timezone offset sub datetime_to_utc_datetime($$) { my ($datetime, $time_zone) = @_; $datetime = get_datetime( $datetime, $time_zone ); return $datetime->format_cldr("yyyy-MM-ddTHH:mm:ssZZZZZ"); } #add hours to datetime string sub add_hours_to_datetime($;$) { my ($datetime, $hours) = @_; $hours = 0 unless defined $hours; return time_to_datetime( datetime_to_time($datetime) + ( 3600 * $hours ) ); } #add minutes to datetime string sub add_minutes_to_datetime($;$) { my ($datetime, $minutes) = @_; $minutes = 0 unless defined $minutes; return time_to_datetime( datetime_to_time($datetime) + ( 60 * $minutes ) ); } #add days to datetime string sub add_days_to_datetime($;$) { my ($datetime, $days) = @_; $days = 0 unless defined $days; my $time = datetime_to_array($datetime); ( $time->[0], $time->[1], $time->[2] ) = Date::Calc::Add_Delta_Days( $time->[0] + 0, $time->[1] + 0, $time->[2] + 0, $days ); return array_to_datetime($time); } sub add_days_to_date($;$) { my ($datetime, $days) = @_; $days = 0 unless defined $days; my $date = date_to_array($datetime); ( $date->[0], $date->[1], $date->[2] ) = Date::Calc::Add_Delta_Days( $date->[0] + 0, $date->[1] + 0, $date->[2] + 0, $days ); return array_to_date($date); } # convert unix time to datetime format sub time_to_datetime(;$) { my ($time) = @_; $time = time() unless ( defined $time ) && ( $time ne '' ); my @t = localtime($time); return sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $t[5] + 1900, $t[4] + 1, $t[3], $t[2], $t[1], $t[0] ); } # convert unix time to date format sub time_to_date(;$) { my ($time) = @_; $time = time() unless ( defined $time ) && ( $time ne '' ); my @t = localtime($time); return sprintf( '%04d-%02d-%02d', $t[5] + 1900, $t[4] + 1, $t[3] ); } # convert datetime to a array of date/time values sub datetime_to_array(;$) { my ($datetime) = @_; $datetime ||= ''; if ( $datetime =~ /(\d\d\d\d)\-(\d+)\-(\d+)([T\s]+(\d+)\:(\d+)(\:(\d+))?)?/ ) { my $year = $1; my $month = $2; my $day = $3; my $hour = $5 || '00'; my $minute = $6 || '00'; my $second = $8 || '00'; return [ $year, $month, $day, $hour, $minute, $second ]; } return undef; } # convert datetime to date sub datetime_to_date(;$) { my $datetime = $_[0] || ''; if ( $datetime =~ /(\d\d\d\d)\-(\d+)\-(\d+)/ ) { my $year = $1; my $month = $2; my $day = $3; return sprintf( "%04d-%02d-%02d", $year, $month, $day ); } return undef; } #convert datetime array or single value to datetime string sub array_to_datetime(;$) { my ($date, $month, $day, $hour, $minute, $second) = @_; if ( ref($date) eq 'ARRAY' ) { return sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $date->[0], $date->[1], $date->[2], $date->[3], $date->[4], $date->[5] ); } $hour ||= '0'; $minute ||= '0'; $second ||= '0'; return sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $date, $month, $day, $hour, $minute, $second ); } #convert date array or single values to date string sub array_to_date($;$$) { my ($date, $month, $day) = @_; if ( ref($date) eq 'ARRAY' ) { return sprintf( "%04d-%02d-%02d", $date->[0], $date->[1], $date->[2] ); } return sprintf( "%04d-%02d-%02d", $date, $month, $day ); } sub array_to_time(;$) { my ($date, $minute, $second) = @_; if ( ref($date) eq 'ARRAY' ) { return sprintf( "%02d:%02d:%02d", $date->[3], $date->[4], $date->[5] ); } $minute ||= '0'; $second ||= '0'; return sprintf( "%02d:%02d:%02d", $date, $minute, $second ); } sub array_to_time_hm(;$) { my ($date, $minute) = @_; if ( ref($date) eq 'ARRAY' ) { return sprintf( "%02d:%02d", $date->[3], $date->[4] ); } $minute ||= '0'; return sprintf( "%02d:%02d", $date, $minute ); } # get number of days between two days sub days_between($$) { my ($today, $date) = @_; my $delta_days = eval { Date::Calc::Delta_Days( $today->[0], $today->[1], $today->[2], $date->[0], $date->[1], $date->[2] ) }; return $delta_days; } sub dayOfYear($) { my ($datetime) = @_; if ( $datetime =~ /(\d\d\d\d)\-(\d+)\-(\d+)/ ) { my $year = $1; my $month = $2; my $day = $3; return Date::Calc::Day_of_Year( $year, $month, $day ); } return undef; } # get duration in minutes sub get_duration($$$) { my ($start, $end, $timezone) = @_; $start = time::get_datetime( $start, $timezone ); $end = time::get_datetime( $end, $timezone ); my $duration = $end->epoch() - $start->epoch(); return $duration / 60; } # get duration in seconds sub get_duration_seconds($$;$) { my ($start, $end, $timezone) = @_; $timezone ||= 'UTC'; unless ( defined $start ) { print STDERR "time::get_duration_seconds(): start is missing\n"; return 0; } unless ( defined $end ) { print STDERR "time::get_duration_seconds(): end is missing\n"; return 0; } $start = time::get_datetime( $start, $timezone ); $end = time::get_datetime( $end, $timezone ); unless ( defined $start ) { print STDERR "time::get_duration_seconds(): invalid start\n"; return 0; } unless ( defined $end ) { print STDERR "time::get_duration_seconds(): invalid end\n"; return 0; } my $duration = $end->epoch() - $start->epoch(); return $duration; } # convert date string to a array of date values sub date_to_array($) { my $datetime = $_[0]; if ( $datetime =~ /(\d\d\d\d)\-(\d+)\-(\d+)/ ) { my $year = $1; my $month = $2; my $day = $3; return [ $year, $month, $day ]; } return undef; } # parse date string and return date string # pass 'today', return '' on parse error sub date_cond($) { my ($date) = @_; return '' if ( $date eq '' ); if ( $date =~ /(\d\d\d\d)\-(\d\d?)\-(\d\d?)/ ) { my $year = $1; my $month = $2; my $day = $3; return sprintf( "%04d-%02d-%02d", $year, $month, $day ); } return 'today' if ( $date eq 'today' ); return ''; } #parse time and return time string hh:mm:ss #return hh:00 if time is 'now' sub time_cond($) { my ($time) = @_; return '' if ( $time eq '' ); if ( $time =~ /(\d\d?)\:(\d\d?)(\:(\d\d))?/ ) { my $hour = $1; my $minute = $2; my $second = $4 || '00'; return sprintf( "%02d:%02d:%02d", $hour, $minute, $second ); } if ( $time eq 'now' ) { my $date = datetime_to_array( time_to_datetime( time() ) ); my $hour = $date->[3] - 2; $hour = 0 if ( $hour < 0 ); $time = sprintf( "%02d:00", $hour ); return $time; } return ''; } #parse date and time string and return yyyy-mm-ddThh:mm:ss sub datetime_cond($) { my ($datetime) = @_; return '' if ( $datetime eq '' ); ( my $date, my $time ) = split /[ T]/, $datetime; $date = time::date_cond($date); return '' if ( $date eq '' ); $time = time::time_cond($time); return '' if ( $time eq '' ); return $date . 'T' . $time; } sub check_date($) { my ($date) = @_; return "" if ( !defined $date ) || ( $date eq '' ); if ( $date =~ /(\d\d\d\d)\-(\d\d?)\-(\d\d?)/ ) { return $1 . '-' . $2 . '-' . $3; } elsif ( $date =~ /(\d\d?)\.(\d\d?)\.(\d\d\d\d)/ ) { return $3 . '-' . $2 . '-' . $1; } return $date if ( $date eq 'today' || $date eq 'tomorrow' || $date eq 'yesterday' ); return -1; #error("no valid date format given!"); } sub check_time($) { my ($time) = @_; return "" if ( !defined $time ) || ( $time eq '' ); return $time if ( $time eq 'now' ) || ( $time eq 'future' ); if ( $time =~ /(\d\d?)\:(\d\d?)/ ) { return $1 . ':' . $2; } return -1; } sub check_datetime($) { my ($date) = @_; return "" if ( !defined $date ) || ( $date eq '' ); if ( $date =~ /(\d\d\d\d)\-(\d\d?)\-(\d\d?)[T ](\d\d?)\:(\d\d?)/ ) { return sprintf( "%04d-%02d-%02dT%02d:%02d", $1, $2, $3, $4, $5 ); } return -1; } sub check_year_month($) { my ($date) = @_; return -1 unless defined $date; return $date if ( $date eq '' ); if ( $date =~ /(\d\d\d\d)\-(\d\d?)/ ) { return $1 . '-' . $2 . '-' . $3; } return -1; } #TODO: remove config dependency sub date_time_format($$;$) { my ($config, $datetime, $language) = @_; $language ||= $config->{date}->{language} || 'en'; if ( defined $datetime && $datetime =~ /(\d\d\d\d)\-(\d\d?)\-(\d\d?)[\sT](\d\d?\:\d\d?)/ ) { my $time = $4; my $day = $3; my $month = $2; my $year = $1; $month = time::getMonthNamesShort($language)->[ $month - 1 ] || ''; return "$day. $month $year $time"; } return $datetime; } #format datetime to date string #TODO: remove config dependency sub date_format($$;$) { my ($config, $datetime, $language) = @_; $language ||= $config->{date}->{language} || 'en'; if ( defined $datetime && $datetime =~ /(\d\d\d\d)\-(\d\d?)\-(\d\d?)/ ) { my $day = $3; my $month = $2; my $year = $1; $month = time::getMonthNamesShort($language)->[ $month - 1 ] || ''; return "$day. $month $year"; } return $datetime; } #format datetime to time string sub time_format($) { my ($datetime) = @_; if ( defined $datetime && $datetime =~ /(\d\d?\:\d\d?)/ ) { return $1; } return $datetime; } #get offset from given time_zone sub utc_offset($) { my ($time_zone) = @_; my $datetime = DateTime->now(); $datetime->set_time_zone($time_zone); return $datetime->strftime("%z"); } #get weekday from (yyyy,mm,dd) sub weekday($$$) { my ( $year, $month, $day ) = @_; my $time = Time::Local::timelocal( 0, 0, 0, $day, $month - 1, $year ); return ( localtime($time) )[6]; } #get current date, related to starting day_starting_hour #TODO: remove config dependency sub get_event_date($) { my ($config) = @_; my $datetime = time::time_to_datetime( time() ); my $hour = ( time::datetime_to_array($datetime) )->[3]; #today: between 0:00 and starting_hour show last day if ( $hour < $config->{date}->{day_starting_hour} ) { my $date = time::datetime_to_array( time::add_days_to_datetime( $datetime, -1 ) ); return join( '-', ( $date->[0], $date->[1], $date->[2] ) ); } else { #today: between starting_hour and end of day show current day my $date = time::datetime_to_array( time::time_to_datetime( time() ) ); return join( '-', ( $date->[0], $date->[1], $date->[2] ) ); } } #get datetime object from datetime string sub get_datetime(;$$) { my ($datetime, $timezone) = @_; return unless defined $datetime; return if $datetime eq ''; my @l = @{ time::datetime_to_array($datetime) }; return undef if scalar(@l) == 0; # catch invalid datees $datetime = undef; eval { $datetime = DateTime->new( year => $l[0], month => $l[1], day => $l[2], hour => $l[3], minute => $l[4], second => $l[5], time_zone => $timezone ); }; return undef unless defined $datetime; $datetime->set_locale('de_DE'); return $datetime; } #get list of nth weekday in month from start to end sub get_nth_weekday_in_month(;$$$$) { my ($start, $end, $nth, $weekday) = @_; #datetime, datetime, every nth week of month, weekday [1..7,'Mo'-'Su','Mo'-'Fr'] return [] unless defined $start; return [] unless defined $end; return [] unless defined $nth; return [] unless defined $weekday; my $weekdays = time::getWeekdays(); return [] unless defined $weekdays->{$weekday}; $weekday = $weekdays->{$weekday}; my $dates = []; if ( $start =~ /(\d\d\d\d)-(\d\d)-(\d\d)[ T](\d\d)\:(\d\d)/ ) { my $hour = int($4); my $min = int($5); my $sec = 0; my @date = Date::Manip::ParseRecur( "0:1*$nth:$weekday:$hour:$min:$sec", "", $start, $end ); for my $date (@date) { if ( $date =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)\:(\d\d)\:(\d\d)/ ) { push @$dates, "$1-$2-$3 $4:$5:$6"; } } } return $dates; } #do not delete last line! 1;