8

I have the following statemnt.

  timespan = timespan.FromSeconds(236541)
  formattedTimeSpan = String.Format("{0} hr {1} mm {2} sec", Math.Truncate(timespan.TotalHours), timespan.Minutes, timespan.Seconds)

I have to have it formatted as "hrs mn sec" if there are more than one hour. I want to check this within the String.Format above.

Thanks.

1
  • 3
    Just be lazy and write "{0} hr(s)...". Commented Aug 18, 2010 at 5:12

7 Answers 7

9

Probably the cleanest way to do this is to write your own ICustomFormatter. Here's an example of a convenient pluralizer format:

using System;

public class PluralFormatter : IFormatProvider, ICustomFormatter {

   public object GetFormat(Type formatType) {
      if (formatType == typeof(ICustomFormatter))
         return this;
      else
         return null;
   }

   public string Format(string format, object arg, 
                          IFormatProvider formatProvider)
   {   
      if (! formatProvider.Equals(this)) return null;

      if (! format.StartsWith("^")) return null;

      String[] parts = format.Split(new char[] {'^'});
      int choice = ((int) arg) == 1 ? 1 : 2;
      return String.Format("{0} {1}", arg, parts[choice]);
   }

   public static void Main() {
      Console.WriteLine(String.Format(
         new PluralFormatter(),
         "{0:^puppy^puppies}, {1:^child^children}, and {2:^kitten^kittens}", 
         13, 1, 42
      ));
   }
}

The output is, as one might've guessed (and as seen on ideone.com):

13 puppies, 1 child, and 42 kittens

MSDN links

Sign up to request clarification or add additional context in comments.

Comments

7

One possibility is to make the plural part of the format string, and write:

formattedTimeSpan = String.Format("{0} hr{1} {2} mm {3} sec",
    Math.Truncate(timespan.TotalHours),
    Math.Truncate(timespan.TotalHours) == 1 ? "" : "s",
    timespan.Minutes,
    timespan.Seconds);

This will insert a "s" into the output if the output says anything other than "1 hr".

Note that this is not friendly to localization: other languages form plurals differently than English.

1 Comment

I take this as the answer. For cleaner code, I think we should take suggestion from Kobi's answer.
3

No magic here. Why would you code in whatever condition format may offer, when you can code in C#?

To avoid duplicated code, you might as promote some expressions to variables, and use the condition on them:

timespan = timespan.FromSeconds(236541);
int hours = Math.Truncate(timespan.TotalHours);
string hoursUnit = hours == 1 ? "hr" : "hrs";
formattedTimeSpan = String.Format("{0}{1} {2} mm {3} sec",
                        hours, hoursUnit, timespan.Minutes, timespan.Seconds);

3 Comments

Thanks for reply. I know this method. My question is that "Can I do it within String.Format?".
I think you need to include "hours" in the String.Format as well.
@Angkor Wat - Fixed, Thanks! Must have missed that one when I added the new line :P
3

I use a few extension methods which help me with this sort of thing:

public static string Inflect(this string noun, int count)
{
 return (count == 1 || noun.EndsWith("s", StringComparison.InvariantCultureIgnoreCase)) ? noun : noun + "s";
}

public static string Of(this int count, string noun)
{
 return string.Format("{0} {1}", count, noun.Inflect(count));
}

e.g.:

"Hour".Inflect(1) : Hour

"Hour".Inflect(2) : Hours

0.Of("hour") : "0 hours"

1.Of("hour") : "1 hour"

5.Of("hour") : "5 hours"

2 Comments

I actually have a similar method in use. In fact, I wrote a whole control for that. In Hebrew, it is common to have different nouns for 1 2 or more (think "once, twice, 3 times"). Even for English, it would be more useful with two words: (2).Of("Memory", "Memories") - you don't want to rewrite the English Grammar in your code...
+1 @Kobi: Yes, that would be really useful. I normally use this method for just the sort of issue the OP is facing, when I know the noun in question, and that it inflects 'normally'.
2

You should use the various time format specifiers. It helps simplify the call to Format() immensely.

var timespan = TimeSpan.FromSeconds(236541);
var formattedTimeSpan = String.Format("{0:hh} hr{1} {0:mm} mm {0:ss} sec",
    timespan, timespan.Hours != 1 ? "s" : "");

Comments

0

There is no "simple" way of doing this.

You can either do it like this

string templateSingleHour = "{0} hr {1} mm {2} sec";
string templateMultiHour = "{0} hrs {1} mm {2} sec";
string template = Math.Truncate(timespan.TotalHours) > 1 ? templateMultiHour :templateSingleHour;
formattedTimeSpan = String.Format(template ...)

or like this

formattedTimeSpan = String.Format("{0} {3} {1} mm {2} sec", Math.Truncate(timespan.TotalHours), timespan.Minutes, timespan.Seconds, Math.Truncate(timespan.TotalHours) > 1 ? "hrs" : "hr");

1 Comment

Why write Math.Truncate(timespan.TotalHours) > 1 when you can use timespan.TotalHours >= 2?
0

FWIW, if your app is going to be used my non-english speakers ever then this is something that's not trivial. There is a blog post that expounds on the problem at http://www.siao2.com/2007/07/24/4022881.aspx

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.