Calculating an elapsed Time - c#

I want to calculate the elapsed time which a process needs to execute based on 2 strings with timestamps in the format HH:mm:ss:ff. Therefore I splitted those strings, turned them into an integer and subtracted them.
What I tried is to subtract the last timestamp from the first. It also works sometimes. But I also get a lot of weird feedback out of this - for example: 0:0:-3:-18 I think this is the result of not handling the case if a value is higher than another and they get divided.
Here is the function I use to subtract the strings:
static string calculateElapsedTime(string startTime, string endTime)
{
try
{
string[] startTimeSplit = startZeit.Split(new char[] { ':', '.' });
string[] endTimeSplit = endZeit.Split(new char[] { ':', '.' });
int[] elapsedTime = new int[4];
endTimeSplit[0] = Convert.ToInt32(endTimeSplit[0]) - Convert.ToInt32(startTimeSplit[0]);
endTimeSplit[1] = Convert.ToInt32(endTimeSplit[1]) - Convert.ToInt32(startTimeSplit[1]);
endTimeSplit[2] = Convert.ToInt32(endTimeSplit[2]) - Convert.ToInt32(startTimeSplit[2]);
endTimeSplit[3] = Convert.ToInt32(endTimeSplit[3]) - Convert.ToInt32(startTimeSplit[3]);
string elapsedTimeString = string.Format("{0}:{1}:{2}:{3}", endTimeSplit[0], endTimeSplit[1], endTimeSplit[2], endTimeSplit[3]);
return elapsedTimeString;
}
catch( Exception ex )
{
Console.WriteLine(ex.Message);
return "null";
}
}
And I got the value for the parameters by simply getting the time like:
DateTime.Now.ToString("HH:mm:ss:ff", System.Globalization.DateTimeFormatInfo.InvariantInfo);
SOLUTION:
There is a Function called Stopwatch in the Namespace System.Diagnostics.
You can use it as following:
Stopwatch watch = new Stopwatch();
watch.Start();
//Prozess
watch.Stop();
Console.WriteLine(watch.Elapsed);

You could convert to TimeSpan with the correct format and subtract them, for sample:
string format = "HH:mm:ss:ffff";
TimeSpan startTimeSpan = TimeSpan.ParseExact(startTime, format, null);
TimeSpan endTimeSpan = TimeSpan.ParseExact(endTime, format, null);
TimeSpan result = startTimeSpan - endTimeSpan;
string elapsedTimeString = string.Format("{0}:{1}:{2}:{3}",
result.Hours.ToString("00"),
result.Minutes.ToString("00"),
result.Seconds.ToString("00"),
result.Milliseconds.ToString("00"));
return elapsedTimeString;
Take a look at the TimeSpan Formats at MSDN documentation.

I came across this, and this is what I created. You can add the month's calculation if you wish. You can also use the total<days, hours...>, but it will defeat the code. Generate a converter to convert to DateTime. It helps a lot.
public static string TimeElapsed(DateTime start_Time, DateTime end_time)
{
string result = "";
var subtractedDate = end_time.Subtract(start_Time);
if (subtractedDate.Days >= 7)
{
var weeks = (int)(subtractedDate.Days / 7);
if (weeks >= 52)
{
var years = (int)(weeks / 52);
result = $"{years} years ago";
}
else
{
result = $"{weeks} weeks ago";
}
}
else if (subtractedDate.Days > 0 && subtractedDate.Days < 7)
{
result = $"{subtractedDate.Days} days ago";
}
else
{
if (subtractedDate.Hours> 0)
{
result = $"{subtractedDate.Hours} hours ago";
}
else
{
if (subtractedDate.Minutes > 0)
{
result = $"{subtractedDate.Minutes} mins ago";
}
else
{
result = "< 1 min ago";
}
}
}
return result;
}

Related

if statement keeps triggering

