Given a set of date/times in Excel (formatted to maintain and display milliseconds)
2020-01-01 12:00:00.500 AM
2020-01-01 12:00:01.500 AM
2020-01-01 12:00:02.500 AM
2020-01-01 12:00:03.500 AM
2020-01-01 12:00:04.500 AM
2020-01-01 12:00:05.500 AM
2020-01-01 12:00:06.500 AM
2020-01-01 12:00:07.500 AM
2020-01-01 12:00:08.500 AM
2020-01-01 12:00:09.500 AM
2020-01-01 12:00:10.500 AM
Excel rounds these when displayed (or collected by a Pivot Table) in this way
2020-01-01 12:00:00 AM Down
2020-01-01 12:00:01 AM Down
2020-01-01 12:00:03 AM Up
2020-01-01 12:00:03 AM Down
2020-01-01 12:00:04 AM Down
2020-01-01 12:00:05 AM Down
2020-01-01 12:00:06 AM Down
2020-01-01 12:00:07 AM Down
2020-01-01 12:00:08 AM Down
2020-01-01 12:00:09 AM Down
2020-01-01 12:00:11 AM Up
How do I replicate this seemingly random midpoint rounding?
Note
I know that Excel stores dates as doubles, and I actually have direct access to the values stored in Excel. I'm also aware of the floating point precision issues that floating point date-time values can have.
What I'm looking for is a concrete set of operations (preferably in C#) that provides parity with what Excel in obviously doing. I've tried a number of ways to calculate and round to the "nearest" second nothing I've tried can replicate the seemingly random half rounding.
I'm pretty sure there's something to do with precision at play as well, since the date value (whole number portion) can affect how the time (fractional) portion rounds to the nearest second.
Update
Regular DateTime rounding isn't going to cut it.
Converting the double date to a DateTime (which works in ticks) will always result in exactly 500ms and therefore a standard rounding will always round up. This is not what Excel is doing (see data above).
Neither is this an issue of simple accuracy or floating-point rounding... #Jeremy Lakeman provided some code to get a more accurate string representation of the values. So I tried it.
Take the following data. Values in brackets are the stringified double values that are stored in the XLSX file XML for the date-time values shown. Left are unrounded, right are rounded by Excel.
2020-01-01 12:00:16.500 AM [43831.00019097222] --> 2020-01-01 12:00:16.000 AM [43831.000185185185]
2020-01-01 12:00:17.500 AM [43831.000202546296] --> 2020-01-01 12:00:17.000 AM [43831.000196759262]
2020-01-01 12:00:18.500 AM [43831.000214120373] --> 2020-01-01 12:00:19.000 AM [43831.000219907408]
2020-01-01 12:00:19.500 AM [43831.000225694443] --> 2020-01-01 12:00:19.000 AM [43831.000219907408]
The DoubleConverter gives the following outputs for the unrounded values multiplied by seconds: DoubleConverter.ToExactString(d * 86400)
2020-01-01 12:00:16.500 AM --> 3786998416.5
2020-01-01 12:00:17.500 AM --> 3786998417.5
2020-01-01 12:00:18.500 AM --> 3786998418.5
2020-01-01 12:00:19.500 AM --> 3786998419.5
Note that only the 3rd value rounds up, while the other 3 round down.
How do I replicate that?
I know I'm on the right track here, because the following (using DoubleConverter);
var epoch = new DateTime(1900, 1, 1);
var start = new DateTime(2020, 1, 1);
var offset = new TimeSpan(0, 0, 0, 0, 500);
var values = Enumerable.Range(0, 100)
.Select(i =>
{
var v = start + offset * (i * 2 + 1);
var seconds1 = (v - epoch).TotalSeconds;
var seconds2 = (v - epoch).TotalDays * 86400;
return $"{v:yyyy-MM-dd HH:mm:ss.f} => {DoubleConverter.ToExactString(seconds2)}, { (seconds2>seconds1 ? "Up" : "Down")}";
})
.ToList();
Produces;
2020-01-01 00:00:00.5 => 3786825600.5, Down
2020-01-01 00:00:01.5 => 3786825601.5, Down
2020-01-01 00:00:02.5 => 3786825602.500000476837158203125, Up
2020-01-01 00:00:03.5 => 3786825603.5, Down
2020-01-01 00:00:04.5 => 3786825604.5, Down
2020-01-01 00:00:05.5 => 3786825605.499999523162841796875, Down
2020-01-01 00:00:06.5 => 3786825606.5, Down
2020-01-01 00:00:07.5 => 3786825607.5, Down
2020-01-01 00:00:08.5 => 3786825608.5, Down
2020-01-01 00:00:09.5 => 3786825609.5, Down
2020-01-01 00:00:10.5 => 3786825610.500000476837158203125, Up
...
Each of the values that are slightly greater than 0.5 are the values that excel is rounding up, the others round down. Note that TimeSpan.TotalSeconds gives a double representing the exact number of milliseconds, while .TotalDays * 86400 appears to have the same accuracy as your example.
So the trick is to work out which results are slightly above .5 and round them up.
You can perform this same test in excel with =MOD(A2*86400,1). To see the same floating point accuracy problem and determine which values excel will round up.
private static readonly DateTime EPOCH = new DateTime(1900, 1, 1);
public DateTime ExcelRound(DateTime value)
{
var m = value.Ticks % TimeSpan.TicksPerSecond;
if (value.Millisecond > 500)
m -= TimeSpan.TicksPerSecond;
else if (value.Millisecond == 500)
{
var seconds1 = (value - EPOCH).TotalSeconds;
var seconds2 = (value - EPOCH).TotalDays * 86400;
if (seconds2 > seconds1)
m -= TimeSpan.TicksPerSecond;
}
return value.Add(new TimeSpan(-m));
}
However, now that you know what excel is doing. I would personally take that evidence and complain about your requirement. Just because excel has a bug, doesn't mean you should be forced to replicated it.
Related
I have a given time and date and I want to create a counter in my game that is counting down to this given date. I want to display the counter in this format: Days/Hours/Minutes/Seconds
How can I convert the DateTime {2/3/2020 12:00:00 AM} to something like this: 0 Days / 9 Hours / 30 Minutes / 20 Seconds ?
The counter should run until it has reached the given time {2/3/2020 12:00:00 AM} (0 Days / 0 Hours / 0 Minutes / 0 Seconds).
I get the following date and time from the server but I don't know how to make a counter out of it.
How can I create a counter that counts down to a given time and date?
var NextLeaderboardReset = resultleaderboard.Result.NextReset;
A DateTime minus a DateTime will give you a TimeSpan object, which is what you want here. Then you just need to get the correct string format for your countdown:
var countDownEnd = new DateTime(2020,2,3);
var timeSpan = DateTime.Now - countDownEnd;
var countDownString = $"Time left: {timeSpan.ToString(#"dd\:h\:m\:s")}";
Say I have an year, 2017.
I then have a date range, 01/07/2017 - 01-07-2018 OR 01/07/2017 - 01-01-2017 OR 01/01/2016 - 01/01/2018 ( <- this should return 365 days)
I now need to calculate how many total days are there in the given range for the given year.
Note that dates are stored as dd/mm/yyyy with an always 00:00:00 time.
What would the best logic be considering all possible cases of ranges?
You can compute the start and end dates for a year easily:
var start2017 = new DateTime(2017,1,1);
var end2017 = new DateTime(2017,12,31);
And then you can compute the overlap between this new range and your other range1:
var startOverlap = start2017 < startOtherRange ? startOtherRange : start2017;
var endOverlap = end2017 > endOtherRange ? endOtherRange : end2017;
var totalDays = (endOverlap - startOverlap).TotalDays + 1;
The above is correct if ranges are meant to include both their start and end dates. If you want, say, an exclusive endpoint then we'd adjust the end of out 2017 computed range one day further forwards and would no longer require the +1 adjustment at the end)
(And I presume you can derive from there how to turn it into a function if required that takes year, startRange, endRange parameters and does the above with some appropriate renaming)
1I had some vague recollection of DateTime.Min(value1, value2) and similarly for Max but it's definitely not in the BCL that I can see. Those would replace the conditional operators on the following lines. Once C# has "extension everything" these functions could be written as static extensions to DateTime.
My task is to get quantity of Days when I subtract the Current Date from a fixed date.
<script language="C#" runat="server">
System.DateTime thisDay = DateTime.Today;
System.DateTime dateCountFrom = new DateTime(2016, 6, 1, 0, 00, 00);
</script>
So I have 2 dates and everything is working perfectly. But how can I subtract one from the other?
(dateCountFrom-thisDay).totaldayscount;
causes an error, tried other ways - same result.
I succeeded only by doing this:
<%= ((DateTime.Now-(new DateTime(2016,6,1))) *-1).TotalDays %>
But I need to eval result and multiply by -1.
Any ideas how I can do this?
You should use the DateTime.Subtract method, this will return a TimeSpan variable containing the difference in time between the dates
TimeSpan diff = dateCountFrom.Subtract(DateTime.Now);
diff = TimeSpan.FromTicks(diff.Ticks * -1);
Instead of this though, if you want it multiplied by -1, you can just do the opposite sum
TimeSpan diff = DateTime.Now.Subtract(dateCountFrom);
(thisDay - dateCountFrom).TotalDays and (dateCountFrom-thisDay).TotalDays are both perfectly legal and won't result in an error. The only difference is that one result will be negative and the other positive.
If you only need the number of days between the two dates, regardless of which one is later, you can use
int days = Math.Abs((thisDay - dateCountFrom).TotalDays)
If you know, one of the dates will always be later then the other one, (for instance dateCountFrom will always be in the future) use this as minuend (ie the date, which is subtracted from) and the other one as subtrahend (ie the date, that is substracted). Then the number of days will also be always positive.
All i have to do that just find the overtime of employee. The attendance sheet comes in CSV file and I've already saved the data to a table. The data Field hours is 12:10:00.0000000, and The Working Hour per day is 11:00:00.0000000. I've calculated the difference between these two times using Timespan.
DateTime date, hours, working_hours ;
TimeSpan ot_hours = TimeSpan.Zero;
TimeSpan tot_hours = TimeSpan.Zero;
if (hours > working_hours)
{
ot_hours = (hours - working_hours);
}
This code has set in a loop. after completing the loop I need to stote the total overtime into another variable. so wrote
tot_hours += tot_hours + ot_hours;
And next I need to find the salary overtime amount for the employee.
I've tried to convert this tot_hours(TimeSpan ) into Decimal. But it didn't work.
tothrsvalue = Convert.ToDecimal(tot_hours);
totalvalue = rate * tothrsvalue;
Anyone here.. Please have a look and help me.. Thanks in advance.
the total calculation part is given below:
decimal basics = Convert.ToDecimal(dtamt.Rows[0]["Amount"].ToString());
decimal rate = 0;
rate = ((basics / 30) / 8);
txtrate.Text = rate.ToString("0.000");
tothrsvalue = Convert.ToDecimal(total);
totalvalue = rate * tothrsvalue;
txtamount.Text = totalvalue.ToString("0.000");
amt = Convert.ToDecimal(txtamount.Text);
Parsing TimeSpan to decimal does not too much sense because time is not a numeric value at all.
But you can use it's TotalHours property which returns double.
var total = tot_hours.TotalHours;
Y need to to convert a TimeSpan to a decimal ? Its cannot be done becuase it isn't numeric.
You probably want tot_hours.TotalHours, which is a double that includes the fractional portion.
or tot_hours.TotalHours.ToString("#.00");
ot_hours is now a Timestamps difference = Seconds ! Divide them by 60 to make minutes
example : total = 2 minutes = 120 seconds etc.)
I'm using the following code:
sqlcom.CommandText = "SELECT * FROM myTable"
+ " WHERE CAST(myTime AS DATE) >= CAST(#mySTime AS DATE)"
+ " AND CAST(myTime AS DATE) <= CAST(#myETime AS DATE)"
+ "order by myTime ";
sqlcom.Parameters.AddWithValue("#mySTime", stime);
sqlcom.Parameters.AddWithValue("#myETime", etime);
stime and etime are both DateTime columns. The following is an abbreviation of the code that sets them:
sTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0).AddDays(-1);
eTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0).AddDays(-1).AddDays(1).AddMilliseconds(-1);
Which for example leads to:
sTime = '2015-10-19 00:00:00';
eTime = '2015-10-19 23:59:59';
when displayed in the debugger (stime and etime have a few other options how they could be set that is why the sql is dynamically taking them in but in this current case the above holds true).
Now when I run the above SQL I get everything even from the current day!
BUT when I change AS DATE to AS DATETIME it works as intended that I just get the LAST day and nothing from today.
Now my question is: Is there any reason why the original sql/date comparison fails? (could it be because of that it is just the millisecond -1 that it rounds it up to the next day? OR is there any other reason there?)
eTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0).AddDays(-1).AddDays(1).AddMilliseconds(-1);
Why are you using .AddDays(-1).AddDays(1). It seems useless.
.AddMilliseconds(-1)
Your data type is datetime. datetime has an accuracy of 3ms with increments of .000, 003 or .007 seconds.
Therefore, any of these 3 values minus 1 (ms) is always rounded back to the original value:
xxx.000 - .001 = .999 => rounded to .000
xxx.003 - .001 = .002 => rounded to .003
xxx.007 - .001 = .006 => rounded to .007
This seems useless as well.
Round up
'2015-10-19 23:59:59' won't be rounded but '2015-10-19 23:59:59.999' will be round up to '2015-10-20 00:00:00.000' because 999 is surrounded by 997 and 000. 000 is the closest value.
<= 18-10-2015 23:59:59
You will miss any time above 23:59:59.000 and below of equal to 23:59:59.997
CAST(myTime AS DATE)
This will most likely prevent the usage of index on myTime. It should not be used.
It is fine to stick to datetime although datetime2 would be a better choice. If you are looking for value on a specific day, you must look for value between DAY at 00:00:00 and the next day at 00:00:00.
You can find a lot of useful information about date comparison on most of the answer here, including my own anwser: Why does my query search datetime not match?