简体   繁体   English

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

[英]C# UTC conversion and Daylight Saving Time in game save

So, I've made a game in which it is required to check the time when saving and loading. 因此,我制作了一款需要检查保存和加载时间的游戏。

The relevant chunk of loading code: 相关的加载代码块:

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();
}

Obviously, all this does is check to see if the currently saved timestamp can be parsed - if so, we make sure it's UTC, if not, we set the stamp to the current time and continue. 显然,所有这一切都是检查是否可以解析当前保存的时间戳-如果可以,请确保它是UTC,否则请设置为当前时间并继续。 If the elapsed time between the loaded timestamp and current time is negative, we know the player has messed with their clock to exploit the system. 如果加载的时间戳记与当前时间之间的经过时间为负,那么我们知道玩家已经弄乱了他们的时钟来利用该系统。

The potential problem arises when the clocks move an hour back for DST. 当时钟返回夏令时一个小时后,就会出现潜在的问题。

This is the relevant code in the save function, if it matters: 如果重要,这是保存功能中的相关代码:

if (loadStamp == System.DateTime.MinValue) {
    loadStamp = System.DateTime.UtcNow;
}

playerData.timeStamp = loadStamp.AddSeconds(sessionSeconds).ToString("o");

My question is: 我的问题是:

Will this currently used method potentially cause any problems when the clocks move back and falsely deem players cheaters? 当时钟向后移动并错误地认为玩家作弊时,这种当前使用的方法是否会引起任何问题?

Thank you in advance. 先感谢您。

EDIT: Forgot to add that it seems to not cause any problems on the computer when the time is set to when the clocks move back, but the game is mobile. 编辑:忘了补充一点,当时间设置为时钟移回时,它似乎不会在计算机上引起任何问题,但是游戏是可移动的。 Again, if that matters at all. 再说一次,如果那很重要的话。 Not quite sure. 不太确定。 I've not done much with time-based rewards and stuff in games thus far. 到目前为止,我对基于时间的奖励和游戏中的东西还没有做太多事情。

Update I have significantly updated this answer in respect of comments made by @theMayer and the fact that while I was wrong, it may have highlighted a bigger issue. 更新我已经对@theMayer的评论以及我虽然错了但可能突出了一个更大的问题这一事实进行了重大更新。


I believe there is an issue here in the fact that the code is reading the UTC time in, converting it to local time, then converting it back to UTC. 我认为这里存在一个问题,因为代码正在读取UTC时间,将其转换为本地时间,然后将其转换回UTC。

The save routine records the value of loadStamp expressed with the Round Trip format specifier o , and as loadStamp is always set from DateTime.UtcNow , the value stored in the file will always be a UTC time with a trailing "Z" indicating UTC time. 保存例程记录用往返格式说明符o表示的loadStamp的值,并且由于总是从DateTime.UtcNow设置loadStamp ,因此文件中存储的值将始终是UTC时间,尾部的“ Z”表示UTC时间。

For example: 例如:

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

The issue was reported in the Brazil time zone, with a UTC offset of UTC-02:00 (BRST) until 2018-02-18T02:00:00Z and a UTC offset of UTC-03:00 (BRT) after. 此问题是在巴西时区报告的,UTC偏移为UTC-02:00(BRST),直到2018-02-18T02:00:00Z,UTC偏移为UTC-03:00(BRT)。

The code reaches this line: 代码到达此行:

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

DateTime.TryParse() (which uses the same rules as DateTime.Parse() ) will encounter this string. DateTime.TryParse() (使用与DateTime.Parse()相同的规则)将遇到此字符串。 It will then convert the UTC time into a local time, and set stamp to equal: 然后它将UTC时间转换为本地时间,并将stamp设置为等于:

2018-02-17T23:30:00 DateTimeKind.Local 2018-02-17T23:30:00 DateTimeKind.Local

The code then reaches: 代码然后到达:

stamp = stamp.ToUniversalTime();

At this point, stamp should represent an Ambiguous time, ie one that exists as a valid BRST and a valid BRT time, and MSDN states: 此时, stamp 表示一个模糊时间,即存在一个有效的BRST和有效的BRT时间,并且MSDN指出:

If the date and time instance value is an ambiguous time, this method assumes that it is a standard time. 如果日期和时间实例值是不明确的时间,则此方法假定它是标准时间。 (An ambiguous time is one that can map either to a standard time or to a daylight saving time in the local time zone) (歧义时间是指可以映射到当地时区的标准时间或夏令时的时间)

This means that .NET could be changing the UTC value of any ambiguous DateTime values that are converted to Local time and back again. 这意味着.NET 可能会更改任何不确定的DateTime值的UTC值,这些日期值将转换为Local time并再次转换为Local time。

Although the documentation states this clearly, I have been unable to reproduce this behaviour in the Brazilian time zone. 尽管文档清楚地说明了这一点,但我无法在巴西时区重现此行为。 I am still investigating this. 我仍在对此进行调查。