I am fairly new to C# (using it for a crpytographic process).
Some help would be greatly appreciated!
I have made a timer that should print out my hash speed every minute. See code below
using System;
using System.Text;
using System.Security.Cryptography;
namespace HashConsoleApp {
class Program {
static void Main(string[] args) {
long Nonce = 19989878659;
long Noncestart = 19989878659;
int Tick = 0;
DateTime start = DateTime.UtcNow;
while (Tick == 0) {
string noncestr = Nonce.ToString();
string plainData = "1" + noncestr + "Sjoerd0000000000000000000000000000000000000000000000000000000000000000";
string hashedData = ComputeSha256Hash(plainData);
// if 10-zeroes hash is found, save to disk
if (hashedData.Substring(0, 10) == "0000000000") {
Tick = Tick + 1;
string writestring = "Nonce: " + noncestr + "\n" + "hashed data: " + hashedData;
System.IO.File.WriteAllText("hash_10.txt", writestring);
}
// print hash speed per second, each minute
DateTime end = DateTime.UtcNow;
TimeSpan span1 = end.Subtract(start);
TimeSpan span2 = end.Subtract(start);
if (span1.Minutes >= 1) {
long diff = (int)(Nonce - Noncestart) / 60;
string diffs = diff.ToString();
Console.Write("Hash speed: " + diffs + " h/s");
System.IO.File.WriteAllText("test.txt", Nonce.ToString());
Noncestart = Nonce;
span1 = TimeSpan.Zero;
}
// save Nonce every hour, reset clock
if (span2.Minutes >= 60) {
start = DateTime.UtcNow;
System.IO.File.WriteAllText("hourly_nonce.txt", Nonce.ToString());
span2 = TimeSpan.Zero;
}
//Console.WriteLine("Raw data: {0}", plainData);
//Console.WriteLine("Hash {0}", hashedData);
//Console.WriteLine(ComputeSha256Hash("1"+noncestr+"Sjoerd0000000000000000000000000000000000000000000000000000000000000000"));
}
}
static string ComputeSha256Hash(string rawData) {
// Create a SHA256
using(SHA256 sha256Hash = SHA256.Create()) {
// ComputeHash - returns byte array
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
// Convert byte array to a string
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++) {
builder.Append(bytes[i].ToString("x2"));
}
return builder.ToString();
}
}
}
}
However, after the 1 minute mark this repeadetly keeps on printing on my screen. it looks like it gets stuck in the if statement. Is there something wrong with my code?
This should do the trick for your minute-timer (resetting minuteStart instead of span1):
static void Main(string[] args) {
long Nonce = 19989878659;
long Noncestart = 19989878659;
int Tick = 0;
DateTime start = DateTime.UtcNow;
DateTime minuteStart = DateTime.UtcNow; // ##### (added)
while (Tick == 0) {
// [Process stuff]
// print hash speed per second, each minute
DateTime end = DateTime.UtcNow;
TimeSpan span1 = end.Subtract(minuteStart); // ##### (modified)
if (span1.TotalMinutes >= 1) { // ##### (modified but Minutes should work fine here)
long diff = (int)(Nonce - Noncestart) / 60;
string diffs = diff.ToString();
Console.Write("Hash speed: " + diffs + " h/s");
System.IO.File.WriteAllText("test.txt", Nonce.ToString());
Noncestart = Nonce;
minuteStart = DateTime.UtcNow; // ##### (added)
//span1 = TimeSpan.Zero; // ##### (deleted)
}
// [...]
}
}
(See the lines with // #### comments)
The trick is that resetting span1 is useless because of this line:
TimeSpan span1 = end.Subtract(start);
However, if (span2.Minutes >= 60) will never be entered, as TimeSpan.Minutes "ranges from -59 through 59".
You probably are looking for TotalMinutes here.
Your if (span1.Minutes >= 1) { statement won't mean the printout only occurs once per minute, it will simply cause it to print whenever at least one minute has passed since the program started.
You need to check whether 1 minute has passed since the last printout . Therefore you need to reset the start time every time you run a printout. (N.B. Setting span1 = TimeSpan.Zero as you do now has no effect because you just overwrite that as soon as the loop runs again).
Also your minute and hour tests will conflict with each other once you do this, so you need separate date counters.
So add
DateTime start2 = DateTime.UtcNow;
just below the line where you declare start already.
Then please replace span1 = TimeSpan.Zero; with
start2 = DateTime.UtcNow;
and change TimeSpan span1 = end.Subtract(start);
to
TimeSpan span1 = end.Subtract(start2);
Lastly, replace if (span2.Minutes >= 60) { with
if (span2.TotalMinutes >= 60) {
otherwise this part won't work either because Minutes only reports the minutes in the current hour. You can also remove span2 = TimeSpan.Zero;, this is redundant like the similar line in the first if block.

C# Datetime - Add days or month

I'm trying to add days or month to a datetime. What determines rather it should add days or month to the datetime is what dayOrMonth ends with. So for example if dayOrMonth ends with MM it should add month, if it ends with DD it should add days.
dayOrMonth could look like this "90DD" (should add 90 days) and "90MM" should add 90 month.
I'm thinking about creating an extension method of some kind, but I'm struggling abit with the approach to this, as adding more if statements is not an option.
//... Set payment dates.
string dayOrMonth;
for (int x = 0; x < installmentDates.Count; x++)
{
if (installmentDates.Count > 0)
{
installmentdDateRow[colName] = installmentdDateRow[colName] + Convert.ToDateTime(installmentDates[x]).ToString("dd'-'MM'-'yyyy") + "\n";
//... Future payment dates.
int futurePaymentColumn = installmentdFuturePayments.Table.Columns.IndexOf(colName);
if (colName == "1. rate" && installmentDates.Count - 1 == x)
{
installmentdFuturePayments[futurePaymentColumn + 1] = installmentdFuturePayments[futurePaymentColumn + 1] + Convert.ToDateTime(installmentDates[x]).AddMonths(3).ToString("dd'-'MM'-'yyyy") + "\n";
}
if (colName == "2. rate" && installmentDates.Count - 1 == x && Functions.GetProductInfo(unit.Key.ToString().Split('-')[0])[9] != "€ 0,00")
{
installmentdFuturePayments[futurePaymentColumn + 1] = installmentdFuturePayments[futurePaymentColumn + 1] + Convert.ToDateTime(installmentDates[x]).AddMonths(3).ToString("dd'-'MM'-'yyyy") + "\n";
}
}
}
You have described an input string composed of two parts:
An integer magnitude to apply to some operation
A two character string defining the operation to use
As such, you know there should always be at least three characters. You also know the trailing two characters define the operation, so you can use Substring to separate those characters from the rest of the string.
An extension method is a great idea. It should include tests to help enforce the format of the input string, and make it easy to parse the numeric component.
public static DateTime ApplyInput(this DateTime dt, string input)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input), "The input string must not be null.");
}
if (input.Length < 3)
{
throw new ArgumentException("The input string is too short to include both a number and an operation.", nameof(input));
}
string numberChars = input.Substring(0, input.Length - 2);
if (!int.TryParse(numberChars, out int number))
{
throw new ArgumentException("The start of the input string must be an integer.", nameof(input));
}
string endingChars = input.Substring(input.Length - 2);
switch (endingChars.ToUpperInvariant())
{
case "MM":
return dt.AddMonths(number);
case "DD":
return dt.AddDays(number);
default:
throw new ArgumentException($"The characters {endingChars} were not recognized as a valid operation.", nameof(input));
}
}
This approach will perform better than using RegEx, Contains, or Replace. It is also extensible by adding more case statement to the switch.
Note that .ToUpperInvariant() makes the operation characters case-insensitive so you can pass mm or dd if you like. If you don't wan't that behavior, then simply remove .ToUpperInvariant().
Using StackOverflow - Seperating characters and numbers
You can use a regular expression to seperate the numbers from the characters in a given string like so:
Regex re = new Regex(#"([a-zA-Z]+)(\d+)");
Match result = re.Match(input);
string alphaPart = result.Groups[1].Value;
string numberPart = result.Groups[2].Value;
Then you can create a factory method or a project wide reference where you can use that code snippet to achieve what youre asking:
public DateTime AddDaysOrMonths(string input, DateTime dt)
{
Regex re = new Regex(#"([a-zA-Z]+)(\d+)");
Match result = re.Match(input);
string alphaPart = result.Groups[1].Value;
string numberPart = result.Groups[2].Value;
if(alphaPart == "DD")
{
int days;
if(Integer.TryParse(numberPart,out days) == true)
{
dt.AddDays(days)
}
}
else if (alphaPart == "MM")
{
int months;
if(Integer.TryParse(numberPart,out months) == true)
{
dt.AddMonths(months);
}
}
return dt;
}
Ofcourse, you should implement more extenstive error/null checking and better string comparison but this should be enough to get you going in the right direction.
I solved my problem by creating this extension method
public static DateTime test1(this DateTime d, string inputType)
{
if (inputType.Contains("DD"))
{
d = d.AddDays(Convert.ToInt32(inputType.Replace("DD", "")));
}
if (inputType.Contains("MM"))
{
d = d.AddMonths(Convert.ToInt32(inputType.Replace("MM", "")));
}
return d;
}

Invalid cast from double to datetime error

Hi I am getting invalid cast from double to datetime error when i run my ASP.NET MVC code.
This is my code :
Update: Hi I am adding my full code below. Please have a look into that.
Boolean locked = false;
if (frmcollection["lockStart"] != null && frmcollection["lockStart"] != "")
{
locked = Convert.ToBoolean(frmcollection["lockStart"].ToString());
}
else if (datelock == "")
{
locked = Convert.ToBoolean("0");
}
Boolean valid = true;
double inteval = 86400000 * Convert.ToDouble(frmcollection["autoFrequency"].ToString());
DateTime schedulestartDate = Convert.ToDateTime(frmcollection["autoStart"].ToString());
int startHour = Convert.ToInt32(frmcollection["autoStartHour"].ToString());
DateTime sd = schedulestartDate;
sd.AddHours(startHour);
DateTime filterStart = Convert.ToDateTime(frmcollection["periodStart"].ToString());
int filterStartHour = Convert.ToInt32(frmcollection["periodStartHour"].ToString());
DateTime fsd = filterStart;
fsd.AddHours(filterStartHour);
DateTime filterEnd = Convert.ToDateTime(frmcollection["periodEnd"].ToString());
int filterEndHour = Convert.ToInt32(frmcollection["periodEndHour"].ToString());
DateTime fed = filterEnd;
fed.AddHours(filterEndHour);
double sDate = sd.Second;
double sPeriod = sDate - fsd.Second;
double ePeriod = sDate - fed.Second;
if (sPeriod < ePeriod || sPeriod < 0 || ePeriod < 0)
{
valid = false;
}
if (valid)
{
for (int i = 0; i < 5; i++)
{
DateTime date = Convert.ToDateTime(sDate + (inteval * i));
if (locked)
{
DateTime psdate = Convert.ToDateTime(sDate - sPeriod);
}
else
{
DateTime psdate = Convert.ToDateTime(sDate + (inteval * i) - sPeriod);
}
DateTime pedate = Convert.ToDateTime(sDate + (inteval * i) - ePeriod);
}
}
else
{
}
When i debug I am gettin error in this line :
DateTime date = Convert.ToDateTime(sDate + (inteval * i));
Can someone help me in this??
You're adding a double to whatever interval * i resolves to.
You can't convert (cast) that to a DateTime, which is exactly what the error is telling you.
It seems as if you're looking for the date some (interval * i) seconds after the date "sd". If so, try:
for (int i = 0; i < 5; i++)
{
DateTime date = sd.AddSeconds(inteval * i);
if (locked)
{
DateTime psdate = sd.AddSeconds(-sPeriod);
}
else
{
DateTime psdate = sd.AddSeconds((inteval * i) - sPeriod));
}
DateTime pedate = sd.AddSeconds((inteval * i) - ePeriod);
}
//...
DateTime has a lot of methods to perform calculations on a specific date. For example DateTime.AddMillisecons which takes a double and returns a date.
MSDN DateTime.AddMilliseconds

Calculate DateTime for upcoming day of week

This is the code I have at the moment:
String getDayRequested;
public void setDay(String getDayFromForm1)
{
getDayRequested = getDayFromForm1;
{
if (getDayRequested.Contains("today"))
{
getDayRequested = DateTime.Today.DayOfWeek.ToString();
}
else if (getDayRequested.Contains("tomorrow"))
{
getDayRequested = DateTime.Today.AddDays(1).DayOfWeek.ToString();
}
}
This checks my TextBox.Text string from Form1, and checks to see if the text "today" or "tomorrow" is in it.
Can anyone help me in the right direction of how to check the string for information asked about upcoming days; ie: "What will be the date this saturday", and add the appropriate number of days depending on what the day is when asked.
UPDATE
Using the code in the accepted answer, I used the following in my above else if statement to complete what I was after:
else if (getDayRequested.Contains("monday"))
{
getDayRequested = GetFutureDay(DateTime.Now, DayOfWeek.Monday).ToString("dd");
}
This handy little method will return a future day of the week.
public DateTime GetFutureDay(DateTime start, DayOfWeek day)
{
int daysToAdd = (day - start.DayOfWeek + 7) % 7;
return start.AddDays(daysToAdd);
}
It would be called like:
var day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), getDayFromForm1);
var getDayRequested = GetFutureDay(DateTime.Now, day);
Consider the following snippet of code...
DateTime date;
public void setDay(String day)
{
DayOfWeek futureDay = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), day);
int futureDayValue = (int)futureDay;
int currentDayValue = (int)DateTime.Now.DayOfWeek;
int dayDiff = futureDayValue - currentDayValue;
if (dayDiff > 0)
{
date = DateTime.Now.AddDays(dayDiff);
}
else
{
date = DateTime.Now.AddDays(dayDiff + 7);
}
}
Good Luck!

