【发布时间】:2011-10-25 03:41:48
【问题描述】:
是否有一个简单的函数可以将DateTimeUP四舍五入到最接近的 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
【问题讨论】:
是否有一个简单的函数可以将DateTimeUP四舍五入到最接近的 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
【问题讨论】:
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}
【讨论】:
DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
想出了一个不涉及乘法和除法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
【讨论】:
RoundUp 中的最后一个%d.Ticks 有必要吗? d.Ticks - (dt.Ticks % d.Ticks)) 必然小于d.Ticks,所以答案应该一样对吗?
如果您需要四舍五入到最近的时间间隔(不是向上) 那么我建议使用以下
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);
}
【讨论】:
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
【讨论】:
2011-08-11 17:00:01 被截断为2011-08-11 17:00:00
由于我讨厌重新发明轮子,我可能会按照这个算法将 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 的好代码,但我还希望能够将 up 舍入为 @987654328 的倍数@。将MidpointRounding.AwayFromZero 传递给Round 并没有达到预期的效果。通过接受MidpointRounding 参数,您是否还有其他想法?
我的版本
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);
【讨论】:
注意:以上公式不正确,即:
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);
}
【讨论】:
/ d.Ticks 向下舍入到最接近的 15 分钟间隔(我们称之为“块”),因此仅添加半块并不能保证向上舍入。考虑当你有 4.25 个块时。如果你添加 0.5 个块,然后测试你有多少个整数块,你仍然只有 4 个。比一个完整块添加一个刻度是正确的操作。它确保您始终向上移动到下一个块范围(向下舍入之前),但阻止您在精确块之间移动。 (IE,如果你将一个完整的块添加到 4.0 块中,5.0 会四舍五入到 5,当你想要 4。4.99 将是 4。)
一个更详细的解决方案,它使用模数并避免不必要的计算。
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);
}
}
【讨论】:
我见过很多有用的实现,比如来自@dtb 或@redent84 的实现。 由于性能差异可以忽略不计,我远离位移,只是创建了可读的代码。我经常在我的实用程序库中使用这些扩展。
public static class DateTimeExtensions
{
public static DateTime RoundToTicks(this DateTime target, long ticks) => new DateTime((target.Ticks + ticks / 2) / ticks * ticks, target.Kind);
public static DateTime RoundUpToTicks(this DateTime target, long ticks) => new DateTime((target.Ticks + ticks - 1) / ticks * ticks, target.Kind);
public static DateTime RoundDownToTicks(this DateTime target, long ticks) => new DateTime(target.Ticks / ticks * ticks, target.Kind);
public static DateTime Round(this DateTime target, TimeSpan round) => RoundToTicks(target, round.Ticks);
public static DateTime RoundUp(this DateTime target, TimeSpan round) => RoundUpToTicks(target, round.Ticks);
public static DateTime RoundDown(this DateTime target, TimeSpan round) => RoundDownToTicks(target, round.Ticks);
public static DateTime RoundToMinutes(this DateTime target, int minutes = 1) => RoundToTicks(target, minutes * TimeSpan.TicksPerMinute);
public static DateTime RoundUpToMinutes(this DateTime target, int minutes = 1) => RoundUpToTicks(target, minutes * TimeSpan.TicksPerMinute);
public static DateTime RoundDownToMinutes(this DateTime target, int minutes = 1) => RoundDownToTicks(target, minutes * TimeSpan.TicksPerMinute);
public static DateTime RoundToHours(this DateTime target, int hours = 1) => RoundToTicks(target, hours * TimeSpan.TicksPerHour);
public static DateTime RoundUpToHours(this DateTime target, int hours = 1) => RoundUpToTicks(target, hours * TimeSpan.TicksPerHour);
public static DateTime RoundDownToHours(this DateTime target, int hours = 1) => RoundDownToTicks(target, hours * TimeSpan.TicksPerHour);
public static DateTime RoundToDays(this DateTime target, int days = 1) => RoundToTicks(target, days * TimeSpan.TicksPerDay);
public static DateTime RoundUpToDays(this DateTime target, int days = 1) => RoundUpToTicks(target, days * TimeSpan.TicksPerDay);
public static DateTime RoundDownToDays(this DateTime target, int days = 1) => RoundDownToTicks(target, days * TimeSpan.TicksPerDay);
}
【讨论】:
优雅?
dt.AddSeconds(900 - (x.Minute * 60 + x.Second) % 900)
【讨论】:
这是一个四舍五入到最接近的 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;
【讨论】:
您可以使用此方法,它使用指定的日期来确保它保持之前在 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);
}
如果你想使用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);
}
【讨论】:
var d = new DateTime(2019, 04, 15, 9, 40, 0, 0); // 应该是 9:42 但这些方法都不是这样工作的,会发生什么?
Ramon Smits 的解决方案,使用 DateTime.MaxValue 检查:
DateTime RoundUp(DateTime dt, TimeSpan d) =>
dt switch
{
var max when max.Equals(DateTime.MaxValue) => max,
var v => new DateTime((v.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, v.Kind)
};
【讨论】:
我的 DateTimeOffset 版本,基于 Ramon 的回答:
public static class DateExtensions
{
public static DateTimeOffset RoundUp(this DateTimeOffset dt, TimeSpan d)
{
return new DateTimeOffset((dt.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, dt.Offset);
}
}
【讨论】:
你可以试试这个:
string[] parts = ((DateTime)date_time.ToString("HH:mm:ss").Split(':');
int hr = Convert.ToInt32(parts[0]);
int mn = Convert.ToInt32(parts[1]);
int sec2min = (int)Math.Round(Convert.ToDouble(parts[2]) / 60.0, 0);
string adjTime = string.Format("1900-01-01 {0:00}:{1:00}:00.000",
(mn + sec2min > 59 ? (hr + 1 > 23 ? 0 : hr + 1) : hr),
mn + sec2min > 59 ? 60 - mn + sec2min : mn + sec2min);
每个部分(hr,min)必须递增并调整为适当的溢出值,例如59 min> 00然后将hr加1,如果是23,hr变为00。 前任。 07:34:57 舍入为 07:35,09:59:45 舍入为 10:00,23:59:45 舍入为次日的 00:00。
【讨论】: