将我的DateTime转换为UTC时遇到问题

58

我在数据库中以UTC格式存储所有日期。我向用户询问他们的时区,希望使用他们的时区和我猜测的服务器时间来为他们找出UTC。

一旦我得到了UTC,我想进行搜索,以查看数据库中使用他们新转换的UTC日期的范围。

但我总是遇到这个异常。

System.ArgumentException was unhandled by user code  
Message="The conversion could not be completed because the   
supplied DateTime did not have the Kind property set correctly.  
For example, when the Kind property is DateTimeKind.Local,   
the source time zone must be TimeZoneInfo.Local.  
Parameter name: sourceTimeZone"

我不知道为什么会出现这种情况。

我尝试了两种方法。

 TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(id);
 // I also tried DateTime.UtcNow
 DateTime now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local); 
 var utc = TimeZoneInfo.ConvertTimeToUtc(now , zone );

这失败了,所以我尝试了

 DateTime now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local); 
 var utc = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(now, 
                                           ZoneId, TimeZoneInfo.Utc.Id);

这个方法也出现了同样的错误。我做错了什么?

编辑:这样行吗?

 DateTime localServerTime = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local);
 TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(id);

 var usersTime = TimeZoneInfo.ConvertTime(localServerTime, info);

 var utc = TimeZoneInfo.ConvertTimeToUtc(usersTime, userInfo);

编辑2 @ Jon Skeet

是的,我刚才想到可能根本不需要做所有这些。时间的问题让我感到困惑,所以帖子可能没有应该有的那么清晰。我从不知道DateTime.Now在获取什么(我尝试将时区更改为另一个时区,但它仍然获取我的当地时间)。

这就是我想要实现的:用户来到网站,添加一些警报并将其保存为utc(之前是DateTime.Now,然后有人建议将所有内容都存储为UTC)。

因此,以前用户会来到我的网站,取决于我的托管服务器的位置,可能会像下一天一样。因此,如果警报被设置为8月30日(他们的时间)显示,但由于服务器的时差,他们可能会在8月29日进入,警报将会显示。

所以我想要处理这个问题。现在我不确定是否应该只存储他们的本地时间,然后使用这个offset?还是只存储UTC时间。仅存储UTC时间仍然可能是错误的,因为用户仍然可能会按本地时间思考,而我不确定UTC如何真正工作。这仍然可能导致时间差异。

编辑3

 var info = TimeZoneInfo.FindSystemTimeZoneById(id)

 DateTimeOffset usersTime = TimeZoneInfo.ConvertTime(DataBaseUTCDate,
                                             TimeZoneInfo.Utc, info);

DateTime.Now.Kind 已经是 Local,你不需要调用 SpecifyKind - SLaks
@编辑2:将警报存储为UTC时间,并按照用户提供的时区显示,如我的答案所示。 - dtb
那么这该怎么做呢?我获取DateTime.UtcNow,然后从数据库中过滤警报?然后将找到的结果转换为本地时间? - chobo2
请查看编辑3是否正确。谢谢。 - chobo2
你说的“type”是什么意思?它是由DateTime.UtcNow创建的Utc日期时间。 - chobo2
显示剩余3条评论
5个回答

100
您需要将Kind设置为未指定的,像这样:
DateTime now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified);
var utc = TimeZoneInfo.ConvertTimeToUtc(now , zone);

DateTimeKind.Local 表示本地时区,而不是其他任何时区。这就是为什么你会收到错误信息的原因。


好的,我会尝试这样做。我刚刚编辑的内容会起作用吗?当你从一个时区转到另一个时区时,它似乎有点不准确(西非时区相差约10分钟),但UTC时间是正确的。 - chobo2
嗯,所以它不起作用是因为它不知道正确的时区?那么我仍然缺少某些东西,因为我认为我得到了错误的UTC时间。 - chobo2
嗯,好的,但我想这样做很多无用功吧?就像DateTime.UtcNow似乎会得到相同的结果。而且我认为如果我只是从一个时区转到另一个时区,它会出错。 - chobo2
2
“Unspecified”是一个可怕的名称,用于表示“除本地时间或协调世界时以外的其他时间”。 - smirkingman
我不明白为什么“Local”无法工作。我本地时间,我想将其转换为UTC? - PeterX
@PeterX:对于这个问题,使用Local即可解决。只有在从某些非本地时区进行转换时才会失败,因为那时它不是本地时间。 - SLaks

