Question

I am trying to convert a time represented in double, something like 23.40 which means 23 hours and 40 minutes, using the following method:

private TimeSpan DoubleToTimeSpan(double time)
{
    double hour = Math.Floor(time);
    double minute = (time - hour) * 100d;
    TimeSpan ts = new TimeSpan((int)hour, (int)minute, 0);
    return ts;
}

When testing it on some times, like 23.40 for example:

Console.WriteLine(DoubleToTimeSpan(23.40));

It shows 23:39:00, a whole minute has been stolen by the system! Where is my minute?

Note: I know about TimeSpan.FromHours, this doesn't help me because this method considers the minutes as percentage, so 23.40 is 23 hours and 40% of an hour, which is 23:24:00.

Was it helpful?

Solution

Since 0.4 cannot be exactly represented in the Double floating-point format, you get its nearest representation, which in the case of (23.4-23)*100 is probably something like 39.999999999999858. When you use (int), you truncate the fraction, leaving you with 39.

You need to round instead of truncate, so use (int)Math.Round(minute).

Alternatively, you can use the Decimal type, which can exactly represent decimal numbers like 23.40.

OTHER TIPS

Try rounding it instead of int

private TimeSpan DoubleToTimeSpan(double time)
{
    double hour = Math.Floor(time);
    hour.Dump();
    double minute = (time - hour) * 100d;
    minute.Dump();
    TimeSpan ts = new TimeSpan((int)hour, (int)Math.Round(minute), 0);
    return ts;
}

If you do just (int)minute it will take the integer part of your double.

When you cast from a double to an int, it's truncating your value for minute -- thus your lost minute.

(time - hour) * 100d

Will evaluate to 39.999999999999858, and thus 39 when casting to int.

You've used Math.Floor for the hour, so use Math.Ceiling for the minute. It seems to work ok.

private static TimeSpan DoubleToTimeSpan(double time)
{
    return new TimeSpan((int)Math.Floor(time), (int)Math.Ceiling((time - Math.Floor(time)) * 100d), 0);
}

Another option to use decimals. This will prevent precision loss.

decimal minute = ((decimal)time)-((decimal)hour)*100;

Not sure if it's a better solution, but if you're on .NET 4.0 or later, you can also go through a string. It seems like "23.40" is a sequence of characters with one interpretation as a double, and another one as a TimeSpan.

If you like that idea, use:

TimeSpan DoubleToTimeSpan(double time)
{
    string str = time.ToString("F2", CultureInfo.InvariantCulture);
    TimeSpan ts = TimeSpan.ParseExact(str, "h'.'mm", CultureInfo.InvariantCulture);
    return ts;
}

Maybe not as fast as the multiply-by-100-and-round approach, though I haven't tested that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top