package time; use warnings "all"; use strict; use utf8; use Time::Local(); use DateTime(); use Date::Calc(); use Date::Manip(); use POSIX qw(strftime); use Data::Dumper; use config(); use base 'Exporter'; 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 = shift || 'en'; return $NAMES->{$language}->{weekdays}; } sub getWeekdayNamesShort { my $language = shift || 'en'; return $NAMES->{$language}->{weekdays_abbr}; } sub getMonthNames { my $language = shift || 'en'; return $NAMES->{$language}->{months}; } sub getMonthNamesShort { my $language = shift || 'en'; return $NAMES->{$language}->{months_abbr}; } sub getWeekdayIndex($) { my $weekday = shift || ''; 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 = shift; 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]; # print $datetime."\n"; 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 = shift; my $time_zone = shift; $datetime = get_datetime( $datetime, $time_zone ); return $datetime->epoch(); } # get full utc datetime including timezone offset sub datetime_to_utc_datetime { my $datetime = shift; my $time_zone = shift; $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 = shift; my $hours = shift; $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 = shift; my $minutes = shift; $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 = shift; my $days = shift; $days = 0 unless defined $days; my $time = datetime_to_array($datetime); #print STDERR Dumper($time); ( $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 = shift; my $days = shift; $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 = shift; $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 = shift; $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 = $_[0] || ''; 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 = shift; 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] ); } my $month = shift; my $day = shift; my $hour = shift || '0'; my $minute = shift || '0'; my $second = shift || '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 = shift; if ( ref($date) eq 'ARRAY' ) { return sprintf( "%04d-%02d-%02d", $date->[0], $date->[1], $date->[2] ); } my $month = shift; my $day = shift; return sprintf( "%04d-%02d-%02d", $date, $month, $day ); } sub array_to_time { my $date = shift; if ( ref($date) eq 'ARRAY' ) { return sprintf( "%02d:%02d:%02d", $date->[3], $date->[4], $date->[5] ); } my $minute = shift || 0; my $second = shift || 0; return sprintf( "%02d:%02d:%02d", $date, $minute, $second ); } sub array_to_time_hm { my $date = shift; if ( ref($date) eq 'ARRAY' ) { return sprintf( "%02d:%02d", $date->[3], $date->[4] ); } my $minute = shift || 0; return sprintf( "%02d:%02d", $date, $minute ); } # get number of days between two days sub days_between { my $today = $_[0]; my $date = $_[1]; 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 = $_[0]; 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 = shift; my $end = shift; my $timezone = shift; $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 = shift; my $end = shift; my $timezone = shift || '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 = shift; 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 = shift; 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 = shift; 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 = shift; 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 = shift; 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 = shift; 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 = shift; 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 = shift; my $datetime = shift; my $language = shift || $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 = shift; my $datetime = shift; my $language = shift || $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 = shift; if ( defined $datetime && $datetime =~ /(\d\d?\:\d\d?)/ ) { return $1; } return $datetime; } #get offset from given time_zone sub utc_offset { my $time_zone = shift; 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 = shift; my $datetime = time::time_to_datetime( time() ); my $hour = ( time::datetime_to_array($datetime) )->[3]; #print STDERR "datetime=$datetime hour=$hour\n"; #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 = shift; my $timezone = shift; 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 = shift; # datetime string my $end = shift; # datetime string my $nth = shift; # every nth week of month my $weekday = shift; # 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;