I would like to output the time remaining for a process on the following format:
d hh:mm:ss
When the process start, it sets a DateTime startTimevariable with the current time.
From there I have timer on my GUI that updates a label every second and here is what I have to calculated the remaining time:
TimeSpan timeRemaining = TimeSpan.FromTicks(DateTime.Now.Subtract(startTime).Ticks * (iProgress.Maximum - iProgress.Value) / iProgress.Value);
And using on the label as:
labelTimeRemaining.Text = timeRemaining.ToString(#"d\ hh\:mm\:ss");
However the time variation is displayed is going on very oddly jumping several minutes with getting into a steady mode.
I know it should jump on the begin but it should stabilize at some point right ?
I process about 1~3 records per second however the time remaining is jumping 10~20 minutes every update.
What am I doing wrong or how could I fix this issue ?
Let me know if u need more info or code.
UPDATE
Below is portion of the code I am using:
private Stopwatch _timer = new Stopwatch();
private int _total = 0;
private int _current = 0;
Start a thread or task with:
private void MyTask()
{
_timer.Reset();
_timer.Start();
for (int i = 0; i < _total; i++)
{
_current++;
Thread.Sleep(500);
}
_timer.Stop();
}
Create a winforms Timer with the below function to run every second:
if (_timer.IsRunning && _current > 0)
{
float elapsedMin = ((float)_timer.ElapsedMilliseconds / 1000) / 60;
float minLeft = (elapsedMin / _current) * (_total - _current);
TimeSpan eta = TimeSpan.FromMinutes(minLeft);
iStatus.Text = eta.ToString(#"d\ hh\:mm\:ss");
}
Based on the comments you provided, when you are working with 10K to 10M records, that a fluctuation is rather big as you could see. Here is the test example that will show you the fluctuation.
DateTime dt = DateTime.Now;
Thread.Sleep(3000);
TimeSpan ts = TimeSpan.FromTicks(DateTime.Now.Subtract(dt).Ticks * (100000 - 7) / 7);
Debug.WriteLine(ts.ToString());
Thread.Sleep(1000);
ts = TimeSpan.FromTicks(DateTime.Now.Subtract(dt).Ticks * (100000 - 9) / 9);
Debug.WriteLine(ts.ToString());
Also, even if you take care of integer division
DateTime dt = DateTime.Now;
Thread.Sleep(3000);
TimeSpan ts = TimeSpan.FromTicks((long)((double)DateTime.Now.Subtract(dt).Ticks * (100000.0 - 7.0) / 7.0));
Debug.WriteLine(ts.ToString());
Thread.Sleep(1000);
ts = TimeSpan.FromTicks((long)((double)DateTime.Now.Subtract(dt).Ticks * (100000.0 - 9.0) / 9.0));
Debug.WriteLine(ts.ToString());
But if you could normalize your records number to percents. Let's say you have 100K records, 1K is one percent and that is done in approximately 1000/2/60 around 8 minutes. After 32 minutes you will have 4% done. After another you will have 6% done and after another 24 minutes you will have around 9% done but lets say 10%. After more 30 minutes you should have 14% done. And now, if you update your textBox rarely the users will have the illusion that you really know how long it will take, and you will end up changing time for hour or two, but do it on every minute or on every percent changed (have you ever copied 30GB on Windows?)
DateTime dt1 = DateTime.Now;
DateTime dt2 = DateTime.Now;
dt2 = dt2.AddMinutes(32);
ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 4) / 4);
Debug.WriteLine(ts.ToString());
dt2 = dt2.AddMinutes(16);
ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 6) / 6);
Debug.WriteLine(ts.ToString());
dt2 = dt2.AddMinutes(24);
ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 10) / 10);
Debug.WriteLine(ts.ToString());
dt2 = dt2.AddMinutes(30);
ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 14) / 14);
Debug.WriteLine(ts.ToString());
Related
I have datetime like 2019-02-10 20:39:23 and I want to round this time to the next one apart 15 min to the closest one. So it means the next one should be 2019-02-10 21:45:00 or another example 21:24:17 should became 21:45:00... The code below works fine until I have datetime like 2019-02-10 23:54:20. Then the next one rounded should be 2019-03-10 00:00:00 but I get 2019-02-10 00:00:00.
Here is how I'm doing it:
static void Main(string[] args)
{
DateTime dt = DateTime.Parse("2019-02-10 23:54:23");
var interval = TimeSpan.FromMinutes(15);
DateTime last = NextTime(dt, interval);
Console.WriteLine(last);
}
private static DateTime NextTime(DateTime value, TimeSpan interval)
{
var temp = value.Add(new TimeSpan(interval.Ticks / 2));
var time = new TimeSpan((temp.TimeOfDay.Ticks / interval.Ticks) * interval.Ticks);
return value.Date.Add(time);
}
For output I get 2019-02-10 00:00:00 instead of 2019-03-10 00:00:00
Can't figure out why doesn't turn to next day...
The return value is being calculated from the wrong variable. Use temp instead of value:
private static DateTime NextTime(DateTime value, TimeSpan interval)
{
var temp = value.Add(new TimeSpan(interval.Ticks / 2));
var time = new TimeSpan((temp.TimeOfDay.Ticks / interval.Ticks) * interval.Ticks);
return temp.Date.Add(time);
}
The reason for this is because you're adding your interval to the value. If it rolls over a midnight/end of day your value.Date will return the wrong day. Since you store temp, you can return temp.Date.Add(time)
Using DateTime.Add(TimeSpan) the time is concat in the date.
I'v changed your code in this way and it did the trick:
private static DateTime NextTime(DateTime value, TimeSpan interval)
{
var temp = value.Add(new TimeSpan(interval.Ticks / 2));
var time = new TimeSpan((temp.TimeOfDay.Ticks / interval.Ticks) * interval.Ticks);
if (time == new TimeSpan(0, 0, 0)) { time = new TimeSpan(24, 0,0); }
var timeDiff = time - value.TimeOfDay;
var finalDate = value.AddHours(timeDiff.Hours);
finalDate = finalDate.AddMinutes(timeDiff.Minutes);
finalDate = finalDate.AddSeconds(timeDiff.Seconds);
return finalDate;
}
I believe that must have some way more beautifull to do that but it works.
I want to implement clock method in c# and get difference time of two clock as describe in below code. I use timers ,stopwatch and DateTime in c# but i didn't get correct time as i get in c++.
C++ code is describe below :
clock_t start_time, diff_time;
start_time = clock();
int i = 0;
for(i=0;i<10;i++)
{
.....
}
diff_time = clock();
diff_time -= start_time;
C# code is :
var start_time = Stopwatch.StartNew();
for (int i = 0; i <= 10; i++)
i++;
var diff_time = Stopwatch.StartNew();
var diff_times = (start_time.ElapsedTicks - diff_time.ElapsedTicks);
I think StopWatch should be used to measure code performance and other similar comparisons. If is the case, ok.
Or else, maybe you should consider DateTime.Now
The C function clock() returns a clock_t value that is the number of clock ticks elapsed since the program started. The number of seconds used by the CPU is obtained dividing this result by CLOCKS_PER_SEC.
For DateTime, check:
https://learn.microsoft.com/pt-br/dotnet/api/system.datetime?view=netframework-4.8
-> Some parts below:
The DateTime value type represents dates and times measured in 100-nanosecond units called ticks. Example: in the gregorian calendar a ticks value of 31241376000000000L represents the date Friday, January 01, 0100 12:00:00 midnight.
...
If you are working with a ticks value that you want to convert to some other time interval, such as minutes or seconds, you should use the TimeSpan.TicksPerDay, TimeSpan.TicksPerHour, TimeSpan.TicksPerMinute, TimeSpan.TicksPerSecond, or TimeSpan.TicksPerMillisecond constant to perform the conversion. For example, to add the number of seconds represented by a specified number of ticks to the Second component of a DateTime value, you can use the expression
dateValue.Second + nTicks/Timespan.TicksPerSecond.
...
Here is a simple implementation of the Clock() function in C#
static void Main(string[] args)
{
long CLOCKS_PER_SEC = 10000000;
long lngElapsedTime_t = Clock();
long lngElapsedTime;
int i, j, k, m;
for (i = 0; i < 5000000; i++)
{
j = i / 50;
k = j * 50;
m = i - k;
if (m == 1) { Console.WriteLine(i); }
}
lngElapsedTime = (Clock() - lngElapsedTime_t) / CLOCKS_PER_SEC;
Console.WriteLine("<<{0}>> The time in Ticks", lngElapsedTime_t);
Console.WriteLine("<<{0}>> The elapsed time in seconds", lngElapsedTime);
Console.ReadKey();
}
public static long Clock()
{
long clock_t = (long)DateTime.Now.Ticks;
return clock_t;
}
I have a MsSql database which calculates the timespan between two dates in seconds. That works fine. I use this column afterwards in C# and write them in an array.
This array is the input for a chart later on.
So far this works well, but I cannot find a way to display the seconds in a format like hhh:mm:ss as the timespan can be greater than 24h.
I tried ChartArea.AxisY.LabelStyle.Format = "hhmmss"; but it does not work at all.
Does anybody has an idea how I could do that?
EDIT:
I add the data this way:
chart2.Series.Clear();
chart2.ChartAreas.Clear();
Series BoxPlotSeries = new Series();
ChartArea ChartArea2 = new ChartArea();
ChartArea ChartArea3 = new ChartArea();
chart2.ChartAreas.Add(ChartArea2);
chart2.ChartAreas.Add(ChartArea3);
ChartArea2.Name = "Data Chart Area";
ChartArea3.Name = "BoxPlotArea";
BoxPlotSeries.Name = "BoxPlotSeries";
BoxPlotSeries.ChartType = SeriesChartType.BoxPlot;
BoxPlotSeries.ChartArea = "BoxPlotArea";
chart2.Series.Add(BoxPlotSeries);
Series Input1 = new Series();
Input1.Name = "Input1";
Input1.ChartType = SeriesChartType.Point;
Input1.ChartArea = "Data Chart Area";
chart2.Series.Add(Input1);
chart2.Series["Input1"].Points.DataBindY(InputArray);
chart2.ChartAreas["BoxPlotArea"].AxisX.CustomLabels.Add(2, 0.0, "BoxPlot1");
chart2.Series["BoxPlotSeries"]["BoxPlotSeries"] = "Input1";
chart2.Series["BoxPlotSeries"]["BoxPlotShowMedian"] = "true";
chart2.Series["BoxPlotSeries"]["BoxPlotShowUnusualValues"] = "false";
chart2.Series["BoxPlotSeries"]["PointWidth"] = "0.5";
chart2.Series["BoxPlotSeries"].IsValueShownAsLabel = false;
ChartArea2.Visible = false;
ChartArea3.BackColor = Color.FromArgb(224,224,224);
//I tried to format it this way but it didn't work
//ChartArea3.AxisY.LabelStyle.Format = "{0:HHHmmss}";
chart2.ChartAreas["BoxPlotArea"].AxisX.LabelStyle.Angle = -90;
EDIT2:
And here's how I populate the input array
int[] InputArray = new int[1000000];
int c = 0;
con.Open();
dr = cmd.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
int n;
if (int.TryParse(dr[0].ToString(),out n) == true)
{
InputArray[c] = Convert.ToInt32(dr[0].ToString());
c++;
}
}
}
if (c == 0) { c = 1; }
Array.Resize(ref InputArray, c - 1);
EDIT 3:
The Boxplot should look like this in the end:
In Excel the format to display hours greater than 24 is called "[h]:mm:ss;#"
EDIT4:
Thanks to #TAW I nearly managed to solve my problem. I made some adjustments to his solution and came up with this:
In the chart code block:
The Value "max" is set before.
ChartArea3.AxisY.MajorTickMark.Interval = addCustomLabels(ChartArea3, BoxPlotSeries, 60 * 60, max);
int addCustomLabels(ChartArea ca, Series series, int interval, int max)
{
int tickNo = 0;
ca.AxisY.CustomLabels.Clear();
if(max / interval > 10)
{
interval = (max / 10) - (max / 10) % (60*30);
tickNo = (max / 10) - (max / 10) % (60*30);
}
if (max / interval <= 2 )
{
interval = (max / 4) - (max / 4) % (60 * 15);
tickNo = (max / 4) - (max / 4) % (60 * 15);
}
for (int i = 0; i < max; i += interval)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = i - interval / 2;
cl.ToPosition = i + interval / 2;
cl.Text = hhh_mm_ss(i);
ca.AxisY.CustomLabels.Add(cl);
}
return tickNo;
}
My problem is now, that sometimes no axis lable (apart from 0:00) is shown even when the code runs through it without any problems.
Has anybody and idea what could be wrong?
Your task involves two parts:
displaying seconds in the hhh:mm:ss format
putting them as labels on the y-axis
There is no suitable date-time formatting string for this in c#, so we can't make use of the built-in automatic labels and their formatting.
There also no way to use expressions that call a function on the automatic labels, unfortunately.
So we can't use those.
Instead we will have to add CustomLabels. This is not very hard but does take a few steps..
But let's start with a function that converts an int to the hhh:mm:ss string we want; this should do the job:
string hhh_mm_ss(int seconds)
{
int sec = seconds % 60;
int min = ((seconds - sec)/60) % 60;
int hhh = (seconds - sec - 60 * min) / 3600;
return hhh > 0 ? string.Format("{2}:{1:00}:{0:00}", sec, min, hhh)
: min + ":" + sec.ToString("00");
}
Maybe it can be optimized, but for our purpose it'll do.
Next we need to create the CustomLabels. They will replace the normal axis labels and we need to add them in a separate loop over the data after each binding.
One special thing about them is their positioning. Which is smack between two values we need to give them: the FromPosition and ToPosition, both in the unit of the axis-values.
Another difference to normal, automatic Labels is that it is up to us to create as many or few of them as we need..
This function tries to create a number that will go up to the maximum y-value and space the CustomLabels at a given interval:
void addCustomLabels(ChartArea ca, Series series, int interval)
{
// we get the maximum form the 1st y-value
int max = (int)series.Points.Select(x => x.YValues[0]).Max();
// we delete any CLs we have
ca.AxisY.CustomLabels.Clear();
// now we add new custom labels
for (int i = 0; i < max; i += interval)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = i - interval / 2;
cl.ToPosition = i + interval / 2;
cl.Text = hhh_mm_ss(i);
ca.AxisY.CustomLabels.Add(cl);
}
}
The first parameters to call this are obvious; the last one however is tricky:
You need to decide to interval you want your labels to have. It will depend on various details of your chart:
the range of values
the size of the chart area
the size of the font of the axis
I didn't set any special Font in the function; CustomLabels use the same Font as normal axis labels, i.e. AxisY.LabelStyle.Font.
For my screenshot I prepared the chart like this:
ca.AxisX.Minimum = 0;
ca.AxisY.MajorTickMark.Interval = 60 * 60; // one tick per hour
addCustomLabels(ca, s, 60 * 30); // one label every 30 minutes
I have also added DataPoint Labels for testing to show the values..:
series.Points[p].Label = hhh_mm_ss((int)y) + "\n" + y;
Here is the result:
UPDATE: This answer may be quite useful for other readers, but it pretty much misses the OP's issues. I'll leave it as it stands, but it will not help in creating specially formatted y-axis labels..
Most Chart problems stem from invalid or useless x-values. The following discussion tries to help avoiding or getting around them..
A number is a number and you can't simply display it as a DateTime, or for that matter a TimeSpan.
So you need to add the X-Values as either DateTime or as double that contain values that can be converted to DateTime. The fomer is what I prefer..
So instead of adding the seconds directly add them as offsets from a given DateTime:
Change something like this
series.Points.AddXY(sec, yValues);
To this:
var dt = new DateTime(0).AddSeconds(sec);
series.Points.AddXY(dt, yValues);
Now you can use the date and time formatting strings as needed..:
chartArea.AxisX.LabelStyle.Format = "{mm:ss}";
You could also add them as doubles that actually are calculated from DateTimes via the ToOADate:
series.Points.AddXY(dt.ToOADate(), yValues);
But now you will have to set the ChartValueType.DateTime and probably also AxisX.IntervalType and AxisX.Interval to make sure the chart gets the formatting right..:
s.XValueType = ChartValueType.DateTime;
ca.AxisX.Interval = 5;
ca.AxisX.IntervalType = DateTimeIntervalType.Seconds;
ca.AxisX.LabelStyle.Format = "{mm:ss}";
Pick values that suit your data!
Note that the problem with your original code is that the X-Values internally always are doubles, but the seconds are not integer values in them but fractional parts; so you need some kind of calculation. That's what ToOADate does. Here is a short test that shows what one second actually does amount to as a OADate double :
Best add the X-Values as DateTimes so all further processing can rely on the type..
Update I just saw that you have finally added the real code to your question and that is uses Points.DataBindY. This will not create meaningful X-Values, I'm afraid. Try to switch to Points.DataBindXY! Of course the X-Values you bind to also need to follow the rules I have explained above..!
You can do a loop over your array and convert the numbers like I shown above; here is a simple example:
int[] seconds = new int[5] { 1, 3, 88, 123, 3333 };
double[] oaSeconds = seconds.Select(x => new DateTime(0).AddSeconds(x).ToOADate())
.ToArray();
If you are trying to show more than 2 digits of hour I think this should work for you
//yourTimeSpan is the TimeSpan that you already have
var hoursDouble = Math.Floor(yourTimeSpan.TotalHours);
string hours;
string minutes;
string seconds;
//check hours
if(hoursDouble < 10)
{
hours = string.Format("0{0}", hoursDouble);
}
else
{
hours = hoursDouble.ToString();
}
//check minutes
if (yourTimeSpan.Minutes < 10)
{
minutes = string.Format("0{0}", yourTimeSpan.Minutes);
}
else
{
minutes = yourTimeSpan.Minutes.ToString();
}
//check seconds
if (yourTimeSpan.Seconds < 10)
{
seconds = string.Format("0{0}", yourTimeSpan.Seconds);
}
else
{
seconds = yourTimeSpan.Seconds.ToString();
}
string formattedSpan = String.Format("{0}:{1}:{2}", hours, minutes, seconds);
Update: I think this should solve the problem you were seeing with single digit numbers
I have a global var that i'm getting the DateTime.Now once i click a button:
dt = DateTime.Now;
Then inside a progresschanged event i'm doing:
var currentTime = DateTime.Now;
Now i need to calculate the difference between the currentTime and the stored time(dt) in seconds. Then i need to Divide BytesSent by the difference in seconds.
This is the progresschanged event i'm using:
double mbSent = 0;
int percentComplete = 0;
static string progress = "";
static string ttt = "";
private void videosInsertRequest_ProgressChanged(IUploadProgress obj)
{
stringProgressReport[1] = obj.Status.ToString();
backgroundWorker1.ReportProgress(0, 1);
mbSent = ((double)obj.BytesSent) / (1 << 20);
stringProgressReport[2] = mbSent.ToString();
backgroundWorker1.ReportProgress(0, 2);
percentComplete = (int)Math.Round(((double)obj.BytesSent) / totalBytes * 100);
stringProgressReport[3] = percentComplete.ToString();
backgroundWorker1.ReportProgress(0, 3);
var currentTime = DateTime.Now;
}
At the bottom of the event i need ot make the calculation and this way i can report the average speed. File upload speed.
EDIT
I'm getting exception when doing:
var currentTime = DateTime.Now;
TimeSpan diff = currentTime - dt;
int diffSeconds = (int)diff.TotalSeconds;
long averageSpeed = obj.BytesSent / diffSeconds;
both obj.BytesSent and diffSeconds are 0 so i get exception cant divide by zero on the averageSpeed.
Why diffSeconds is 0 ? Tried to calculate the time difference between dt and currentTime.
EDIT
This is what i did now:
if (obj.BytesSent != 0)
{
var currentTime = DateTime.Now;
TimeSpan diff = currentTime - dt;
var diffSeconds = (DateTime.Now - dt).TotalSeconds;
long averageSpeed = diffSeconds != 0 ? obj.BytesSent / diffSeconds : 0L;
double MBunits = ConvertBytesToMegabytes(averageSpeed);
stringProgressReport[4] = MBunits.ToString();
backgroundWorker1.ReportProgress(0, 4);
}
But getting error now on the long averageSpeed cant convert type double to long.
And this is the method that i use to convert to MegaBytes:
static double ConvertBytesToMegabytes(long bytes)
{
return (bytes / 1024f) / 1024f;
}
And in the backgroundworker progresschanged event:
label8.Text = stringProgressReport[4];
How can i show the user something nicer ? for example the speed something like:
The average speed is: 0.5 MB/s then 0.9 MB/s then 1.6 MB/s in this format or something else nicer.
You can simply substract two DateTime objects:
TimeSpan diff = currentTime - dt;
int diffSeconds = (int) diff.TotalSeconds;
var elapedSeconds = (DateTime.Now-dt).TotalSeconds;
You need a little bit of defensive programming to prevent the exception:
var diffSeconds = (DateTime.Now - dt).TotalSeconds;
long averageSpeed = diffSeconds !=0 ? obj.BytesSent / diffSeconds : 0L;
how to convert seconds in Minute:Second format
A versatile version is to use TimeSpan like this:
var span = new TimeSpan(0, 0, seconds); //Or TimeSpan.FromSeconds(seconds); (see Jakob C´s answer)
var yourStr = string.Format("{0}:{1:00}",
(int)span.TotalMinutes,
span.Seconds);
int totalSeconds = 222;
int seconds = totalSeconds % 60;
int minutes = totalSeconds / 60;
string time = minutes + ":" + seconds;
Just for completeness I will add an answer using TimeSpan (works as of .NET 4.0):
int seconds = 1045;
var timespan = TimeSpan.FromSeconds(seconds);
Console.WriteLine(timespan.ToString(#"mm\:ss"));
Something like this:
string minSec = string.Format("{0}:{1:00}", seconds / 60, seconds % 60);
Note that that will ensure the seconds are always displayed as two digits, e.g. "2:05" for 125 seconds. The minutes aren't currently treated the same way, but of course they could be.
This doesn't deal well with negative numbers. If your seconds may be negative, you may want something like this:
string minSec = string.Format("{0}:{1:00}", seconds / 60,
(Math.Abs(seconds)) % 60);
Finally, will you always have less than an hour of seconds? It might look odd to see "80:00" when you really mean "1:20:00" for example.
double seconds=125;
TimeSpan.FromSeconds(seconds).ToString()
will give you : 00:02:05. As per my understanding this built-in solution is more extensible, since it can give you hours too, without any plumbing of the logic.
var seconds = 60;
//10,000 ticks in a millisecond
var ticks = seconds*10000*1000;
DateTime dt = new DateTime(ticks);
dt.ToString("mm:ss");
Simple maths. 60 seconds to a minute.
int mins = totalseconds/60;
int secs = totalseconds % 60;
Console.Writeline(string.Format("{0}:{1}", mins, secs));
Not strictly answering the original question, but in case anyone (or myself) in the future comes here from google wanting to format a float with milliseconds as well:
float secs = 23.69;
string m_ss_mmm = string.Format("{0}:{1:00}.{2:000}", (int)secs / 60, (int)(secs % 60), (secs - ((int)secs)) * 1000);
Result:
0:23.690
An alternative way to display the seconds as e.g. "2:05" could be to use PadLeft.
string time = minutes.ToString() + ":" + seconds.ToString().PadLeft(2,'0');
What works for me...
public static string SecondsToMinutes(int seconds)
{
var ts = new TimeSpan(0, 0, seconds);
return new DateTime(ts.Ticks).ToString(seconds >= 3600 ? "hh:mm:ss" : "mm:ss");
}
If you need it as float or double:
float seconds = 339;
float totalTimeInMins = (float) (Math.Floor(seconds / 60) + (seconds % 60) / 100);