5

I am having relative datetime string like:

  • "5 minutes ago"
  • "10 hours ago"
  • "3 days ago" etc.

How can I convert this to exact datetime, as exactly opposite of this question

Community
  • 1
  • 1
Agent007
  • 2,720
  • 3
  • 20
  • 24

3 Answers3

6

You will need to write your own routines to do so, just like the people doing the opposite had to do.

Essentially, you will need to parse the text to find the interval (i.e. minutes, hours, days etc...), the amount and whether it is in the past or future (using ago or from).

At this point you will have enough data to construct an appropriate TimeSpan instance and use it with DateTime.Now to get that time.

In order fro the above to work, you will need to ensure that the string values to parse are standardized.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
6

This code should work:

        string input = "10 days ago";

        DateTime result = DateTime.MinValue;
        int minutesMultiplier = 0;

        if (input.Contains("minute"))
            minutesMultiplier = 1;
        else
            if (input.Contains("hour"))
                minutesMultiplier = 60;
            else
                if (input.Contains("day"))
                    minutesMultiplier = 1440;
                else
                    throw new Exception("Couldn't parse time format");

        string numberStr = input.Split(' ')[0];
        int number;
        if (int.TryParse(numberStr, out number))
            result = DateTime.Now.AddMinutes(-number * minutesMultiplier);

It does the parsing of an interval name (such as minute, hour, day) and multiplies them to get the number of minutes because later on it uses DateTime.Now.AddMinutes method, same thing could be done using TimeSpan and calling DateTime.Now.Add.

Here is one more example that handles case of a string that contains more than one interval name, such as "10 hours and 15 minutes ago":

        // If there are mixed interval types in an input string
        string input = "10 days and 10 hours ago";

        // Parse out the intervals and numbers
        var matches = Regex.Matches(input, 
                       @"(?<number>\d+)\s(?<interval>(day)|(minute)|(hour))");

        // Convert them to dictionary
        var dic = matches
            .Cast<Match>()
            .ToDictionary(
                key => key.Groups["interval"].Value, 
                o => int.Parse(o.Groups["number"].Value));

        // Calculate the total number of minutes for each interval
        DateTime result = DateTime.MinValue;
        int totalMinutes = 0;

        foreach (var keyValue in dic)
        {
             if (keyValue.Key.Contains("minute"))
                 totalMinutes += keyValue.Value;
            else
                 if (keyValue.Key.Contains("hour"))
                    totalMinutes += keyValue.Value * 60;
                else
                     if (keyValue.Key.Contains("day"))
                        totalMinutes += keyValue.Value * 1440;
                    else
                        throw new Exception("Unparsable time format");
        }

        result = DateTime.Now.AddMinutes(-totalMinutes);
Ivan Golović
  • 8,732
  • 3
  • 25
  • 31
  • 1
    Note that this code relies on correctly formatted input strings. – O. R. Mapper Aug 17 '12 at 09:27
  • 1
    Yes, the presumption is that inputs are predictable and possibly generated automatically. – Ivan Golović Aug 17 '12 at 09:30
  • 1
    `string input = "10 hours and 30 minutes ago";` -- what does your code do? I understand that you can't foresee all possible input, but when the input isn't in the format you expect, you should throw an exception, not silently do the wrong thing. –  Aug 17 '12 at 09:48
  • I presumed that in a single string only one interval could be mentioned since OP never said that there could be more than one. I added throwing the exception although it's clear this won't handle the situation with more than one interval in a string. If there can be more than one, parsing has to be done differently. – Ivan Golović Aug 17 '12 at 09:59
  • Your exception won't be thrown with by input, because `input.Contains("minute")` is true, so it subtracts 10 minutes. Based on the input in the question, I'd probably store `input.Split(' ')` in a variable, check that its length is exactly 3, and check that `words[1]` is exactly "minutes" (or "hours", "days", whatever else you want to add), and that `words[2]` is exactly "ago". –  Aug 17 '12 at 10:01
  • @hvd I handled the situation with more than one interval. – Ivan Golović Aug 17 '12 at 10:23
  • Looks good, that's much less likely to fail unless you specifically create intentionally bad input. I actually misread at first, but of course "10 days" works: it contains "10 day", which is matched by your regex. –  Aug 17 '12 at 10:26
  • @IvanG That's great stuff, I was trying to write similar with no success, thanks again ! – Agent007 Aug 17 '12 at 11:27
2

The proper way would be storing your relative value as a TimeSpan value and subtracting that from DateTime.Now (or from whatever DateTime you want to use as a base).

You can use methods such as int.Parse in order to convert the numbers (the number of minutes, hours etc. ago) into integer values and copy those into your TimeSpan value. The exact parsing algorithm depends on the actual format of your strings, i.e. what words are allowed in there, and in what order the numbers may appear.

If the strings are already isolated as shown in your question, you can try and disassemble them using a regular expression (with the Regex class):

^(\d+)\s+([a-z]+)\s+ago$
O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114