如何从.NET DateTime中截取毫秒

421

我正在尝试将传入请求的时间戳与数据库中存储的值进行比较。当从 SQL Server 读取时间时,它会保留毫秒的精度,并在读入 .NET DateTime 后包含这些毫秒。然而,系统传入的请求并没有提供那种精度,所以我需要简单地舍去毫秒。

我觉得我可能漏掉了一些显而易见的东西,但是我还没有找到一个简洁的方法来实现它(C#)。


5
自从20%的答案(1, 2, 3)描述了如何省略或移除格式化后的 string 表示中的毫秒组件,也许需要编辑以明确表达“截断” / “删除”毫秒指的是“生成一个 DateTime 值,其中所有日期/时间组件都相同,除了 TimeOfDay.TotalMilliseconds0。” 当然,并非所有人都会仔细阅读,但为了消除任何歧义,这样做很重要。 - Lance U. Matthews
18个回答

680
以下代码适用于具有小数毫秒的 DateTime,并且保留了 Kind 属性(本地时间、协调世界时或未定义)。
DateTime dateTime = ... anything ...
dateTime = new DateTime(
    dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), 
    dateTime.Kind
    );

或者等效且更简短的说法是:
dateTime = dateTime.AddTicks( - (dateTime.Ticks % TimeSpan.TicksPerSecond));

这可以被概括为一个扩展方法:
public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
{
    if (timeSpan == TimeSpan.Zero) return dateTime; // Or could throw an ArgumentException

    // Some comments suggest removing the following line.  I think the check
    // for MaxValue makes sense - it's often used to represent an indefinite expiry date.
    // (The check for DateTime.MinValue has no effect, because DateTime.MinValue % timeSpan
    // is equal to DateTime.MinValue for any non-zero value of timeSpan.  But I think
    // leaving the check in place makes the intent clearer).
    // YMMV and the fact that different people have different expectations is probably
    // part of the reason such a method doesn't exist in the Framework.
    if (dateTime == DateTime.MinValue || DateTime.MaxValue) return dateTime; // do not modify "guard" values

    return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
}

以下是使用方法:
dateTime = dateTime.Truncate(TimeSpan.FromMilliseconds(1)); // Truncate to whole ms
dateTime = dateTime.Truncate(TimeSpan.FromSeconds(1)); // Truncate to whole second
dateTime = dateTime.Truncate(TimeSpan.FromMinutes(1)); // Truncate to whole minute
...

虽然从技术上讲你是正确的,但对于那些需要从 SQL Server 中读取数据并将其与某些分布式数据(在我的情况下是 Web 请求)进行比较的人来说,这种精度并不是必要的。 - Jeff Putz
1
不错。显然有人需要给DateTime类添加一些扩展方法,以便将其舍入到最接近的任何值,以便这种良好的编码得到重用。 - chris.w.mclean
这种情况很不可能发生,但是当ticks = 0时,这种方法是否会失效? - adotout
@adotout,上面的Truncate方法如果timeSpan参数为零将会抛出DivideByZeroException异常,这是否是你所说的“当ticks = 0时方法失效”的意思?最好在timeSpan为零时抛出ArgumentException异常。 - Joe
3
你真的应该忽略DateTime.MinValue和DateTime.MaxValue吗?如果有人明确使用DateTime.MaxValue.Truncate(TimeSpan.FromSeconds(1)),我期望它能够像字面上所说的那样工作。 - Alex
显示剩余2条评论

192
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);

40
简单明了,只需要记得在构造函数末尾添加",date.Kind"以确保不会丢失重要信息。 - JMcDaniel
12
在性能敏感的代码中要谨慎使用此解决方案。我的应用程序在System.DateTime.GetDatePart上花费了12%的CPU时间。 - Colonel Panic
4
这很简单,但比被标为最佳答案的问题慢。虽然这可能不是瓶颈,但它大约慢了7-8倍。 - Jonas
3
“much slower”这个说法并不完全正确,实际运行时的差异在50%到100%之间;net 4.7.2: 0.35µs vs 0.62 µscore 3.1: 0.18 µs vs 0.12 µs,这是微秒(10^-6秒)的单位。 - juwens
@juwens 如果你有时间/机会的话,你在单词“exactly”中打了一个错字。这在你其他优秀的贡献中留下了一点瑕疵。 - Timo
我认为这是所有方法中最好的。非常感谢你。 - evry1falls

95

这是一个基于之前答案的扩展方法,它可以让你截断到任意分辨率...

用法:

DateTime myDateSansMilliseconds = myDate.Truncate(TimeSpan.TicksPerSecond);
DateTime myDateSansSeconds = myDate.Truncate(TimeSpan.TicksPerMinute)

类:

public static class DateTimeUtils
{
    /// <summary>
    /// <para>Truncates a DateTime to a specified resolution.</para>
    /// <para>A convenient source for resolution is TimeSpan.TicksPerXXXX constants.</para>
    /// </summary>
    /// <param name="date">The DateTime object to truncate</param>
    /// <param name="resolution">e.g. to round to nearest second, TimeSpan.TicksPerSecond</param>
    /// <returns>Truncated DateTime</returns>
    public static DateTime Truncate(this DateTime date, long resolution)
    {
        return new DateTime(date.Ticks - (date.Ticks % resolution), date.Kind);
    }
}

