你能舍入一个 .NETTimeSpan
对象吗?
我的Timespan
值为:00:00:00.6193789
有没有一种简单的方法可以将其保留为TimeSpan
对象但将其舍入到
00:00:00.62 之类的东西?
对不起,伙计们,但到目前为止的问题和流行的答案都是错误的:-)
这个问题是错误的,因为 Tyndall 要求一种舍入方法,但显示了一个截断示例。
Will Dean 的回答是错误的,因为它还解决了truncation而不是rounding。(我想人们可能会争辩说,这两个问题之一的答案是正确的,但让我们暂时把哲学放在一边……)
这是一个简单的舍入技术:
int precision = 2; // Specify how many digits past the decimal point
TimeSpan t1 = new TimeSpan(19365678); // sample input value
const int TIMESPAN_SIZE = 7; // it always has seven digits
// convert the digitsToShow into a rounding/truncating mask
int factor = (int)Math.Pow(10,(TIMESPAN_SIZE - precision));
Console.WriteLine("Input: " + t1);
TimeSpan truncatedTimeSpan = new TimeSpan(t1.Ticks - (t1.Ticks % factor));
Console.WriteLine("Truncated: " + truncatedTimeSpan);
TimeSpan roundedTimeSpan =
new TimeSpan(((long)Math.Round((1.0*t1.Ticks/factor))*factor));
Console.WriteLine("Rounded: " + roundedTimeSpan);
使用示例代码中的输入值和位数,这是输出:
Input: 00:00:01.9365678
Truncated: 00:00:01.9300000
Rounded: 00:00:01.9400000
将精度从 2 位更改为 5 位,然后改为:
Input: 00:00:01.9365678
Truncated: 00:00:01.9365600
Rounded: 00:00:01.9365700
甚至将其更改为 0 以获得此结果:
Input: 00:00:01.9365678
Truncated: 00:00:01
Rounded: 00:00:02
最后,如果您只想对输出进行更多控制,请添加一些格式。这是一个示例,表明您可以将精度与显示的位数分开。精度再次设置为 2,但显示 3 位数字,如格式化控制字符串的最后一个参数中所指定:
Console.WriteLine("Rounded/formatted: " +
string.Format("{0:00}:{1:00}:{2:00}.{3:000}",
roundedTimeSpan.Hours, roundedTimeSpan.Minutes,
roundedTimeSpan.Seconds, roundedTimeSpan.Milliseconds));
// Input: 00:00:01.9365678
// Truncated: 00:00:01.9300000
// Rounded: 00:00:01.9400000
// Rounded/formatted: 00:00:01.940
如果您正在寻找想法,上述材料很有用;从那以后,我有时间为那些寻找即用型代码的人实施一个打包的解决方案。
请注意,这是未注释的代码。带有 XML-doc-comments 的完整注释版本将在本季度末在我的开源库中提供。尽管我犹豫是否要像这样“原始”地发布它,但我认为它仍然对感兴趣的读者有所帮助。
这段代码改进了我上面的代码,虽然它是四舍五入的,但仍然显示了 7 个位置,用零填充。这个完成的版本四舍五入并修剪到指定的位数。
这是一个示例调用:
Console.Write(new RoundedTimeSpan(19365678, 2).ToString());
// Result = 00:00:01.94
这是完整的 RoundedTimeSpan.cs 文件:
using System;
namespace CleanCode.Data
{
public struct RoundedTimeSpan
{
private const int TIMESPAN_SIZE = 7; // it always has seven digits
private TimeSpan roundedTimeSpan;
private int precision;
public RoundedTimeSpan(long ticks, int precision)
{
if (precision < 0) { throw new ArgumentException("precision must be non-negative"); }
this.precision = precision;
int factor = (int)System.Math.Pow(10, (TIMESPAN_SIZE - precision));
// This is only valid for rounding milliseconds-will *not* work on secs/mins/hrs!
roundedTimeSpan = new TimeSpan(((long)System.Math.Round((1.0 * ticks / factor)) * factor));
}
public TimeSpan TimeSpan { get { return roundedTimeSpan; } }
public override string ToString()
{
return ToString(precision);
}
public string ToString(int length)
{ // this method revised 2010.01.31
int digitsToStrip = TIMESPAN_SIZE - length;
string s = roundedTimeSpan.ToString();
if (!s.Contains(".") && length == 0) { return s; }
if (!s.Contains(".")) { s += "." + new string('0', TIMESPAN_SIZE); }
int subLength = s.Length - digitsToStrip;
return subLength < 0 ? "" : subLength > s.Length ? s : s.Substring(0, subLength);
}
}
}
我昨天刚刚发布了我的开源库的新版本,比预期的要早,包括我上面描述的 RoundedTimeSpan。代码在这里;对于 API 从此处开始,然后导航到命名空间RoundedTimeSpan
下CleanCode.Data
。CleanCode.DLL 库包括上面显示的代码,但在完成的包中提供了它。请注意,ToString(int)
自从我在 2010.01.06 发布后,我对上述方法进行了轻微改进。
如果您四舍五入到整秒,则最简单的单行:
public static TimeSpan RoundSeconds( TimeSpan span ) {
return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds ) );
}
四舍五入到最多 3 位数字(例如十分之一、百分之一秒或毫秒:
public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
// TimeSpan.FromSeconds rounds to nearest millisecond, so nDigits should be 3 or less - won't get good answer beyond 3 digits.
Debug.Assert( nDigits <= 3 );
return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds, nDigits ) );
}
对于 3 位以上的数字,它稍微复杂一些 - 但仍然是单线。这也可以用于 3 位或更少的数字 - 它是上面显示的版本的替代品:
public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
return TimeSpan.FromTicks( (long)( Math.Round( span.TotalSeconds, nDigits ) * TimeSpan.TicksPerSecond) );
}
如果你想要一个字符串(根据评论,这种技术最多只能工作 7 位数字):
public static string RoundSecondsAsString( TimeSpan span, int nDigits ) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nDigits; i++)
sb.Append( "f" );
return span.ToString( @"hh\:mm\:ss\." + sb );
}
对于一天中的时间,小时和分钟,四舍五入:
public static TimeSpan RoundMinutes(TimeSpan span)
{
return TimeSpan.FromMinutes(Math.Round(span.TotalMinutes));
}
如果您有 DateTime,并且想要提取四舍五入的时间:
DateTime dt = DateTime.Now();
TimeSpan hhmm = RoundMinutes(dt.TimeOfDay);
要显示四舍五入的 24 小时时间:
string hhmmStr = RoundMinutes(dt.TimeOfDay).ToString(@"hh\:mm");
以当前文化显示一天中的时间:
string hhmmStr = new DateTime().Add(RoundMinutes(dt.TimeOfDay)).ToShortTimeString();
学分:
cc1960 的答案显示使用了 FromSeconds,但他四舍五入到整秒。我的答案概括为指定的位数。
Ed 的回答建议使用格式字符串,并包含指向格式化文档的链接。
Chris Marisic展示了如何应用ToShortTimeString
到 TimeSpan(首先转换为 a DateTime
)。
舍入为其他单位的倍数,例如 1/30 秒:
// Rounds span to multiple of "unitInSeconds".
// NOTE: This will be close to the requested multiple,
// but is not exact when unit cannot be exactly represented by a double.
// e.g. "unitInSeconds = 1/30" isn't EXACTLY 1/30,
// so the returned value won't be exactly a multiple of 1/30.
public static double RoundMultipleAsSeconds( TimeSpan span, double unitInSeconds )
{
return unitInSeconds * Math.Round( span.TotalSeconds / unitInSeconds );
}
public static TimeSpan RoundMultipleAsTimeSpan( TimeSpan span, double unitInSeconds )
{
return TimeSpan.FromTicks( (long)(RoundMultipleAsSeconds( span, unitInSeconds ) * TimeSpan.TicksPerSecond) );
// IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
//return TimeSpan.FromSeconds( RoundMultipleAsSeconds( span, unitInSeconds ) );
}
// Rounds "span / n".
// NOTE: This version might be a hair closer in some cases,
// but probably not enough to matter, and can only represent units that are "1 / N" seconds.
public static double RoundOneOverNAsSeconds( TimeSpan span, double n )
{
return Math.Round( span.TotalSeconds * n ) / n;
}
public static TimeSpan RoundOneOverNAsTimeSpan( TimeSpan span, double n )
{
return TimeSpan.FromTicks( (long)(RoundOneOverNAsSeconds( span, n ) * TimeSpan.TicksPerSecond) );
// IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
//return TimeSpan.FromSeconds( RoundOneOverNAsSeconds( span, n ) );
}
要使用其中一个来舍入到 1/30 秒的倍数:
private void Test()
{
long ticks = (long) (987.654321 * TimeSpan.TicksPerSecond);
TimeSpan span = TimeSpan.FromTicks( ticks );
TestRound( span, 30 );
TestRound( TimeSpan.FromSeconds( 987 ), 30 );
}
private static void TestRound(TimeSpan span, int n)
{
var answer1 = RoundMultipleAsSeconds( span, 1.0 / n );
var answer2 = RoundMultipleAsTimeSpan( span, 1.0 / n );
var answer3 = RoundOneOverNAsSeconds( span, n );
var answer4 = RoundOneOverNAsTimeSpan( span, n );
}
在调试器中查看的结果:
// for 987.654321 seconds:
answer1 987.66666666666663 double
answer2 {00:16:27.6666666} System.TimeSpan
answer3 987.66666666666663 double
answer4 {00:16:27.6666666} System.TimeSpan
// for 987 seconds:
answer1 987 double
answer2 {00:16:27} System.TimeSpan
answer3 987 double
answer4 {00:16:27} System.TimeSpan
TimeSpan 只不过是“Ticks”成员的包装。从另一个 TimeSpan 的刻度的圆形版本创建一个新的 TimeSpan 非常容易。
TimeSpan t1 = new TimeSpan(2345678);
Console.WriteLine(t1);
TimeSpan t2 = new TimeSpan(t1.Ticks - (t1.Ticks % 100000));
Console.WriteLine(t2);
给出:
00:00:00.2345678
00:00:00.2300000
鉴于一些关于四舍五入到秒的评论,我认为四舍五入到任何 TimeSpan 都会很好:
public static TimeSpan Round(this TimeSpan ts, TimeSpan rnd) {
if (rnd == TimeSpan.Zero)
return ts;
else {
var rndTicks = rnd.Ticks;
var ansTicks = ts.Ticks + Math.Sign(ts.Ticks) * rndTicks / 2;
return TimeSpan.FromTicks(ansTicks - ansTicks % rndTicks);
}
}
public static TimeSpan Round(this TimeSpan ts) => ts.Round(TimeSpan.FromSeconds(1));
考虑到在处理小数单位(根据@ToolmakerSteve)时舍入到刻度的潜在不准确性,当您需要更高的精度并舍入到计算机小数秒时,我添加了一个小数舍入选项:
public static TimeSpan RoundToFraction(this TimeSpan ts, long num, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)Math.Round(Math.Round((double)ts.Ticks * (double)den / num / TimeSpan.TicksPerSecond) * (double)num / den * TimeSpan.TicksPerSecond));
public static TimeSpan RoundToFraction(this TimeSpan ts, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)(Math.Round((double)ts.Ticks * den / TimeSpan.TicksPerSecond) / den * TimeSpan.TicksPerSecond));
new TimeSpan(tmspan.Hours, tmspan.Minutes, tmspan.Seconds, (int)Math.Round(Convert.ToDouble(tmspan.Milliseconds / 10)));
不确定 TimeSpan,但您可以在 DateTimes 上查看此帖子:http:
//mikeinmadison.wordpress.com/2008/03/12/datetimeround/
这是一个很好的扩展方法:
public static TimeSpan RoundToSeconds(this TimeSpan timespan, int seconds = 1)
{
long offset = (timespan.Ticks >= 0) ? TimeSpan.TicksPerSecond / 2 : TimeSpan.TicksPerSecond / -2;
return TimeSpan.FromTicks((timespan.Ticks + offset) / TimeSpan.TicksPerSecond * TimeSpan.TicksPerSecond);
}
这里有一些例子:
DateTime dt1 = DateTime.Now.RoundToSeconds(); // round to full seconds
DateTime dt2 = DateTime.Now.RoundToSeconds(5 * 60); // round to full 5 minutes
我的解决方案:
static TimeSpan RoundToSec(TimeSpan ts)
{
return TimeSpan.FromSeconds((int)(ts.TotalSeconds));
}
将毫秒舍入到最接近的秒的另一种方法。
private const long TicksPer1000Milliseconds = 1000 * TimeSpan.TicksPerMillisecond;
// Round milliseconds to nearest second
// To round up, add the sub-second ticks required to reach the next second
// To round down, subtract the sub-second ticks
elapsedTime = new TimeSpan(elapsedTime.Ticks + (elapsedTime.Milliseconds >= 500 ? TicksPer1000Milliseconds - (elapsedTime.Ticks % TicksPer1000Milliseconds) : -(elapsedTime.Ticks % TicksPer1000Milliseconds)));
如果您需要使用扩展方法DateTime
,但仍想节省时间。就我而言,我想四舍五入。
public static DateTime RoundToMinute(this DateTime date)
{
return DateTime.MinValue.AddMinutes(Math.Round((date - DateTime.MinValue).TotalMinutes));
}