Calculate approximate date from approximate timespan - c#

Since Sony changed how their trophy server authentication works, I am looking for an alternative solution to approximate trophy dates. The UK Playstation Network website has a string underneath unlocked trophies approximating how long ago trophies were earned. I have the following code:
private TimeSpan ParseTimeSpan(string datestring)
{
TimeSpan ts = new TimeSpan();
datestring = datestring.Replace("Attained:", ""); // strip Attained:
string[] parts = datestring.Split(' ');
int numeric = int.Parse(parts[0]);
string duration = parts[1];
switch(duration)
{
case "minutes":
ts = new TimeSpan(0, numeric, 0);
break;
case "hours":
ts = new TimeSpan(numeric, 0, 0);
break;
case "days":
ts = new TimeSpan(numeric, 0, 0, 0);
break;
case "months":
ts = new TimeSpan(numeric * 30, 0, 0);
break;
case "years":
ts = new TimeSpan(numeric * 365, 0, 0);
break;
}
return ts;
}
If I have the string Attained:17 months ago underneath one of my trophies, it should take the current month and subtract 17 months. If I have the string Attained:3 hours ago, it should take the current date and subtract 3 hours to approximate an earned date. If I have the string Attained: 5 minutes ago, it should take the current date and subtract 5 minutes to approximate an earned date.
My plan is have this code run as a web service and accompany with a desktop client. I'm unsure of whether I should return a TimeSpan or do just calculate the date directly? Is there a better approach? Not all months are 30 days and some years are more than 365 days (leap years) so doing hard coded calculations won't necessarily work.

You don't have to worry about some months having 30 days and some not. If you have the timespan you can directly decrement the current date by that span.
TimeSpan ts = new TimeSpan(5,0,0);
var earned = DateTime.Now - ts;
Console.Write(earned);
This can be done in or out of your service, but I would perform it in the service if the actual date is what's needed.
Also you can add a regular expression if you need to add some flexibility later on.
string input = "Attained:17 months ago";
string pattern = #"Attained:(?<value>[0-9]+) (?<unit>(years|months|days)) ago";
var match = Regex.Match(input, pattern);
if(match.Success)
{
int value = Int32.Parse(match.Groups["value"].Value);
string unit = match.Groups["unit"].Value;
Console.WriteLine(value);
Console.WriteLine(unit);
}
Could make unit an enum as well

Noda Time is a much more flexible framework for working with time and date values than the types provided in the .NET base class library. It includes, among other things, a Period type, which represents some amount of time in elapsed years, months, days, etc. The actual elapsed time represented is unfixed. As you say, some months or years are longer than others, so Period is the type to use if you are not dealing with a fixed amount of time (as opposed to a fixed Duration). A Period can be added to the various date/time types in the Noda Time library.
While you can add and subtract a TimeSpan from a DateTime[Offset], it's not a terribly useful type if you need to deal with units of time longer than hours. Consider using Noda Time for nontrivial time/date computations.

Related

Humanizer for DateTime

I have this code:
Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(.75);
var dateTime1 = DateTime.UtcNow.AddYears(2).AddMonths(-5);
var text1 = dateTime1.Humanize();
In the text1 variable I get "one year from now". But this is not very accurate. Is there any way to get "one year and seven months from now"?
Update 1:
Solution #Daniel Hoffman has some problems, for example if my date is in the past:
//UtcNow is 11.07.2021
var dateTime6 = new DateTime(2021, 4, 24);
TimeSpan dateTimeSpan6 = dateTime6 - DateTime.UtcNow;
var text6 = dateTime6.Humanize();
string textSpan6 = dateTimeSpan6.Humanize(maxUnit: TimeUnit.Year, precision: 2);
then I get "2 months, 11 weeks" which contains basically the same information twice but in different units.
Update 2:
I have fixed the problem with dates in the past, by using Duration() method:
var timeSpan = date - DateTime.UtcNow;
return timeSpan.Duration().Humanize(maxUnit: TimeUnit.Year, precision: 2, minUnit: TimeUnit.Day);
[Edit]: Using TimeSpan will allow you to specify the precision of your period, but you will lose the ability to have "yesterday" or
"tomorrow", and it omits the " ago" or " from now", all of which are
localized.
A partial workaround would be to use the TimeSpan.Humanize
method for TimeSpans less than 366 days and DateTime.Humanize
otherwise. And if it's only going to be used in one language, the user
can append the appropriate text depending on if the timespan is
negative.
You can use the precision parameter with a TimeSpan:
TimeSpan periodFromNow = DateTime.UtcNow.AddYears(2).AddMonths(-5) - DateTime.UtcNow;
Then:
string myPeriodFromNow = periodFromNow.Humanize(maxUnit: TimeUnit.Year, precision: 2);
Other examples:
TimeSpan.FromDays(486).Humanize(maxUnit: TimeUnit.Year, precision: 7) => "1 year, 3 months, 29 days" // One day further is 1 year, 4 month
TimeSpan.FromDays(517).Humanize(maxUnit: TimeUnit.Year, precision: 7) => "1 year, 4 months, 30 days" // This month has 30 days and one day further is 1 year, 5 months
See also: https://github.com/Humanizr/Humanizer#humanize-timespan
It seems like its not currently possible in Humanizer to do what you want.
Check out this method PrecisionHumanize() on line 102, if the amount of days exceeds 365 then only years will be returned. And in general it seems like only one type of length of time can be returned, there is no years and months or minutes and seconds, just the largest one.
But check out another library called NodaTime it might be able to do what you want.
Here is a link to a different question similar to yours.

