Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW

Sort by Date and Time

by zod (Scribe)
on Jan 31, 2009 at 03:10 UTC ( #740372=perlquestion: print w/replies, xml ) Need Help??
zod has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

I have a bunch of dates in this format: 01-30 22:10

I want to sort them, so I did this:

#!/usr/bin/perl use strict; use warnings; use Date::Manip; Date_Init("TZ=EST5EDT"); my @dates = ( '01-30 22:10', '01-12 05:56', '01-24 01:42', '01-12 05:59', '01-31 01:33', '01-02 01:33' ); foreach my $date (@dates) { $date =~ s/^\s+//; #remove leading space $date =~ s/\s+$//; #remove trailing space $date =~ s/\s/\/09 /; #add the year $date =~ s/-/\//; #change - to / $date = ParseDate($date); } @dates = sort { Date_Cmp($a, $b) } @dates; for (@dates) { print $_, "\n"; }
That seems to work, but I'm wondering if there is a less clunky approach.



Replies are listed 'Best First'.
Re: Sort by Date and Time
by ikegami (Pope) on Jan 31, 2009 at 03:13 UTC
    How about
    #!/usr/bin/perl use strict; use warnings; my @dates = ( '01-30 22:10', '01-12 05:56', '01-24 01:42', '01-12 05:59', '01-31 01:33', '01-02 01:33' ); @dates = sort @dates; for (@dates) { print $_, "\n"; }
      Hmm. I assumed I had to convert dates to datetimes. I knew it looked wrong, though. thanks
        Generally, yes. In this case, they're already in a sortable format. The fields are fixed width and they are ordered by decreasing significance.

        Your code carefully trimmed leading and trailing spaces. To be sure of the sort working on the strings you might also wish to ensure: that each field is the same width; that 1 digit values had leading zeros or spaces consistently; that the separators were consistent; that the spacing was consistent; and so on.

        By the time you've done all that, it might be more straightforward to use a function a convert text dates to numeric dates, which would smooth out format variations for you !

        Tailgating on ikegami's code, here is a more general situation for sorting. This is not applicable here, but if you do enough sorting, it will come up!

        Let's say that for some reason you wanted something different than just an ASCII sort, in this case you lucked out because a simple sort works. But let's say maybe there weren't leading zero's (that makes an ASCII date sort possible!), and say you for some reason wanted sort order, to be month, reverse day, reverse time, here is how that could be accomplished...

        #!/usr/bin/perl use strict; use warnings; my @dates = ( '01-30 22:10', '01-12 05:56', '01-24 01:42', '01-12 05:59', '01-31 01:33', '01-02 01:33', ); @dates = sort @dates; for (@dates) { print $_, "\n"; } #prints: #01-02 01:33 #01-12 05:56 #01-12 05:59 #01-24 01:42 #01-30 22:10 #01-31 01:33 print "\n"; @dates = sort { my ($amonth,$aday,$ahour,$amin) = $a =~/(\d+)-(\d+)\s+(\d+):(\d+)/; my ($bmonth,$bday,$bhour,$bmin) = $b =~/(\d+)-(\d+)\s+(\d+):(\d+)/; $amonth <=> $bmonth or $bday <=> $aday or $bhour <=> $ahour or $bmin <=> $amin }@dates; for (@dates) { print $_, "\n"; } #prints: #01-31 01:33 #01-30 22:10 #01-24 01:42 #01-12 05:59 #01-12 05:56 #01-02 01:33
        The sort function can take a subroutine that you supply! That subroutine produces -1,0,1 depending upon how you figure things should be compared. $a and $b are "magic variables" and will "just be there as part of the sort". The basic procedure is to take the $a and $b variables, use split or regex to break them apart into the things that are relevant for your tricky sort. Then compare them as you wish. My example above shows how I do this with the code layout that I prefer (a clear "stack" of comparisons). For numeric compare, use the "spaceship" operator <=>, for string compare use "cmp". Here I used numeric compares. Notice that what happen in the regex's in sort{} didn't affect the result (they are still just ascii). The list that comes into the sort is what comes out of the sort. You are just supplying a function that compares the lines as the sort routine requests. Note to reverse the order I just swapped the "a" version and the "b" version in the compare functions.

        There are ways to make this computationally more efficient but that requires building lists of anonymous lists (basically do all the splitting,regexing at once instead of doing it each time for the $a and $b variables). Master this type of sorting first.

        Hope this gives an idea of how to solve more complex problems and happy sorting! Perl is great at it!

Re: Sort by Date and Time
by hbm (Hermit) on Feb 01, 2009 at 03:40 UTC

    Generally speaking, if you're making numerous changes to a string, $_ helps. And you can clarify by choosing a delimiter that isn't in your string, so you don't have to escape it.

    foreach (@dates) { s|^\s+||; #remove leading space s|\s+$||; #remove trailing space s|\s|/09 |; #add the year s|-|/|; #change - to / ... }

    But the other responses are better...

Re: Sort by Date and Time
by ack (Deacon) on Feb 02, 2009 at 05:14 UTC

    Here is a possible solution with your own sort functio (this one sorts the dates in descending order, if you change the oder of the comparisons then you can easily get it to sort in ascending order).

    #!/user/bin/perl use warnings; use strict; my @dates = ( '01-31 01:33', '01-30 22:10', '01-12 05:59', '01-02 01:33', '01-24 01:42', '01-12 05:56', ); print "The dates to be sorted are:\n"; my $numDates = scalar(@dates); my $colCount = 0; for(my $i = 0; $i < $numDates; $i++){ $colCount++; if($colCount > 3){ $colCount = 1; print "\n"; } # end if print " $dates[$i]"; } # end for $i loop print "\n"; my @sortedDates = sort mySort @dates; print "The sorted dates are:\n"; $numDates = scalar(@dates); $colCount = 0; for(my $i = 0; $i < $numDates; $i++){ $colCount++; if($colCount > 3){ $colCount = 1; print "\n"; } # end if print " $dates[$i]"; } # end for $i loop print "\n"; exit(0); sub mySort { my($part1a,$part2a) = split(/\s+/,$a); my($part1b,$part2b) = split(/\s+/,$b); my($mon_a,$day_a) = split(/-/,$part1a); my($mon_b,$day_b) = split(/-/,$part1b); my($hr_a,$min_a) = split(/:/,$part2a); my($hr_b,$min_b) = split(/:/,$part2b); return 0 if($a eq $b); if($mon_a == $mon_b) { if($day_a == $day_b) { if($hr_a == $hr_b) { return ($min_a <=> $min_b); } elsif($hr_a < $hr_b) { return -1; } else { return 1; } } elsif($day_a < $day_b) { return -1; } else { return 1; } } elsif($mon_a < $mon_b) { return -1; } else { return 1; } } # end sub mySort()

    Note that I moved some of the dates in your @dates around so that you can see that it does, indeed, sort them.

    ack Albuquerque, NM

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://740372]
Approved by ikegami
[Discipulus]: ovedpo15 A subroutine may be called using an explicit & prefix. The & is optional in modern Perl... see perlsub in docs and search for ampersand
[Discipulus]: it has some, even dangerous, implication
[Discipulus]: I still use but I also attract many critics for this: I use when I call subs defined in the very same file, just to recognize them. You can avoid (but sometimes is needed)
[marto]: believe it or not this is a SPAM account :P
[Discipulus]: it seems a legitimate one.. grin ..

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (10)
As of 2018-05-22 12:14 GMT
Find Nodes?
    Voting Booth?