代码之家  ›  专栏  ›  技术社区  ›  M. W.

游戏保存中的C#UTC转换和夏令时

  •  0
  • M. W.  · 技术社区  · 8 年前

    因此,我制作了一个游戏,其中需要检查保存和加载时的时间。

    加载代码的相关区块:

    playerData = save.LoadPlayer();
    
    totalSeconds = playerData.totalSeconds;
    
    System.DateTime stamp = System.DateTime.MinValue;
    
    if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
            playerData.timeStamp = System.DateTime.UtcNow.ToString("o");
            stamp = System.DateTime.Parse(playerData.timeStamp);
    }
    
    stamp = stamp.ToUniversalTime();
    
    loadStamp = System.DateTime.UtcNow;
    
    long elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
    
    if (elapsedSeconds < 0) {
            ui.Cheater();
    }
    

    显然,所有这一切都是为了检查当前保存的时间戳是否可以被解析——如果可以,我们确保它是UTC,如果不是,我们将戳设置为当前时间并继续。如果加载的时间戳和当前时间之间的经过时间是负数,我们就知道玩家弄乱了时钟来利用系统。

    当时钟为DST向后移动一小时时,可能会出现问题。

    这是“保存”功能中的相关代码(如果重要):

    if (loadStamp == System.DateTime.MinValue) {
        loadStamp = System.DateTime.UtcNow;
    }
    
    playerData.timeStamp = loadStamp.AddSeconds(sessionSeconds).ToString("o");
    

    我的问题是:

    当时钟向后移动并错误地认为玩家是骗子时,这种当前使用的方法是否会导致任何问题?

    提前谢谢你。

    编辑: 忘了补充一下,当时间设置为时钟向后移动时,似乎不会在计算机上造成任何问题,但游戏是移动的。再说一次,如果这有什么关系的话。不太确定。到目前为止,我还没有在游戏中使用基于时间的奖励和其他东西。

    2 回复  |  直到 8 年前
        1
  •  1
  •   Trevor    8 年前

    使现代化 我已经针对@theMayer的评论对这个答案进行了重大更新,虽然我错了,但它可能突出了一个更大的问题。


    我认为这里有一个问题,代码正在读取UTC时间,将其转换为本地时间,然后将其转换回UTC。

    保存例程记录 loadStamp 用往返格式说明符表示 o ,和as 装载戳记 始终从设置 DateTime.UtcNow ,文件中存储的值将始终是UTC时间,后面的“Z”表示UTC时间。

    例如:

    2018-02-18T01:30:00.0000000Z(=UTC-02:00中的2018-02-17T23:30:00)

    该问题已在 Brazil 时区,在2018-02-18T02:00:00Z之前的UTC偏移量为UTC-02:00(BRST),之后的UTC偏移量为UTC-03:00(BRT)。

    代码到达此行:

    if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
    

    DateTime.TryParse() (使用的规则与 DateTime.Parse() )将遇到此字符串。然后将UTC时间转换为本地时间,并设置 stamp 等于:

    2018-02-17T23:30:00 DateTimeKind。地方的

    然后代码到达:

    stamp = stamp.ToUniversalTime();
    

    此时, 邮票 应该 表示不明确的时间,即作为有效BRST和有效BRT时间存在的时间,以及 MSDN 国家:

    如果日期和时间实例值是不明确的时间,则此方法假定它是标准时间。(不明确的时间是可以映射到标准时间或本地时区中的夏令时的时间)

    这意味着。净额 能够 更改任何转换为本地时间并再次转换回本地时间的不明确日期时间值的UTC值。

    尽管文件明确指出了这一点,但我无法在巴西时区再现这种行为。我仍在调查此事。


    我处理此类问题的方法是使用 DateTimeOffset 键入而不是 DateTime . 它表示与当地时间、时区或夏令时无关的时间点。

    关闭该漏洞的另一种方法是改变:

    if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
        playerData.timeStamp = System.DateTime.UtcNow.ToString("o");
        stamp = System.DateTime.Parse(playerData.timeStamp);
    }
    stamp = stamp.ToUniversalTime();
    

    if (!System.DateTime.TryParse(playerData.timeStamp, null, DateTimeStyles.RoundtripKind, out stamp)) {
        stamp = System.DateTime.UtcNow;
        playerData.timeStamp = stamp.ToString("o");
    }
    

    再次假设已保存 playerData.timeStamp 将始终从UTC日期开始,因此位于“Z”时区,添加 DateTimeStyles.RoundtripKind 应该意味着它被直接解析为 DateTimeKind.Utc 不转换为当地时间 DateTimeKind.Local . 它还消除了调用 ToUniversalTime() 将其转换回。

    希望这有帮助

        2
  •  1
  •   theMayer    8 年前

    表面上看,似乎什么都没有 明显地 此代码错误。

    执行时 DateTime 比较操作时,重要的是要确保比较的时区 日期时间 的是一致的。该框架仅比较实例的值,而不管您认为它们处于哪个时区。由您来确保两个时区之间的一致性 日期时间 实例。在这种情况下,似乎正在这样做,因为在与其他UTC时间进行比较之前,时间会从本地时间转换为UTC时间:

    stamp = stamp.ToUniversalTime();
    elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
    

    一些注意事项

    一个经常让人困惑的问题是,重复查询时间值(每次调用 DateTime.UtcNow )-其中 能够 每次生成不同的值。然而,差异将是无穷小的,并且大多数时间为零,因为此代码的执行速度将比 resolution of the processor clock .

    我在评论中提到的另一个事实是 "Round Trip Format Specifier" 用于编写 日期时间 to字符串旨在保留时区信息—在本例中,它 应该 在时间中添加一个“Z”以表示UTC。转换回时(通过 TryParse ), 解析器将此时间从UTC转换为本地时间 如果存在Z。 这可能是一个重要的问题,因为它会导致 日期时间 与序列化为字符串的值不同的值,并且在某种程度上与。NET framework句柄 日期时间 的(即忽略时区信息)。如果传入字符串中不存在“Z”,但字符串为UTC,则存在问题,因为它将比较第二次调整其值的UTC时间(从而使其成为UTC+2的时间)。

    还应注意,在中。净额1.1, DateTime.ToUniversalTime() 不是 idempotent function . 它将抵消 日期时间 每次调用时,根据本地时区和UTC之间的时区差进行实例。从…起 the documentation :

    此方法假定当前日期时间包含本地时间值,而不是UTC时间。因此,每次运行时,当前方法都会对DateTime执行必要的修改,以派生UTC时间,无论当前DateTime是否保留本地时间。

    使用更高版本框架的程序可能需要也可能不需要担心这一点,这取决于使用情况。