date ranges as days

by punkish (Priest)
 on Feb 23, 2011 at 01:29 UTC Need Help??
punkish has asked for the wisdom of the Perl Monks concerning the following question:

Its been a long day here in Wisconsin, so pardon me if the following be too basic a question --

The user provides a date, or start/end dates, or month/year or year (all of which could be resolved into start/end dates).

The program returns --

Option 1: an array of integers representing the ordinal day since Jan 1, 1980. For example, if the user enters "Feb 1, 1981" to "Feb 5, 1982", the array returned is (397 .. 766) as Feb 1, 1981 is the 397th day since Jan 1, 1980 (index starting at 0), and so on.

Potential solution: Use something like Time::JulianDay to convert start and end dates into integers, and just subtract "epoch" start day from each. Critique, por favor.

Option 2: a hash in which the keys are the years, and the values are the days for the period prescribed by the start/end dates in the input. So, something like so ( "1981" => [31 .. 365], "1982" => [0 .. 35] );. Suggestions, por favor.

--

when small people start casting long shadows, it is time to go to bed

Replies are listed 'Best First'.
Re: date ranges as days
by wind (Priest) on Feb 23, 2011 at 01:37 UTC

For date calculations use the cpan module: Date::Calc and possibly Date::Parse

Here's a quick and dirty script that takes the dates you specified and prints the difference in days between them. Note that there are differences between how Date::Parse treats month ranges and how Date::Calc does.

```use Date::Parse;
use Date::Calc qw(Delta_Days);

use strict;

my \$base = 'Jan 1, 1980';
my \$start = 'Feb 1, 1981';
my \$end = 'Feb 5, 1982';

print days_diff(\$start, \$base) . "\n"; # 397
print days_diff(\$end, \$base) . "\n"; # 766

sub days_diff {
my \$minuend = shift;
my \$subtrahend = shift;

my (undef,undef,undef,\$d2,\$m2,\$y2,undef) = strptime(\$minuend);
my (undef,undef,undef,\$d1,\$m1,\$y1,undef) = strptime(\$subtrahend);

return Delta_Days(\$y1, \$m1+1, \$d1, \$y2, \$m2+1, \$d2);
}

1;

__END__
Re: date ranges as days
by ikegami (Pope) on Feb 23, 2011 at 02:03 UTC
Using DateTime:
```use strict;
use warnings;
use feature qw( say );

use DateTime qw( );

# Or using your favourite DateTime::Format parser.
my \$date1 = DateTime->new( year => 1981, month => 2, day => 1 );
my \$date2 = DateTime->new( year => 1982, month => 2, day => 5 );

Part 1:

```my \$ref = DateTime->new( year => 1980, month => 1, day => 1 );
say \$date1->delta_days(\$ref)->in_units('days');
say \$date2->delta_days(\$ref)->in_units('days');

Part 2:

```sub last_day_of_year {
( my \$dt = DateTime->new( year => \$_[0], month => 1, day => 1 ) )
->add( years => 1, days => -1 );
return \$dt->day_of_year();
}

my %in_range;
my \$year1 = \$date1->year();
my \$year2 = \$date2->year();
if (\$year1 == \$year2) {
\$in_range{\$year1} = [ \$date1->day_of_year()-1 .. \$date2->day_of_yea
+r()-1 ];
} else {
\$in_range{\$year1} = [ \$date1->day_of_year()-1 .. last_day_of_year(\$
+year1)-1 ];
for my \$year (\$year1+1 .. \$year2-1) {
\$in_range{\$year} = [ 0 .. last_day_of_year(\$year)-1 ];
}
\$in_range{\$year2} = [ 0 .. \$date2->day_of_year()-1 ];
}

for my \$year (sort { \$a <=> \$b } keys(%in_range)) {
say "\$year: @{ \$in_range{\$year} }";
}

PS - You said 365 where you should have said 364.

Re: date ranges as days
by punkish (Priest) on Feb 23, 2011 at 20:33 UTC
Thanks wind and ikegami. Once again, my apologies for this very simple problem that I should have figured out myself. I am adding my own solution to your contributions for the benefit of the archives

```use Time::JulianDay;

my @start = (1981, 2, 1);
my @end   = (1982, 2, 5);
my \$days = days('start' => \@start, 'end' => \@end, 'type' => 'yearly'
+);
my \$days = days('start' => \@start, 'end' => \@end, 'type' => 'range')
+;

sub days {
my (%a) = @_;

my (\$start, \$end, \$type) = (\$a{'start'}, \$a{'end'}, \$a{'type'});

if (\$type eq 'range') {
my \$j0 = julian_day(1980, 1, 1);
my \$j1 = julian_day(@\$start);
my \$j2 = julian_day(@\$end);

return ((\$j1 - \$j0), (\$j2 - \$j0));
}
elsif (\$type eq 'yearly') {
my \$start_year = \$start->[0];
my \$end_year = \$end->[0];

my %days;
for my \$year (\$start_year .. \$end_year) {
my \$j0 = julian_day(\$year, 1, 1);
my \$j1; my \$j2;
if (\$year == \$start_year) {
\$j1 = julian_day(\$year, \$start->[1], \$start->[2]);
\$j2 = julian_day(\$year, 12, 31);
}
elsif (\$year == \$end_year) {
\$j1 = julian_day(\$year, 1, 1);
\$j2 = julian_day(\$year, \$end->[1], \$end->[2]);
+
}
else {
\$j1 = julian_day(\$year, 1, 1);
\$j2 = julian_day(\$year, 12, 31);
}

\$days{"\$year"} = [(\$j1 - \$j0) .. (\$j2 - \$j0)];
}

return \%days;
}
}
--

when small people start casting long shadows, it is time to go to bed

Create A New User
Node Status?
node history
Node Type: perlquestion [id://889705]
Approved by ww
Front-paged by FalseVinylShrub
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (7)
As of 2018-06-22 21:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
Should cpanminus be part of the standard Perl release?

Results (124 votes). Check out past polls.

Notices?