My approach to this type of issue is to use the DateTimeOffset type instead of DateTime . 解决此类问题的方法是使用DateTimeOffset类型而不是DateTime It represents a point-in-time that is irrelevant of local time, time zones, or Daylight Savings. 它表示与当地时间,时区或夏令时无关的时间点。

An alternative approach to closing this hole would be to change: 解决此漏洞的另一种方法是更改​​:

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

to

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

Again assuming that the saved playerData.timeStamp will always be from a UTC date and therefore be in "Z" timezone, adding the DateTimeStyles.RoundtripKind should mean it gets parsed straight into DateTimeKind.Utc and not converted into Local time as DateTimeKind.Local . 再次假设保存的playerData.timeStamp将始终来自UTC日期,因此处于“ Z”时区,则添加DateTimeStyles.RoundtripKind应该意味着将其直接解析为DateTimeKind.Utc而不转换为LocalTime作为DateTimeKind.Local It also eliminates the need to call ToUniversalTime() on it to convert it back. 它还消除了对其调用ToUniversalTime()以将其转换回去的需要。

Hope this helps 希望这可以帮助

On the surface, there does not appear to be anything obviously wrong with this code. 从表面上看,此代码似乎没有任何明显的错误。

When doing DateTime comparison operations, it is important to ensure that the time zone of the compared DateTime 's are consistent. 在进行DateTime比较操作时,重要的是要确保比较的DateTime的时区一致。 The framework only compares the values of the instances, irrespective of whatever time zone you think they are in. It is up to you to ensure consistent time zones between DateTime instances. 该框架仅比较实例的值,而不管您认为它们处于哪个时区。取决于您如何确保DateTime实例之间的时区一致。 It looks like that is being done in this case, as times are converted from local to UTC prior to being compared with other UTC times: 在与其他UTC时间进行比较之前,时间已从本地转换为UTC,因此在这种情况下似乎正在这样做:

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

Some Caveats 一些警告

One item that often trips people up is that the time value is repeatedly queried (each call to DateTime.UtcNow ) - which could result in different values each time. 经常使人绊倒的一项是重复查询时间值(每次调用DateTime.UtcNow )-每次可能导致不同的值。 However, the difference would be infinitesimal, and most of the time zero as this code will execute faster than the resolution of the processor clock . 但是,差异将是无穷小,并且大多数时间为零,因为此代码的执行速度将比处理器时钟分辨率快。

Another fact, which I brought up in the comments, the "Round Trip Format Specifier" used to write the DateTime to string is intended to preserve time zone information - in this case, it should add a "Z" to the time to denote UTC. 我在评论中提到的另一个事实是,用于将DateTime写入字符串的“往返格式说明符”旨在保留时区信息-在这种情况下, 应该在时间上添加“ Z”以表示UTC 。 Upon conversion back (via TryParse ), the parser will convert this time from the UTC to a local time if the Z is present. 在转换回(通过TryParse )后, 如果Z出现解析器会将此时间从UTC转换为本地时间 This can be a significant gotcha as it results in an actual DateTime value that is different from the one serialized to the string, and in a way is contrary to every other way that the .NET framework handles DateTime 's (which is to ignore time zone info). 这可能是一个重大难题,因为它会导致实际的DateTime值不同于序列化到字符串的值,并且在某种程度上与.NET框架处理DateTime的其他方式相反(即忽略时间)区域信息)。 If you have a case where the "Z" is not present in the incoming string, but the string is otherwise UTC, then you have a problem there because it will be comparing a UTC time whose value has been adjusted a second time (thus making it the time of UTC +2). 如果输入的字符串中不存在“ Z”,而字符串不是UTC,则存在问题,因为它将比较第二次调整了其值的UTC时间(因此它是UTC +2的时间)。

It should also be noted that in .Net 1.1, DateTime.ToUniversalTime() is NOT an idempotent function . 还应注意,在.Net 1.1中, DateTime.ToUniversalTime()不是幂等函数 It will offset the DateTime instance by the difference in time zone between the local time zone and UTC each time it is called. 每次调用时,都会使DateTime实例偏移本地时区与UTC之间的时区差。 From the documentation : 文档中

This method assumes that the current DateTime holds the local time value, and not a UTC time. 此方法假定当前的DateTime保留本地时间值,而不是UTC时间。 Therefore, each time it is run, the current method performs the necessary modifications on the DateTime to derive the UTC time, whether the current DateTime holds the local time or not. 因此,无论当前DateTime是否保存本地时间,当前方法每次运行时,当前方法都会对DateTime进行必要的修改以得出UTC时间。

Programs using a later version of the framework may or may not have to worry about this, depending on usage. 使用此框架的更高版本的程序可能不必担心这一点,具体取决于用法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM