开发者

DateTime Comparison Precision

开发者 https://www.devze.com 2023-01-02 12:37 出处:网络
I\'m doing DateTime comparison but I don\'t want to do compa开发者_开发知识库rison at second, millisecond and ticks level. What\'s the most elegant way?

I'm doing DateTime comparison but I don't want to do compa开发者_开发知识库rison at second, millisecond and ticks level. What's the most elegant way?

If I simply compare the DateTime, then they are seldom equal due to ticks differences.


What about using a timespan.

if (Math.Truncate((A - B).TotalMinutes) == 0)
{
    //There is less than one minute between them
}

Probably not the most elegant way, but it allows for cases which are one second apart and yet have different days/hours/minutes parts such as going over midnight.

Edit: it occured to me that the truncate is unecessary...

if (Math.Abs((A - B).TotalMinutes) < 1)
{
    //There is less than one minute between them
}

Personally I think this is more elegant...


One approach could be to create two new DateTimes from your values you want to compare, but ignore anything from the seconds on down and then compare those:

DateTime compare1 = new DateTime(year1, month1, day1, hour1, minute1, 0);
DateTime compare2 = new DateTime(year2, month2, day2, hour2, minute2, 0);

int result = DateTime.Compare(compare1, compare2);

I'd be the first to admit it's not elegant, but it solves the problem.


Using a TimeSpan you get all the granularity you want :

DateTime dt1, dt2;
double d = (dt2 - dt1).TotalDays;
double h = (dt2 - dt1).TotalHours;
double m = (dt2 - dt1).TotalMinutes;
double s = (dt2 - dt1).TotalSeconds;
double ms = (dt2 - dt1).TotalMilliseconds;
double ticks = (dt2 - dt1).Ticks;


public class DateTimeComparer : Comparer<DateTime>
{
    private Prescision _Prescision;

    public enum Prescision : sbyte
    {
        Millisecons,
        Seconds,
        Minutes,
        Hour,
        Day,
        Month,
        Year,
        Ticks
    }

    Func<DateTime, DateTime>[] actions = new Func<DateTime, DateTime>[]
        {
            (x) => { return x.AddMilliseconds(-x.Millisecond);},
            (x) => { return x.AddSeconds(-x.Second);},
            (x) => { return x.AddMinutes(-x.Minute);},
            (x) => { return x.AddHours(-x.Hour);},
            (x) => { return x.AddDays(-x.Day);},
            (x) => { return x.AddMonths(-x.Month);},
        };

    public DateTimeComparer(Prescision prescision = Prescision.Ticks)
    {
        _Prescision = prescision;
    }

    public override int Compare(DateTime x, DateTime y)
    {
        if (_Prescision == Prescision.Ticks)
        {
            return x.CompareTo(y);
        }

        for (sbyte i = (sbyte)(_Prescision - 1); i >= 0; i--)
        {
            x = actions[i](x);
            y = actions[i](y);
        }

        return x.CompareTo(y);
    }
}

Usage example:

new DateTimeComparer(DateTimeComparer.Prescision.Day).Compare(Date1, Date2)


How about this ComparerClass?

public class DateTimeComparer : Comparer<DateTime>
{
    private string _Format;

    public DateTimeComparer(string format)
    {
        _Format = format;
    }

    public override int Compare(DateTime x, DateTime y)
    {
        if(x.ToString(_Format) == y.ToString(_Format))
            return 0;

        return x.CompareTo(y);
    }
}

This can be used by

List.Sort(new DateTimeComparer("hh:mm"));


You can convert them to String format and compare the string with each other.

This also gives freedom to choose your comparison parameters, like only the time without the date, etc.

if (String.Format("{0:ddMMyyyyHHmmss}", date1) == String.Format("{0:ddMMyyyyHHmmss}", date2))
{
     // success
}


I've written this to help myself:

    internal class ImpreciseCompareDate : IComparer<DateTime>
{
    private readonly double _Tolerance;

    public ImpreciseCompareDate(double MillisecondsTolerance)
    {
        _Tolerance = MillisecondsTolerance;
    }

    public int Compare(DateTime x, DateTime y)
    {
        return Math.Abs((x - y).TotalMilliseconds) < _Tolerance ? 0 : x.CompareTo(y);
    }
}

Tolerance can be set to (10d/3d) to account for SQL servers 1/300th of a ms. If tolerance is exceeded, delegate to default comparer.


Another way is to convert first by processing on ticks level with a simple (non-rounding) calculation:

var now = DateTime.UtcNow;
// 636340541021531973, 2017-06-26T06:08:22.1531973Z

