1

我正在捕获 C# 程序中的一些事件,这些事件以时间戳作为系统滴答计数(自开始时间以来的毫秒数)返回。

根据我看到的其他问题,知道我可以从System.Environment.TickCount属性(或其他东西)获得相同的数字,我如何推断与收到的 TickCount 对应的 DateTime 对象?

4

4 回答 4

6

你不能,没有更多信息(即使那样它也可能是模棱两可的)。Environment.TickCount返回:

一个 32 位有符号整数,包含自上次启动计算机以来经过的时间量(以毫秒为单位)。

...因此,除非您能找出计算机从某个地方启动的时间,否则您就不走运了。您很可能可以通过注册表项或系统调用来找出上次启动时间,但我不知道它们是否在我的脑海中。当然,您可以通过自己和尽可能早(或早)获得近似值,并找出两者之间的差异:Environment.TickCountDateTime.UtcNow

public static DateTime UnreliableDateTimeFromTickCount(int tickCount)
{
    DateTime now = DateTime.UtcNow;
    DateTime boot = now - TimeSpan.FromMilliseconds(Environment.TickCount);
    return boot + TimeSpan.FromMilliseconds(tickCount);
}

但是,即使这样该值也会每 24.9 天循环一次,因此如果计算机的运行时间超过此时间,则计数是不明确的。

我建议Environment.TickCount尽可能避免使用,基本上 - 这完全在你的控制之下吗?

于 2012-07-04T18:26:32.490 回答
2

我承认这是一个非常古老的问题,但由于这是我搜索时谷歌的第一次点击,我觉得其他人可能会登陆这里。@JonSkeet 答案中的所有要点都是有效的,请务必阅读它们并充分理解它适用于您的地方。对于我的具体情况,我知道我需要转换的滴答计数值将在最近几天内,但是存在捕获的值在 TickCount 溢出之前的风险,并且转换将发生在之后。下面是我编写的方法,它应该处理 TickCount 溢出的情况,并将给定的滴答计数转换为 DateTime,只要它在过去 49 天内。

详细说明 Environment.TickCount 的工作原理:当计算机打开时,它从 0 开始,每毫秒递增。启动 24.9 天后,达到 Int32 的容量,TickCount 从 Int32.MaxValue 翻转到 Int32.MinValue。初始包装后,它将每 49.7 天继续溢出。

/// <summary>
/// Converts the given tick count into a DateTime. Since TickCount rolls over after 24.9 days, 
/// then every 49.7 days, it is assumed that the given tickCount occurrs in the past and is 
/// within the last 49.7 days.
/// </summary>
/// <param name="tickCount">A tick count that has occurred in the past 49.7 days</param>
/// <returns>The DateTime the given tick count occurred</returns>
private DateTime ConvertTickToDateTime(int tickCount)
{
    // Get a reference point for the current time
    int nowTick = Environment.TickCount;
    DateTime currTime = DateTime.Now;
    Int64 mSecElapsed = 0;

    // Check for overflow condition
    if( tickCount < nowTick) // Then no overflow has occurred since the recorded tick
    {
        // MIN|--------------TC---------------0------------Now-------------|MAX
        mSecElapsed = nowTick - tickCount;
    }
    else // tickCount >= currTick; Some overflow has occurred since the recorded tick
    {
        // MIN|--------------Now---------------0------------TC-------------|MAX
        mSecElapsed = Convert.ToInt64((int.MaxValue - tickCount) + (nowTick + Math.Abs(Convert.ToDouble(int.MinValue))));   // Time BEFORE overflow + time since the overflow
    }

    DateTime tickCountAsDateTime = currTime - TimeSpan.FromMilliseconds(mSecElapsed);
    return tickCountAsDateTime;        
}

为了测试该方法,我使用了以下代码:

