如何将时间四舍五入到最接近的X分钟?


160

有舍入一个简单的函数UPDateTime到最近的15分钟?

例如

2011-08-11 16:59 变成 2011-08-11 17:00

2011-08-11 17:00 保持为 2011-08-11 17:00

2011-08-11 17:01 变成 2011-08-11 17:15

Answers:


287
DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime((dt.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, dt.Kind);
}

例:

var dt1 = RoundUp(DateTime.Parse("2011-08-11 16:59"), TimeSpan.FromMinutes(15));
// dt1 == {11/08/2011 17:00:00}

var dt2 = RoundUp(DateTime.Parse("2011-08-11 17:00"), TimeSpan.FromMinutes(15));
// dt2 == {11/08/2011 17:00:00}

var dt3 = RoundUp(DateTime.Parse("2011-08-11 17:01"), TimeSpan.FromMinutes(15));
// dt3 == {11/08/2011 17:15:00}

13
该解决方案只是将它作为扩展方法放入了我的实用程序库。
JYelton 2011年

1
注意接近上限的舍入时间。如果您计算的刻度大于DateTime.MaxValue.Ticks,则可能导致引发异常。请注意安全,并取最小的计算值和DateTime.MaxValue.Ticks。
保罗·拉夫

4
您是否使用此方法从DateTime对象中丢失了信息?喜欢的种类和时区,是否有设定?
Evren Kuzucuoglu 2014年

11
@ user14 ..(+ d.Ticks-1)确保在必要时将其舍入。/和*四舍五入。将示例12舍入到下一个5:(12 +
5-1

12
@dtb一个小的补充,否则可能有点bug:您需要保持日期时间类型;-) DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
njy

107

提出了不涉及相乘除法 的解决方案long

public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
    var modTicks = dt.Ticks % d.Ticks;
    var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
    return new DateTime(dt.Ticks + delta, dt.Kind);
}

public static DateTime RoundDown(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    return new DateTime(dt.Ticks - delta, dt.Kind);
}

public static DateTime RoundToNearest(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    bool roundUp = delta > d.Ticks / 2;
    var offset = roundUp ? d.Ticks : 0;

    return new DateTime(dt.Ticks + offset - delta, dt.Kind);
}

用法:

var date = new DateTime(2010, 02, 05, 10, 35, 25, 450); // 2010/02/05 10:35:25
var roundedUp = date.RoundUp(TimeSpan.FromMinutes(15)); // 2010/02/05 10:45:00
var roundedDown = date.RoundDown(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00
var roundedToNearest = date.RoundToNearest(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00

8
我可以肯定,这会比使用乘法和除法更快,但是我的测试表明事实并非如此。经过1000万次迭代,模数方法在我的计算机上花费了约610ms,而mult / div方法花费了约500ms。我想FPU使得旧的担忧不再是问题。这是我的测试代码:pastie.org/8610460
Viggity 2014年

1
大量使用扩展程序。谢谢!
TravisWhidden

1
@Alovchin谢谢。我已经更新了答案。我用您的代码创建了这个ideone来显示差异:ideone.com/EVKFp5
redent84 2015年

1
这是很老,但最后%d.TicksRoundUp必要吗?d.Ticks - (dt.Ticks % d.Ticks))必定会小于d.Ticks,因此答案应该正确吗?
Nate Diamond'3

1
只是指出,模数是需要在CPU上进行除法运算的。但是我同意使用整数除法的路由属性更为优雅。
亚历克斯(Alex)

19

如果您需要舍入到最近的时间间隔(不向上),那么我建议使用以下方法

    static DateTime RoundToNearestInterval(DateTime dt, TimeSpan d)
    {
        int f=0;
        double m = (double)(dt.Ticks % d.Ticks) / d.Ticks;
        if (m >= 0.5)
            f=1;            
        return new DateTime(((dt.Ticks/ d.Ticks)+f) * d.Ticks);
    }

这个答案不能正确取整。user1978424唯一能正确显示如何舍入到以下最近间隔的帖子:(具有讽刺意味的是,由于问题是abt的四舍五入而被否决)
stitty 2014年

8
void Main()
{
    var date1 = new DateTime(2011, 8, 11, 16, 59, 00);
    date1.Round15().Dump();

    var date2 = new DateTime(2011, 8, 11, 17, 00, 02);
    date2.Round15().Dump();

    var date3 = new DateTime(2011, 8, 11, 17, 01, 23);
    date3.Round15().Dump();

    var date4 = new DateTime(2011, 8, 11, 17, 00, 00);
    date4.Round15().Dump();
}

public static class Extentions
{
    public static DateTime Round15(this DateTime value)
    {   
        var ticksIn15Mins = TimeSpan.FromMinutes(15).Ticks;

        return (value.Ticks % ticksIn15Mins == 0) ? value : new DateTime((value.Ticks / ticksIn15Mins + 1) * ticksIn15Mins);
    }
}

结果:

8/11/2011 5:00:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:00:00 PM

3
2011-08-11 17:00:01被截断为2011-08-11 17:00:00
JYelton 2011年

1
@JYelton:感谢您指出+1。我更改了代码以适应这种情况。
弗拉德·贝兹登(Flad Bezden)2011年

提供您的代码Linqpad格式以方便验证可以节省大量时间。很好用。
亚当·加纳

6

因为我讨厌重新发明轮子,所以我可能会遵循以下算法将DateTime值四舍五入到指定的时间增量(时间跨度):

  • DateTime要舍入的值转换为十进制浮点值,该整数代表单位的整数和小数TimeSpan
  • 使用将其四舍五入为整数Math.Round()
  • 通过将四舍五入的整数乘以TimeSpan单位中的刻度数,可以缩小刻度。
  • DateTime从四舍五入的滴答数实例化一个新值,并将其返回给调用方。

这是代码:

public static class DateTimeExtensions
{

    public static DateTime Round( this DateTime value , TimeSpan unit )
    {
        return Round( value , unit , default(MidpointRounding) ) ;
    }

    public static DateTime Round( this DateTime value , TimeSpan unit , MidpointRounding style )
    {
        if ( unit <= TimeSpan.Zero ) throw new ArgumentOutOfRangeException("unit" , "value must be positive") ;

        Decimal  units        = (decimal) value.Ticks / (decimal) unit.Ticks ;
        Decimal  roundedUnits = Math.Round( units , style ) ;
        long     roundedTicks = (long) roundedUnits * unit.Ticks ;
        DateTime instance     = new DateTime( roundedTicks ) ;

        return instance ;
    }

}

这是四舍五入到漂亮的代码最接近的 DateTime,但我也希望有能力圆到的倍数unit 。传递MidpointRounding.AwayFromZero给您Round并没有获得预期的效果。通过接受MidpointRounding争论,您还有其他想法吗?
HappyNomad13年

2

我的版本

DateTime newDateTimeObject = oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);