var millisecondsPrecision = new DateTime(now.Ticks / 10000 * 10000, now.Kind);
// 636340541021530000, 2017-06-26T06:08:22.1530000Z

var secondsPrecision = new DateTime(now.Ticks / 10000000 * 10000000, now.Kind);
// 636340541020000000, 2017-06-26T06:08:22.0000000Z

var minutePrecision = new DateTime(now.Ticks / (10000000*60) * (10000000*60), now.Kind);
// 636340541000000000, 2017-06-26T06:08:00.0000000Z


@ALZ's solution looks nice but it's too complicated and has a bug. So I decided to combine it with @ChrisF's solution.

    public class DateTimeComparer : Comparer<DateTime>
    {
        public enum Precision
        {
            Years = 0,
            Months,
            Days,
            Hours,
            Minutes,
            Seconds,
            Millisecons,
            Ticks
        }

        private Precision _precision;

        public DateTimeComparer(Precision precision =  Precision.Ticks)
        {
            _precision = precision;
        }

        public override int Compare(DateTime x, DateTime y)
        {
            if (_precision == Precision.Ticks)
            {
                return x.CompareTo(y);
            }

            var xx = AssembleValue(x, _precision);
            var yy = AssembleValue(y, _precision);

            return xx.CompareTo(yy);
        }

        private static DateTime AssembleValue(DateTime input, Precision precision)
        {
            var p = (int)precision;
            var i = 1;
            return new DateTime(input.Year,
                                p >= i++ ? input.Month : 1,
                                p >= i++ ? input.Day : 1,
                                p >= i++ ? input.Hour : 0,
                                p >= i++ ? input.Minute : 0,
                                p >= i++ ? input.Second : 0,
                                p >= i++ ? input.Millisecond : 0);
        }
    }


Very simple solution from my own code:

TimeSpan timeDifference = presentLastSavedDate.Subtract(previousLastSavedDate);
if (timeDifference.Seconds > 0)
{
    return Content(HttpStatusCode.Conflict, ALREADY_CHANGED_MSG);
}


I have create a very fast compare functions to compare DateTime with different Precission. All are arithmetical calculations and no new object are created.

public enum DateTimeComparePrecision : long
{
    Millisecond  = TimeSpan.TicksPerMillisecond,
    Second = TimeSpan.TicksPerSecond,
    Minute = TimeSpan.TicksPerMinute,
    Hour = TimeSpan.TicksPerHour,
    Day = TimeSpan.TicksPerDay,
}


public static bool DatesAreEqual(DateTime d1, DateTime d2, DateTimeComparePrecision Precision)
{
    return (d1.Ticks - (d1.Ticks % (long)Precision)) == (d2.Ticks - (d2.Ticks % (long)Precision));
}        


public static int DatesCompare(DateTime d1, DateTime d2, DateTimeComparePrecision Precision)
{
    long Day1 = (d1.Ticks - (d1.Ticks % (long)Precision));
    long Day2 = (d2.Ticks - (d2.Ticks % (long)Precision));

    if (Day2 > Day1) 
        return 1;            

    if (Day2 < Day1)            
        return -1;            

    return 0;
}

How Ticks Transformed for the compare

DateTime NowIs = DateTime.UtcNow;
Console.WriteLine($"{NowIs:dd MM yyyy HH:mm:ss.fffffff}");

DateTime d1 = new DateTime((NowIs.Ticks - (NowIs.Ticks % TimeSpan.TicksPerMillisecond)));
Console.WriteLine($"{d1:dd MM yyyy HH:mm:ss.fffffff}");

d1 = new DateTime((NowIs.Ticks - (NowIs.Ticks % TimeSpan.TicksPerSecond)));
Console.WriteLine($"{d1:dd MM yyyy HH:mm:ss.fffffff}");

d1 = new DateTime((NowIs.Ticks - (NowIs.Ticks % TimeSpan.TicksPerMinute)));
Console.WriteLine($"{d1:dd MM yyyy HH:mm:ss.fffffff}");

d1 = new DateTime((NowIs.Ticks - (NowIs.Ticks % TimeSpan.TicksPerHour)));
Console.WriteLine($"{d1:dd MM yyyy HH:mm:ss.fffffff}");

d1 = new DateTime((NowIs.Ticks - (NowIs.Ticks % TimeSpan.TicksPerDay)));
Console.WriteLine($"{d1:dd MM yyyy HH:mm:ss.fffffff}");

output

01 03 2022 12:51:26.7237323
01 03 2022 12:51:26.7230000
01 03 2022 12:51:26.0000000
01 03 2022 12:51:00.0000000
01 03 2022 12:00:00.0000000
01 03 2022 00:00:00.0000000
0

精彩评论

暂无评论...
验证码 换一张
取 消