3

I'm working with a PHP script that allows the chronological browsing of Eggdrop-generated IRC logs. Initially, I was reading the directory contents and inserting the log names into an array based on file modification date. After a recent server move, however, the files have had their modification dates updated and the navigation is now disorderly!

The log filename structure looks like:

channel.log.dayMONTHyear.txt

for example:

shrawberry.log.08Apr2011.txt

which, for being quite human-readable, is difficult to order properly.

Since the month code is always three characters long and comes in a predictable position in the sequence, I could manually parse the nonstandard date code into a Unix timestamp, iterate through the list and add each item to an array with that timestamp, and then sort the array by that number.

But that sounds excessive.

Am I on the money, or is the solution I proposed ideal?


With Marc B.'s help, I've implemented the following:

function dateFromEggLog($string){
    $month = substr($string,-11,-8);
    $day = substr($string,-13,-11);
    $year = substr($string,-8,-4);

    for($i=1;$i<=12;$i++){
        if(strtolower(date("M", mktime(0, 0, 0, $i, 1, 0))) == strtolower($month)){
            $month = $i;
            break;
        }
    }

    return "$year-$month-$day";
}

function my_compare($a, $b) {
    $a_date = dateFromEggLog($a);
    $b_date = dateFromEggLog($b);
    if ($a_date == $b_date) {
        return 0;
    }
    $a = strtotime($a_date); // convert to PHP timestamp
    $b = strtotime($b_date); // convert to PHP timestamp

    return (($a < $b) ? -1 : 1);
}

This successfully sorts my logs, without needing to muck around with my array.

Winfield Trail
  • 5,251
  • 2
  • 22
  • 43

3 Answers3

3

Your example filename doesn't match the "looks like" sample - the 'txt' and date string are reversed. but in any case, you can use the PHP usort() function, which lets a user-defined function do the comparisons.

It may not be particularly efficient, but you'd do something like this:

function my_compare($a, $b) {
    $a_date = ... extract date field from filename in $a
    $b_date = ... extract date field from filename in $b
    if ($a_date == $b_date) {
        return 0;
    }
    $a = strtotime($a_date); // convert to PHP timestamp
    $b = strtotime($b_date); // convert to PHP timestamp

    return (($a < $b) ? -1 : 1);
}

usort($array_of_filenames, 'my_compare');
Marc B
  • 340,537
  • 37
  • 382
  • 468
  • Aha, I did completely screw that up. Fixed, I'm giving your example a shot now. Efficiency isn't a major sticking point. – Winfield Trail Apr 20 '11 at 15:08
  • Thanks a lot. Posting specifics of my implementation in the question as well. – Winfield Trail Apr 20 '11 at 17:35
  • The mktime business would be some major overhead, given that you're just using it to get localized month names... pregenerate those and and save yourself some major overhead in the sort function. Remember - that function gets called many times while the array's being sorted and you're wasting tons of cpu time creating/recreating month names only to throw them away each time. The comparison function needs to be as fast/lean as is possible. Otherwise, looks good. – Marc B Apr 20 '11 at 17:44
  • Agh, I knew I'd get that wrong one way or another. I'd written it all out and done a switch statement before but thought it looked too ugly. What the hell is wrong with me. – Winfield Trail Apr 20 '11 at 17:48
0

You used filemtime before? Why not use filectime now?

Luceos
  • 6,265
  • 1
  • 31
  • 62
  • 1
    Create time was changed as well. And beyond that, I've learned just how bad an idea it is to order files based on such fungible metadata. :) – Winfield Trail Apr 20 '11 at 15:10
0

Why not, in your sort function, re-order channel.log.dayMONTHyear.txt to channel.log.yyyymmdd.txt using string manipulation, then use basic string comparison?

<?php

/* Helper stuff */

$months = Array(
  "Jan" => "01", "Feb" => "02", "Mar" => "03",
  "Apr" => "04", "May" => "05", "Jun" => "06",
  "Jul" => "07", "Aug" => "08", "Sep" => "09",
  "Oct" => "10", "Nov" => "11", "Dec" => "12"
);

/* The hard work */

function convertLogFilename($file) {

   $pos = strpos($file, ".log.");
   if ($pos === FALSE)
      throw new Exception("Invalid log file format");

   $pos += strlen(".log.");

   $dd   = substr($file, $pos, 2);
   $mm   = substr($file, $pos + 2, 3);
   $yyyy = substr($file, $pos + 5, 4);

   return substr($file, 0, $pos) . "$yyyy${months[$mm]}$dd.txt";
}

function sort_func($a, $b) {
   return convertLogFilename($a) < convertLogFilename($b);
}


/* Your program */

$files = Array(
   "channel.log.18Apr2011.txt",
   "channel2.log.21Jan2002.txt"
);

usort($files, 'sort_func');
print_r($files);
?>

Output:

Array
(
    [0] => channel2.log.21Jan2002.txt
    [1] => channel.log.18Apr2011.txt
)

This ought to be significantly quicker than creating full date representations for each filename.

Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989
  • The logbase is being updated continually, on the order of 1-20 writes to up to six files every minute. If I renamed files I'd need to avoid renaming the current day's log, and I'd also need to account for today's log being named according to a different structure than the others. It's obviously not an insurmountable problem, especially if Eggdrop has configurable logging filenames. – Winfield Trail Apr 20 '11 at 18:49
  • ...which, uh, apparently it does. Yeah, Tomalak, your suggestion is right on the money. In my defense, the logs have been running for more than five years now and I forgot that they aren't immutable. Facepalm. – Winfield Trail Apr 20 '11 at 18:51
  • @Kerin: I'm not talking about renaming the actual files; just the filename strings in your PHP, and this is local to your comparison function too. – Lightness Races in Orbit Apr 20 '11 at 23:34