我的时代,他们正在改变,那是因为我需要他们改变。 我正在测试一些涉及我使用的调度程序的案例,这涉及到与 daylight saving time 之间的转换行为。 .
代码
来自 this post我得到了一种工作方法,使我能够以编程方式更改系统日期(重新发布大部分代码):
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetSystemTime(ref SYSTEMTIME st);
为了我自己的方便,我只是将它包装在我实际调用的这个函数中:
public static void SetSytemDateTime(DateTime timeToSet)
{
DateTime uniTime = timeToSet.ToUniversalTime();
SYSTEMTIME setTime = new SYSTEMTIME()
{
wYear = (short)uniTime.Year,
wMonth = (short)uniTime.Month,
wDay = (short)uniTime.Day,
wHour = (short)uniTime.Hour,
wMinute = (short)uniTime.Minute,
wSecond = (short)uniTime.Second,
wMilliseconds = (short)uniTime.Millisecond
};
SetSystemTime(ref setTime);
}
必须额外转换为世界时,否则我无法在我的时钟(在任务栏下方)中看到我传递给该方法的日期。
现在考虑以下代码可以正常工作:
DateTime timeToSet = new DateTime(2014, 3, 10, 1, 59, 59, 0);
Console.WriteLine("Attemting to set time to {0}", timeToSet);
SetSytemDateTime(timeToSet);
Console.WriteLine("Now time is {0}, which is {1} (UTC)", DateTime.Now, DateTime.UtcNow);
Thread.Sleep(TimeSpan.FromSeconds(5));
DateTime actualSystemTime = GetNetworkTime();
SetSytemDateTime(actualSystemTime);
GetNetworkTime
方法实际上只是从over here 中抓取的, 所以我可以在测试后将我的时钟设置回“真实”时间,你可以为了这个问题而忽略它。
示例输出 #1
这就是您所期望的(德语 DateTime 格式,不要混淆):
在任务栏中我也看到了我所期望的:
示例输出#2(过渡到夏令时)
但现在到了奇怪的部分: 将第一行调用代码切换为
// one second before transition to daylight saving time in Berlin
DateTime timeToSet = new DateTime(2015, 3, 29, 1, 59, 59, 0);
现在命令行输出实际上似乎满足我们所期望看到的:
但是然后我们向下看任务栏的右侧,进入皱眉之地,看到那天实际上不应该存在的时间:
示例输出#3(转换出夏令时)
现在,有趣的是,当我在夏令时转换之前第二次尝试同样的事情时,更改被“接受”(再次切换第一个调用代码行):
// one second before transition out of daylight saving time in Berlin
DateTime timeToSet = new DateTime(2014, 10, 26, 2, 59, 59, 0);
我们在命令行输出中看到了我们期望的内容:
也在任务栏时钟中:
但这个故事也有一个悲伤的结局,一秒钟过去了,你会期望时钟显示 2 点,但相反:
该时间实际应在该特定日期晚一小时出现(如果您在 Windows 中手动切换时间,此转换会按预期进行)。
问题
现在,我在这里缺少什么,为什么我不能在转换到夏令时之前将第二个作为目标,为什么当我以这种方式以编程方式更改日期时间时,我看不到夏令时的转换?
我需要添加/设置什么才能做到这一点?
最佳答案
我可以解释您的示例 #3。
2014 年 10 月 26 日在德国,当时钟接近凌晨 3:00 时,小时将重置为凌晨 2:00,从 2:00:00 到 2:59:59 的值重复两次。这称为“回退”过渡。
当您在此转换中的本地日期时间调用
ToUniversalTime
时,它是不明确的。 .Net 将假设您的意思是原始值是在标准时间 - 而不是夏令时。换句话说,时间 2:59:59 存在两次,.Net 假定第二个。
因此,一秒后确实是3:00:00。
如果您想对此进行控制,您可以使用 DateTimeOffset
类型而不是 DateTime
类型 - 您可以在其中明确指定偏移量。您还可以使用 TimeZoneInfo.IsAmbiguousTime
测试此条件。
关于您的示例 #2,SetSystemTime
似乎与 SetLocalTime
in the MSDN 描述的问题相同.当您设置系统时间时,您是通过 UTC 正确设置时间,但为了显示它正在使用当前设置转换为本地时区。
具体来说,注册表中的 ActiveTimeBias
设置用于执行 UTC 到本地的转换。 More in this article .
从实验来看,如果距离 DST 转换时间超过一个小时,那么它也会触发对 ActiveTimeBias
的更新,一切都很好。
总而言之,只有满足以下所有条件时,您才会出现此行为:
您设置的时间是标准时间。
您当前的本地时间是夏令时。
您设置的时间不超过 Spring 夏令时过渡前 一小时。
考虑到这一点,我编写了应该解决这两个问题的代码:
public static void SetSystemDateTimeSafely(DateTime timeToSet,
bool withEarlierWhenAmbiguous = true)
{
TimeZoneInfo timeZone = TimeZoneInfo.Local;
bool isAmbiguous = timeZone.IsAmbiguousTime(timeToSet);
DateTime utcTimeToSet = timeToSet.ToUniversalTime();
if (isAmbiguous && withEarlierWhenAmbiguous)
utcTimeToSet = utcTimeToSet.AddHours(-1);
TimeSpan offset = timeZone.GetUtcOffset(utcTimeToSet);
TimeSpan offsetOneHourLater = timeZone.GetUtcOffset(utcTimeToSet.AddHours(1));
if (offset != offsetOneHourLater)
{
TimeSpan currentOffset = timeZone.GetUtcOffset(DateTime.UtcNow);
if (offset != currentOffset)
{
SetSystemDateTime(utcTimeToSet.AddHours(-1));
}
}
SetSystemDateTime(utcTimeToSet);
}
private static void SetSystemDateTime(DateTime utcDateTime)
{
if (utcDateTime.Kind != DateTimeKind.Utc)
{
throw new ArgumentException();
}
SYSTEMTIME st = new SYSTEMTIME
{
wYear = (short)utcDateTime.Year,
wMonth = (short)utcDateTime.Month,
wDay = (short)utcDateTime.Day,
wHour = (short)utcDateTime.Hour,
wMinute = (short)utcDateTime.Minute,
wSecond = (short)utcDateTime.Second,
wMilliseconds = (short)utcDateTime.Millisecond
};
SetSystemTime(ref st);
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetSystemTime(ref SYSTEMTIME st);
您现在可以使用您喜欢的任何日期调用 SetSystemDateTimeSafely
,它将补偿这种奇怪的行为。
这是通过首先设置一个在有问题的范围之前的值来工作的,但仅在需要时才这样做。然后它会立即设置正确的值。
我能想到的唯一缺点是它会引发两个 WM_TIMECHANGE
消息,在系统事件日志中阅读时可能会造成混淆。
如果您将 withEarlierWhenAmbiguous
参数保留为默认 true
,它将具有选择您期望的第一个实例的行为你的例子#3。如果将其设置为 false,它将具有 .NET 的默认行为,即选择第二个 实例。
关于c# - 为什么我不能将我的系统时间设置为接近夏令时转换的时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25489465/