Merging overlapping time intervals?

I have the following:
public class Interval
{
DateTime Start;
DateTime End;
}
I have a List<Interval> object containing multiple intervals. I am trying to achieve the following (I used numbers to make it easy to understand):
[(1, 5), (2, 4), (3, 6)] ---> [(1,6)]
[(1, 3), (2, 4), (5, 8)] ---> [(1, 4), (5,8)]
I currently do this in Python as follows:
def merge(times):
saved = list(times[0])
for st, en in sorted([sorted(t) for t in times]):
if st <= saved[1]:
saved[1] = max(saved[1], en)
else:
yield tuple(saved)
saved[0] = st
saved[1] = en
yield tuple(saved)
but am trying to achieve the same in C# (LINQ would be best but optional). Any suggestions on how to do this efficiently?
Here's a version using yield return - I find it easier to read than doing an Aggregate query, although it's still lazy evaluated. This assumes you've ordered the list already, if not, just add that step.
IEnumerable<Interval> MergeOverlappingIntervals(IEnumerable<Interval> intervals)
{
var accumulator = intervals.First();
intervals = intervals.Skip(1);
foreach(var interval in intervals)
{
if ( interval.Start <= accumulator.End )
{
accumulator = Combine(accumulator, interval);
}
else
{
yield return accumulator;
accumulator = interval;
}
}
yield return accumulator;
}
Interval Combine(Interval start, Interval end)
{
return new Interval
{
Start = start.Start,
End = Max(start.End, end.End),
};
}
private static DateTime Max(DateTime left, DateTime right)
{
return (left > right) ? left : right;
}
I was beset by "Not Created Here" syndrome tonight, so here's mine. Using an Enumerator directly saved me a couple lines of code, made it clearer (IMO), and handled the case with no records. I suppose it might run a smidge faster as well if you care about that...
public IEnumerable<Tuple<DateTime, DateTime>> Merge(IEnumerable<Tuple<DateTime, DateTime>> ranges)
{
DateTime extentStart, extentEnd;
using (var enumerator = ranges.OrderBy(r => r.Item1).GetEnumerator()) {
bool recordsRemain = enumerator.MoveNext();
while (recordsRemain)
{
extentStart = enumerator.Current.Item1;
extentEnd = enumerator.Current.Item2;
while ((recordsRemain = enumerator.MoveNext()) && enumerator.Current.Item1 < extentEnd)
{
if (enumerator.Current.Item2 > extentEnd)
{
extentEnd = enumerator.Current.Item2;
}
}
yield return Tuple.Create(extentStart, extentEnd);
}
}
}
In my own implementation, I use a TimeRange type to store each Tuple<DateTime, DateTime>, as other here do. I didn't include that here simply to stay focused / on-topic.
This may not be the prettiest solution, but it may work as well
public static List<Interval> Merge(List<Interval> intervals)
{
var mergedIntervals = new List<Interval>();
var orderedIntervals = intervals.OrderBy<Interval, DateTime>(x => x.Start).ToList<Interval>();
DateTime start = orderedIntervals.First().Start;
DateTime end = orderedIntervals.First().End;
Interval currentInterval;
for (int i = 1; i < orderedIntervals.Count; i++)
{
currentInterval = orderedIntervals[i];
if (currentInterval.Start < end)
{
end = currentInterval.End;
}
else
{
mergedIntervals.Add(new Interval()
{
Start = start,
End = end
});
start = currentInterval.Start;
end = currentInterval.End;
}
}
mergedIntervals.Add(new Interval()
{
Start = start,
End = end
});
return mergedIntervals;
}
Any feedback will be appreciated.
Regards
This kind of merging would typically be considered as a fold in functional languages. The LINQ equivalent is Aggregate.
IEnumerable<Interval<T>> Merge<T>(IEnumerable<Interval<T>> intervals)
where T : IComparable<T>
{
//error check parameters
var ret = new List<Interval<T>>(intervals);
int lastCount
do
{
lastCount = ret.Count;
ret = ret.Aggregate(new List<Interval<T>>(),
(agg, cur) =>
{
for (int i = 0; i < agg.Count; i++)
{
var a = agg[i];
if (a.Contains(cur.Start))
{
if (a.End.CompareTo(cur.End) <= 0)
{
agg[i] = new Interval<T>(a.Start, cur.End);
}
return agg;
}
else if (a.Contains(cur.End))
{
if (a.Start.CompareTo(cur.Start) >= 0)
{
agg[i] = new Interval<T>(cur.Start, a.End);
}
return agg;
}
}
agg.Add(cur);
return agg;
});
} while (ret.Count != lastCount);
return ret;
}
I made the Interval class generic (Interval<T> where T : IComparable<T>), added a bool Contains(T value) method, and made it immutable, but you should not need to change it much if you want to use the class definition as you have it now.
I used TimeRange as a container storing the ranges:
public class TimeRange
{
public TimeRange(DateTime s, DateTime e) { start = s; end = e; }
public DateTime start;
public DateTime end;
}
It divides the problem in combining two time ranges. Therefor, the current time range (work) is matched with the time ranges previously merged. If one of the previously added time ranges is outdated, it is dropped and the new time range (combined from work and the matching time range) is used.
The cases I figured out for two ranges () and [] are as follows:
[] ()
([])
[(])
[()]
([)]
()[]
public static IEnumerable<TimeRange> Merge(IEnumerable<TimeRange> timeRanges)
{
List<TimeRange> mergedData = new List<TimeRange>();
foreach (var work in timeRanges)
{
Debug.Assert(work.start <= work.end, "start date has to be smaller or equal to end date to be a valid TimeRange");
var tr = new TimeRange(work.start, work.end);
int idx = -1;
for (int i = 0; i < mergedData.Count; i++)
{
if (tr.start < mergedData[i].start)
{
if (tr.end < mergedData[i].start)
continue;
if (tr.end < mergedData[i].end)
tr.end = mergedData[i].end;
}
else if (tr.start < mergedData[i].end)
{
tr.start = mergedData[i].start;
if (tr.end < mergedData[i].end)
tr.end = mergedData[i].end;
}
else
continue;
idx = i;
mergedData.RemoveAt(i);
i--;
}
if (idx < 0)
idx = mergedData.Count;
mergedData.Insert(idx, tr);
}
return mergedData;
}

Categories