static void Main(string[] args)
{
    Console.WriteLine("Test Start Time: {0}", DateTime.Now);

    // 10 seconds ago
    int tc0 = CalculateTC(TimeSpan.FromSeconds(10));
    Console.WriteLine("Expect 10 seconds ago: {0}", ConvertTickToDateTime(tc0));

    // 10 minutes ago
    int tc1 = CalculateTC(TimeSpan.FromMinutes(10));
    Console.WriteLine("Expect 10 minutes ago: {0}", ConvertTickToDateTime(tc1));

    // 10 hours ago
    int tc2 = CalculateTC(TimeSpan.FromHours(10));
    Console.WriteLine("Expect 10 hours ago: {0}", ConvertTickToDateTime(tc2));

    // 1 Day ago
    int tc3 = CalculateTC(TimeSpan.FromDays(1));
    Console.WriteLine("Expect 1 Day ago: {0}", ConvertTickToDateTime(tc3));

    // 10 Day ago
    int tc4 = CalculateTC(TimeSpan.FromDays(10));
    Console.WriteLine("Expect 10 Days ago: {0}", ConvertTickToDateTime(tc4));

    // 30 Day ago
    int tc5 = CalculateTC(TimeSpan.FromDays(30));
    Console.WriteLine("Expect 30 Days ago: {0}", ConvertTickToDateTime(tc5));

    // 48 Day ago
    int tc6 = CalculateTC(TimeSpan.FromDays(48));
    Console.WriteLine("Expect 48 Days ago: {0}", ConvertTickToDateTime(tc6));

    // 50 Day ago (Should read as a more recent time because of the Environment.TickCount wrapping limit - within a day or two)
    int tc7 = CalculateTC(TimeSpan.FromDays(50));
    Console.WriteLine("Expect to not see 50 Days ago: {0}", ConvertTickToDateTime(tc7));

    // 10 Seconds ahead (Should read as a very old date - around 50 days ago)
    int tc8 = Convert.ToInt32(Environment.TickCount + TimeSpan.FromSeconds(10).TotalMilliseconds);
    Console.WriteLine("Expect to not see 10 seconds from now: {0}", ConvertTickToDateTime(tc8));    
}


private static int CalculateTC(TimeSpan timespan)
{
    int nowTick = Environment.TickCount;
    double mSecToGoBack = timespan.TotalMilliseconds;

    int tc;

    if (Math.Abs(nowTick - int.MinValue) >= mSecToGoBack) // Then we don't have to deal with an overflow
    {
        tc = Convert.ToInt32(nowTick - mSecToGoBack);
    }
    else // Deal with the overflow wrapping
    {
        double remainingTime = nowTick + Math.Abs(Convert.ToDouble(int.MinValue));
        remainingTime = mSecToGoBack - remainingTime;

        tc = Convert.ToInt32(int.MaxValue - remainingTime);
    }
    return tc;
}

以下是测试应用程序的输出:

Test Start Time: 5/3/2019 4:30:05 PM
Expect 10 seconds ago: 5/3/2019 4:29:55 PM
Expect 10 minutes ago: 5/3/2019 4:20:05 PM
Expect 10 hours ago: 5/3/2019 6:30:05 AM
Expect 1 Day ago: 5/2/2019 4:30:05 PM
Expect 10 Days ago: 4/23/2019 4:30:05 PM
Expect 30 Days ago: 4/3/2019 4:30:05 PM
Expect 48 Days ago: 3/16/2019 4:30:05 PM
Expect to not see 50 Days ago: 5/3/2019 9:32:53 AM
Expect to not see 10 seconds from now: 3/14/2019 11:27:28 PM

我希望这对可能与我处于类似情况的人有所帮助。

于 2019-05-03T16:39:24.030 回答
1

而不是滴答计数int

似乎您更喜欢不同的数据类型:

  • 系统正常运行时间为TimeSpan
  • 系统启动时间为DateTime

(但是,根据您问题的措辞,我不能 100% 确定)

下面是使用Windows Management Instrumentation获取这些属性的代码示例。

using System;
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Options;
using System.Linq;

