From 5f3c6f1a647c83247aea8811184d2b93f5147b46 Mon Sep 17 00:00:00 2001 From: jmdavis Date: Sat, 4 Oct 2014 02:28:59 -0700 Subject: [PATCH] Implement issue 13433: coarser realtime clock This adds an option for getting the time using an alternative clock chosen via core.time.ClockType, including access to a coarser clock as requested by issue# 13433. For ClockType.normal, the normal clock which has always been used is used. For ClockType.coarse, a faster clock is used if available even if it's coarser so long as it still has sub-second precision, and the normal clock is used if no such clock is available. For ClockType.precise, a more precise clock is used if available (currently only on FreeBSD), otherwise the normal clock is used. For ClockType.second, a faster clock is used which only has second precision. If no such clock is available, then the normal clock is used, but the result is truncated to second precision. --- std/datetime.d | 158 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 34 deletions(-) diff --git a/std/datetime.d b/std/datetime.d index 2f9f7f0c8c0..4fbf5b638bd 100644 --- a/std/datetime.d +++ b/std/datetime.d @@ -310,84 +310,174 @@ public: /++ Returns the current time in the given time zone. + Params: + clockType = The $(CXREF time, ClockType) indicates which system + clock to use to get the current time. Very few programs + need to use anything other than the default. + tz = The time zone for the SysTime that's returned. + Throws: - $(XREF exception, ErrnoException) (on Posix) or $(XREF exception, Exception) (on Windows) - if it fails to get the time of day. + $(LREF DateTimeException) if it fails to get the time. +/ - static SysTime currTime(immutable TimeZone tz = LocalTime()) @safe + static SysTime currTime(ClockType clockType = ClockType.normal)(immutable TimeZone tz = LocalTime()) @safe { - return SysTime(currStdTime, tz); + return SysTime(currStdTime!clockType, tz); } unittest { + import std.format : format; + assert(currTime().timezone is LocalTime()); assert(currTime(UTC()).timezone is UTC()); - //I have no idea why, but for some reason, Windows/Wine likes to get - //time_t wrong when getting it with core.stdc.time.time. On one box - //I have (which has its local time set to UTC), it always gives time_t - //in the real local time (America/Los_Angeles), and after the most recent - //DST switch, every Windows box that I've tried it in is reporting - //time_t as being 1 hour off of where it's supposed to be. So, I really - //don't know what the deal is, but given what I'm seeing, I don't trust - //core.stdc.time.time on Windows, so I'm just going to disable this test - //on Windows. + // core.stdc.time.time does not always work correctly on Windows systems. + // In particular, sometimes it applies the local DST to time_t, even + // though time_t is in UTC. I'm fairly certain that it's a bug in dmc's + // implementation, but it needs to be investigated. Regardless, the + // result is that for now, we can't run this test on Windows. version(Posix) { + static import std.math; immutable unixTimeD = currTime().toUnixTime(); immutable unixTimeC = core.stdc.time.time(null); - immutable diff = unixTimeC - unixTimeD; + assert(std.math.abs(unixTimeC - unixTimeD) <= 2); + } + + auto norm1 = Clock.currTime; + auto norm2 = Clock.currTime(UTC()); + assert(norm1 <= norm2, format("%s %s", norm1, norm2)); + assert(abs(norm1 - norm2) <= seconds(2)); - assert(diff >= -2); - assert(diff <= 2); + import std.typetuple; + foreach(ct; TypeTuple!(ClockType.coarse, ClockType.precise, ClockType.second)) + { + scope(failure) writefln("ClockType.%s", ct); + auto value1 = Clock.currTime!ct; + auto value2 = Clock.currTime!ct(UTC()); + assert(value1 <= value2, format("%s %s", value1, value2)); + assert(abs(value1 - value2) <= seconds(2)); } } + /++ Returns the number of hnsecs since midnight, January 1st, 1 A.D. for the current time. + Params: + clockType = The $(CXREF time, ClockType) indicates which system + clock to use to get the current time. Very few programs + need to use anything other than the default. + Throws: $(LREF DateTimeException) if it fails to get the time. +/ - static @property long currStdTime() @trusted + static @property long currStdTime(ClockType clockType = ClockType.normal)() @trusted { + static if(clockType != ClockType.coarse && + clockType != ClockType.normal && + clockType != ClockType.precise && + clockType != ClockType.second) + { + import std.format : format; + static assert(0, format("ClockType.%s is not supported by Clock.currTime or Clock.currStdTime", clockType)); + } + version(Windows) { FILETIME fileTime; GetSystemTimeAsFileTime(&fileTime); - - return FILETIMEToStdTime(&fileTime); + immutable result = FILETIMEToStdTime(&fileTime); + static if(clockType == ClockType.second) + { + // This should probably just use core.stdc.time.time, but dmc's + // time function seems to apply DST to time_t, which is + // incorrect and thus only works with dmc's other C functions. + return convert!("seconds", "hnsecs")(convert!("hnsecs", "seconds")(result)); + } + else + return result; } else version(Posix) { - enum hnsecsToUnixEpoch = 621_355_968_000_000_000L; + enum hnsecsToUnixEpoch = unixTimeToStdTime(0); - static if(is(typeof(clock_gettime))) + version(OSX) + { + static if(clockType == ClockType.second) + return unixTimeToStdTime(core.stdc.time.time(null)); + else + { + timeval tv; + if(gettimeofday(&tv, null) != 0) + throw new TimeException("Call to gettimeofday() failed"); + return convert!("seconds", "hnsecs")(tv.tv_sec) + + convert!("usecs", "hnsecs")(tv.tv_usec) + + hnsecsToUnixEpoch; + } + } + else version(linux) { + static if(clockType == ClockType.second) + return unixTimeToStdTime(core.stdc.time.time(null)); + else + { + import core.sys.linux.time; + static if(clockType == ClockType.coarse) alias clockArg = CLOCK_REALTIME_COARSE; + else static if(clockType == ClockType.normal) alias clockArg = CLOCK_REALTIME; + else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME; + else static assert(0, "Previous static if is wrong."); + timespec ts; + if(clock_gettime(clockArg, &ts) != 0) + throw new TimeException("Call to clock_gettime() failed"); + return convert!("seconds", "hnsecs")(ts.tv_sec) + + ts.tv_nsec / 100 + + hnsecsToUnixEpoch; + } + } + else version(FreeBSD) + { + import core.sys.freebsd.time; + static if(clockType == ClockType.coarse) alias clockArg = CLOCK_REALTIME_FAST; + else static if(clockType == ClockType.normal) alias clockArg = CLOCK_REALTIME; + else static if(clockType == ClockType.precise) alias clockArg = CLOCK_REALTIME_PRECISE; + else static if(clockType == ClockType.second) alias clockArg = CLOCK_SECOND; + else static assert(0, "Previous static if is wrong."); timespec ts; - - if(clock_gettime(CLOCK_REALTIME, &ts) != 0) - throw new TimeException("Failed in clock_gettime()."); - + if(clock_gettime(clockArg, &ts) != 0) + throw new TimeException("Call to clock_gettime() failed"); return convert!("seconds", "hnsecs")(ts.tv_sec) + ts.tv_nsec / 100 + hnsecsToUnixEpoch; } - else - { - timeval tv; + else static assert(0, "Unsupported OS"); + } + else static assert(0, "Unsupported OS"); + } + + unittest + { + import std.math : abs; + import std.format : format; + enum limit = convert!("seconds", "hnsecs")(2); - if(gettimeofday(&tv, null) != 0) - throw new TimeException("Failed in gettimeofday()."); + auto norm1 = Clock.currStdTime; + auto norm2 = Clock.currStdTime; + assert(norm1 <= norm2, format("%s %s", norm1, norm2)); + assert(abs(norm1 - norm2) <= limit); - return convert!("seconds", "hnsecs")(tv.tv_sec) + - convert!("usecs", "hnsecs")(tv.tv_usec) + - hnsecsToUnixEpoch; - } + import std.typetuple; + foreach(ct; TypeTuple!(ClockType.coarse, ClockType.precise, ClockType.second)) + { + scope(failure) writefln("ClockType.%s", ct); + auto value1 = Clock.currStdTime!ct; + auto value2 = Clock.currStdTime!ct; + assert(value1 <= value2, format("%s %s", value1, value2)); + assert(abs(value1 - value2) <= limit); } } + /++ The current system tick. The number of ticks per second varies from system to system. currSystemTick uses a monotonic clock, so it's