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 )