40

DateTime 结构只支持两个时区:

  • 本地计算机所在的 本地 时区。
  • 以及 UTC 时区。

可以查看 DateTimeOffset 结构。

var info = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");

DateTimeOffset localServerTime = DateTimeOffset.Now;

DateTimeOffset usersTime = TimeZoneInfo.ConvertTime(localServerTime, info);

DateTimeOffset utc = localServerTime.ToUniversalTime();

Console.WriteLine("Local Time:  {0}", localServerTime);
Console.WriteLine("User's Time: {0}", usersTime);
Console.WriteLine("UTC:         {0}", utc);

输出:

Local Time:  30.08.2009 20:48:17 +02:00
User's Time: 31.08.2009 03:48:17 +09:00
UTC:         30.08.2009 18:48:17 +00:00

似乎可以工作。时间方面有些困惑,我需要阅读一下DateTimeOffSet并弄清它为什么有效,以及转换方面的事情不行。但是我在想,我不能只使用DateTime.UtcNow吗?就像当我这样做时,它会将我的服务器时间转换为UTC日期,并且如果我使用您的dateoffSet(使用西非时间),那么它将是相同的。或者它们会不同? - chobo2
1
正如Jon Skeet所说,如果你只想获取当前的UTC时间,只需使用DateTime.UtcNowDateTimeOffset.UtcNow即可。 - dtb
2
我想知道为什么DateTime类没有提供TimeZoneInfo属性,这样我们就可以指定日期、时间和时区了! - brighty

10

其他人的回答似乎过于复杂。我有一个具体的要求,这个方法对我来说很有效:

void Main()
{
    var startDate = DateTime.Today;
    var StartDateUtc = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTime.SpecifyKind(startDate.Date, DateTimeKind.Unspecified), "Eastern Standard Time", "UTC");
    startDate.Dump();
    StartDateUtc.Dump();
}

从linqpad输出的结果与我预期的相同:

2013年12月20日 上午12:00:00

2013年12月20日 上午5:00:00

感谢Slaks提供未指定类型的提示。这就是我错过的东西。但是,关于日期只有两种类型(本地和UTC)的讨论使问题变得混乱。

顺便说一下 - 我运行这个程序的机器处于中央时区,夏令时没有生效。


7
正如dtb所说,如果您想存储带有特定时区的日期/时间,则应使用DateTimeOffset。
但是,从您的帖子中并不清楚您是否真正需要它。您只提供使用DateTime.Now的示例,并且您说您正在“猜测”您正在使用服务器时间。您实际上想要什么时间?如果您只想要当前的UTC时间,请使用DateTime.UtcNow或DateTimeOffset.UtcNow。您不需要知道时区就可以知道当前的UTC时间,因为它是全球通用的。
如果您以其他方式从用户那里获取日期/时间,请提供更多信息 - 这样我们将能够弄清楚您需要做什么。否则我们只能猜测。

2

世界标准时间(UTC)只是一个时间区,作为公认的标准时间。具体来说,它包含伦敦,英国。

UTC唯一特别之处在于,在.Net中使用它比其他任何时区要容易得多(如DateTime.UtcNowDateTime.ToUniversalTime和其他成员)。

因此,像其他人提到的那样,你最好在数据库中存储所有日期为UTC时间,然后在显示前将其转换为用户的本地时间(通过编写TimeZoneInfo.ConvertTime(time, usersTimeZone))。


如果你想更高级一些,可以通过IP地址进行地理定位,自动猜测他们的时区。


那将是未来的版本哈哈。首先必须掌握基础知识。 - chobo2
那么现在你想知道什么? - SLaks
那么这样做不会导致任何时间差异。比如先按UTC获取所有警报,然后一旦找到就将它们转换为本地时间,这样不会导致任何警报被错过吗?就像有些警报只是因为几分钟左右的偏差而被错过了一样吗? - chobo2
我正在尝试弄清楚如何根据用户的时区,在正确的时间提供所有必要的警报。这就是我所需要的。 - chobo2
6
UTC并不等同于伦敦的时区。最重要的是,伦敦在夏季会使用夏令时——因此尽管处于Europe/London时区,但当前与UTC相差一个小时。 - Jon Skeet
修复了,谢谢。还有其他的差异吗?(超过一秒钟的) - SLaks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接