ฉันจะปัดเศษเป็นนาทีที่ใกล้ที่สุดได้อย่างไร


160

มีฟังก์ชั่นที่ง่ายสำหรับการปัดเศษขึ้นDateTimeที่ใกล้ที่สุด 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

คำตอบ:


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

1
ระวังเวลาการปัดเศษที่อยู่ใกล้กับจุดสูงสุด สิ่งนี้อาจทำให้เกิดข้อยกเว้นได้หากเห็บที่คุณคำนวณนั้นมีค่ามากกว่า DateTime.MaxValue.Ticks ปลอดภัยและใช้ค่าต่ำสุดของการคำนวณและ DateTime.MaxValue.Ticks ของคุณ
Paul Raff

4
คุณไม่สูญเสียข้อมูลจากวัตถุ DateTime ด้วยวิธีนี้หรือไม่? เช่นเดียวกับชนิดและเขตเวลาหากมีการตั้งค่าไว้
Evren Kuzucuoglu

11
@ user14 .. (+ d.Ticks - 1) ทำให้แน่ใจว่ามันจะถูกปัดเศษหากจำเป็น กระบวนการ / และ * กำลังปัดเศษ ตัวอย่างรอบ 12 ถึง 5 ถัดไป: (12 + 5 - 1) = 16,
16/5

12
@dtb นอกจากนี้เล็ก ๆ หนึ่งมิฉะนั้นอาจเป็นเรื่องเล็ก ๆ น้อย ๆ : คุณจำเป็นต้องเก็บ datetime ชนิด ;-) 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
ฉันคิดว่าแน่นอนว่ามันจะเร็วกว่าการใช้การคูณและการหาร แต่การทดสอบของฉันแสดงว่ามันไม่ใช่ การทำซ้ำนี้มากกว่า 10,000,000 วิธีวิธีโมดูลัสใช้เวลาประมาณ 610ms บนเครื่องของฉันในขณะที่วิธีการแบบหลายส่วนใช้เวลาประมาณ 500ms ฉันเดาว่า FPU จะทำให้ความกังวลของเก่าไม่ใช่ปัญหา นี่คือรหัสทดสอบของฉัน: pastie.org/8610460
viggity

1
ใช้ส่วนขยายได้อย่างยอดเยี่ยม ขอบคุณ!
TravisWhidden

1
@Allochin ขอบคุณ ฉันได้อัพเดตคำตอบแล้ว ฉันสร้าง ideone นี้พร้อมรหัสของคุณเพื่อแสดงความแตกต่าง: ideone.com/EVKFp5
redent84

1
นี่คือเก่าสวย แต่เป็นครั้งสุดท้าย%d.TicksในRoundUpจำเป็น? d.Ticks - (dt.Ticks % d.Ticks))จะต้องน้อยกว่าd.Ticksดังนั้นคำตอบควรถูกต้องเหมือนกันหรือไม่
Nate Diamond

1
เพียงแค่ชี้ให้เห็นว่าโมดูลัสเป็นสิ่งจำเป็นในการดำเนินงานการแบ่งบน CPU แต่ฉันเห็นด้วยว่ามันดีกว่าการใช้คุณสมบัติ rouding down ของจำนวนเต็ม
อเล็กซ์

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

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

1
@JYelton: ขอบคุณที่ชี้ +1 ฉันเปลี่ยนรหัสเพื่อรองรับสิ่งนั้น
Vlad Bezden

การระบุรหัสรูปแบบ Linqpad ของคุณเพื่อให้การตรวจสอบง่ายเป็นวิธีที่ประหยัดเวลา ใช้งานง่ายมาก
Adam Garner

6

เนื่องจากฉันเกลียดการสร้างวงล้อใหม่ฉันอาจทำตามอัลกอริทึมนี้เพื่อปัดเศษค่า DateTime เป็นการเพิ่มเวลาตามที่ระบุ (Timespan):

  • แปลง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โต้แย้งหรือไม่?
HappyNomad

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 อย่างเท่านั้นการเพิ่มขีดหนึ่งขีดที่น้อยกว่าบล็อกเต็มคือการกระทำที่ถูกต้อง มันช่วยให้แน่ใจว่าคุณจะเลื่อนขึ้นไปยังช่วงบล็อกถัดไป (ก่อนที่จะปัดเศษลง) แต่จะป้องกันไม่ให้คุณเคลื่อนที่ระหว่างบล็อกที่แน่นอน (IE ถ้าคุณเพิ่มบล็อกเต็มเป็นบล็อก 4.0, 5.0 จะเป็น 5 เมื่อคุณต้องการ 4. 4.99 จะเป็น 4)
เบรนแดนมัวร์

1

โซลูชัน verbose เพิ่มเติมที่ใช้ modulo และหลีกเลี่ยงการคำนวณที่ไม่จำเป็น

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 นาทีที่ใกล้ที่สุด มันจะเก็บรักษา TimeZone และชนิดข้อมูลของ DateTime มันสามารถปรับเปลี่ยนเพื่อให้เหมาะกับความต้องการของคุณเองเพิ่มเติม (ถ้าคุณต้องการปัดเศษให้ใกล้ที่สุด 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

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

หากคุณต้องการใช้ TimeSpan เพื่อปัดเศษคุณสามารถใช้สิ่งนี้ได้

/// <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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.