Formatting Types

Summary

Formatting Overview

First, some key definitions:

Format Specifiers

Format specifiers or format strings take the general form of Axx where A is the actual format specifier and xx is a non-negative integer called the precision specifier. The format specifier A controls the type of formatting applied to the value being represented as a string, whereas the precision specifier xx controls the number of significant digits or decimal places. For example:

Format Value Output Notes
D4 123 0123 D is the format specifier and 4 is the precision specifier.
F2 12345.6789 12345.68 F is the format specifier and 2 is the precision specifier.

Format specifiers are used with:    

.NET Framework provides three types of format specifiers:

Format Providers

Any class that implements IFormatProvider is referred to as a format provider. The following .NET Framework objects implement IFormatProvider and are used to provide formatting information to the .NET Framework:

Formatting Interfaces

There are two interfaces used for formatting - IFormattable, and IFormatProvider. These are discussed below:

IFormattable Interface

IFormattable is an interface implemented by all .NET base types (Int32, Int64, etc.) to format the value of that base type to a string.

When formatting a number to be represented as a currency string, the actual currency symbol will be produced in the output string as well. For example:

Format Value Output Notes
C 123.678 $123.68 culture is en-US

The currency symbol produced in the output string varies by culture. In general, when format outputs include symbols that vary by culture, a formatting object will supply the actual characters used in the string representation. This formatting object can either be supplied as an IFormatProvider object passes as a method parameter, or the method might use the default formatting object.

A class need to implement IFormattable interface if it needs more control over the formatting of strings than Object.ToString methods. In this case, the class implements its IFormattable.ToString method and uses the current thread's CurrentCulture property to produce a string version of the object.

IFormatProvider Interface

Because the meaning of a format specifier (format string) varies by culture, it is the formatting object that supplied the actual characters used in that specific culture

A class implements IFormatProvider.GetFormat method to obtain an object that provides format information for the implementing type. For example, NumberFormatInfo implements IFormatProvider.GetFormat to provide culture-specific information used to format number in base types

Examples

Base types

In general, to format a base type, you use its ToString method passing the appropriate parameters.  You have the following options for passing parameters:

Case Format Specifier Provided Format Provider Provided Notes
1 No No The default format specifier 'G' is automatically used. The default format provider associated with the current thread is also used. This default format provider gets its settings (for example currency symbol and so) from system settings.
2 Yes No The default format provider associated with the current thread is used..
3 No Yes The default format specifier 'G' is used.
4 Yes Yes No defaults are used.

The following example illustrates the fours cases above:

private void btnFormatting_Click(object sender, System.EventArgs e)
{
    double dAmount = 134.56;

    // Case 1: Passing nothing (default format specifier and default format provider will be used)
    // Default format specifier is 'G' and default format provider is that which is provided
    // by the current thread
    string strAmount1 = dAmount.ToString();
    Trace.WriteLine( strAmount1 );

    // Case 2: Passing a specific format specifier. The default format provider (that of the current thread)
    // will be used
    string strAmount2 = dAmount.ToString( "c" );
    Trace.WriteLine( strAmount2 );

    // Case 3: Passing a specific format provider (note how I change the currency symbol). The default format
    // specifier will be used which is 'G'. The specific format provider has no effect because 'G'
    // format specifier will be used
    System.Globalization.NumberFormatInfo nfi = new System.Globalization.NumberFormatInfo();
    nfi.CurrencySymbol = "$";
    string strAmount3= dAmount.ToString(nfi);
    Trace.WriteLine( strAmount3 );

    // Case 4: Passing a specific format specifier and a specific format provider
    string strAmount4= dAmount.ToString("c", nfi);
    Trace.WriteLine( strAmount4 );
}

Output
134.56
£134.56
134.56
$134.56

Composite Formatting

Composite formatting allows you to use format specifier to format various kinds of text - especially text written with Console.WriteX methods. With composite formatting, the format specifier takes the following form: {n,a:A} where,

Processing of formatting proceeds as follows:

For example,

// Example 1
int    n1 = 100;
double d1 = 123.45; 
Console.WriteLine( "n1 = {0:c} and d1 = {1:F}", n1, d1 );                // Output: "n1 = £100.00 and d1 = 123.45"

// Example 2
Console.WriteLine( "n1 = *{0,-10:c}* and d1 = *{1,10:F}*", n1, d1 );      // Output: "n1 = *£100.00 * and d1 = * 123.45*"

