如何在特定时区给DateTime添加一天

3

当我们需要在另一个时区中添加一天到日期,同时尊重该特定时区的不同调整规则(如夏令时等),这是否是可能的呢?

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

1
这个能回答你的问题吗?https://stackoverflow.com/questions/47710262/c-sharp-add-1-day-in-specific-timezone-to-datetimeoffset - C4d
AddDays 存在,但它需要一个 double 参数,而你在这种情况下传递了两个参数。除非我误解了你的问题。 - Charmander
@C4d 谢谢。它几乎做到了,但我需要指定时区的新时间 - 这样如果跳过一个小时等情况,我就不能抛出异常。 - monkeycsharp
@Charmander 谢谢。是的,我知道这是一个虚构的方法。我想要一个接受双精度和时区信息作为参数的方法。 - monkeycsharp
@monkeycsharp,你已经有了你的dayInSpecificTimezone,它是根据特定时区创建的(正如其名称所示)。AddDays()不是已经考虑了传递的时区吗? - C4d
2个回答

2
这里有一些您可以用于此的扩展方法。
首先,这个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方法正是为此而设计的。

2
这是完美的 - 正是我所需要的。非常感谢! - monkeycsharp
太好了!非常感谢!然而,除非我的测试中有错误,否则在计算DST到ST转换时的本地日长度时,我不得不回退到calendarDate并在calendarDate上使用标准方法,然后使用另一个ToDateTimeOffset方法,它更喜欢ST或: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
@Nae - 如果在每个时间处理后都调用了.ToUniversalTime()se的值才会是如此。换句话说,ToDateTimeOffset的结果是根据提供的时区而不是UTC来计算的。 - Matt Johnson-Pint
@MattJohnson-Pint 当然,我正在使用.UtcDateTime,我只是在UTC中键入了值,而不是它们的dto表示。无论如何,我的测试失败了,因为calendarDate被解析为01:00 UTC+3,并且应用Add会再次将其解析为时区中的一天后的01:00 UTC+3,这不是我想要的结果。我认为所提出的方法对我的情况并不适用。感谢您的回复! - Nae

0

将一天添加到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会考虑时区(例如,在某些情况下,它会添加23小时而不是24小时)-问题在于它操作的是“LocalTime”,而我无法将其指定到与服务器实际“LocalTime”不同的时区。 - monkeycsharp
1
实际上,内置的 AddDays 完全忽略时区,即使 Kind 设置为 Local,本地时区也不用于这些计算。它只假定整天。因此,如果您 需要 跨越转换(DST 或其他方式),那么您可能会发现结果有偏差。特别是在转换期间开始或停止。这里显示的示例没有问题的原因是它在一月份开始和停止,在给定时区中没有发生任何转换。 - Matt Johnson-Pint
@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); (a-b).TotalHours // 24 (a.ToUniversalTime() - b.ToUniversalTime()).TotalHours // 23
那么,这不是表明它确实考虑了时区,还是我误读了结果? :-)
- monkeycsharp
ToUniversalTime会将时间转换为协调世界时。AddDays则不会。 - Matt Johnson-Pint

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