[英]C# UTC conversion and Daylight Saving Time in game save
因此,我制作了一款需要检查保存和加载时间的游戏。
相关的加载代码块:
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,否则请设置为当前时间并继续。 如果加载的时间戳记与当前时间之间的经过时间为负,那么我们知道玩家已经弄乱了他们的时钟来利用该系统。
当时钟返回夏令时一个小时后,就会出现潜在的问题。
如果重要,这是保存功能中的相关代码:
if (loadStamp == System.DateTime.MinValue) {
loadStamp = System.DateTime.UtcNow;
}
playerData.timeStamp = loadStamp.AddSeconds(sessionSeconds).ToString("o");
我的问题是:
当时钟向后移动并错误地认为玩家作弊时,这种当前使用的方法是否会引起任何问题?
先感谢您。
编辑:忘了补充一点,当时间设置为时钟移回时,它似乎不会在计算机上引起任何问题,但是游戏是可移动的。 再说一次,如果那很重要的话。 不太确定。 到目前为止,我对基于时间的奖励和游戏中的东西还没有做太多事情。
更新我已经对@theMayer的评论以及我虽然错了但可能突出了一个更大的问题这一事实进行了重大更新。
我认为这里存在一个问题,因为代码正在读取UTC时间,将其转换为本地时间,然后将其转换回UTC。
保存例程记录用往返格式说明符o
表示的loadStamp
的值,并且由于总是从DateTime.UtcNow
设置loadStamp
,因此文件中存储的值将始终是UTC时间,尾部的“ Z”表示UTC时间。
例如:
2018-02-18T01:30:00.0000000Z(= 2018-02-17T23:30:00在UTC-02:00)
此问题是在巴西时区报告的,UTC偏移为UTC-02:00(BRST),直到2018-02-18T02:00:00Z,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.Local
代码然后到达:
stamp = stamp.ToUniversalTime();
此时, stamp
应表示一个模糊时间,即存在一个有效的BRST和有效的BRT时间,并且MSDN指出:
如果日期和时间实例值是不明确的时间,则此方法假定它是标准时间。 (歧义时间是指可以映射到当地时区的标准时间或夏令时的时间)
这意味着.NET 可能会更改任何不确定的DateTime值的UTC值,这些日期值将转换为Local time并再次转换为Local time。
尽管文档清楚地说明了这一点,但我无法在巴西时区重现此行为。 我仍在对此进行调查。
解决此类问题的方法是使用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
而不转换为LocalTime作为DateTimeKind.Local
。 它还消除了对其调用ToUniversalTime()
以将其转换回去的需要。
希望这可以帮助
从表面上看,此代码似乎没有任何明显的错误。
在进行DateTime
比较操作时,重要的是要确保比较的DateTime
的时区一致。 该框架仅比较实例的值,而不管您认为它们处于哪个时区。取决于您如何确保DateTime
实例之间的时区一致。 在与其他UTC时间进行比较之前,时间已从本地转换为UTC,因此在这种情况下似乎正在这样做:
stamp = stamp.ToUniversalTime();
elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
一些警告
经常使人绊倒的一项是重复查询时间值(每次调用DateTime.UtcNow
)-每次可能导致不同的值。 但是,差异将是无穷小,并且大多数时间为零,因为此代码的执行速度将比处理器时钟的分辨率快。
我在评论中提到的另一个事实是,用于将DateTime
写入字符串的“往返格式说明符”旨在保留时区信息-在这种情况下, 应该在时间上添加“ Z”以表示UTC 。 在转换回(通过TryParse
)后, 如果Z出现 , 解析器会将此时间从UTC转换为本地时间。 这可能是一个重大难题,因为它会导致实际的DateTime
值不同于序列化到字符串的值,并且在某种程度上与.NET框架处理DateTime
的其他方式相反(即忽略时间)区域信息)。 如果输入的字符串中不存在“ Z”,而字符串不是UTC,则存在问题,因为它将比较第二次调整了其值的UTC时间(因此它是UTC +2的时间)。
还应注意,在.Net 1.1中, DateTime.ToUniversalTime()
不是幂等函数 。 每次调用时,都会使DateTime
实例偏移本地时区与UTC之间的时区差。 从文档中 :
此方法假定当前的DateTime保留本地时间值,而不是UTC时间。 因此,无论当前DateTime是否保存本地时间,当前方法每次运行时,当前方法都会对DateTime进行必要的修改以得出UTC时间。
使用此框架的更高版本的程序可能不必担心这一点,具体取决于用法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.