【问题标题】:Is there a better way in C# to round a DateTime to the nearest 5 seconds?C# 中是否有更好的方法将 DateTime 舍入到最接近的 5 秒?
【发布时间】:2010-10-20 11:30:00
【问题描述】:

我想将 DateTime 舍入到最接近的 5 秒。这是我目前正在这样做的方式,但我想知道是否有更好或更简洁的方式?

DateTime now = DateTime.Now;
int second = 0;

// round to nearest 5 second mark
if (now.Second % 5 > 2.5)
{
    // round up
    second = now.Second + (5 - (now.Second % 5));
}
else
{
    // round down
    second = now.Second - (now.Second % 5);
}

DateTime rounded = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, second);

请注意,我找到了 these two 以前的问题,但是它们截断而不是轮回时间。

【问题讨论】:

    标签: c# algorithm datetime performance


    【解决方案1】:

    我无法识别 C# 和一块肥皂之间的区别(好吧,当我最初写这个答案时我无法识别,从那以后的几年里情况发生了很大变化)但是,如果你正在寻找一个更简洁的解决方案,我只是把整个东西放在一个函数中 - 在你的代码中没有什么比简单地调用所说的函数更简洁了:

    DateTime rounded = roundTo5Secs (DateTime.Now);
    

    然后你可以在函数中放任何你想要的东西并记录它是如何工作的,例如(假设这些都是整数运算):

    secBase = now.Second / 5;
    secExtra = now.Second % 5;
    if (secExtra > 2) {
        return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute,
            secBase + 5);
    }
    return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute,
        secBase);
    

    如果 secBase 达到 60,您可能还需要一些额外的检查(除非 C# DateTime 对象足够聪明,可以提高分钟(如果分钟达到 60,则小时,等等)。

    【讨论】:

    • 是的,公平点。我会这样做 - 我只是没有在问题中说清楚。
    • 洗澡时间对你来说一定很难;-)
    • 很好,@Gordon,我现在对 C# 了解得更多,但洗澡时间 相当困难。虽然这更多地与有小孩有关,而不是我缺乏协调:-)
    【解决方案2】:

    从技术上讲,您永远无法正确舍入到仅给定秒数的奇数间隔。

    2、4、6、8、10

    如果您按间隔“分配”时间并且抖动很低,则截断很多 更易驾驭。

    如果您可以通过毫秒并在 500 毫秒标记处舍入,您将能够奇数 秒,并减少或完全消除抖动的影响。

    【讨论】:

      【解决方案3】:

      (抱歉,复活了;我知道这是一个古老且已回答的问题 - 只是为了 Google 的缘故添加了一些额外的代码。)

      我从JayMcClellan's answer 开始,但后来我希望它更通用,四舍五入到任意间隔(不仅仅是 5 秒)。因此,我最终将 Jay 的方法留给了在刻度上使用 Math.Round 的方法,并将其放入可以采用任意间隔的扩展方法中,并且还提供了更改舍入逻辑的选项(银行家的舍入与远离零的舍入)。我在这里发帖以防这对其他人也有帮助:

          public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval, MidpointRounding roundingType) {
              return new TimeSpan(
                  Convert.ToInt64(Math.Round(
                      time.Ticks / (decimal)roundingInterval.Ticks,
                      roundingType
                  )) * roundingInterval.Ticks
              );
          }
      
          public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval) {
              return Round(time, roundingInterval, MidpointRounding.ToEven);
          }
      
          public static DateTime Round(this DateTime datetime, TimeSpan roundingInterval) {
              return new DateTime((datetime - DateTime.MinValue).Round(roundingInterval).Ticks);
          }
      

      它不会因为简单的效率而赢得任何奖项,但我发现它易于阅读且使用直观。示例用法:

      new DateTime(2010, 11, 4, 10, 28, 27).Round(TimeSpan.FromMinutes(1)); // rounds to 2010.11.04 10:28:00
      new DateTime(2010, 11, 4, 13, 28, 27).Round(TimeSpan.FromDays(1)); // rounds to 2010.11.05 00:00
      new TimeSpan(0, 2, 26).Round(TimeSpan.FromSeconds(5)); // rounds to 00:02:25
      new TimeSpan(3, 34, 0).Round(TimeSpan.FromMinutes(37); // rounds to 03:42:00...for all your round-to-37-minute needs
      

      【讨论】:

      • 这是用于舍入到 最近 DateTime 的好代码,但我还希望能够将 up 舍入到 @987654326 的倍数@。我尝试将DateTime 重载修改为也采用MidpointRounding,然后将其传递给另一个重载。但这并没有增加所需的能力。
      • @HappyNomad MidpointRounding 仅在值处于实际中点时才起作用。如果您总是想四舍五入,则需要添加重载或将第一个函数更改为使用Math.Ceiling 而不是Math.Round,并完全忽略roundingType
      • 建议您在您创建的新 DateTime 上保持相同的 Kind
      【解决方案4】:

      这个怎么样(将几个答案混合在一起)?我认为它很好地传达了含义,并且由于AddSeconds,应该优雅地处理边缘情况(四舍五入到下一分钟)。

      // truncate to multiple of 5
      int second = 5 * (int) (now.Second / 5);
      DateTime dt = new DateTime(..., second);
      
      // round-up if necessary
      if (now.Second % 5 > 2.5)
      {
          dt = dt.AddSeconds(5);
      }
      

      Jay 展示的Ticks 方法更简洁,但可读性可能稍差。如果你使用这种方法,至少参考TimeSpan.TicksPerSecond

      【讨论】:

      • -1:如果原始时间包含几分之一秒,则不起作用。
      • 你是对的。我回滚到之前的编辑,在处理这种情况时更清楚
      • 感谢您让我诚实 :) 我今天早上醒来看到这个 -1 并且就像:“啊!愚蠢的错误。不要以为你可以在困倦时优化!”
      • 出于好奇,导致异常的答案(由于错误处理 second == 60 的情况)是否也不会被否决?
      【解决方案5】:

      就像您提到的,截断相当容易。因此,只需添加 2.5 秒,然后向下截断。

      【讨论】:

      • 如果我加上 2.5 秒,截断到最接近的 5 秒并减去 2.5 秒,我将得到 2.5 秒、7.5 秒、12.5 秒等......
      【解决方案6】:

      我想不出更好的方法,尽管我可能会考虑使用圆形方法:

      static int Round(int n, int r)
      {
          if ((n % r) <= r / 2)
          {
              return n - (n % r); 
          }
          return n + (r - (n % r));
      }
      

      此外,% 返回一个 int,因此将它与 2.5 进行比较让我觉得有点奇怪,即使它是正确的。我会使用 >= 3。

      【讨论】:

      • 是的,我知道你将它与 2.5 进行比较是什么意思——感觉有点不舒服。正如你所说,它是正确的,它使意图是什么更清楚。 2.5 显然是 5 的一半,而 3 似乎不适合。
      • 我更喜欢像这样对整数进行四舍五入:((n + (r&gt;&gt;1)) / r) * r(向上舍入中点)或((n + r&gt;&gt;1 - 1) / r) * r(向下舍入中点)。如果我知道r 是奇数,我只使用第一个,因为它们对奇数r 的工作方式相同。与您的函数相比,这种方法仅使用一个除法(vs 3)并且没有分支。
      【解决方案7】:

      DateTime 的 Ticks 计数表示 100 纳秒的间隔,因此您可以通过四舍五入到最接近的 50000000 滴答间隔来四舍五入到最接近的 5 秒,如下所示:

        DateTime now = DateTime.Now;
        DateTime rounded = new DateTime(((now.Ticks + 25000000) / 50000000) * 50000000);
      

      这样更简洁,但不一定更好。这取决于您是否更喜欢简洁和速度而不是代码清晰。你的可以说更容易理解。

      【讨论】:

      • 这很有效,因为 59 秒四舍五入到最接近的 5 将产生 60,您不能将其作为 'seconds' 参数传递给 DateTime 构造函数。这样你就可以避免这个陷阱。
      • 批评我自己的答案的一个潜在陷阱是,我不确定 DateTime 如何解释闰秒。滴答计数是从 0001 年 1 月 1 日午夜 12:00:00 开始测量的。因此,根据此后的闰秒数以及 DateTime 是否考虑它们,您可能会发现生成的 Seconds 值不是 5 的倍数。
      • 哇,现在已经很详细了...我想我很高兴能够忽略大约每 18 个月出现的潜在错误。
      • 您可以使用 TimeSpan.TicksPerSecond 使其更具可读性。
      • 您可以使用TimeSpan.TicksPerSecondMath.Round 来提高可读性
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-07
      • 1970-01-01
      • 2018-10-25
      • 1970-01-01
      • 1970-01-01
      • 2016-05-25
      相关资源
      最近更新 更多