Bagaimana saya bisa mengumpulkan waktu hingga X menit terdekat?


160

Apakah ada fungsi sederhana untuk pembulatan UP sebuah DateTimeke terdekat 15 menit?

Misalnya

2011-08-11 16:59 menjadi 2011-08-11 17:00

2011-08-11 17:00 tetap sebagai 2011-08-11 17:00

2011-08-11 17:01 menjadi 2011-08-11 17:15

Jawaban:


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

Contoh:

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
Solusi ini berhasil masuk ke perpustakaan utilitas saya sebagai metode ekstensi.
JYelton

1
Watch out untuk kali pembulatan yang dekat dengan ekstrim atas. Ini dapat menyebabkan pengecualian dilemparkan jika Kutu yang Anda hitung lebih besar dari DateTime.MaxValue.Ticks. Aman dan ambil nilai minimum Anda yang dihitung dan DateTime.MaxValue.Ticks.
Paul Raff

4
Apakah Anda tidak kehilangan informasi dari objek DateTime dengan metode ini? Suka jenis dan zona waktu, apakah ada yang ditetapkan?
Evren Kuzucuoglu

11
@ user14 .. (+ d.Ticks - 1) memastikan itu akan membulatkan jika perlu. Tanda / dan * membulat. Contoh ronde 12 hingga 5 berikutnya: (12 + 5 - 1) = 16, 16/5 = 3 (karena ini adalah tipe data integer), 3 * 5 = 15. tada :)
Diego Frehner

12
@dtb satu tambahan kecil, kalau tidak, mungkin agak disadap: Anda perlu menyimpan jenis datetime ;-) DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
njy

107

Datang dengan solusi yang tidak melibatkan mengalikan dan membagi long angka.

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

Pemakaian:

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
Saya berpikir pasti bahwa ini akan lebih cepat daripada menggunakan perkalian dan pembagian, tetapi pengujian saya menunjukkan bahwa tidak. Ini lebih dari 10.000.000 iterasi, metode modulus mengambil ~ 610ms pada mesin saya, sedangkan metode mult / div mengambil ~ 500ms. Saya kira FPU membuat kekhawatiran lama bukan masalah. Ini kode pengujian saya: pastie.org/8610460
viggity

1
Penggunaan ekstensi yang bagus. Terima kasih!
TravisWhidden

1
@Alovchin Terima kasih. Saya sudah memperbarui jawabannya. Saya buat ideone ini dengan kode Anda untuk menunjukkan perbedaan: ideone.com/EVKFp5
redent84

1
Ini cukup lama, tetapi yang terakhir %d.Ticksdi RoundUpdiperlukan? d.Ticks - (dt.Ticks % d.Ticks))akan selalu kurang dari d.Ticks, jadi jawabannya harus sama benar?
Nate Diamond

1
Hanya menunjukkan, modulus adalah membutuhkan operasi pembagian pada CPU. Tetapi saya setuju bahwa lebih elegan menggunakan properti rouding down dari divisi integer.
Alex

19

jika Anda perlu membulatkan ke interval waktu terdekat (tidak naik) maka saya sarankan untuk menggunakan yang berikut

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

Jawaban ini tidak membulatkan dengan benar. user1978424 memiliki satu-satunya posting yang menunjukkan dengan benar cara membulatkan ke interval terdekat di bawah ini: (ironisnya turun karena orang pertanyaannya abt pembulatan ke atas)
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);
    }
}

Hasil:

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:01akan terpotong ke2011-08-11 17:00:00
JYelton

1
@JYelton: Terima kasih telah menunjukkan +1. Saya mengubah kode saya untuk mengakomodasi itu.
Vlad Bezden

Memberikan kode Anda Format Linqpad untuk verifikasi yang mudah adalah penghemat waktu yang tepat. Sangat mudah digunakan.
Adam Garner

6

Karena saya benci menciptakan kembali roda, saya mungkin akan mengikuti algoritma ini untuk membulatkan nilai DateTime ke peningkatan waktu tertentu (Timespan):

  • Konversikan DateTimenilai untuk dibulatkan menjadi nilai floating-point desimal yang mewakili jumlah keseluruhan dan fraksional TimeSpanunit.
  • Bulatkan itu menjadi integer, gunakan Math.Round().
  • Skala kembali ke kutu dengan mengalikan bilangan bulat bulat dengan jumlah kutu di TimeSpanunit.
  • Instantiate DateTimenilai baru dari jumlah kutu bulat dan kembalikan ke pemanggil.

Berikut kodenya:

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

}

Ini adalah kode yang bagus untuk pembulatan ke terdekat DateTime , tapi saya juga ingin kemampuan untuk putaran sampai ke kelipatan unit . Melewati MidpointRounding.AwayFromZeroke Roundtidak memiliki efek yang diinginkan. Apakah Anda memikirkan hal lain dengan menerima MidpointRoundingargumen?
HappyNomad

2

Versi saya

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

Sebagai metode itu akan mengunci seperti ini

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

dan disebut seperti itu

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

ini tidak dihitung untuk detik
Alex Norcliffe

1

Anggun?

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

1
Versi yang lebih benar adalah: x.AddSeconds (900 - (x.AddSeconds (-1) .Minute * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), yang menangani kondisi "tetap".
Olaf

1

Perhatian: rumus di atas salah, yaitu yang berikut:

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

harus ditulis ulang sebagai:

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

1
Saya tidak setuju. Karena pembagian bilangan bulat / d.Ticksmembulatkan ke interval 15 menit terdekat (sebut saja "blok" ini), menambahkan hanya setengah blok tidak menjamin pembulatan ke atas. Pertimbangkan kapan Anda memiliki 4,25 blok. Jika Anda menambahkan 0,5 blok, lalu menguji berapa banyak blok bilangan bulat yang Anda miliki, Anda masih memiliki 4. Menambahkan satu centang kurang dari satu blok penuh adalah tindakan yang benar. Ini memastikan Anda selalu naik ke kisaran blok berikutnya (sebelum pembulatan ke bawah), tetapi mencegah Anda bergerak di antara blok yang tepat. (Yaitu, jika Anda menambahkan satu blok penuh ke 4.0 blok, 5.0 akan membulatkan menjadi 5, ketika Anda menginginkan 4. 4.99 akan menjadi 4.)
Brendan Moore

1

Solusi yang lebih verbose, yang menggunakan modulo dan menghindari perhitungan yang tidak perlu.

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

Ini adalah solusi sederhana untuk mengumpulkan hingga 1 menit terdekat. Ini menjaga informasi TimeZone dan Jenis DateTime. Ini dapat dimodifikasi agar sesuai dengan kebutuhan Anda lebih lanjut (jika Anda perlu membulatkan ke 5 menit terdekat, dll).

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

Anda dapat menggunakan metode ini, ia menggunakan tanggal yang ditentukan untuk memastikan ia mempertahankan segala jenis globalisasi dan datetime yang sebelumnya ditentukan dalam objek 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);
}

. Tes Biola Bersih

Jika Anda ingin menggunakan TimeSpan untuk membulatkan, Anda dapat menggunakan ini.

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

TimeSpan Fiddle


Apa yang terjadi jika Anda ingin membulatkan ke menit ke-7 terdekat var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// seharusnya 9:42 tetapi tidak satupun dari metode ini bekerja seperti itu?
DotnetShadow

Edit sepertinya jawaban @soulflyman akan menghasilkan hasil yang tepat
DotnetShadow
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.