// Example 3
string str1 = string.Format( "{0:ddd/MM/yyyy}", DateTime.Now );           // Output: "Thu/08/2004"
string str2 = DateTime.Now.ToString( "ddd/MM/yyyy" );                     // Output: "Thu/08/2004"

// Example 4. Note how the parameter specifier {1} is used multiple times to display the same argument differently
string strName = "John";
string str3 = string.Format( "Client: {0} - Hours: {1:hh}, Minutes: {1:mm}",
                             strName, DateTime.Now );                     // Output: "Client: John - Hours: 08, Minutes: 53"

By the way, Console.WriteLine and string.Format do the same exact thing except that Console.WriteLine writes the result to the stream associated with the Console object, whereas string.Format writes the result to the returned string.

Formatting for Different Cultures

Recall that format providers provide formatting information to the .NET Framework such

 as characters to use for the decimal point, spelling and placement of currency symbols, thousands separator, and so on. The ToString method of all base types has an overload that takes a format provider to provide formatting information. However, if no format provider was given, .NET will use the format provider already associated with the current thread.

.NET already supplies a couple of format providers. These include CultureInfo, DateTimeFormatInfo, and NumberFormatInfo. For example, CultureInfo is a format provider that provides formatting information about a specific culture, whereas DateTimeFormatInfo is another format provider that provides formatting information on how DateTime values should be formatted and displayed. For example:

// Use the current culture
string strDate1 = DateTime.Now.ToString( "m");

// Use CultureInfo format provider to use a different culture
System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo( "ar-JO" );
string strDate2 = DateTime.Now.ToString( "m", ci );

// Use DataTimeFormatInfo format provider to provide a differnt format for time
System.Globalization.DateTimeFormatInfo dfi = new System.Globalization.DateTimeFormatInfo();
dfi.MonthDayPattern = "MMMM 'the' dddd";
string strDate3 = DateTime.Now.ToString( "m", dfi );

Output:

"13 August"
"13 آب"
"August the Friday"

Numeric Format Strings

Numeric format strings are used to control formatting produced by numeric data types or other methods that use formatting like Console.WriteLine, string.Format, and others. Numeric format strings fall into two categories:

Note that for both standard and custom formatting on floating types (single and double), if the value being formatted is not a number or is positive / negative infinity, then the format string provided is given by NaNSymbol, PositiveInfinitySymbol and NegativeInfinitySymbol of the current NumberFormatInfo format provider.

Standard numeric format strings

Standard numeric format strings take the form Axx where:

Any numeric format string that does not fit the Axx definition is interpreted as a custom numeric format string. For example, "cf" is a custom numeric format string because 'c' is one of the standard numeric format specifiers, but "cf" does not follow the Axx pattern. Also note, that "z" can be interpreted as standard format string because it follows the Axx pattern, however, because "z" is not one of the standard numeric format specifiers, .NET will throw a FormatException exception. The following example illustrates:

double d = 12345.6789;
Trace.WriteLine( "C format: " + d.ToString( "C") );
Trace.WriteLine( "E format: " + d.ToString( "E") );
Trace.WriteLine( "F format: " + d.ToString( "F") );
Trace.WriteLine( "G format: " + d.ToString( "G") );
Trace.WriteLine( "N format: " + d.ToString( "N") );
Trace.WriteLine( "P format: " + d.ToString( "P") );

C format: £12,345.68
E format: 1.234568E+004
F format: 12345.68
G format: 12345.6789
N format: 12,345.68
P format: 1,234,567.89 %

The following example is the same as above but it uses the precision specifier (xx in the Axx general format string). The numbers in bold illustrate the effect of the associated precision specifier:

Trace.WriteLine( "C1 format: " + d.ToString( "C1") );
Trace.WriteLine( "E2 format: " + d.ToString( "E2") );
Trace.WriteLine( "F3 format: " + d.ToString( "F3") );
Trace.WriteLine( "G4 format: " + d.ToString( "G4") );
Trace.WriteLine( "N5 format: " + d.ToString( "N5") );
Trace.WriteLine( "P6 format: " + d.ToString( "P6") );

C1 format: £12,345.7
E2 format: 1.23E+004
F3 format: 12345.679
G4 format: 1.235E+04
N5 format: 12,345.67890
P6 format: 1,234,567.890000 %

