Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

A few C# Time Utilities for Fractional Time Values


:P
On this page:

I found myself working with displaying time values a bit today for a demo app I'm building, and figured I share a few useful functions that I ended up creating. Basically, I have a time tracking sample and I need to display time values in a variety of ways as well as deal with dates that are rounded to a specific minute interval. This isn't exactly rocket science although I have to admit I stumbled a bit on the time value parsing routine.

Here are the routines:

public class TimeUtilities
{
 
    /// <summary>
    /// Converts a fractional hour value like 1.25 to 1:15  hours:minutes format
    /// </summary>
    /// <param name="hours">Decimal hour value</param>
    /// <param name="format">An optional format string where {0} is hours and {1} is minutes.</param>
    /// <returns></returns>
    public static string FractionalHoursToString(decimal hours, string format)
    {
        if (string.IsNullOrEmpty(format))
            format = "{0}:{1}";
 
        TimeSpan tspan = TimeSpan.FromHours( (double) hours);            
        return string.Format(format, tspan.Hours, tspan.Minutes);
    }
    /// <summary>
    /// Converts a fractional hour value like 1.25 to 1:15  hours:minutes format
    /// </summary>
    /// <param name="hours">Decimal hour value</param>
    public static string FractionalHoursToString(decimal hours)
    {
        return FractionalHoursToString(hours, null);
    }
     /// <summary>
    /// Displays a long date in friendly notation
    /// </summary>
    /// <param name="Date"></param>
    /// <param name="ShowTime"></param>
    /// <returns></returns>
    public static string FriendlyDateString(DateTime Date, bool ShowTime)
    {

        string FormattedDate = "";
        if (Date.Date == DateTime.Today)
            FormattedDate = "Today"; //Resources.Resources.Today; 
        else if (Date.Date == DateTime.Today.AddDays(-1))
            FormattedDate = "Yesterday"; //Resources.Resources.Yesterday;
        else if (Date.Date > DateTime.Today.AddDays(-6))
            // *** Show the Day of the week
            FormattedDate = Date.ToString("dddd").ToString();
        else
            FormattedDate = Date.ToString("MMMM dd, yyyy");

        if (ShowTime)
            FormattedDate += " @ " + Date.ToString("t").ToLower();

        return FormattedDate;
    }
 
    /// <summary>
    /// Rounds an hours value to a minute interval
    /// 0 means no rounding
    /// </summary>
    /// <param name="minuteInterval">Minutes to round up or down to</param>
    /// <returns></returns>
    public static decimal RoundDateToMinuteInterval(decimal hours, int minuteInterval,
                                                    RoundingDirection direction)
    {
        if (minuteInterval == 0)
            return hours;
 
        decimal fraction = 60 / minuteInterval;
 
        switch (direction)
        {
            case RoundingDirection.Round:
                return Math.Round(hours * fraction, 0) / fraction;        
            case RoundingDirection.RoundDown:
                return Math.Truncate(hours * fraction) / fraction;        
 
        }
        return Math.Ceiling(hours * fraction) / fraction;        
    }
 
    /// <summary>
    /// Rounds a date value to a given minute interval
    /// </summary>
    /// <param name="time">Original time value</param>
    /// <param name="minuteInterval">Number of minutes to round up or down to</param>
    /// <returns></returns>
    public static DateTime RoundDateToMinuteInterval(DateTime time, int minuteInterval,
                                                     RoundingDirection direction)
    {
        if (minuteInterval == 0)
            return time;
 
        decimal interval = (decimal) minuteInterval;
        decimal actMinute = (decimal) time.Minute;
 
        if (actMinute == 0.00M)
            return time;
 
        int newMinutes = 0;
 
        switch(direction)
        {
            case RoundingDirection.Round:
                newMinutes = (int) ( Math.Round(actMinute / interval,0) * interval);             
                break;
            case RoundingDirection.RoundDown:
                newMinutes = (int)(Math.Truncate(actMinute / interval) * interval);      
                break;
            case RoundingDirection.RoundUp:
                newMinutes = (int)(Math.Ceiling(actMinute / interval) * interval);
                break;
        }
 
        // *** strip time 
        time = time.AddMinutes(time.Minute * -1);
        time = time.AddSeconds(time.Second * -1);
        time = time.AddMilliseconds(time.Millisecond * -1);
 
        // *** add new minutes back on            
        return time.AddMinutes(newMinutes);
    }
 
}
 
