所以,我做了一个游戏,需要检查保存和加载的时间。
相关的加载代码块:
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");
我的问题是:
这种当前使用的方法是否会在时钟倒退并错误地认为玩家作弊时会引起任何问题?
提前谢谢。
编辑: 忘了添加它,当时间设置为时钟向后移动时,它似乎不会在计算机上造成任何问题,但游戏是移动的。再说一遍,如果这很重要的话。不太确定。到目前为止,我在游戏中没有做过基于时间的奖励和内容。
答案 0 :(得分:1)
更新我已经对@theMayer发表的评论以及虽然我错了,这可能突出了一个更大的问题这一事实已经大大更新了这个答案。
我认为这里存在一个问题,即代码正在读取UTC时间,将其转换为本地时间,然后将其转换回UTC。
保存例程记录以往返格式说明符loadStamp
表示的o
的值,并且loadStamp
始终从DateTime.UtcNow
设置,值存储在文件将始终为UTC时间,尾随“Z”表示UTC时间。
例如:
2018-02-18T01:30:00.0000000Z(= 2018-02-17T23:30:00,UTC-02:00)
此问题在Brazil时区报告,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值,这些值将转换为本地时间并再次返回。
虽然文档清楚地说明了这一点,但我无法在巴西时区重现此行为。我还在调查这个。
我对此类问题的处理方法是使用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()
以将其转换回来的需要。
希望这有帮助
答案 1 :(得分:1)
从表面上看,此代码似乎没有明显错误。
进行DateTime
比较操作时,务必确保比较DateTime
的时区一致。该框架仅比较实例的值,无论您认为它们处于何种时区。您需要确保DateTime
个实例之间的时区一致。看起来在这种情况下正在进行,因为在与其他UTC时间进行比较之前,时间从本地转换为UTC:
stamp = stamp.ToUniversalTime();
elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
一些注意事项
经常绊倒一个人的一个项目是反复查询时间值(每次调用DateTime.UtcNow
) - 每次可能导致不同的值。但是,差异将是无穷小的,并且大部分时间为零,因为此代码的执行速度将快于resolution of the processor clock。
我在评论中提到的另一个事实是"Round Trip Format Specifier"用于将DateTime
写入字符串,旨在保留时区信息 - 在这种情况下,应该添加&#34; Z&#34;到了表示UTC的时间。转换回来后(通过TryParse
),如果Z存在,解析器会将此时间从UTC转换为本地时间。 这可能很重要因为它导致实际的DateTime
值不同于序列化到字符串的值,并且在某种程度上违反了.NET框架处理DateTime
的所有其他方式(这是忽略时区信息)。如果你有一个&#34; Z&#34;传入的字符串中不存在,但是字符串是UTC,那么你在那里遇到问题,因为它将比较第二次调整其值的UTC时间(因此使其成为UTC +2的时间)。 / p>
还应注意,在.Net 1.1中,DateTime.ToUniversalTime()
不是idempotent function。它会将DateTime
实例与每次调用本地时区和UTC之间的时区差异进行偏移。来自the documentation:
此方法假定当前DateTime保存本地时间值,而不是UTC时间。因此,每次运行时,当前方法都会对DateTime执行必要的修改以获得UTC时间,无论当前DateTime是否保持本地时间。
使用更高版本框架的程序可能会也可能不会担心这一点,具体取决于使用情况。