Exact c# result of sql datediff

I'm trying to get the number of days (calculated byu datediff) in sql and the number of days in c# (calculated by DateTime.now.Substract) to be the same, but they return different results....
//returns 0
int reso = DateTime.Now.Subtract(expirationDate).Days;
vs
//returns 1
dateDiff(dd,getDate(),ExpirationDate)
In both cases, ExpirationDate is '10/1/2011 00:00:00', and the code and the DB are sitting on the same server. I want the return int to be the same. I suspect I'm missing something stupid... ideas??
dateDiff(dd,getDate(),ExpirationDate) Is doing a days comparison. DateTime.Now.Subtract(expirationDate).Days is doing a date and time
For example
SELECT dateDiff(dd,'10/1/2011 23:59:00' , '10/2/2011') returns one day even when only one minute apart.
If you want the same in C# you need to remove the time component
e.g.
DateTime dt1 = new DateTime(2011,10,1, 23,59,0);
DateTime dt2 = new DateTime(2011,10,2, 0,0,0);
Console.WriteLine((int) dt2.Subtract(dt1.Subtract(dt1.TimeOfDay)));
So in your case it would be something like
DateTime CurrentDate = DateTime.Now;
int reso = CurrentDate.Subtract(CurrentDate.TimeOfDay).Subtract(DateTime.expirationDate).Days;
I haven't tested it but I would not do
DateTime.Now.Subtract(DateTime.Now.Subtract.TimeOfDay)
Because the second call to Now wouldn't be guaranteeing to be the same as first call to Now
In any case Stealth Rabbi's answer seems more elegant anyway since you're looking for a TimeSpan not a DateTime
10/1/2011 is less than 1 day away from DateTime.Now. Since you're getting back a TimeSpan and then applying Days to it, you're getting back a TimeSpan that is < 1 day. So it'll return 0 Days.
Instead, just use the Date component of those DateTimes and it'll correctly report the number of days apart - like this:
DateTime now = DateTime.Now;
DateTime tomorrow = new DateTime(2011, 10, 1);
var val = (tomorrow.Date - now.Date).Days;
This will yield you 1 day.
I'm assuming you want the number of Total days, not the number of days from the largest previous unit. You'd want to use the TotalDays property. Also, you may find it easier to use the minus operator to do a subtraction
DateTime d1 = DateTime.Now;
DateTime d2 = new DateTime(2009, 1, 2);
TimeSpan difference = d1 - d2;
Console.WriteLine(difference.TotalDays); // Outputs (today):1001.46817997424

Time Math Guidance needed

I have a DateTime object that is 10:00 AM
This time represents what time of day a report should be run.
I want to calculate the amount of time remaining from NOW until 10:00 AM
part of my confusion is NOW might be after 10:am or BEFORE 10am,
I keep playing around with TimeSpan, but my results are not quite right... I am sure this is simple, but it is one of those things I have been working of for a few hours and I need a push in the right direction...
I want the timespan object timeTillRun to be correct...here is what I have tried:
{
DateTime scheduledRun = DateTime.Today.AddHours(_timeToStart);//_timeToStart = 10
TimeSpan timeTillRun = DateTime.Now - scheduledRun;
}
This will work... but you need to reverse the order of subtraction:
TimeSpan timeTillRun = scheduledRun - DateTime.Now;
Note that if it's currently after 10AM, timeTillRun will be negative. You will presumably also need to check if the current time is on or after 10AM, then add 10 hours and one day to DateTime.Today to obtain the next run time. Alternatively, you could test if timeTillRun is negative; if so, just add one day to it (timeTillRun += new TimeSpan(1, 0, 0, 0)).
Try this
DateTime timeToStart = DateTime.Today.AddHours(10);
TimeSpan timeTillRun;
// Checking to see if current time is passed schedule run, if it is then we add a day (this is assuming this is run daily, if days are skipped like weekends for example then this would need some tweaking)
if (DateTime.Now > timeToStart)
timeTillRun = DateTime.Now.AddDays(1.0) - timeToStart;
else
timeTillRun = DateTime.Today - timeToStart;
double totalHoursRemaining = timeTillRun.TotalHours; // get total hours remaining
string prettyRemaining = String.Format("{0} day and {1} hours", timeTillRun.Days, timeTillRun.Hours); // can do some outputting here

How can I increment a date?

