我正在捕获 C# 程序中的一些事件,这些事件以时间戳作为系统滴答计数(自开始时间以来的毫秒数)返回。
根据我看到的其他问题,知道我可以从System.Environment.TickCount
属性(或其他东西)获得相同的数字,我如何推断与收到的 TickCount 对应的 DateTime 对象?
你不能,没有更多信息(即使那样它也可能是模棱两可的)。Environment.TickCount
返回:
一个 32 位有符号整数,包含自上次启动计算机以来经过的时间量(以毫秒为单位)。
...因此,除非您能找出计算机从某个地方启动的时间,否则您就不走运了。您很可能可以通过注册表项或系统调用来找出上次启动时间,但我不知道它们是否在我的脑海中。当然,您可以通过自己和尽可能早(或早)获得近似值,并找出两者之间的差异:Environment.TickCount
DateTime.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
尽可能避免使用,基本上 - 这完全在你的控制之下吗?
我承认这是一个非常古老的问题,但由于这是我搜索时谷歌的第一次点击,我觉得其他人可能会登陆这里。@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
我希望这对可能与我处于类似情况的人有所帮助。
而不是滴答计数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;
}
}
}
我似乎从 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;
}