From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use strict;
use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT @EXPORT_OK);
$VERSION = '5.32';
=head1 NAME
Labyrinth::DTUtils - Date & Time Utilities for Labyrinth
=head1 SYNOPSIS
use Labyrinth::DTUtils;
=head1 DESCRIPTION
Various date & time utilities.
=head1 EXPORT
everything
=cut
# -------------------------------------
# Export Details
require Exporter;
@ISA = qw(Exporter);
%EXPORT_TAGS = (
'all' => [ qw(
DaySelect MonthSelect YearSelect PeriodSelect
formatDate unformatDate isMonth
) ]
);
@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
@EXPORT = ( @{ $EXPORT_TAGS{'all'} } );
#############################################################################
#Libraries
#############################################################################
#############################################################################
#Variables
#############################################################################
my @months = (
{ 'id' => 1, 'value' => "January", },
{ 'id' => 2, 'value' => "February", },
{ 'id' => 3, 'value' => "March", },
{ 'id' => 4, 'value' => "April", },
{ 'id' => 5, 'value' => "May", },
{ 'id' => 6, 'value' => "June", },
{ 'id' => 7, 'value' => "July", },
{ 'id' => 8, 'value' => "August", },
{ 'id' => 9, 'value' => "September", },
{ 'id' => 10, 'value' => "October", },
{ 'id' => 11, 'value' => "November", },
{ 'id' => 12, 'value' => "December" },
);
my @dotw = ( "Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" );
my @days = map {{'id'=>$_,'value'=> $_}} (1..31);
my @periods = (
{act => 'evnt-month', value => 'Month'},
{act => 'evnt-week', value => 'Week'},
{act => 'evnt-day', value => 'Day'}
);
my %formats = (
1 => 'YYYY',
2 => 'MONTH YYYY',
3 => 'DD/MM/YYYY',
4 => 'DABV MABV DD TIME24 YYYY',
5 => 'DAY, DD MONTH YYYY',
6 => 'DAY, DDEXT MONTH YYYY',
7 => 'DAY, DD MONTH YYYY (TIME12)',
8 => 'DAY, DDEXT MONTH YYYY (TIME12)',
9 => 'YYYY/MM/DD',
10 => 'DDEXT MONTH YYYY',
11 => 'YYYYMMDDThhmmss', # iCal date string
12 => 'YYYY-MM-DDThh:mm:ssZ', # RSS date string
13 => 'YYYYMMDD', # backwards date
14 => 'DABV, DDEXT MONTH YYYY',
15 => 'DD MABV YYYY',
16 => 'DABV, dd MABV YYYY hh:mm:ss TZ', # RFC-822 date string
17 => 'DAY, DD MONTH YYYY hh:mm:ss',
18 => 'DD/MM/YYYY hh:mm:ss',
19 => 'DDEXT MONTH YYYY',
20 => 'DABV, DD MABV YYYY hh:mm:ss',
21 => 'YYYY-MM-DD hh:mm:ss',
22 => 'YYYYMMDDhhmm',
);
my %unformats = (
11 => '(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})', # iCal date string
12 => '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z', # ISO 8601 date string
13 => '(\d{4})(\d{2})(\d{2})', # backwards date
22 => '(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})',
);
# decrees whether the date format above should be UTC
# time based, or allow for any Summer Time variations.
my %zonetime = (12 => 1, 16 => 1);
#############################################################################
#Subroutines
#############################################################################
=head1 FUNCTIONS
=head2 Dropdown Boxes
=over 4
=item DaySelect($opt,$blank)
Provides a Day dropdown selection box.
The option $opt allows the given day (numerical 1 - 31) to be the selected
option in the dropdown. If blank is true, a 'Select Day' option is added as
the first option to the dropdown.
=item MonthSelect($opt,$blank)
Provides a Month dropdown selection box.
The option $opt allows the given month (numerical 1 - 12) to be the selected
option in the dropdown. If blank is true, a 'Select Month' option is added as
the first option to the dropdown.
=item YearSelect($opt,$range,$blank,$dates)
Provides a Year dropdown selection box.
The option $opt allows the given month (numerical 1 - 12) to be the selected
option in the dropdown. If blank is true, a 'Select Month' option is added as
the first option to the dropdown.
If is specified, then the following criteria is used:
0 - default
1 - given dates, see $dates list
2 - oldest year to current year
3 - current year to future year
For oldest year, this is determined by the configuration setting
'year_past_offset' or 'year_past'. For the future year, this is determined by
the configuration setting 'year_future_offset'.
If the range is set to 1, the list of dates given in the $dates array
reference will be used.
=item PeriodSelect($opt,$blank)
Provides a Period dropdown selection box.
The option $opt allows the given period to be the selected option in the
dropdown. If blank is true, a 'Select Period' option is added as the first
option to the dropdown.
Current valid periods are:
opt value
-------------------
evnt-month Month
evnt-week Week
evnt-day Day
=back
=cut
sub DaySelect {
my ($opt,$blank) = @_;
my @list = @days;
unshift @list, {id=>0,value=>'Select Day'} if(defined $blank && $blank == 1);
DropDownRows($opt,'day','id','value',@list);
}
sub MonthSelect {
my ($opt,$blank) = @_;
my @list = @months;
unshift @list, {id=>0,value=>'Select Month'} if(defined $blank && $blank == 1);
DropDownRows($opt,'month','id','value',@list);
}
sub YearSelect {
my ($opt,$range,$blank,$dates) = @_;
my $year = formatDate(1);
my $past_offset = $settings{year_past_offset} || 0;
my $future_offset = defined $settings{year_future_offset} ? $settings{year_future_offset} : 4;
my $past = $past_offset ? $year - $past_offset : $settings{year_past};
my $future = $year + $future_offset;
$past ||= $year;
my @range = ($past .. $future);
if(defined $range) {
if($range == 1) { @range = @$dates }
elsif($range == 2) { @range = ($past .. $year) }
elsif($range == 3) { @range = ($year .. $future) }
}
my @years = map {{'id'=>$_,'value'=> $_}} @range;
unshift @years, {id=>0,value=>'Select Year'} if(defined $blank && $blank == 1);
DropDownRows($opt,'year','id','value',@years);
}
sub PeriodSelect {
my ($opt,$blank) = @_;
my @list = @periods;
unshift @list, {act=>'',value=>'Select Period'} if(defined $blank && $blank == 1);
DropDownRowsText($opt,'period','act','value',@list);
}
## ------------------------------------
## Date Functions
=head2 Date Formatting
=over 4
=item formatDate
=item unformatDate
=item isMonth
=back
=cut
sub formatDate {
my ($format,$time) = @_;
my $now = $time ? 0 : 1;
my $dt;
my $timezone = $settings{timezone} || 'Europe/London';
if($time) {
$dt = DateTime->from_epoch( epoch => $time, time_zone => $timezone );
} else {
$dt = DateTime->now( time_zone => $timezone );
}
return $dt->epoch unless($format);
#LogDebug("formatDate format=$format, time=".$dt->epoch);
# create date mini strings
my $fmonth = $dt->month_name;
my $amonth = $dt->month_abbr;
my $fdotw = $dt->day_name;
my $adotw = $dt->day_abbr;
my $fsday = sprintf "%d", $dt->day; # short form, ie 6
my $fday = sprintf "%02d", $dt->day; # long form, ie 06
my $fmon = sprintf "%02d", $dt->month;
my $fyear = sprintf "%04d", $dt->year;
my $fddext = sprintf "%d%s", $dt->day, _ext($dt->day);
my $time12 = sprintf "%d:%02d%s", $dt->hour_12, $dt->minute, lc $dt->am_or_pm;
my $time24 = sprintf "%d:%02d:%02d", $dt->hour, $dt->minute, $dt->second;
my $fhour = sprintf "%02d", $dt->hour;
my $fminute = sprintf "%02d", $dt->minute;
my $fsecond = sprintf "%02d", $dt->second;
my $tz = 'UTC';
eval { $tz = $dt->time_zone->short_name_for_datetime };
my $fmt = $formats{$format};
# transpose format string into a date string
$fmt =~ s/hh/$fhour/;
$fmt =~ s/mm/$fminute/;
$fmt =~ s/ss/$fsecond/;
$fmt =~ s/DMY/$fday-$fmon-$fyear/;
$fmt =~ s/MDY/$fmon-$fday-$fyear/;
$fmt =~ s/YMD/$fyear-$fmon-$fday/;
$fmt =~ s/MABV/$amonth/;
$fmt =~ s/DABV/$adotw/;
$fmt =~ s/MONTH/$fmonth/;
$fmt =~ s/DAY/$fdotw/;
$fmt =~ s/DDEXT/$fddext/;
$fmt =~ s/YYYY/$fyear/;
$fmt =~ s/MM/$fmon/;
$fmt =~ s/DD/$fday/;
$fmt =~ s/dd/$fsday/;
$fmt =~ s/TIME12/$time12/;
$fmt =~ s/TIME24/$time24/;
$fmt =~ s/TZ/$tz/;
return $fmt;
}
sub unformatDate {
my ($format,$time) = @_;
return time unless($format && $time);
my (@fields,@values);
my @basic = qw(ss mm hh DD MM YYYY);
my %forms = map {$_ => 0 } @basic, 'dd';
if($unformats{$format}) {
@fields = reverse @basic;
@values = $time =~ /$unformats{$format}/;
} else {
my $pattern = $formats{$format};
$pattern =~ s!TIME24!hh::mm:ss!;
$pattern =~ s!TIME12!hh::ampm!;
@fields = split(qr![ ,/:()-]+!,$pattern);
@values = split(qr![ ,/:()-]+!,$time);
}
@forms{@fields} = @values;
$forms{$_} = int($forms{$_}||0) for(@basic);
#use Data::Dumper;
#LogDebug("format=[$format], time=[$time]");
#LogDebug("fields=[@fields], values=[@values]");
#LogDebug("before=".Dumper(\%forms));
($forms{DD}) = $forms{dd} =~ /(\d+)/ if($forms{dd});
($forms{DD}) = $forms{DDEXT} =~ /(\d+)/ if($forms{DDEXT});
$forms{MM} = isMonth($forms{MONTH}) if($forms{MONTH});
$forms{MM} = isMonth($forms{MABV}) if($forms{MABV});
($forms{mm},$forms{AMPM}) = ($forms{ampm} =~ /(\d+)(am|pm)/) if($forms{ampm});
$forms{hh}+=12 if($forms{AMPM} && $forms{AMPM} eq 'pm');
@values = map {$forms{$_}||0} @basic;
my $timezone = $settings{timezone} || 'Europe/London';
my $dt = DateTime->new(
year => $values[5], month => $values[4] || 1, day => $values[3] || 1,
hour => $values[2], minute => $values[1], second => $values[0],
time_zone => $timezone );
return $dt->epoch;
}
sub _ext {
my $day = shift;
my $ext = "th";
if($day == 1 || $day == 21 || $day == 31) { $ext = "st" }
elsif($day == 2 || $day == 22) { $ext = "nd" }
elsif($day == 3 || $day == 23) { $ext = "rd" }
return $ext;
}
sub isMonth {
my $month = shift;
return (localtime)[4]+1 unless(defined $month && $month);
foreach (@months) {
return $_->{id} if($_->{value} =~ /$month/);
return $_->{value} if($month eq $_->{id});
}
return 0;
}
1;
__END__
=head1 SEE ALSO
Time::Local
Labyrinth
=head1 AUTHOR
Barbie, <barbie@missbarbell.co.uk> for
Miss Barbell Productions, L<http://www.missbarbell.co.uk/>
=head1 COPYRIGHT & LICENSE
Copyright (C) 2002-2015 Barbie for Miss Barbell Productions
All Rights Reserved.
This module is free software; you can redistribute it and/or
modify it under the Artistic License 2.0.
=cut