This is an archived low-energy page for bots and other anonmyous visitors.
Please sign up if you are a human and want to interact.
electronicMacks has asked for the wisdom of the Perl Monks concerning the following question:
Fellow Monks,
Iâm writing a program that parses a log containing dates in the format mm/dd/yy, I store these dates in a hash with their respective data, and at the end of the program Iâd like to report the dates in order.
Iâm thinking something along the lines of:
foreach $day (sort (keys (%totals))) {
But with an appropriate argument given to sort.
I could certainly write my own subroutine to sort it, but I feel like Iâd be reinventing the wheel, does anyone know of an existing, elegant way to sort mm/dd/yy dates???
(Date::Manip) Re: sorting mm/dd/yy
by mwp (Hermit) on Dec 14, 2000 at 23:10 UTC
|
use Date::Manip qw(ParseDate Date_Cmp);
sub sortDate {
my($date1, $date2);
$date1 = &ParseDate($a);
$date2 = &ParseDate($b);
return (&Date_Cmp($date1,$date2));
}
my @dates = ("Fri 16 Aug 96",
"Mon 19 Aug 96",
"Thu 15 Aug 96");
my @i = sort &sortDate @dates;
(edited somewhat for clarity)
You could also write this as:
my @i = sort {
&Date_Cmp(&ParseDate($a),&ParseDate($b))
} @dates;
| [reply] [d/l] [select] |
|
|
That'd be incredibly expensive for more than a few dates though. So, if you have, say, more than 5 or 10 dates, then Use The Power Of The Schwartz:
my @sorted_dates = map $_->[0], sort { Date_Cmp($a->[1], $b->[1]) }
map [$_, ParseDate($_)], @dates;
-- Randal L. Schwartz, Perl hacker | [reply] [d/l] |
(tye)Re: sorting mm/dd/yy
by tye (Sage) on Dec 14, 2000 at 23:17 UTC
|
foreach( keys %totals ) {
$totals{$_}= "20" . join "-",(
split m#/#, delete $totals{$_}
)[2,0,1];
}
and thank vroom that the y2k thing finally blew over
so you don't have to do that 2-digit-to-4-digit year
conversion thing the "proper" way anymore.
update: got the one true date format
wrong the first time... i'm so ashamed...
-
tye
(and not even my friends are sure when I'm joking) | [reply] [d/l] |
|
|
++'s to tye for stating the answer which should always be considered first:P Why work so hard when the work has already been done for you?
unix epoch is good too as long as you don't have to do too much date interaction with users. I love using straight time() in my programs because I get everything I could ever want to know in one shot If there is ever a chance to switch date formats, YYYYMMDD and unix epoch rank right up there.
| [reply] |
|
|
If that's any help convincing your boss, customer or yourself
that this is an acceptable format for date it is an ISO
standard, ISO-8601 (pdf)
and I found this page
to be a very good description of the standard (TIMTOWTFAD - There
Is More Than One Way To Format A Date) and why use it.
| [reply] |
Re: sorting mm/dd/yy
by Hot Pastrami (Monk) on Dec 14, 2000 at 23:26 UTC
|
If you're stuck with that lousy date format, something like this could work for your sorting sub:
sub sortDates {
my ($month1, $day1, $year1) = split /\//, $a;
my ($month2, $day2, $year2) = split /\//, $b;
$year1 += 1900 if $year1 < 50;
$year2 += 1900 if $year2 < 50;
$year1 <=> $year2
or
$month1 <=> $month2
or
$day1 <=> $day2;
}
...however this assumes that none of the dates are older than 1950... you may need to adjust the '50' to fit your values.
Alan "Hot Pastrami" Bellows | [reply] [d/l] |
Re: sorting mm/dd/yy
by Hrunting (Pilgrim) on Dec 15, 2000 at 03:02 UTC
|
alakaboo mentions Date::Manip which is an excellent module but is very much a heavyweight module (in fact, the POD for Date::Manip even recommends not to use it if you're looking for simple date transformations of the form you're looking for). Try using Time::ParseDate which is much more lightweight and does exactly what you're looking for too much else. Combine it with a Schwartzian transform (see merlyn's post above) like so:
use Time::Parsedate;
...
foreach my $day (
map { $_->[0] } sort { $a->[1] <=> $b->[1] }
map { [ $_, parsedate( $_ ) ] } @dates
) {
...
}
And that will be all she wrote. parsedate() from Time::Parsedate is pretty speedy and comparing the dates using a simple numeric compare is probably going to be faster than using Date_Cmp() (parsedate() returns numeric timestamps).
| [reply] [d/l] |
Re: sorting mm/dd/yy
by I0 (Priest) on Dec 15, 2000 at 06:11 UTC
|
foreach $day (
map{join'/',(split/-/)[1,2,0]}
sort
map{join'-',(split'/')[2,0,1]}
keys%totals
){
print "$day\n";
}
| [reply] [d/l] |
Re: sorting mm/dd/yy
by Anonymous Monk on Dec 15, 2000 at 14:42 UTC
|
Store them internally as YYMMDD and use a straight numeric sort. | [reply] |
|
|
Just one more thing:
Please use a four digit year (YYYY) to avoid any ambiguities.
| [reply] |
|
|
If you're dealing with data more than 12 months old,
then it might be better to use YYYYMMDD.
--
<http://www.dave.org.uk>
"Perl makes the fun jobs fun
and the boring jobs bearable" - me
| [reply] |
Re: sorting mm/dd/yy
by royalanjr (Chaplain) on Dec 15, 2000 at 15:23 UTC
|
Which format do you folks would be best for storing dates
then? The ISO YYYYMMDD, or unix epoch?
Roy Alan
| [reply] |
|
|
| [reply] |
|
|
That is what I have been doing at the moment. I use
Time:ParseDate to turn the date into epoch and store that.
Then when I need the formated into something a human can
understand, I use localtime() to change it back.
Someone changed the time zone on the server and it
knocked all the dates out of whack. I had to write a script
to make up for the time zone hour shift to make the dates
right.
Now, did I just do something bone-headed when I chose
that scheme for the dates, or what?
Roy Alan
| [reply] |
|
|
Re: sorting mm/dd/yy
by InfiniteSilence (Curate) on Dec 15, 2000 at 16:40 UTC
|
I mostly wrote the following as a tutorial to myself as to how Perl's sorting function works:
#!/usr/bin/perl -w
use strict;
#simple sort tests
my @a;
@a = (3,5,9,2,15,90,200);
print "Standard sort: " , join(" ",sort(@a));
print "\nCorreted numeric sort: ", join(" ", sort {$a <=> $b} @a);
#try dates
my @b = ('11/5/99','2/5/87','11/6/99');
print "\nUnsorted Dates: " , join(" ", @b) , "\n";
print "\nCustom Date Sort: ", join(" ", sort {date_sort($a, $b)} @b);
sub date_sort {
my ($first, $second) = @_;
if($first =~ m/(\d+)\/(\d+)\/(\d+)/g)
{
my (@mm, @dd, @yy);
($mm[1], $dd[1], $yy[1]) = ($1, $2, $3);
if ($yy[1] > 50) {$yy[1] += 1900} else {$yy[1] += 2000};
if($second =~ m/(\d+)\/(\d+)\/(\d+)/g)
{
($mm[2], $dd[2], $yy[2]) = ($1, $2, $3);
}
if ($yy[2] > 50) {$yy[2] += 1900} else {$yy[2] += 2000};
if ($yy[1] > $yy[2]) {return($first cmp $second)}
elsif ($yy[1] < $yy[2]) {return($second cmp $first)}
else {
#fall into testing month dates
if($mm[1] > $mm[2]) {return ($first cmp $second)}
elsif($mm[2] > $mm[1]) {return ($second cmp $first)}
else
{
#if that fails, test the days
if($dd[1] > $dd[2]) {return ($first cmp $second)}
else {return ($second cmp $first)}
}
}
;
}
}
1;
Quick explanation
The first and second sorts use Perl's standard sorting syntax. Nothing special here.
Perl also allows you to call a special sorting routine sort {date_sort($a, $b)} @b where the stuff between the curly braces is your call to the special function. The function basically checks the date and returns either one of two options...either $a is greater than $b or the reverse.
This is a very involved way to sort some bloody dates but, if you think about it, you could reuse the code if you needed a very, very specialized kind of date comparison function...e.g. perhaps something that seeks a particular range of dates and places them at the top of the sort order or something.
Oops...I tried a different set for the test data for this function and I am getting incorrect results. Don't use this for dates < 1950.
Celebrate Intellectual Diversity | [reply] [d/l] |
|
|
Uh, no. Don't do this. You have duplicated the code for parsing $a and $b. Once you've done that, you run into maintenance headaches or worse, you could
be treating them asymetrically, and on older versions of Perl this could actually
dump core.
PLEASE DON'T copy this style at all. There have been other much better
postings in this thread.
-- Randal L. Schwartz, Perl hacker
| [reply] |
|
|
Your way of calling a special sorting routine really isn't the way that feature was designed to be used.
Instead of declaring a sort block that calls your sort routine explicitly, with $a and $b as args, you should write a sub that processes $a and $b directly, and just give the name of the sub to sort. Here's an example:
sub backwards { $b cmp $a }
sort backwards @a;
| [reply] [d/l] |
|
|