Nick DeMayo
Nick DeMayo

Reputation: 1086

Find the closest time from a list of times

So, here's the scenario. I have a file with a created time, and I want to choose a time from a list of times that that file's created time is closest or equal too...what would be the best way to accomplish this?

Upvotes: 28

Views: 35717

Answers (12)

johnthom
johnthom

Reputation: 21

This is a generalized solution to the question, "Find the closest time from a list of times". This solution finds the closest time before and after a given search time.

//For finding the closest time in a list using a given search time...

var listOfTimes = new List<DateTime>();
listOfTimes.Add(DateTime.Parse("1/1/2000"));
listOfTimes.Add(DateTime.Parse("1/2/2000"));
listOfTimes.Add(DateTime.Parse("1/3/2000"));
listOfTimes.Add(DateTime.Parse("1/4/2000"));
listOfTimes.Add(DateTime.Parse("1/5/2000"));

var searchTime = DateTime.Parse("1/3/2000");

var closestBefore = listOfTimes.LastOrDefault(t => t < searchTime);
var closestAfter = listOfTimes.FirstOrDefault(t => t > searchTime);

Console.WriteLine(closestBefore);
Console.WriteLine(closestAfter);

/*
searchTime: 1/3/2000
before:     1/2/2000 12:00:00 AM
after:      1/4/2000 12:00:00 AM

searchTime: 1/1/1900 (edge case)
before:     1/1/0001 12:00:00 AM (DateTime.MinValue)
after:      1/1/2000 12:00:00 AM

searchTime: 1/1/2001 (edge case)
before:     1/5/2000 12:00:00 AM 
after:      1/1/0001 12:00:00 AM (DateTime.MinValue)
*/

Upvotes: 2

luvieere
luvieere

Reputation: 37494

Something like this:

DateTime fileDate, closestDate;
ArrayList theDates;
long min = long.MaxValue;

foreach (DateTime date in theDates)
 if (Math.Abs(date.Ticks - fileDate.Ticks) < min)
 {
   min = Math.Abs(date.Ticks - fileDate.Ticks);
   closestDate = date;
 }

Upvotes: 12

Darren Street
Darren Street

Reputation: 1830

I thought I would update this post to include a real world scenario. I wanted this sort of function as I have a blog showing news of the latest movie screenings.

However I don't want to list screening in the past (ie screening date past the current date) and as I wanted to show a record I needed some sort of ID passed to pick up the record.

I have left if simple so that you can follow the process and no doubt make it more efficient with LINQ et al.

First the model

        public class LatestScreeeningsModel
    {
        public int Id { get; set; }
        public DateTime Date { get; set; }
    }

Then the code block you can call from your controller

        private static LatestScreeeningsModel GetLatestScreening(IPublishedContent currentNode)
    {
        LatestScreeeningsModel latestScreening = new LatestScreeeningsModel();

        DateTime fileDate;

        // get a list of screenings that have not shown yet
        var screenings = currentNode.AncestorsOrSelf("siteLanguage")
                                   .FirstOrDefault().Descendants("screening")
                                   .Select(x => new LatestScreeeningsModel() { Id = x.Id, Date = x.GetPropertyValue<DateTime>("date") })
                                   .Where(x => x.Date > DateTime.Now).ToList();

        fileDate = DateTime.Today;

        long min = Math.Abs(fileDate.Ticks - screenings[0].Date.Ticks);
        long diff;
        foreach (var comingDate in screenings)
        {
            diff = Math.Abs(fileDate.Ticks - comingDate.Date.Ticks);
            if (diff <= min)
            {
                min = diff;
                latestScreening = comingDate;
            }
        }

        return latestScreening;

    }

I am using Umbraco to get the date items but it would work with any custom model, List et al.

Hope it helps

Upvotes: 0

LukeH
LukeH

Reputation: 269348

var closestTime = listOfTimes.OrderBy(t => Math.Abs((t - fileCreateTime).Ticks))
                             .First();

If you don't want the performance overhead of the OrderBy call then you could use something like the MinBy extension method from MoreLINQ instead:

var closestTime = listOfTimes.MinBy(t => Math.Abs((t - fileCreateTime).Ticks));

Upvotes: 76

Kevin
Kevin

Reputation: 8561

The accepted answer is completely wrong. What you want is something like this:

  DateTime fileDate, closestDate;
  List<DateTime> theDates;

  fileDate = DateTime.Today;       //set to the file date
  theDates = new List<DateTime>(); //load the date list, obviously

  long min = Math.Abs(fileDate.Ticks - theDates[0].Ticks);
  long diff;
  foreach (DateTime date in theDates)
  {
    diff = Math.Abs(fileDate.Ticks - date.Ticks);
    if (diff < min)
    {
      min = diff;
      closestDate = date;
    }
  }

Upvotes: 8

cdkMoose
cdkMoose

Reputation: 1636

Not an answer, but a question regarding the various LINQ solutions proposed above. How efficient is LINQ? I have not written any "real" programs with LINQ yet, so I'm not sure on the performance.

In this example, the "listOfTimes" collection implies that we have already iterated over some file system based objects to gather the times. Would it have been more efficient to do the analysis during the iteration instead of later in LINQ? I recognize that these solutions may be more "elegant" or nicely abstract the "collection as database" idea, but I tend to choose efficiency (must be readable though) over elagance in my programming. Just wondering if the cost of LINQ might outweigh the elegance here?

Upvotes: 1

Thomas Levesque
Thomas Levesque

Reputation: 292405

var closestTime = (from t in listOfTimes
                   orderby (t - fileInfo.CreationTime).Duration()
                   select t).First();

Upvotes: 8

leppie
leppie

Reputation: 117220

var min = listoftimes.Select(
    x => new { diff = Math.Abs((x - timeoffile).Ticks), time = x}).
    OrderBy(x => x.diff).
    First().time;

Note: Assumes at least 1 entry in listoftimes.

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490108

How often will you be doing this with the same list of times? If you're only doing it once, the fastest way is probably to just scan through the list and keep track of the closest time you've seen yet. When/if you encounter a time that's closer, replace the "closest" with that closer time.

If you're doing it very often, you'd probably want to sort the list, then use a binary search.

Upvotes: 5

Yuriy Faktorovich
Yuriy Faktorovich

Reputation: 68667

var creationTimes = new [] {DateTime.Now.AddDays(-1), DateTime.Now.AddDays(-2)};
FileInfo fi = new FileInfo("C:/test.xml");
var closestTime = creationTimes
    .OrderBy(c => Math.Abs(c.Subtract(fi.CreationTime).Days))
    .First();

Upvotes: 0

Adriaan Stander
Adriaan Stander

Reputation: 166376

Use the minimum absolute time difference between the file time and the time in the list. You might get 2 entries being the same, and then you would need a different method to differ between these.

Upvotes: 1

ps.
ps.

Reputation: 4360

get the difference of your file creatime and every time in your list and sort the absolute value of each time difference. the first one should be the answer you are looking for.

Upvotes: 2

Related Questions