Skip to content

Commit

Permalink
Merge pull request #4089 from CyberShadow/pull-20160316-181610-std-fi…
Browse files Browse the repository at this point in the history
…le-subsecond

fix Issue 15803 - std.file should support sub-second file time precision on POSIX
  • Loading branch information
DmitryOlshansky committed Apr 22, 2016
2 parents a7dcfdd + 28211cb commit 29b0c55
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 30 deletions.
51 changes: 48 additions & 3 deletions std/datetime.d
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ else version(Posix)
{
import core.sys.posix.stdlib;
import core.sys.posix.sys.time;
import core.sys.posix.signal : timespec;
}

version(unittest)
Expand Down Expand Up @@ -2662,6 +2663,50 @@ public:
}


version(StdDdoc)
{
private struct timespec {}
/++
Returns a $(D timespec) which represents this $(LREF SysTime).

$(BLUE This function is Posix-Only.)
+/
timespec toTimeSpec() @safe const pure nothrow;
}
else
version(Posix)
{
timespec toTimeSpec() @safe const pure nothrow
{
immutable tv_sec = toUnixTime!(typeof(timespec.tv_sec))();
immutable fracHNSecs = removeUnitsFromHNSecs!"seconds"(_stdTime - 621_355_968_000_000_000L);
immutable tv_nsec = cast(typeof(timespec.tv_nsec))convert!("hnsecs", "nsecs")(fracHNSecs);
return timespec(tv_sec, tv_nsec);
}

unittest
{
assert(SysTime(DateTime(1970, 1, 1), UTC()).toTimeSpec() == timespec(0, 0));
assert(SysTime(DateTime(1970, 1, 1), hnsecs(9), UTC()).toTimeSpec() == timespec(0, 900));
assert(SysTime(DateTime(1970, 1, 1), hnsecs(10), UTC()).toTimeSpec() == timespec(0, 1000));
assert(SysTime(DateTime(1970, 1, 1), usecs(7), UTC()).toTimeSpec() == timespec(0, 7000));

assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), UTC()).toTimeSpec() == timespec(1, 0));
assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(9), UTC()).toTimeSpec() == timespec(1, 900));
assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), hnsecs(10), UTC()).toTimeSpec() == timespec(1, 1000));
assert(SysTime(DateTime(1970, 1, 1, 0, 0, 1), usecs(7), UTC()).toTimeSpec() == timespec(1, 7000));

assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_999), UTC()).toTimeSpec() == timespec(0, -100));
assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), hnsecs(9_999_990), UTC()).toTimeSpec() == timespec(0, -1000));

assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999_999), UTC()).toTimeSpec() == timespec(0, -1_000));
assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), usecs(999), UTC()).toTimeSpec() == timespec(0, -999_001_000));
assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), msecs(999), UTC()).toTimeSpec() == timespec(0, -1_000_000));
assert(SysTime(DateTime(1969, 12, 31, 23, 59, 59), UTC()).toTimeSpec() == timespec(-1, 0));
assert(SysTime(DateTime(1969, 12, 31, 23, 59, 58), usecs(17), UTC()).toTimeSpec() == timespec(-1, -999_983_000));
}
}

/++
Returns a $(D tm) which represents this $(LREF SysTime).
+/
Expand Down Expand Up @@ -27436,7 +27481,7 @@ public:
}

