c# - 为什么我不能将我的系统时间设置为接近夏令时转换的时间

标签 c# .net datetime timezone dst

我的时代,他们正在改变,那是因为我需要他们改变。 我正在测试一些涉及我使用的调度程序的案例,这涉及到与 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 格式,不要混淆): cmdli output attemting to change system time 1

在任务栏中我也看到了我所期望的:

taskbar clock showing time 1

示例输出#2(过渡到夏令时)

但现在到了奇怪的部分: 将第一行调用代码切换为

// one second before transition to daylight saving time in Berlin
DateTime timeToSet = new DateTime(2015, 3, 29, 1, 59, 59, 0);

现在命令行输出实际上似乎满足我们所期望看到的: cmdli output attemting to change system time 2

但是然后我们向下看任务栏的右侧,进入皱眉之地,看到那天实际上不应该存在的时间:

taskbar clock showing time 2

示例输出#3(转换出夏令时)

现在,有趣的是,当我在夏令时转换之前第二次尝试同样的事情时,更改被“接受”(再次切换第一个调用代码行):

// one second before transition out of daylight saving time in Berlin
DateTime timeToSet = new DateTime(2014, 10, 26, 2, 59, 59, 0);

我们在命令行输出中看到了我们期望的内容:

cmdli output attemting to change system time 3

也在任务栏时钟中:

taskbar clock showing time 3

但这个故事也有一个悲伤的结局,一秒钟过去了,你会期望时钟显示 2 点,但相反:

taskbar clock showing time 4

该时间实际应在该特定日期晚一小时出现(如果您在 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/

相关文章:

c# - 反射(reflection) |发射|动态资源生成 - 使用 IResourceWriter.AddResource(key,value) 添加的资源值无法读取

c# - 有条件地改变目标框架版本

php - 空 DATETIME 值存储为 0000-00-00 00 :00:00 instead of NULL in MySQL

.net - 事务中的数据库过程是否仍会从 ADO.net 事务中回滚?

来自字符串的 PHP 日期时间

python-3.x - 有没有一种Pythonic方法将日期时间上的数据帧与具有不规则日期时间戳的数据对合并

c# - LibGit2Sharp DllNotFoundException : Unable to load DLL 'git2-106a5f2'

c# - "struct"从哪里来?

c# - LINQ可以构造并返回一个多维数组吗

c# - 如何使用 Trello.NET 从 Trello 获取所有看板?