【问题标题】:How can I add a day to a DateTime in a specific timezone如何在特定时区的 DateTime 中添加一天
【发布时间】:2020-05-14 02:21:42
【问题描述】:

是否可以(相当容易地)在另一个时区的日期中添加一天而不是 DateTime.Local,同时尊重该特定时区的不同调整规则(DST 等)?

var rst = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
var dayInSpecificTimezone = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, rst); // Got the datetime in specified timezone
// now I would like to add a "day" to that, still observing the rules of that timezone.. something like:
var sameTimeTheDayAfterThat = dayInSpecificTimezone.AddDays(1, rst); // no such method exists

【问题讨论】:

  • 这个能回答你的问题吗? stackoverflow.com/questions/47710262/…
  • AddDays 确实存在,但它需要一个 double 参数,并且在这种情况下您要传递两个参数。除非我误解了你的问题。
  • @C4d 谢谢。它几乎做到了,但我需要指定时区的新时间 - 所以如果例如跳过一个小时等,我不能抛出异常。
  • @Charmander 谢谢,是的 - 我知道这是一个虚构的方法 - 我想要一个将双精度和时区信息作为参数的方法..
  • @monkeycsharp 你已经有了你的dayInSpecificTimezone,它是在一个特定的时区创建的(正如名字所说)。 AddDays() 不是已经考虑到传递的时区了吗?

标签: c# datetime timezone


【解决方案1】:

以下是您可以使用的扩展方法。

首先,这个AddDays 方法与您询问的签名相匹配。它在DateTime 值上运行:

public static DateTime AddDays(this DateTime dt, double days, TimeZoneInfo tz)
{
    // If the kind is Local or Utc, convert that point in time to the given time zone
    DateTimeKind originalKind = dt.Kind;
    if (originalKind != DateTimeKind.Unspecified)
    {
        dt = TimeZoneInfo.ConvertTime(dt, tz);
    }

    // Add days with respect to the wall time only
    DateTime added = dt.AddDays(days);

    // Resolve the added value to a specific point in time
    DateTimeOffset resolved = added.ToDateTimeOffset(tz);

    // Return only the DateTime portion, but take the original kind into account
    switch (originalKind)
    {
        case DateTimeKind.Local:
            return resolved.LocalDateTime;
        case DateTimeKind.Utc:
            return resolved.UtcDateTime;
        default: // DateTimeKind.Unspecified
            return resolved.DateTime;
    }
}

这是该扩展方法的另一种变体。这个操作在DateTimeOffset 值上:

public static DateTimeOffset AddDays(this DateTimeOffset dto, double days, TimeZoneInfo tz)
{
    // Make sure the input time is in the provided time zone
    dto = TimeZoneInfo.ConvertTime(dto, tz);

    // Add days with respect to the wall time only
    DateTime added = dto.DateTime.AddDays(days);

    // Resolve the added value to a specific point in time
    DateTimeOffset resolved = added.ToDateTimeOffset(tz);

    // Return the fully resolved value
    return resolved;
}

上述两种方法都依赖于下面的ToDateTimeOffset 扩展方法(我现在已经在几篇不同的帖子中使用过)。

public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
    if (dt.Kind != DateTimeKind.Unspecified)
    {
        // Handle UTC or Local kinds (regular and hidden 4th kind)
        DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
        return TimeZoneInfo.ConvertTime(dto, tz);
    }

    if (tz.IsAmbiguousTime(dt))
    {
        // Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
        TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
        TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
        return new DateTimeOffset(dt, offset);
    }

    if (tz.IsInvalidTime(dt))
    {
        // Advance by the gap, and return with the daylight offset  (2:30 ET becomes 3:30 EDT)
        TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
        TimeSpan gap = offsets[1] - offsets[0];
        return new DateTimeOffset(dt.Add(gap), offsets[1]);
    }

    // Simple case
    return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}

最后,我要指出还有另一个选项需要考虑:使用Noda Time 库。它的ZoneDateTime.Add 方法正是这个目的。