unittest
{
{
assert(LocalTime().stdName !is null);

version(Posix)
Expand Down Expand Up @@ -27507,7 +27552,7 @@ public:
}

unittest
{
{
assert(LocalTime().dstName !is null);

version(Posix)
Expand Down Expand Up @@ -27573,7 +27618,7 @@ public:
}

unittest
{
{
LocalTime().hasDST;

version(Posix)
Expand Down
133 changes: 106 additions & 27 deletions std/file.d
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,28 @@ unittest
}


// Reads a time field from a stat_t with full precision.
version(Posix)
private SysTime statTimeToStdTime(char which)(ref stat_t statbuf)
{
auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`);
long stdTime = unixTimeToStdTime(unixTime);

static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `tim`))))
stdTime += mixin(`statbuf.st_` ~ which ~ `tim.tv_nsec`) / 100;
else
static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `timensec`))))
stdTime += mixin(`statbuf.st_` ~ which ~ `timensec`) / 100;
else
static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `time_nsec`))))
stdTime += mixin(`statbuf.st_` ~ which ~ `time_nsec`) / 100;
else
static if (is(typeof(mixin(`statbuf.__st_` ~ which ~ `timensec`))))
stdTime += mixin(`statbuf.__st_` ~ which ~ `timensec`) / 100;

return SysTime(stdTime);
}

/++
Get the access and modified times of file or folder $(D name).
Expand Down Expand Up @@ -866,8 +888,8 @@ void getTimes(R)(R name,
string names = null;
cenforce(trustedStat(namez, statbuf) == 0, names, namez);

accessTime = SysTime(unixTimeToStdTime(statbuf.st_atime));
modificationTime = SysTime(unixTimeToStdTime(statbuf.st_mtime));
accessTime = statTimeToStdTime!'a'(statbuf);
modificationTime = statTimeToStdTime!'m'(statbuf);
}
}

Expand Down Expand Up @@ -1130,20 +1152,40 @@ void setTimes(R)(R name,
else version(Posix)
{
auto namez = name.tempCString!FSChar();
static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted
static if (is(typeof(&utimensat)))
{
return utimes(namez, times);
}
timeval[2] t = void;
static auto trustedUtimensat(int fd, const(FSChar)* namez, const ref timespec[2] times, int flags) @trusted
{
return utimensat(fd, namez, times, flags);
}
timespec[2] t = void;

t[0] = accessTime.toTimeVal();
t[1] = modificationTime.toTimeVal();
t[0] = accessTime.toTimeSpec();
t[1] = modificationTime.toTimeSpec();

static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
alias names = name;
static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
alias names = name;
else
string names = null;
cenforce(trustedUtimensat(AT_FDCWD, namez, t, 0) == 0, names, namez);
}
else
string names = null;
cenforce(trustedUtimes(namez, t) == 0, names, namez);
{
static auto trustedUtimes(const(FSChar)* namez, const ref timeval[2] times) @trusted
{
return utimes(namez, times);
}
timeval[2] t = void;

t[0] = accessTime.toTimeVal();
t[1] = modificationTime.toTimeVal();

static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
alias names = name;
else
string names = null;
cenforce(trustedUtimes(namez, t) == 0, names, namez);
}
}
}

Expand All @@ -1157,7 +1199,8 @@ void setTimes(R)(auto ref R name,

@safe unittest
{
static assert(__traits(compiles, setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init)));
if (false) // Test instatiation
setTimes(TestAliasedString("foo"), SysTime.init, SysTime.init);
}

unittest
Expand All @@ -1170,19 +1213,26 @@ unittest
if (!exists(dir)) mkdirRecurse(dir);
{ auto f = File(file, "w"); }

foreach (path; [file, dir]) // test file and dir
void testTimes(int hnsecValue)
{
SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30));
SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30));
setTimes(path, atime, mtime);
foreach (path; [file, dir]) // test file and dir
{
SysTime atime = SysTime(DateTime(2010, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
SysTime mtime = SysTime(DateTime(2011, 10, 4, 0, 0, 30), hnsecs(hnsecValue));
setTimes(path, atime, mtime);

SysTime atime_res;
SysTime mtime_res;
getTimes(path, atime_res, mtime_res);
assert(atime == atime_res);
assert(mtime == mtime_res);
SysTime atime_res;
SysTime mtime_res;
getTimes(path, atime_res, mtime_res);
assert(atime == atime_res);
assert(mtime == mtime_res);
}
}

testTimes(0);
version (linux)
testTimes(123_456_7);

rmdirRecurse(newdir);
}

Expand Down Expand Up @@ -1220,7 +1270,7 @@ SysTime timeLastModified(R)(R name)
string names = null;
cenforce(trustedStat(namez, statbuf) == 0, names, namez);

return SysTime(unixTimeToStdTime(statbuf.st_mtime));
return statTimeToStdTime!'m'(statbuf);
}
}

Expand Down Expand Up @@ -1291,7 +1341,7 @@ SysTime timeLastModified(R)(R name, SysTime returnIfMissing)

return trustedStat(namez, statbuf) != 0 ?
returnIfMissing :
SysTime(unixTimeToStdTime(statbuf.st_mtime));
statTimeToStdTime!'m'(statbuf);
}
}

Expand All @@ -1315,6 +1365,35 @@ unittest
}


// Tests sub-second precision of querying file times.
// Should pass on most modern systems running on modern filesystems.
// Exceptions:
// - FreeBSD, where one would need to first set the
// vfs.timestamp_precision sysctl to a value greater than zero.
// - OS X, where the native filesystem (HFS+) stores filesystem
// timestamps with 1-second precision.
version (FreeBSD) {} else
version (OSX) {} else
unittest
{
import core.thread;

if(exists(deleteme))
remove(deleteme);

SysTime lastTime;
foreach (n; 0..3)
{
write(deleteme, "a");
auto time = timeLastModified(deleteme);
remove(deleteme);
assert(time != lastTime);
lastTime = time;
Thread.sleep(10.msecs);
}
}


/**
* Determine whether the given file (or directory) exists.
* Params:
Expand Down Expand Up @@ -2856,21 +2935,21 @@ else version(Posix)
{
_ensureStatDone();

return SysTime(unixTimeToStdTime(_statBuf.st_ctime));
return statTimeToStdTime!'c'(_statBuf);
}

@property SysTime timeLastAccessed()
{
_ensureStatDone();

return SysTime(unixTimeToStdTime(_statBuf.st_ctime));
return statTimeToStdTime!'a'(_statBuf);
}

@property SysTime timeLastModified()
{
_ensureStatDone();

return SysTime(unixTimeToStdTime(_statBuf.st_mtime));
return statTimeToStdTime!'m'(_statBuf);
}

@property uint attributes()
Expand Down

0 comments on commit 29b0c55

Please sign in to comment.