作为一种方法,它将像这样锁定

public static DateTime GetNextQuarterHour(DateTime oldDateTimeObject)
{
    return oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);
}

被这样称呼

DateTime thisIsNow = DateTime.Now;
DateTime nextQuarterHour = GetNextQuarterHour(thisIsNow);

这并不占秒
Alex Norcliffe

1

优雅?

dt.AddSeconds(900 - (x.Minute * 60 + x.Second) % 900)

1
一个更正确的版本是:x.AddSeconds(900-(x.AddSeconds(-1).Minute * 60 + x.AddSeconds(-1).Second)%900).AddSeconds(-1),它负责“停留”条件。
奥拉夫

1

注意:上面的公式不正确,即以下内容:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks);
}

应该改写为:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks/2) / d.Ticks) * d.Ticks);
}

1
我不同意。由于整数除法会/ d.Ticks向下舍入到最接近的15分钟间隔(我们将其称为“块”),因此仅添加半个块并不能保证舍入。考虑何时使用4.25块。如果添加0.5个块,然后测试您有多少个整数块,那么您仍然只有4个。将整块少加一个勾号是正确的操作。这样可以确保您始终向上移动到下一个程序段范围(在四舍五入之前),但可以防止您在精确的程序段之间移动。(即,如果您将完整的块添加到4.0块,则当您希望将4. 4.99设为4时,5.0将四舍五入。)
Brendan Moore

1

更详细的解决方案,它使用模并避免不必要的计算。

public static class DateTimeExtensions
{
    public static DateTime RoundUp(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, true);
    }

    public static DateTime RoundDown(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, false);
    }

    private static DateTime Round(DateTime dt, TimeSpan ts, bool up)
    {
        var remainder = dt.Ticks % ts.Ticks;
        if (remainder == 0)
        {
            return dt;
        }

        long delta;
        if (up)
        {
            delta = ts.Ticks - remainder;
        }
        else
        {
            delta = -remainder;
        }

        return dt.AddTicks(delta);
    }
}

0

这是一个简单的解决方案,可以四舍五入到最接近的1分钟。它保留DateTime的TimeZone和Kind信息。可以对其进行修改以进一步满足自己的需要(如果需要四舍五入到最接近的5分钟等)。

DateTime dbNowExact = DateTime.Now;
DateTime dbNowRound1 = (dbNowExact.Millisecond == 0 ? dbNowExact : dbNowExact.AddMilliseconds(1000 - dbNowExact.Millisecond));
DateTime dbNowRound2 = (dbNowRound1.Second == 0 ? dbNowRound1 : dbNowRound1.AddSeconds(60 - dbNowRound1.Second));
DateTime dbNow = dbNowRound2;

0

您可以使用此方法,它使用指定的日期来确保它维护先前在datetime对象中指定的任何全球化和datetime类型。

const long LNG_OneMinuteInTicks = 600000000;
/// <summary>
/// Round the datetime to the nearest minute
/// </summary>
/// <param name = "dateTime"></param>
/// <param name = "numberMinutes">The number minute use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, int numberMinutes = 1)
{
    long roundedMinutesInTicks = LNG_OneMinuteInTicks * numberMinutes;
    long remainderTicks = dateTime.Ticks % roundedMinutesInTicks;
    if (remainderTicks < roundedMinutesInTicks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundedMinutesInTicks - remainderTicks);
}

.Net小提琴测试

如果要使用“时间跨度”舍入,可以使用它。

/// <summary>
/// Round the datetime
/// </summary>
/// <example>Round(dt, TimeSpan.FromMinutes(5)); => round the time to the nearest 5 minutes.</example>
/// <param name = "dateTime"></param>
/// <param name = "roundBy">The time use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, TimeSpan roundBy)
{            
    long remainderTicks = dateTime.Ticks % roundBy.Ticks;
    if (remainderTicks < roundBy.Ticks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundBy.Ticks - remainderTicks);
}

时光小提琴


如果您想四舍五入到最接近的第7分钟var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// //应该是9:42,但这些方法都不能那样工作怎么办?
DotnetShadow

编辑看起来像@soulflyman答案会产生正确的结果
DotnetShadow
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.