2
这是一个非常灵活和可重用的解决方案,既简洁又有表现力,而不会过于冗长。我投票支持它作为最佳解决方案。 - Jaans
2
你实际上不需要在%操作数周围加括号。 - ErikE
20
在我看来,括号可以增强清晰度。 - orion elenzil

38
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);

79
仅当 DateTime 值不包含毫秒分数时才有效。 - Joe
8
使用这种方法导致我的一些单元测试失败: 期望值:2010年5月5日15:55:49.000 实际值:2010年5月5日15:55:49.000。我猜是由于Joe提到的毫秒分数引起的。 - Seth Reno
6
不支持序列化,例如输出为2010-12-08T11:20:03.000099+15:00,并未完全去掉毫秒部分。 - joedotnot
6
Millisecond 属性 返回一个介于 0 到 999(包括边界)之间的 整数。因此,如果操作之前的时间是 23:48:49.1234567,那么这个整数将是 123,而操作之后的时间是 23:48:49.0004567。所以它没有截断为整秒数。 - Jeppe Stig Nielsen

16

有时候你想要按照日历基础来截断数据,比如年或月。下面是一个扩展方法,可以让你选择任意的截断精度。

public enum DateTimeResolution
{
    Year, Month, Day, Hour, Minute, Second, Millisecond, Tick
}

public static DateTime Truncate(this DateTime self, DateTimeResolution resolution = DateTimeResolution.Second)
{
    switch (resolution)
    {
        case DateTimeResolution.Year:
            return new DateTime(self.Year, 1, 1, 0, 0, 0, 0, self.Kind);
        case DateTimeResolution.Month:
            return new DateTime(self.Year, self.Month, 1, 0, 0, 0, self.Kind);
        case DateTimeResolution.Day:
            return new DateTime(self.Year, self.Month, self.Day, 0, 0, 0, self.Kind);
        case DateTimeResolution.Hour:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerHour));
        case DateTimeResolution.Minute:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMinute));
        case DateTimeResolution.Second:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerSecond));
        case DateTimeResolution.Millisecond:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMillisecond));
        case DateTimeResolution.Tick:
            return self.AddTicks(0);
        default:
            throw new ArgumentException("unrecognized resolution", "resolution");
    }
}

11

为什么不比较时间差,而是要去掉毫秒后再进行比较?

DateTime x; DateTime y;
bool areEqual = (x-y).TotalSeconds == 0;
或者
TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;

4
第一个选项不起作用,因为TotalSeconds是一个双精度浮点数;它还会返回毫秒。 - Jowen
2
比较差异与截断后再比较的结果不同。例如,5.900和6.100相差不到一秒钟,因此使用您的方法会被视为相等。但是截断后的值5和6是不同的。哪种方法更适合取决于您的要求。 - Joe

10

向下舍入到秒:

dateTime.AddTicks(-dateTime.Ticks % TimeSpan.TicksPerSecond)

将其替换为TicksPerMinute可将结果向下舍入至分钟。


如果您的代码对性能敏感,请谨慎考虑。

new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second)

我的应用程序在System.DateTime.GetDatePart中花费了12%的CPU时间。


@Alex 简单来说 - 不行。 - Robbie Dee

9

不太显眼,但速度比原来快了两倍以上:

// 10000000 runs

DateTime d = DateTime.Now;

// 484,375ms
d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

// 1296,875ms
d = d.AddMilliseconds(-d.Millisecond);

3
请注意,第二个选项 d.AddMilliseconds(-d.Millisecond) 并不一定将 DateTime 正好移动到前一个完整秒。您所选的那一秒之后的 d.Ticks % TimeSpan.TicksPerMillisecond 个滴答声(在 0 到 9,999 之间)仍会保留。 - Technetium

7

这个解决方案不是最快的,但简单易懂:

DateTime d = DateTime.Now;
d = d.Date.AddHours(d.Hour).AddMinutes(d.Minute).AddSeconds(d.Second)

4
一种方便阅读的方法是...
//Remove milliseconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm:ss"), "yyyy-MM-dd HH:mm:ss", null);

以及更多...

//Remove seconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm"), "yyyy-MM-dd HH:mm", null);

//Remove minutes
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH"), "yyyy-MM-dd HH", null);

//and go on...

我知道这很容易理解,但它缺乏性能。


8
在性能方面,将数据转换为字符串和解析字符串是一个糟糕的想法。 - Jeff Putz
3
@JeffPutz 说得没错,但这确实很简单。适用于自动化测试,在这种情况下,从数据库中插入和提取的值会失去时刻(我的确切情况)。然而,这个答案甚至可以更简单,var now = DateTime.Parse(DateTime.Now.ToString()) 就可以正常工作。 - Grimm The Opiner
1
@GrimmTheOpiner - “……大多数情况下都能正常工作”,但不能保证。它的作用是:“将DateTime舍入到当前用户控制面板首选项中配置为‘长时间’的精度”。通常情况下,但不一定是秒。 - Joe
1
就自动化测试而言,性能并不是一个问题,正如它的简单性一样。 - liang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接