views:

193

answers:

5

I have a tool with a configurable delay (Timespan), and I want to set the text of a label depending on the value. Here is my code as it stands:

    StringBuilder time = new StringBuilder();
    if (Settings.Default.WaitPeriod.Hours > 0)
    {
        time.AppendFormat("{0} hour(s)", Settings.Default.WaitPeriod.Hours);
    }

    if (Settings.Default.WaitPeriod.Minutes > 0)
    {
        time.AppendFormat("{0}{1} minute(s)", time.Length > 0 ? " and " : "", Settings.Default.WaitPeriod.Minutes);
    }

    if (Settings.Default.WaitPeriod.Seconds > 0)
    {
        time.AppendFormat("{0}{1} second(s)", time.Length > 0 ? " and " : "", Settings.Default.WaitPeriod.Seconds);
    }

    this.questionLabel.Text = String.Format("What have you been doing for the past {0}?", time);

I find the "(s)" particulary irritating - but don't want a million if/ternary if statements.

So, ignoring the non-localized strings, can you think of a nicer way to do this?

+7  A: 
private void AddValue(StringBuilder time, int units, string unitLabel)
{
    if (units > 0)
    {
        if (0 < time.Length)
        {
            time.Append(" and ");
        }
        time.AppendFormat("{0} {1}{2}", units, unitLabel, (1 == units ? String.Empty : "s"));
    }
}

private void YourExistingMethod()
{
    StringBuilder time = new StringBuilder();
    AddValue(time, Settings.Default.WaitPeriod.Hours, "hour");
    AddValue(time, Settings.Default.WaitPeriod.Minutes, "minute");
    AddValue(time, Settings.Default.WaitPeriod.Seconds, "second");
    this.questionLabel.Text = String.Format("What have you been doing for the past {0}?", time);
}
scwagner
Good answer, but "if(0 < time.Length)" and "(1 == units)" yuck....
BFree
Yeah, old holdouts from my C days of putting numbers on the left side of comparisons to prevent accidental assignments.
scwagner
@scwagner but you aren't consistent (units > 0)
llamaoo7
@llamaoo7 Yes, that's because when I did the first "if" I wrote in the "acceptable" format being conscious of it, and then went back into my "normal" style later.
scwagner
A: 
        StringBuilder time = new StringBuilder();
        TimeSpan useme = Settings.Default.WaitPeriod;
        int[] j = new int[] { useme.Hours, useme.Minutes, useme.Seconds };
        string[] single = new string[] { "hour", "minute", "second" };
        string[] multiple = new string[] { "hours", "minutes", "seconds" };

        for (int i = 0; i < j.Length; i++)
        {
            if (j[i] > 0) time.AppendFormat("{0}{1} {2}", time.Length > 0 ? " and " : "", j[i], j[i] > 1 ? multiple[i] : single[i]);
        }

        string output = String.Format("What have you been doing for the past {0} {1}?", time);
Stormenet
+2  A: 

Here's a solution that addresses Brian Schroth's comment. Sample outputs:

2 hours and 1 second
1 hour, 4 minutes, and 50 seconds
1 minute

The code:

public static string FriendlyTime(int hours, int minutes, int seconds) { 
 List<string> terms = new List<string>();
 AddValue(terms, hours, "hour", "hours");
 AddValue(terms, minutes, "minute", "minutes");
 AddValue(terms, seconds, "second", "seconds");  
 return FriendlyJoin(terms);
}

public static void AddValue(List<string> list, int count, string single, string plural) {
 if (count != 0) {
  list.Add(String.Format("{0} {1}", count, count == 1 ? single : plural));
 }
}

public static string FriendlyJoin(List<string> list) {
 if (list.Count == 0) {
  return "";
 } else if (list.Count == 1) {
  return list[0];
 } else if (list.Count == 2) {
  return String.Format("{0} and {1}", list[0], list[1]);
 } else {  
  StringBuilder sb = new StringBuilder();  
  for (int i=0; i<list.Count-1; i++) {
   sb.AppendFormat("{0}, ", list[i]);
  }
  sb.AppendFormat("and {0}", list[list.Count-1]);
  return sb.ToString();
 }
}
Amnon
Thank you for answering, but this seems more complex than my original code!
Philip Wallace
A: 

If I didn't care about readability I'd do something like the following for efficiency's sake:

        time = new StringBuilder();
        if (duration.Hours > 0) 
            time.Append(duration.Hours).Append(" hour").Append(duration.Hours > 1 ? "s" : String.Empty)
                .Append(duration.Minutes > 0 || duration.Seconds > 0 ? (duration.Minutes > 0 && duration.Seconds > 0 ? ", " : " and ") : String.Empty);
        if (duration.Minutes > 0) 
            time.Append(duration.Minutes).Append(" minute").Append(duration.Minutes > 1 ? "s" : String.Empty)
                .Append(duration.Seconds > 0 ? " and " : String.Empty);
        if (duration.Seconds > 0) 
            time.Append(duration.Seconds).Append(" second").Append(duration.Seconds > 1 ? "s" : String.Empty);

... or if you really wanted to confuse people, go with the one-liner approach:

string message = String.Format("{0}{1}{2}{3}{4}", duration.Hours <= 0 ? String.Empty : String.Format("{0} hour{1}", duration.Hours, duration.Hours > 1 ? "s" : String.Empty), duration.Hours > 0 && (duration.Minutes > 0 || duration.Seconds > 0) ? (duration.Seconds > 0 ? ", " : " and ") : String.Empty, duration.Minutes <= 0 ? String.Empty : String.Format("{0} minute{1}", duration.Minutes, duration.Minutes > 1 ? "s" : String.Empty), duration.Minutes > 0 && duration.Seconds > 0 ? " and " : String.Empty, duration.Seconds <= 0 ? String.Empty : String.Format("{0} second{1}", duration.Seconds, duration.Seconds > 1 ? "s" : String.Empty));

LOL, I have to admit scwagner's answer would be easier to maintain.

csharptest.net
A: 

To convert an int number of minutes to the nearest quarter hour:

int quarters = (minutes + 7) / 15;

So, your method would probably look something like:

private static string Hours(Timespan ts)
{
    int quarters = (ts.Minutes + 7) / 15;
    if (ts.Hours == 0)
    {
       return quarters == 0 ? "" : String.Format("{0}/4 hour", quarters);
    }
    if (quarters == 0)
    {
       return String.Format("{0} hour", ts.Hours) + (ts.Hours != 1 ? "s" : "");
    }
    return String.Format("{0} {1}/4  hours", ts.Hours, quarters);
}
Robert Rossney