【讨论】:

  • 这是完美的——正是我所需要的。非常感谢!
  • 这太棒了!非常感谢!但是,除非我的测试有误,否则在计算 DST 到 ST 转换的当地日长时,我不得不回退到 calendarDate 并在 calendarDate 上使用标准方法,然后使用另一个更喜欢 ST 的 ToDateTimeOffset 方法或:var calendarDate = DateTime.Parse("2022-10-28 00:59:59.9999999").Date; var tz = TimeZoneInfo.FindSystemTimeZoneById("Asia/Amman"); var s = calendarDate.ToDateTimeOffset(tz); var e = calendarDate.AddDays(1).ToDateTimeOffset(tz); 预期 s2022-10-27 21:00e2022-10-28 22:00
  • @Nae - se 的值只有在 .ToUniversalTime() 调用之后才会出现在 ToDateTimeOffset 之后。换句话说,ToDateTimeOffset 的结果与提供的时区相关,而不是 UTC。
  • @MattJohnson-Pint 当然,我使用的是.UtcDateTime,我只是输入了 UTC 格式的值,而不是它们的 dto 表示。无论如何,我的测试失败了,因为 calendarDate 被解析为 01:00 UTC+3,并且在 01:00 UTC+3 再次将 Add 解析到时区的一天后,这不是我想要的。我认为建议的方法只是不适合我的情况。感谢您的回复!
【解决方案2】:

向 DateTime 对象添加一天和在特定时区显示日期是两件不同的事情。

DateTime.AddDays 函数可用于添加日期(即,将 24 小时添加到当前变量)。然后,您可以在您喜欢的任何时区显示该日期时间。

例如:

var rst = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
var dayInSpecificTimezone = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, rst); // Got the datetime in specified timezone

Console.WriteLine("RST datetime now is {0}", dayInSpecificTimezone);

var sameTimeTheDayAfterThat = dayInSpecificTimezone.AddDays(1);

Console.WriteLine("RST datetime in 1 day is {0}", sameTimeTheDayAfterThat);
Console.WriteLine("local datetime in 1 day is {0}", TimeZoneInfo.ConvertTime(sameTimeTheDayAfterThat, rst, TimeZoneInfo.Local));

会给出类似的输出:

RST datetime now is 29/01/2020 4:31:14 AM
RST datetime in 1 day is 30/01/2020 4:31:14 AM
local datetime in 1 day is 30/01/2020 1:31:14 PM

【讨论】:

  • 谢谢,但我认为 AddDays 确实考虑了 TimeZone(例如,在某些情况下,它增加了 23 小时而不是 24 小时) - 问题是它在“LocalTime”上运行,我无法指定不同的时区比服务器的实际“LocalTime”。
  • 其实AddDays(内置的)完全忽略了时区,即使Kind设置为Local,在这些计算中也没有使用本地时区。它只是假设一整天。因此,如果您需要跨越过渡(DST 或其他),那么您可能会发现结果是关闭的。特别是如果您在过渡期间开始或停止。此处显示的示例似乎没有问题的原因是它在一月份开始和停止,在给定的时区没有发生转换。
  • @MattJohnson-Pint 也许我只是很困惑,但在我的时区(浪漫标准),我可以这样做:> var a = new DateTime(2020, 3, 28, 10, 0, 0, DateTimeKind.Local).AddDays(1); > var b = new DateTime(2020, 3, 28, 10, 0, 0, DateTimeKind.Local); > (ab).TotalHours // 24 >(a.ToUniversalTime() - b.ToUniversalTime()).TotalHours // 23 那么,这是否表明它实际上考虑了时区,或者我误读了结果? :-)
  • ToUniversalTime 确实如此。 AddDays 没有。
猜你喜欢
  • 2013-05-03
  • 2018-11-24
  • 1970-01-01
  • 1970-01-01
  • 2017-12-30
  • 2018-07-28
  • 2019-03-15
  • 1970-01-01
相关资源
最近更新 更多