Làm thế nào tôi có thể làm tròn thời gian đến X phút gần nhất?


160

Có một chức năng đơn giản để làm tròn UP một DateTimeđến 15 phút gần nhất?

Ví dụ

2011-08-11 16:59 trở thành 2011-08-11 17:00

2011-08-11 17:00 vẫn như 2011-08-11 17:00

2011-08-11 17:01 trở thành 2011-08-11 17:15

Câu trả lời:


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

Thí dụ:

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
Giải pháp này chỉ đưa nó vào thư viện tiện ích của tôi như một phương thức mở rộng.
JYelton

1
Xem ra cho thời gian làm tròn gần cực trên. Điều này có thể khiến ngoại lệ bị ném nếu Ticks bạn tính toán lớn hơn DateTime.MaxValue.Ticks. Hãy an toàn và lấy tối thiểu giá trị tính toán của bạn và DateTime.MaxValue.Ticks.
Paul Raff

4
Bạn không bị mất thông tin từ đối tượng DateTime với phương thức này? Giống như các loại và múi giờ, nếu có được thiết lập?
Evren Kuzucuoglu

11
@ user14 .. (+ d.Ticks - 1) đảm bảo nó sẽ làm tròn nếu cần. / Và * đang làm tròn. Ví dụ vòng 12 đến 5 tiếp theo: (12 + 5 - 1) = 16, 16/5 = 3 (vì đó là kiểu dữ liệu số nguyên), 3 * 5 = 15. tada :)
Diego Frehner

12
@dtb một bổ sung nhỏ, nếu không thì có lẽ là một lỗi nhỏ: bạn cần giữ loại datetime ;-) DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
njy

107

Đến với một giải pháp không liên quan đến nhânchia long số.

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);
}

Sử dụng:

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
Tôi nghĩ chắc chắn rằng điều này sẽ nhanh hơn việc sử dụng phép nhân và chia, nhưng thử nghiệm của tôi cho thấy rằng điều đó không xảy ra. Điều này hơn 10000000 lần lặp, phương pháp mô đun mất ~ 610ms trên máy của tôi, trong khi phương thức mult / div mất ~ 500ms. Tôi đoán FPU làm cho những mối quan tâm cũ không thành vấn đề. Đây là mã kiểm tra của tôi: pastie.org/8610460
viggity

1
Công dụng tuyệt vời của tiện ích mở rộng. Cảm ơn!
TravisWhidden

1
@Alovchin Cảm ơn. Tôi đã cập nhật câu trả lời. Tôi đã tạo ideone này bằng mã của bạn để hiển thị sự khác biệt: ideone.com/EVKFp5
redent84

1
Điều này là khá cũ, nhưng là người cuối cùng %d.Tickstrong RoundUpcần thiết? d.Ticks - (dt.Ticks % d.Ticks))sẽ nhất thiết phải ít hơn d.Ticks, vì vậy câu trả lời phải giống nhau?
Kim cương Nate ngày

1
Chỉ cần chỉ ra, mô-đun là một hoạt động đòi hỏi phải phân chia trên CPU. Nhưng tôi đồng ý rằng sẽ thanh lịch hơn khi sử dụng thuộc tính rouding down của các số nguyên.
Alex

19

nếu bạn cần làm tròn đến một khoảng thời gian gần nhất (không lên) thì tôi đề nghị sử dụng như sau

    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);
    }

Câu trả lời này không làm tròn chính xác. user1978424 có chỉ gửi rằng chương trình một cách chính xác làm thế nào để vòng đến khoảng gần dưới đây: (trớ trêu thay xuống bình chọn vì câu hỏi là abt làm tròn UP)
stitty

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);
    }
}

Các kết quả:

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:01bị cắt cụt2011-08-11 17:00:00
JYelton

1
@JYelton: Cảm ơn bạn đã chỉ ra +1. Tôi đã thay đổi mã của tôi để phù hợp với điều đó.
Vlad Bezden