public enum RoundingDirection
{
    RoundUp,
    RoundDown,
    Round
}

The first method FractionalHoursToString is kind of useful in displaying time values, since rarely do you want to look at time values as fractional hours. My time entry took 1.25 hours doesn't quite roll of the tounge  like 1 hour and 25 minutes or - more commonly - 1:15 or 1h 15min. The ideas is that you can provide a format string to format the value appropriately. For example:

TimeTrakkerPunchout1

the value. The hour value is updated as users tab out of any of the date fields and the values are recalculated via AJAX callbacks on the server. The time fields in yellow are also updated and rounded up in this case to 5 minute intervals. The server side callback that handles this gives you an idea of how these two functions are used:

/// <summary>
/// Ajax Callback method from the page that handles updating 
/// the calculation of time totals.
/// </summary>
/// <param name="Pk"></param>
/// <returns></returns>
[CallbackMethod]
public object UpdateTotals(string Pk)
{
    int pk = 0;
    int.TryParse(Pk, out pk);
 
    // *** Load Entity into Entry.Entity
    this.Entry.Load(pk);
 
    // *** Use form controls/formvars to read
    // *** this information and update same
    // *** as a regular postback page
    this.UpdateTotalsOnEntry();
 
    this.Entry.Entity.TimeIn = 
        TimeUtilities.RoundDateToMinuteInterval(this.Entry.Entity.TimeIn, 
                                            App.Configuration.MinimumMinuteInterval, 
                                            RoundingDirection.RoundUp);
    this.Entry.Entity.TimeOut =
        TimeUtilities.RoundDateToMinuteInterval(this.Entry.Entity.TimeOut,
                                            App.Configuration.MinimumMinuteInterval,
                                            RoundingDirection.RoundUp);
    this.Entry.CalculateItemTotals();
 
    // *** Return a projected result object
    return new { TotalHours = TimeUtilities.FractionalHoursToString( Entry.Entity.TotalHours,"{0}h {1}min"), 
                 ItemTotal = (decimal) Entry.Entity.ItemTotal,
                 TimeIn = Entry.Entity.TimeIn.ToString("t"),
                 TimeOut = Entry.Entity.TimeOut.ToString("t") };
} 

 

The second pair of functions rounds a number of hours or a date time value to a specific minute interval that you specify providing options to round up or down or well - rounding round. Rarely do you want to bill in minute or second increments - most likely you'll use 5 or 15 minute intervals. The routines above perform the rounding either for a fractional hour number value or an actual DateTime value. So in the above the time values can optionally be adjusted with the rounding functionality.

It's possible that there are easier ways to do this, but this is as good as I could figure out with what's built-in. Hopefully some of you will find this useful - surely not today but next time you search for time formatting <g>...

Posted in .NET  CSharp  

The Voices of Reason


 

Einar G.
September 25, 2007

# re: A few C# Time Utilities for Fractional Time Values


I hope you aint forgetting this one since you use IsNullOrEmpty

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=113102

since you use it or you are using .net 3,5 :)

Rick Strahl
September 25, 2007

# re: A few C# Time Utilities for Fractional Time Values

Interesting - I use string.IsNullOrEmpty() everywhere and never have run into this particular problem. It seems to me from the problem report the problem's not really particular to string.IsNull but any inlineable method call in optimizable loops. Good to know though...

In this case the code is running .NET 3.5 though...

David Acres
April 11, 2008

# re: A few C# Time Utilities for Fractional Time Values

Hi,

Its friday at 14:51 and the brain has begun to go backwards, this code was exactly what I was looking for! cheers mate, saved me thinking for another day.

Cheers,
David.

James
September 04, 2008

# re: A few C# Time Utilities for Fractional Time Values

Handy, but the FractionalHoursToString falls down if the value it's fed is longer than a day.

In my case, I am feeding it 204.8 hours - the timespan structure then contains days, hours, minutes, seconds, but the hours value is only the number of hours on top of the days of course. The totalHours property still contains a fractional figure then though.

Cheers!

Kevin Le
March 11, 2010

# re: A few C# Time Utilities for Fractional Time Values

@Rick and @James: FractionalHoursToString can be easily fixed:

return string.Format(format, hours > 24 ? tspan.Days*24 + tspan.Hours : tspan.Hours, ...);

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024