I have two dates as duedate as 03/03/2011 and returndate as 03/09/2011. I want to find the fine in double when I subtract duedate from returndate.How can duedate be incremented?
Following code might help you:
var dueDate = new DateTime(2011, 3, 3);
//if you want to increment six days
var dueDatePlus6Days = dueDate.AddDays(6);
//if you want to increment six months
var dueDatePlus6Months = dueDate.AddMonths(6);
var daysDiff1 = (dueDatePlus6Days - dueDate).TotalDays; //gives 6
var daysDiff2 = (dueDatePlus6Months - dueDate).TotalDays; //gives 184
The logical solution would seem to be the AddDays method, as in the other answers.
However, I try (in general) never to use floating point (i.e. a double) when working with monetary or date values.
DateTime contains a time component and AddDays takes a double as argument (fractional part becomes time), so I tend to avoid use of that method.
Instead, I use
dueDatePlusOne = dueDate.AddTicks(TimeSpan.TicksPerDay);
This should result in slightly faster execution too. Not that it still matters much on today's hardware, but I started out coding for microprocessors with a <1 MHz clock speed and old PDP-8's and -11's and stuff like that back in the 1970's, and some habits never die ;)
Assuming returnDate, dueDate are DateTime objects:
double extraDays = (returnDate - dueDate).TotalDays;
May this can help you
DateTime dt_duedate = DateTime.Now;
DateTime dt_returndate = DateTime.Now.AddDays(2);
System.TimeSpan diffResult = dt_returndate.Subtract(dt_duedate);

Parsing times above 24 hours in C#

Suppose a time stamp (just time or date and time) where the time can roll over to the next day:
00:00:00 <- midnight
01:00:00 <- 1 AM
23:00:00 <- 11 PM
24:00:00 <- midnight, day + 1
25:00:00 <- 1 AM, day + 1
What would be a way to parse it easily into a C# DateTime that would perform the carry-over to the next day? In other words, "01:00:00" would become "0001-01-01 01:00:00" and "25:00:00" would become "0001-01-02 01:00:00".
EDIT:
I should mention that this fails miserably (i.e FormatException):
DateTime.ParseExact("0001-01-01 25:00:00", "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
Since you're trying to represent a period of time from an arbitrary point, rather than as a specific date, perhaps you would be better off using the System.TimeSpan class? This allows you to set values of more than 24 hours in the constructor, and can be used with DateTime objects like this:
System.TimeSpan timestamp = new System.TimeSpan(25, 0, 0);
System.DateTime parsedDateTime = new DateTime(0, 0, 0);
parsedDateTime = parsedDateTime.Add(timestamp);
Console.WriteLine(parsedDateTime.ToString("yyyy-MM-dd HH:mm:ss")); //Output as "0001-01-02 01:00:00"
NOTE: Code is untested.
EDIT: In terms of parsing the strings, I can't think of any basic .NET objects that parse strings with values greater than 23 for the hour (since 25 is an invalid hour of the day), but assuming that the format is consistent, you could create a very simple string parsing routine (or even a regular expression) to read the values individually, and load the constructor manually.
If you have an existing DateTime value you can add to, you can always use a TimeSpan:
string dt = "25:00:00";
int hours = int.Parse(dt.Split(':')[0]);
TimeSpan ts = TimeSpan.FromHours(hours);
TimeSpan.Parse() doesn't work directly in this case because it complains (fair enough!) about the 25 in the hour notation.
If you want to code it out... this should be a starting point:
string dateString = "0001-01-01 25:00:00";
string[] parts = dateString.Split(' '); //now have '0001-01-01' and '25:00:00'
string datePart = parts[0]; // '0001-01-01'
string[] timeParts = parts[1].Split(':'); //now have '25', '00', and '00
DateTime initialDate = DateTime.ParseExact(datePart, "yyyy-MM-dd", CultureInfo.InvariantCulture);//use the date as a starting point
//use the add methods to get your desired datetime
int hours = int.Parse(timeParts[0]);
int minutes = int.Parse(timeParts[1]);
int seconds = int.Parse(timeParts[2]);
DateTime resultDate = initialDate.AddHours(hours)
.AddMinutes(minutes)
.AddSeconds(seconds);
Of course, it makes assumptions that the input is formatted properly and is parsable, etc..
In addition, you could definitely use timespan instead of the individual add methods for hour, minute, second as some other answers are..
In case nobody points out an out-of-the-box answer, here is a neat ActionScript class I wrote to parse time inputs (human input)...
https://github.com/appcove/AppStruct/blob/master/Flex/AppStruct/src/AppStruct/TimeInput.as
It would be very simple to port this to C#, and you could tweak the 24 hour logic to result in #days, #hours, #minutes.
Good luck!
You are specifying an invalid date. So not only can you not parse it, you cannot store it!
How about a nice TimeSpan object instead? (It also has a Parse() method.)
Alternatively, use a sscanf()-type function like the one at http://www.blackbeltcoder.com/Articles/strings/a-sscanf-replacement-for-net to extract each number separate. (Best if you have no control over the string format being read.)

Categories