Cung cấp định dạng Linqpad mã của bạn để xác minh dễ dàng là một trình tiết kiệm thời gian tuyệt vời. Rất dễ sử dụng.
Adam Garner

6

Vì tôi ghét phát minh lại bánh xe, có lẽ tôi nên theo thuật toán này để làm tròn giá trị DateTime đến khoảng thời gian xác định (Timespan):

  • Chuyển đổi DateTimegiá trị được làm tròn thành giá trị dấu phẩy động thập phân biểu thị toàn bộ và số TimeSpanđơn vị phân số .
  • Làm tròn số đó thành một số nguyên, sử dụng Math.Round().
  • Thu nhỏ lại các dấu tick bằng cách nhân số nguyên được làm tròn với số lượng đánh dấu trong TimeSpanđơn vị.
  • Khởi tạo một DateTimegiá trị mới từ số tick đã làm tròn và trả lại cho người gọi.

Đây là mã:

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 ;
    }

}

Đây là mã đẹp để làm tròn đến gần nhất DateTime , nhưng tôi cũng muốn khả năng làm tròn lên bội số unit . Đi qua trong MidpointRounding.AwayFromZerođể Roundkhông có hiệu quả mong muốn. Bạn có điều gì khác trong tâm trí bằng cách chấp nhận một MidpointRoundingcuộc tranh cãi?
HappyNomad

2

Phiên bản của tôi

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

Như một phương pháp, nó sẽ khóa như thế này

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

và được gọi như thế

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

điều này không tính đến giây
Alex Norcliffe

1

Thanh lịch?

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

1
Một phiên bản chính xác hơn sẽ là: x.AddSeconds (900 - (x.AddSeconds (-1) .Minute * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), sẽ chăm sóc điều kiện "ở lại".
Olaf

1

Chú ý: công thức trên không chính xác, nghĩa là như sau:

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

nên được viết lại thành:

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

1
Tôi không đồng ý. Vì phép chia số nguyên làm / d.Tickstròn xuống khoảng 15 phút gần nhất (hãy gọi các "khối" này), chỉ thêm một nửa khối không đảm bảo làm tròn số. Hãy xem xét khi bạn có 4,25 khối. Nếu bạn thêm 0,5 khối, sau đó kiểm tra xem bạn có bao nhiêu khối nguyên, bạn vẫn chỉ có 4. Thêm một đánh dấu nhỏ hơn một khối đầy đủ là hành động chính xác. Nó đảm bảo bạn luôn di chuyển đến phạm vi khối tiếp theo (trước khi làm tròn xuống), nhưng ngăn bạn di chuyển giữa các khối chính xác. (IE, nếu bạn đã thêm một khối đầy đủ vào các khối 4.0, 5.0 sẽ làm tròn thành 5, khi bạn muốn 4. 4.99 sẽ là 4.)
Brendan Moore

1

Một giải pháp dài dòng hơn, sử dụng modulo và tránh tính toán không cần thiết.

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

Đây là một giải pháp đơn giản để làm tròn đến 1 phút gần nhất. Nó lưu giữ thông tin TimeZone và Kind của DateTime. Nó có thể được sửa đổi để phù hợp với nhu cầu của riêng bạn hơn nữa (nếu bạn cần làm tròn đến 5 phút gần nhất, v.v.).

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

Bạn có thể sử dụng phương thức này, nó sử dụng ngày đã chỉ định để đảm bảo nó duy trì bất kỳ loại toàn cầu hóa và datetime nào được chỉ định trước đó trong đối tượng 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 Fiddle Test

Nếu bạn muốn sử dụng TimeSpan để làm tròn, bạn có thể sử dụng cái này.

/// <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);
}

Fiddle thời gian


Điều gì xảy ra nếu bạn muốn làm tròn đến phút thứ 7 gần nhất var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// phải là 9:42 nhưng không có phương pháp nào trong số này hoạt động như vậy?
DotnetShadow

Chỉnh sửa trông giống như câu trả lời @soulflyman sẽ tạo ra kết quả đúng
DotnetShadow
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.