Search code examples
datesortingdirectoryd

Getting directory list sorted by modification date in Dlang


I am trying to get the contents of a directory, sorted by modified time. I don't think there is a way to do this directly within the dirEntries call, so my strategy is to collect all file times and names, then sort the two arrays in lock-step.

Problem 1: I can't figure out how to convert a sysTime into an integer.
Problem 2: I can't figure out how to sort two arrays in parallel.

As with every problem in D, it's impossible to find out how to do this :(

Here is my code:

import std.file;
import std.stdio : writeln;
import std.algorithm;
import std.datetime;

void main() {
    string[] myFiles;
    double[] myTimes;

    foreach (DirEntry e; dirEntries("c:/users/istaffel/", SpanMode.shallow)) {
        // calculate unix time (this doesn't work)
        auto dur = (cast(Date)e.timeLastModified) - Date(1970,1,1);

        // store modified time and filename
        myTimes ~= dur.seconds;
        myFiles ~= e.name;
    }

    // now find a way to sort myFiles in order of ascending myTimes...

    // print in order
    for (int i = 0; i < myTimes.length; i++) {
        writeln(myTimes[i], " ", myFiles[i]);
    }
}

Solution

  • In general, unless you're interacting with C, I would suggest that converting SysTime to an integral value is a bad idea (and that converting it to double is even worse). But if you really need to convert it to time_t like you seem to be doing, then just use SysTime's toUnixTime function:

    auto timeT = e.timeLastModified.toUnixTime();
    

    The simplest solution to do what you're trying to do would be something like

    import std.algorithm;
    import std.array;
    import std.datetime;
    import std.file;
    import std.stdio;
    
    void main(string[] args)
    {
        auto directoryToList = args[1];
        auto files = array(dirEntries(directoryToList, SpanMode.shallow));
        sort!"a.timeLastModified < b.timeLastModified"(files);
        foreach(file; files)
            writefln("%s %s", file.timeLastModified, file.name);
    }
    

    And if you really want a time_t, then make it file.timeLastModified.toUnixTime().

    dirEntries returns a range, so you can just iterate it and operate on it directly, but in order to sort it, you need a random-access range (which the result of dirEntries isn't, since it accesses the files lazily). So, you can use std.array.array to create an array from it, and then sort that. sort takes a predicate (be it a string converted to a function with std.functional, a lambda literal, a delegate, a function pointer, or anything callable with two arguments of the type that you're sorting - in this case I used a string, and in the example below, I use a lambda literal for map).

    If you're dealing with a lot of files and want to minimize the amount of memory used (as DirEntry does have several member variables, and so an array of them all may take up more more memory than you'd like if all you care about is the files' names and modifications times), then it gets more entertaining to do, but it's still quite feasible.

    import std.algorithm;
    import std.array;
    import std.datetime;
    import std.file;
    import std.stdio;
    import std.typecons;
    
    void main(string[] args)
    {
        auto directoryToList = args[1];
        auto files = dirEntries(directoryToList, SpanMode.shallow);
        auto pairs = array(map!(a => tuple(a.timeLastModified, a.name))(files));
        sort!"a[0] < b[0]"(pairs);
        foreach(pair; pairs)
            writefln("%s: %s", pair[0], pair[1]);
    }
    

    And again, if you really want a time_t, just make that pair[0].toUnixTime().

    This ends up creating an array of Tuples which contain just the time and name, and those then get sorted using just the time.

    If you aren't very familiar with ranges, then I would suggest that you read this chapter from an online book on D. D's standard library uses ranges quite heavily, which may be why you feel like you have a hard time figuring out how to do things in D. Ranges are an extremely powerful concept, but they do take some getting used to.