namespace MachineTimeStamps
{
    class Program
    {
        /// <summary>
        /// Print the system Uptime and Last Bootup Time (using Win32_OperatingSystem LocalDateTime & LastBootUpTime properties).
        /// </summary>
        public static void Main(string[] args)
        {
            var uptime = GetSystemUptime("COMPUTER_NAME");

            if (!uptime.HasValue)
            {
                throw new NullReferenceException("GetSystemUptime() response was null.");
            }

            var lastBootUpTime = GetSystemLastBootUpTime("COMPUTER_NAME");

            if (!lastBootUpTime.HasValue)
            {
                throw new NullReferenceException("GetSystemLastBootUpTime() response was null.");
            }

            Console.WriteLine($"Uptime: {uptime}");
            Console.WriteLine($"BootupTime: {lastBootUpTime}");
            Console.ReadKey();
        }

        /// <summary>
        /// Retrieves the duration (TimeSpan) since the system was last started.
        /// Note: can be used on a local or a remote machine.
        /// </summary>
        /// <param name="computerName">Name of computer on network to retrieve uptime for</param>
        /// <returns>WMI Win32_OperatingSystem LocalDateTime - LastBootUpTime</returns>
        private static TimeSpan? GetSystemUptime(string computerName)
        {
            string namespaceName = @"root\cimv2";
            string queryDialect = "WQL";

            DComSessionOptions SessionOptions = new DComSessionOptions();
            SessionOptions.Impersonation = ImpersonationType.Impersonate;

            CimSession session = CimSession.Create(computerName, SessionOptions);

            string query = "SELECT * FROM Win32_OperatingSystem";
            var cimInstances = session.QueryInstances(namespaceName, queryDialect, query);

            if (cimInstances.Any())
            {
                var cimInstance = cimInstances.First();
                var lastBootUpTime = Convert.ToDateTime(cimInstance.CimInstanceProperties["LastBootUpTime"].Value);
                var localDateTime = Convert.ToDateTime(cimInstance.CimInstanceProperties["LocalDateTime"].Value);

                var uptime = localDateTime - lastBootUpTime;
                return uptime;
            }

            return null;
        }

        /// <summary>
        /// Retrieves the last boot up time from a system.
        /// Note: can be used on a local or a remote machine.
        /// </summary>
        /// <param name="computerName">Name of computer on network to retrieve last bootup time from</param>
        /// <returns>WMI Win32_OperatingSystem LastBootUpTime</returns>
        private static DateTime? GetSystemLastBootUpTime(string computerName)
        {
            string namespaceName = @"root\cimv2";
            string queryDialect = "WQL";

            DComSessionOptions SessionOptions = new DComSessionOptions();
            SessionOptions.Impersonation = ImpersonationType.Impersonate;

            CimSession session = CimSession.Create(computerName, SessionOptions);

            string query = "SELECT * FROM Win32_OperatingSystem";
            var cimInstances = session.QueryInstances(namespaceName, queryDialect, query);

            if (cimInstances.Any())
            {
                var lastBootUpTime = Convert.ToDateTime(cimInstances.First().CimInstanceProperties["LastBootUpTime"].Value);
                return lastBootUpTime;
            }

            return null;
        }
    }
}
于 2019-11-08T17:30:48.657 回答
0

我似乎从 Jerren 的解决方案中得到了错误的结果;这可能是一个捏造/不正确的 - 涉及溢出的复杂性,但这让我接近了我的测试的正确结果,试图近似结果:

    // TimeSpan result

    var approxUpTime = TryApproximateUpTime();

    private static TimeSpan? TryApproximateUpTime()
    {
        TimeSpan? retVal;

        var envTickCountInMs =
            Environment.TickCount;

        try
        {
            retVal = 
                envTickCountInMs > 0
                    ?
                        new DateTime()
                            .AddMilliseconds(Environment.TickCount) -
                                DateTime.MinValue
                    :
                        new TimeSpan(
                            new DateTime(
                                ((long)int.MaxValue + (envTickCountInMs & int.MaxValue)) * 10 * 1000).Ticks);
        }
        catch (Exception)
        {
            // IGNORE
            retVal = null;
        }

        return retVal;
    }
于 2020-01-09T12:07:01.780 回答