开发者

Using Perl, how do I compare dates in the form of YYYY-MM-DD?

I have an array with n strings in format of YYYY-MM-DD (Exam开发者_C百科ple, "2010-10-31").

How do I compare a date to the strings in this array?

For example, delete the strings more than 30 day ago?


The great thing about YYYY-MM-DD-formatted dates is that you can compare them using simple string comparison. In Perl, that's the lt and gt operators.

In this case, it sounds like you're just looking to check whether the dates in the array are earlier or later than a given target date (which just happens to be "30 days ago"). For that case, the approach I would take would be to first determine what the date was 30 days ago and then compare that as a string against each date in the array. I would not introduce the overhead of converting all the YYYY-MM-DD strings into "proper" date objects, epoch times, etc. and back just for the sake of testing which represents the earlier date.

#!/usr/bin/env perl

use strict;
use warnings;

my $thirty_days = 30 * 24 * 60 * 60;
my ($old_day, $old_month, $old_year) = (localtime(time - $thirty_days))[3..5];
my $cutoff = sprintf('%04d-%02d-%02d', 
                     $old_year + 1900, $old_month + 1, $old_day);

my @dates = ('2010-10-12', '2010-09-12', '2010-08-12', '2010-09-13');
for my $date (@dates) {
  print "$date\n" if $date gt $cutoff;
} 


Guess there's more than one way to do it, but I like Date::Simple for stuff like this ..

An example from the docs:

use Date::Simple ('date', 'today');

# Difference in days between two dates:
$diff = date('2001-08-27') - date('1977-10-05');

# Offset $n days from now:
$date = today() + $n;
print "$date\n";  # uses ISO 8601 format (YYYY-MM-DD)

It's great for doing arithmetic on objects ++.

Only dates however, no hours, minutes or seconds


use strict; use warnings;
use DateTime ();
use DateTime::Duration ();
use DateTime::Format::Natural ();

my $parser = DateTime::Format::Natural->new;
my $now    = DateTime->now;
my $delta  = DateTime::Duration->new( days => 30 );
my $cutoff = $now->subtract_duration( $delta );

my @new_dates = map  { $_->[1] }
                grep { -1 == $_->[0] }
                map  { 
                    chomp;
                    [
                        DateTime->compare(
                            $parser->parse_datetime( $_ ),
                            $cutoff
                        ),
                        $_ 
                    ]
                } <DATA>;

print "@new_dates";

__DATA__
2010-07-31
2010-08-31
2010-09-30
2010-10-31


A good start is to read The Many Dates of Perl and the DateTime site.

The YYYY-MM-DD format is a form of ISO 8601 date representation. There are variants of it that are considered acceptable, such as YYYY-MM-DD and YYYYMMDD and even YYMM in older data. You should look at a definitive list before you choose a method to compare these dates.

If ISO 8601 dates strings are: 1) valid dates; 2) in the same format with or without the - delimiter; 3) lacking in leading and trailing whitespace, an attractive property is that you can sort or compare the strings with simple lexicographical string comparisons.

In general then:

  1. IFF you aren't going to check if the dates are valid and IFF they are the same format, and IFF there is not leading or trailing whitespace, you can compare against another string representing the target date in that same format.

--- Otherwise ---

  1. Decide on a CPAN module to parse your date string (or match it yourself),

  2. Convert to epoch time if if your dates are in that range, (or use a CPAN module that does larger scale date / time manipulation like Date::Manip or Date::Calc)

  3. Perform the arithmetic on the type of time (epoch time, absolute days, whatever)

  4. Convert the time back into the format that you want...

Here is code that does that:

use warnings; use strict;
use Date::Calc qw/:all/;

my (@date_strings, @abs_days);

my $target=Date_to_Days(2010, 1, 15);

# set @date_string to "YYYY-MM-DAY" between some dates
for my $AbsDay(Date_to_Days(2009,1,1)..Date_to_Days(2011,12,31)) {
   my ($year, $mon, $day)=Add_Delta_Days(1,1,1,$AbsDay-1);
   my $s="$year-$mon-$day";
   push @date_strings, $s;
}

foreach my $s (@date_strings) {
    my ($year, $mon, $day);

    if(($year, $mon, $day)=$s=~/^(\d+)-(\d+)-(\d+)/) {
        my $days=Date_to_Days($year, $mon, $day);
        push @abs_days, $days 
             if ($target-$days <= 30 && $target-$days >= -30 );
    }
}

print "absolute day way:\n";
foreach my $days (@abs_days) {
    my ($year, $mon, $day)=Add_Delta_Days(1,1,1,$days-1);
    print "$year-$mon-$day\n";
}


You can use Time::ParseDate module,

use strict;
use warning;
use Time::ParseDate;

my @dates = ('2010-10-12', '2010-09-14', '2010-08-12', '2010-09-13');
my @dates = 
  grep {parsedate($_, NO_RELATIVE => 1, UK => 1) > parsedate('-30 days') }@dates;   
 #output: 2010-10-12 2010-09-14


I did it like this, kind of verbose but it's easy to understand and gets the job done. @out2 is a 2d array, I'm reading in values using a for loop. Each loop I compare the input with the @out2 to see if it's an earlier or later time/date. If it is then I write the values to the array and then compare the next input.

if ($year < $out2[$j][7]) {
  $lt = 1;
  goto JUMP;
}
if ($year > $out2[$j][7]) {
  $gt = 1;
  goto JUMP;
}
if ($month < $out2[$j][5]) {
  $lt = 1;
  goto JUMP;
}
if ($month > $out2[$j][5]) {
  $gt = 1;
  goto JUMP;
}
if ($day < $out2[$j][6]) {
  $lt = 1;
  goto JUMP;
}
if ($day > $out2[$j][6]) {
  $gt = 1;
  goto JUMP;
}
if ($time < $out2[$j][4]) {
  $lt = 1;
  goto JUMP;
}
if ($time > $out2[$j][4]) {
  $gt = 1;
  goto JUMP;
}

JUMP:

if ($lt == 1) {
  $out2[$j][2] = "$dtime $month\/$day\/$year";
  $out2[$j][4] = $time;
  $out2[$j][5] = $month;
  $out2[$j][6] = $day;
  $out2[$j][7] = $year;
  $lt = 0;
  }

if ($gt == 1) {
  $out2[$j][3] = "$dtime $month\/$day\/$year";
  $out2[$j][4] = $time;
  $out2[$j][5] = $month;
  $out2[$j][6] = $day;
  $out2[$j][7] = $year;
  $gt = 0;
}


Why not the CORE since 5.10 Time::Piece and Time::Seconds, not the first few results of a CPAN search?

use strict;
use warnings;
use Time::Piece (); # we don't need to include overloaded locatime
use Time::Seconds;
use Data::Dumper;

my @dates = qw/2010-10-31 2012-10-16 2011-09-08/;

my $now = Time::Piece::localtime();

my @date_objects = map {
    Time::Piece->strptime( $_, '%F')  # %F is the same as %Y-%m-%d 
} @dates;

my @filtered_dates = grep {
    $now - $_ < (ONE_DAY * 30)
} @date_objects;

print Dumper(map($_->strftime('%F'), @filtered_dates));


To find a minimal date in a loop:

var minDate = ...;
var maxDate = ...;

foreach my $date ( @$dates ) {
    if ($minDate gt $date){ # Less than.
        $minDate = $date; # Minimal date.
    }
    if ($minDate lt $date){ # Greater than.
        $minDate = $date; # Maxamal date.
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