Date-Components
view release on metacpan or search on metacpan
lib/Date/Components.pm view on Meta::CPAN
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
our @EXPORT_OK = ( qw(
date_only_parse
is_valid_date
format_date
is_leap_year
is_valid_month
is_valid_day_of_month
is_valid_day_of_week
is_valid_year
is_valid_400_year_cycle
get_year_phase
number_of_day_within_year
day_number_within_year_to_date
day_number_within_400_year_cycle_to_date
get_number_of_day_within_400yr_cycle
get_days_remaining_in_400yr_cycle
day_name_to_day_number
day_number_to_day_name
get_num_days_in_year
get_days_remaining_in_year
get_numeric_day_of_week
get_month_from_string
get_dayofmonth_from_string
get_year_from_string
get_number_of_days_in_month
get_days_remaining_in_month
get_first_of_month_day_of_week
month_name_to_month_number
month_number_to_month_name
set_day_to_day_name_abbrev
set_day_to_day_name_full
set_day_to_day_number
set_month_to_month_name_abbrev
set_month_to_month_name_full
set_month_to_month_number
date1_to_date2_delta
number_of_weekdays_in_range
compare_date1_and_date2
year1_to_year2_delta
compare_year1_and_year2
date_offset_in_days
date_offset_in_weekdays
date_offset_in_years
calculate_day_of_week_for_first_of_month_in_next_year
get_global_year_cycle
),
);
# This allows declaration use Date::Components ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = (
'all' => [ @EXPORT_OK, @EXPORT ],
);
use version; our $VERSION = qv('0.2.1');
# According to the Royal Greenwich Observatory, the calendar year is 365 days
# long, unless the year is exactly divisible by four, then an extra day is
# added to February so the year is 366 days long. If the year is the last year
# of a century, e.g., 2000, 2100, 2200, 2300, 2400, then it is only a leap
# year if it is exactly divisible by 400. So, 2100 won't be a leap year but
# 2000 is. The next century year, exactly divisible by 400, won't occur until
# 2400--400 years away.
Readonly my $DATE_BASELINE_YEAR_2000 => '2000';
#Readonly my $DATE_BASELINE_MONTHNUM => '1';
#Readonly my $DATE_BASELINE_MONTHNAME => 'January';
#Readonly my $DATE_BASELINE_DAYNUM => '6';
#Readonly my $DATE_BASELINE_DAYNAME => 'Saturday';
Readonly my $NUMBER_OF_YEAR_PHASES => 400;
#Readonly my $MIN_NUMBER_OF_DAYS_IN_YEAR => 365;
#Readonly my $MAX_NUMBER_OF_DAYS_IN_YEAR => 366;
#Readonly my $MIN_NUMBER_OF_DAYS_IN_A_MONTH => 28;
#Readonly my $NUMBER_OF_MONTHS_IN_YEAR => 12;
Readonly my $NUMBER_OF_DAYS_IN_400_YEAR_CYCLE => (300 * 365) + (100 * 366) - 3; # three is subtracted for the three of the four century years which are NOT leap years
Readonly my $BASELINE_DAY_OF_WEEK_ON_JAN_1_2000 => 6;
# Create READ ONLY hash to hold day of week on Jan 1 for each year phase
my %hash_intermediate_00;
$hash_intermediate_00{'0'} = $BASELINE_DAY_OF_WEEK_ON_JAN_1_2000;
for ( my $iii_003=1; $iii_003<$NUMBER_OF_YEAR_PHASES; $iii_003++ )
{
my $num_days_in_year_05 = get_num_days_in_year($iii_003 - 1);
$hash_intermediate_00{$iii_003} = calculate_day_of_week_for_first_of_month_in_next_year( $num_days_in_year_05, $hash_intermediate_00{$iii_003 - 1} );
}
Readonly my %DAY_OF_WEEK_ON_FIRST_OF_YEAR => %hash_intermediate_00;
# Create READ ONLY hash to hold day of week on each first of month for each year phase
my %hash_intermediate_01;
for ( my $iii_007=0; $iii_007<$NUMBER_OF_YEAR_PHASES; $iii_007++ )
{
$hash_intermediate_01{$iii_007}{1} = get_first_of_month_day_of_week( 1, $iii_007 );
$hash_intermediate_01{$iii_007}{2} = get_first_of_month_day_of_week( 2, $iii_007 );
$hash_intermediate_01{$iii_007}{3} = get_first_of_month_day_of_week( 3, $iii_007 );
$hash_intermediate_01{$iii_007}{4} = get_first_of_month_day_of_week( 4, $iii_007 );
$hash_intermediate_01{$iii_007}{5} = get_first_of_month_day_of_week( 5, $iii_007 );
$hash_intermediate_01{$iii_007}{6} = get_first_of_month_day_of_week( 6, $iii_007 );
$hash_intermediate_01{$iii_007}{7} = get_first_of_month_day_of_week( 7, $iii_007 );
$hash_intermediate_01{$iii_007}{8} = get_first_of_month_day_of_week( 8, $iii_007 );
$hash_intermediate_01{$iii_007}{9} = get_first_of_month_day_of_week( 9, $iii_007 );
$hash_intermediate_01{$iii_007}{10} = get_first_of_month_day_of_week( 10, $iii_007 );
$hash_intermediate_01{$iii_007}{11} = get_first_of_month_day_of_week( 11, $iii_007 );
$hash_intermediate_01{$iii_007}{12} = get_first_of_month_day_of_week( 12, $iii_007 );
}
Readonly my %NUMERIC_DAY_OF_WEEK_ON_FIRST_OF_MONTH => %hash_intermediate_01;
lib/Date/Components.pm view on Meta::CPAN
for ( my $iii_004=0; $iii_004<$week_remainder_00; $iii_004++ )
{
if ( $number_of_days_in_range_00 > 0 ) # range is positive
{
$current_dayofweek_00++;
if ( $current_dayofweek_00 > 7 )
{
$current_dayofweek_00 -= 7;
}
if ( $current_dayofweek_00 < 6 ) # weekdays
{
$number_weekdays_00++;
}
}
if ( $number_of_days_in_range_00 < 0 ) # range is negative
{
$current_dayofweek_00--;
if ( $current_dayofweek_00 < 1 )
{
$current_dayofweek_00 += 7;
}
if ( $current_dayofweek_00 < 6 ) # weekdays
{
$number_weekdays_00++;
}
}
}
# Put correct sign to number of days in range
if ( $number_of_days_in_range_00 > 0 )
{
return( $number_weekdays_00 );
}
elsif ( $number_of_days_in_range_00 < 0 )
{
return( -$number_weekdays_00 );
}
else
{
return( '0' );
}
}
###############################################################################
# Usage : date_offset_in_weekdays( SCALAR, SCALAR )
# Purpose : find a WEEKDAY date in the future or past offset by the number of weekdays from the given starting WEEKDAY date
# Returns : - date of the WEEKDAY day offset from the given WEEKDAY date if successful
# Parameters : (
# : WEEKDAY date in any format,
# : number of weekdays offset, positive is future date, negative is past date, zero is current date (no offset)
# : )
# Throws : Throws exception for any invalid input INCLUDING weekend dates
# Comments : This effectively functions as if ALL weekend dates were removed
# : from the calendar. This function accepts ONLY weekday dates and
# : outputs ONLY weekday dates
# See Also : N/A
###############################################################################
sub date_offset_in_weekdays
{
my (
$date_in_05,
$date_delta_01
)
= @_;
# Incoming Inspection
my $num_input_params_36 = 2;
( @_ == $num_input_params_36) or croak "\n\n ($0) '${\(caller(0))[3]}' should have exactly $num_input_params_36 parameter(s), a date string followed by the number of offset days. '@_'.\n\n\n";
( ref(\$date_in_05) eq 'SCALAR' ) or croak "\n\n ($0) '${\(caller(0))[3]}' Expects a SCALAR parameter for the date string '$date_in_05'.\n\n\n";
( $date_in_05 ne '' ) or croak "\n\n ($0) '${\(caller(0))[3]}' Expects a NON-empty string for the date string '$date_in_05'.\n\n\n";
( date_only_parse($date_in_05) ) or croak "\n\n ($0) '${\(caller(0))[3]}' Cannot parse the date from the input date string '$date_in_05'.\n\n\n";
( ref(\$date_delta_01) eq 'SCALAR' ) or croak "\n\n ($0) '${\(caller(0))[3]}' Expects a SCALAR parameter for the number of offset days '$date_delta_01'.\n\n\n";
( $date_delta_01 ne '' ) or croak "\n\n ($0) '${\(caller(0))[3]}' Expects a NON-empty value for the number of offset days '$date_delta_01'.\n\n\n";
( $date_delta_01 =~ m/^\-{0,1}\d+$/ ) or croak "\n\n ($0) '${\(caller(0))[3]}' Expects an integer value, positive, negative or zero, for the number of offset days '$date_delta_01'.\n\n\n";
# Check that starting date is a WEEKDAY
my $day_of_week_16 = get_numeric_day_of_week($date_in_05);
( $day_of_week_16 < 6 ) or croak "\n\n ($0) '${\(caller(0))[3]}' Expects the starting date, '$date_in_05', to be a WEEKDAY. It is incorrectly a ${\(set_day_to_day_name_full($day_of_week_16))}.\n\n\n";
my $past_future = 1;
if ( $date_delta_01 < 0 )
{
$past_future = -1;
}
# 1 0 0 7/5 2 0 0 7/5 3 0 0 7/5 4 0 0 7/5 5 0 0 7/5
# 1 1 1 int(7/5) 2 1 1 int(7/5) 3 1 1 int(7/5) 4 1 1 int(7/5) 5 1 3 int(7/5) + 2
# 1 2 2 int(7/5) 2 2 2 int(7/5) 3 2 2 int(7/5) 4 2 4 int(7/5) + 2 5 2 4 int(7/5) + 2
# 1 3 3 int(7/5) - 1 2 3 3 int(7/5) - 1 3 3 5 int(7/5) + 1 4 3 5 int(7/5) + 1 5 3 5 int(7/5) + 1
# 1 4 4 int(7/5) - 1 2 4 6 int(7/5) + 1 3 4 6 int(7/5) + 1 4 4 6 int(7/5) + 1 5 4 6 int(7/5) + 1
# 1 0 0 7/5 2 0 0 7/5 3 0 0 7/5 4 0 0 7/5 5 0 0 7/5
# 1 -1 -3 -int(abs(7/5)) - 2 2 -1 -1 -int(abs(7/5)) 3 -1 -1 -int(abs(7/5)) 4 -1 -1 -int(abs(7/5)) 5 -1 -1 -int(abs(7/5))
# 1 -2 -4 -int(abs(7/5)) - 2 2 -2 -4 -int(abs(7/5)) - 2 3 -2 -2 -int(abs(7/5)) 4 -2 -2 -int(abs(7/5)) 5 -2 -2 -int(abs(7/5))
# 1 -3 -5 -int(abs(7/5)) - 1 2 -3 -5 -int(abs(7/5)) - 1 3 -3 -5 -int(abs(7/5)) - 1 4 -3 -3 -int(abs(7/5)) + 1 5 -3 -3 -int(abs(7/5)) + 1
# 1 -4 -6 -int(abs(7/5)) - 1 2 -4 -6 -int(abs(7/5)) - 1 3 -4 -6 -int(abs(7/5)) - 1 4 -4 -6 -int(abs(7/5)) - 1 5 -4 -4 -int(abs(7/5)) + 1
my $weekday_remainder = abs($date_delta_01) % 5;
my $num_days_effective = 'xxx';
if (
( ( $day_of_week_16 == 1 ) && ( $date_delta_01 > 0 ) ) ||
( ( $day_of_week_16 == 5 ) && ( $date_delta_01 < 0 ) )
)
{
foreach ( $weekday_remainder )
{
SWITCH:
{
if ( $_ <= 2 ) { $num_days_effective = $past_future * int( abs($date_delta_01 * (7/5) ) ); last SWITCH; }
lib/Date/Components.pm view on Meta::CPAN
Date::Components - Parses, processes and formats ONLY dates and date components
(time parameters are ignored).
=head1 VERSION
This documentation refers to Date::Components version 0.2.1
=head1 SYNOPSIS
use Carp qw(croak);
use Date::Components qw(
date_only_parse
is_valid_year
set_day_to_day_name_abbrev
format_date
);
# Parse a $date string and extract its components
my $date = 'Mon Sep 17 08:50:51 2007';
my ($month, $day, $year, $dayofweek) = date_only_parse($date);
# Test if $year is valid
( is_valid_year( $year ) ) or croak " Input year, '$year', is not a valid input.\n";
# Set $dayofweek, whether alpha or numeric, to alpha.
my $alpha_day = set_day_to_day_name_abbrev( $dayofweek );
# Re-formats $date to one of several user choices
my $formatted_date = format_date( $date );
=head1 DESCRIPTION
Date::Components parses dates into components on the front end, formats them on
the back end and enables many operations on whole dates and date components in
between.
This unique module was created to combine a parser, formatter, component
operators and time independence into a single unit. Independence of time also
enables the widest date range possible (limited by integer size). Applications
include portfolio management where only dates are relevant. With the variety
of supported date formats, it can be used as an in-line date re-formatter.
Subroutines providing operations specific to the standard 400 year cycle are
included also.
The module is not object oriented. Rather, it supplies a variety of
useful functions to analyze, process and format complete dates and the four
date components of I<month>, I<day-of-month>, I<year> and I<day-of-week>.
B<ALL> representations of time and related parameters are ignored, including
hours, minutes, seconds, time zones, daylight savings time, etc.
Leap year standard is used. According to the Royal Greenwich Observatory, the
calendar year is 365 days long, unless the year is exactly divisible by four,
then an extra day is added to February so the year is 366 days long. If the
year is the last year of a century, e.g., 2000, 2100, 2200, 2300, 2400, then it
is only a leap year if it is exactly divisible by 400. So, 2100 won't be a leap
year but 2000 is. The next century year, exactly divisible by 400, won't occur
until 2400--400 years away.
Subroutines C<is_valid_date>, C<format_date> and C<get_numeric_day_of_week>
are overloaded to accept either a list of date components or a single SCALAR
date string to enable more flexible usage.
Date strings returned by subroutines are always in default format.
=head2 Conventions
To make the code correspond to standard date representations, day of the week
and month numbers both start at 1.
Day numbers are represented as 1-7 corresponding to Mon through Sun.
Month numbers are represented as 1-12 corresponding to Jan through Dec.
=head2 Subroutine List
=over 4
=item Frontend / Backend
=over 4
=item C<date_only_parse>
=item C<format_date>
=back
=item Validity Check
=over 4
=item C<is_valid_date>
=item C<is_valid_month>
=item C<is_valid_day_of_month>
=item C<is_valid_day_of_week>
=item C<is_valid_year>
=item C<is_valid_400_year_cycle>
=back
=item Component Formatting
=over 4
lib/Date/Components.pm view on Meta::CPAN
=item Returns:
Date of the day offset from the given date
=item Parameter(s):
- Date string in any format
- Integer number of days, positive or negative
=item Throws:
Throws exception for any invalid input
=item Comments:
Positive offset is future date, negative is past date, zero is current date (no offset)
=item Examples:
date_offset_in_days('1/1/2000', 1); # Returns '1/2/2000'
date_offset_in_days('1/21/2000', -5); # Returns '1/16/2000'
=back
=item B<date_offset_in_weekdays>
=over 8
=item Usage:
my $offset_date = date_offset_in_weekdays( $date, $num_days );
=item Purpose:
Find a WEEKDAY date in the future or past offset by the number of weekdays from the given starting WEEKDAY date
=item Returns:
Date of the weekday offset from the given weekday date
=item Parameter(s):
- Weekday date string in any format
- Integer number of weekdays, positive or negative
=item Throws:
Throws exception for any invalid input INCLUDING weekend dates
=item Comments:
This effectively functions as if ALL weekend dates were removed
from the calendar. This function accepts ONLY weekday dates and
outputs ONLY weekday dates
=item Examples:
date_offset_in_weekdays('Mon Jul 11 08:50:51 1977', -7); # Returns '06/30/1977'
date_offset_in_weekdays('Tue Jul 12 08:50:51 1977', -3); # Returns '07/07/1977'
date_offset_in_weekdays('Wed Jul 13 08:50:51 1977', 0); # Returns '07/13/1977'
date_offset_in_weekdays('Thu Jul 14 08:50:51 1977', 3); # Returns '07/19/1977'
date_offset_in_weekdays('Fri Jul 15 08:50:51 1977', 7); # Returns '07/26/1977'
=back
=item B<compare_year1_and_year2>
=over 8
=item Usage:
my $compare_result = compare_year1_and_year2( $date_1, date_2 );
=item Purpose:
Compares two dates to find which one is the later year, months and days are ignored
=item Returns:
- '1' if the FIRST year is LATER than the second
- '-1' if the FIRST year is EARLIER than the second
- '0' if both years are the same
=item Parameter(s):
- Date string one in any format
- Date string two in any format
=item Throws:
Throws exception for any invalid input
=item Comments:
Again, the month and day-of-month fields in the input parameters are COMPLETELY ignored.
=item Examples:
# Returns '0', The years in both dates, 9/23/1967 and 4/7/1967, are the same
compare_year1_and_year2('9/23/1967', '4/7/1967');
# Returns '1', Year 2004 is greater than year 2003
compare_year1_and_year2('1/7/2004', '12/19/2003');
# Returns '-1', Year 1387 is less than year 1555
compare_year1_and_year2('Fri May 18 08:50:51 1387', 'Wed Feb 23 08:50:51 1555');
( run in 0.749 second using v1.01-cache-2.11-cpan-39bf76dae61 )