Custom numeric format strings

You can use custom format strings to enhance the output string as required. Recall that a standard numeric format string takes the form Axx - i.e., a single alphanumeric character optionally followed by a number from 0 to 99. Custom numeric format strings are produced using the following specific format characters (please review MSDN - Custom Numeric Format Strings for a full descirption):

Format Character Name
0 Zero placeholder
# Digit placeholder
. Decimal Point
, Thousand separator and number scaling
% Percentage placeholder
E0
E-0
E+0
e0
e-0
e+0
Scientific notation
'ABC'
"ABC"
Literal string
; Section separator (see below)
Other All other characters

Different formatting can be applied to a string based on whether the value is positive, negative, or zero. To produce this behavior, a custom numeric format string must contain a section for each desired output with each section separated by a semicolon:

The following examples illustrate:

double d = 12.45;
Trace.WriteLine( "####: " + d.ToString("####"));
Trace.WriteLine( "0000: " + d.ToString("0000"));
Trace.WriteLine( "(##): " + d.ToString("(##)"));
Trace.WriteLine( "##.#: " + d.ToString("##.#"));
Trace.WriteLine( "##.#%: " + d.ToString("##.#%"));

####: 12
0000: 0012
(##): (12)
##.#: 12.5
##.#%: 1245%

// Format sections
int n1 = 10;
int n2 = 0;
int n3 = -10;
Trace.WriteLine( "##;(##);zero: " + n1.ToString( "##;(##);zero"));
Trace.WriteLine( "##;(##);zero: " + n2.ToString( "##;(##);zero"));
Trace.WriteLine( "##;(##);zero: " + n3.ToString( "##;(##);zero"));

##;(##);zero: 10
##;(##);zero: zero
##;(##);zero: (10)

Date Time Format Strings

Date and time format strings are used to control formatting produced by DateTime class. Numeric format strings fall into two categories:

Standard DateTime format strings

Format strings are interpreted as standard DateTime format strings if they contain only one of the single format specifiers specified below. If the format string is a single character that is not listed in the table below, a FormatException exception will be thrown. Note that the patterns produced by these format specifiers are influenced by settings in the Regional Options control panel.

Format specifier Name
d Short date
D Long date
t Short time
T Long time
f Full date/ short time
F Full date/long time
g General data / short time
G General data / long time
M or m Month day
R or r RFC1123 pattern
s Sortable date/time
u Universal sortable date/time
U Universal sortable date/time
Y or y Year month

The date and time separators displayed by these format strings are defined by the DateTimeFormat.DateSeparator and DateTimeFormat.TimeSeparator characters associated with the current culture. The following examples illustrate:

Trace.WriteLine( "d format: " + DateTime.Now.ToString( "d" ) );
Trace.WriteLine( "D format: " + DateTime.Now.ToString( "D" ) );
Trace.WriteLine( "t format: " + DateTime.Now.ToString( "t" ) );
Trace.WriteLine( "T format: " + DateTime.Now.ToString( "T" ) );
Trace.WriteLine( "f format: " + DateTime.Now.ToString( "f" ) );
Trace.WriteLine( "F format: " + DateTime.Now.ToString( "F" ) );
Trace.WriteLine( "g format: " + DateTime.Now.ToString( "g" ) );
Trace.WriteLine( "G format: " + DateTime.Now.ToString( "G" ) );
Trace.WriteLine( "m format: " + DateTime.Now.ToString( "m" ) );
Trace.WriteLine( "r format: " + DateTime.Now.ToString( "r" ) );
Trace.WriteLine( "s format: " + DateTime.Now.ToString( "s" ) );
Trace.WriteLine( "u format: " + DateTime.Now.ToString( "u" ) );
Trace.WriteLine( "Y format: " + DateTime.Now.ToString( "Y" ) );

d format: 16/08/2004
D format: 16 August 2004
t format: 18:51
T format: 18:51:45
f format: 16 August 2004 18:51
F format: 16 August 2004 18:51:45
g format: 16/08/2004 18:51
G format: 16/08/2004 18:51:45
m format: 16 August
r format: Mon, 16 Aug 2004 18:51:45 GMT
s format: 2004-08-16T18:51:45
u format: 2004-08-16 18:51:45Z
Y format: August 2004

Custom DateTime format strings

You can create your own DateTime formatting patterns using custom DateTime format strings The following table shows some of the custom format specifiers (see MSDN for full details under Custom DateTime Format strings):

Custom format specifier Description
d Current day of the month as a one-digit number for days between 1 and 9, and as a two-digit number between 1 and 31
dd Current day of the month as a two-digit number between 1 and 31
dddd Full name of the day 
hh Hour in the range 1-12
HH Hour in the range 0-23
And many others. See MSDN  

You must pass at least two characters when using custom DateTime format strings. For example, passing "d" for the DataTime format specifier will be interpreted by the CLR as a standard DateTime format string. Likewise, if you pass "h", an exception will be thrown because "h" is not a standard DateTime format string. To format using a single-character custom DateTime format string like "d" and "h", include a space before or after the specifier, i.e., " h" or "d ". The following example illustrates:

Trace.WriteLine( "d format: " + DateTime.Now.ToString( "d " ) );
Trace.WriteLine( "MM format: " + DateTime.Now.ToString( "MM" ) );
Trace.WriteLine( "dddd MMMM yy format: " + DateTime.Now.ToString( "dddd MMMM yy" ) );
Trace.WriteLine( "hh.mm.ss format: " + DateTime.Now.ToString( "hh.mm.ss" ) );
Trace.WriteLine( "hh:mm GMT zz format: " + DateTime.Now.ToString( "hh:mm G\\MT zz" ) );

d format            : 16 
MM format           : 08
dddd MMMM yy format : Monday August 04
hh.mm.ss format     : 07.08.29
hh:mm GMT zz format : 07:08 GMT +01

Enumeration Format Strings

For enum types you can use the ToString method to create a new string object that represents the numeric, hexadecimal or string value of the enum. ToString takes one of these enum formatting strings: 

For example,

public enum Colors {Red, Green, Blue };

Colors MyColor = Colors.Green;
Trace.WriteLine( "G format: " + MyColor.ToString("G" ));
Trace.WriteLine( "F format: " + MyColor.ToString("F" ));
Trace.WriteLine( "D format: " + MyColor.ToString("D" ));
Trace.WriteLine( "X format: " + MyColor.ToString("X" ));

G format: Green
F format: Green
D format: 1
X format: 00000001

Customizing Format Strings

You have two options if you require additional or custom formatting functionality not provided by any of the previous sections:

  1. Create a new format provider class that can be used by existing base types. This is because all base types implement the IFormattable interface in a way that allows them to accept a format provider that implements the IFormatProvider interface. This is discussed in Adding Custom Format Strings to Existing Types.
  2. Create your own base type that accepts a user-defined format string. This is discussed in Adding Custom Format Strings to Custom Types.

Adding Custom Format Strings to Existing Types

To be able to provide additional format specifiers to existing base types:

public class MyFormatter : IFormatProvider, ICustomFormatter
{
    /* IFormatProvider implementation */
    // This method is called to get an instnace of an ICustomFormatter to handle formatting
    public object GetFormat( Type formatType )
    {
        if (formatType == typeof(ICustomFormatter))
            return this;
        else
            return null;
    }

    /* ICustomFormatter implementation */
    // This method is called on each argument to be formatted
    public string Format( string format, object arg, IFormatProvider provider )
    {
        string strRet = "";
        // If format string for the current argument is null, then return the original argument
        if (format == null)
            strRet = arg.ToString();

        // Is this our custom format specifier or is it something else 
        if ((format == "b") || (format == "B"))
        {
            // This is our custom format specifier which coverts a number to its character representation
            if ((int)arg == 1)
                strRet = "ONE";
            if ((int)arg == 2)
                strRet = "TWO";
            if ((int)arg == 3)
                strRet = "THREE";
            if ((int)arg == 4)
                strRet = "FOUR";
            // ... 
        }
        else
        {
            // This is another format specifier. If the argument is formattable, pass it the format specifier
            if (arg is IFormattable)
            {
                strRet = ((IFormattable)arg).ToString( format, provider );
            }
            else
            {
                strRet = arg.ToString();
            }
        }
        return strRet;
    }
}

int n = 1;
string strN = string.Format( new MyFormatter(), "Number = {0:b}", new object[] {n} );    // strN = "Number = ONE"

Adding Custom Format Strings to Custom Types

When you create your own custom type, you should implement the IFormattable interface which requires you to implement at least one version of the ToString method. This method allows your code to accept a custom format provider, or define your own format scheme internally, or both.