Есть ли простой способ создания ординалов в C#?

Есть ли простой способ в C# создать Ordinals для числа? Например:

  • 1 возвращается 1
  • 2 возвращается 2
  • 3 возвращается 3
  • ...так далее

Можно ли это сделать через String.Format() или есть какие-либо функции для этого?

23 ответа

Решение

На этой странице представлен полный список всех пользовательских правил числового форматирования:

http://msdn.microsoft.com/en-us/library/0c899ak8.aspx

Как видите, там нет ничего об ординалах, так что это нельзя сделать с помощью String.Format. Однако не так уж сложно написать функцию для этого.

public static string AddOrdinal(int num)
{
    if( num <= 0 ) return num.ToString();

    switch(num % 100)
    {
        case 11:
        case 12:
        case 13:
            return num + "th";
    }

    switch(num % 10)
    {
        case 1:
            return num + "st";
        case 2:
            return num + "nd";
        case 3:
            return num + "rd";
        default:
            return num + "th";
    }

}

Обновление: Технически Ordinals не существует для <= 0, поэтому я обновил код выше. Также удалены избыточные методы ToString().

Также обратите внимание, что это не интернационализировано. Я понятия не имею, как выглядят ординалы на других языках.

Помните интернационализацию!

Решения здесь работают только для английского языка. Все становится намного сложнее, если вам нужно поддерживать другие языки.

Например, на испанском языке "1-й" будет записываться как "1.o", "1.a", "1.os" или "1.as" в зависимости от того, что вы считаете - мужское, женское или множественное число.!

Поэтому, если ваше программное обеспечение должно поддерживать разные языки, старайтесь избегать порядковых чисел.

Просто, чисто, быстро

    private static string GetOrdinalSuffix(int num)
    {
        if (num.ToString().EndsWith("11")) return "th";
        if (num.ToString().EndsWith("12")) return "th";
        if (num.ToString().EndsWith("13")) return "th";
        if (num.ToString().EndsWith("1")) return "st";
        if (num.ToString().EndsWith("2")) return "nd";
        if (num.ToString().EndsWith("3")) return "rd";
        return "th";
    }

Или еще лучше, как метод расширения

public static class IntegerExtensions
{
    public static string DisplayWithSuffix(this int num)
    {
        if (num.ToString().EndsWith("11")) return num.ToString() + "th";
        if (num.ToString().EndsWith("12")) return num.ToString() + "th";
        if (num.ToString().EndsWith("13")) return num.ToString() + "th";
        if (num.ToString().EndsWith("1")) return num.ToString() + "st";
        if (num.ToString().EndsWith("2")) return num.ToString() + "nd";
        if (num.ToString().EndsWith("3")) return num.ToString() + "rd";
        return num.ToString() + "th";
    }
}

Теперь вы можете просто позвонить

int a = 1;
a.DisplayWithSuffix(); 

или даже прямой

1.DisplayWithSuffix();

Моя версия Джесси версии Стю и Самджудсона:)

Включен модульный тест, чтобы показать, что принятый ответ неверен, когда число < 1

    /// <summary>
    /// Get the ordinal value of positive integers.
    /// </summary>
    /// <remarks>
    /// Only works for english-based cultures.
    /// Code from: http://stackru.com/questions/20156/is-there-a-quick-way-to-create-ordinals-in-c/31066#31066
    /// With help: http://www.wisegeek.com/what-is-an-ordinal-number.htm
    /// </remarks>
    /// <param name="number">The number.</param>
    /// <returns>Ordinal value of positive integers, or <see cref="int.ToString"/> if less than 1.</returns>
    public static string Ordinal(this int number)
    {
        const string TH = "th";
        string s = number.ToString();

        // Negative and zero have no ordinal representation
        if (number < 1)
        {
            return s;
        }

        number %= 100;
        if ((number >= 11) && (number <= 13))
        {
            return s + TH;
        }

        switch (number % 10)
        {
            case 1: return s + "st";
            case 2: return s + "nd";
            case 3: return s + "rd";
            default: return s + TH;
        }
    }

    [Test]
    public void Ordinal_ReturnsExpectedResults()
    {
        Assert.AreEqual("-1", (1-2).Ordinal());
        Assert.AreEqual("0", 0.Ordinal());
        Assert.AreEqual("1st", 1.Ordinal());
        Assert.AreEqual("2nd", 2.Ordinal());
        Assert.AreEqual("3rd", 3.Ordinal());
        Assert.AreEqual("4th", 4.Ordinal());
        Assert.AreEqual("5th", 5.Ordinal());
        Assert.AreEqual("6th", 6.Ordinal());
        Assert.AreEqual("7th", 7.Ordinal());
        Assert.AreEqual("8th", 8.Ordinal());
        Assert.AreEqual("9th", 9.Ordinal());
        Assert.AreEqual("10th", 10.Ordinal());
        Assert.AreEqual("11th", 11.Ordinal());
        Assert.AreEqual("12th", 12.Ordinal());
        Assert.AreEqual("13th", 13.Ordinal());
        Assert.AreEqual("14th", 14.Ordinal());
        Assert.AreEqual("20th", 20.Ordinal());
        Assert.AreEqual("21st", 21.Ordinal());
        Assert.AreEqual("22nd", 22.Ordinal());
        Assert.AreEqual("23rd", 23.Ordinal());
        Assert.AreEqual("24th", 24.Ordinal());
        Assert.AreEqual("100th", 100.Ordinal());
        Assert.AreEqual("101st", 101.Ordinal());
        Assert.AreEqual("102nd", 102.Ordinal());
        Assert.AreEqual("103rd", 103.Ordinal());
        Assert.AreEqual("104th", 104.Ordinal());
        Assert.AreEqual("110th", 110.Ordinal());
        Assert.AreEqual("111th", 111.Ordinal());
        Assert.AreEqual("112th", 112.Ordinal());
        Assert.AreEqual("113th", 113.Ordinal());
        Assert.AreEqual("114th", 114.Ordinal());
        Assert.AreEqual("120th", 120.Ordinal());
        Assert.AreEqual("121st", 121.Ordinal());
        Assert.AreEqual("122nd", 122.Ordinal());
        Assert.AreEqual("123rd", 123.Ordinal());
        Assert.AreEqual("124th", 124.Ordinal());
    }

Вы должны будете катиться самостоятельно. С макушки моей головы:

public static string Ordinal(this int number)
{
  var work = number.ToString();
  if ((number % 100) == 11 || (number % 100) == 12 || (number % 100) == 13)
    return work + "th";
  switch (number % 10)
  {
    case 1: work += "st"; break;
    case 2: work += "nd"; break;
    case 3: work += "rd"; break;
    default: work += "th"; break;
  }
  return work;
}

Вы можете сделать

Console.WriteLine(432.Ordinal());

Отредактировано для исключений 12/12/13. Я СКАЗАЛ от макушки головы:-)

Отредактировано для 1011 - другие уже исправили это, просто хотят убедиться, что другие не схватят эту неправильную версию.

Мне скорее понравились элементы из решений Стю и Самджудсона, и я объединил их в то, что я считаю полезной комбинацией:

    public static string Ordinal(this int number)
    {
        const string TH = "th";
        var s = number.ToString();

        number %= 100;

        if ((number >= 11) && (number <= 13))
        {
            return s + TH;
        }

        switch (number % 10)
        {
            case 1:
                return s + "st";
            case 2:
                return s + "nd";
            case 3:
                return s + "rd";
            default:
                return s + TH;
        }
    }

Хотя я еще не тестировал это, вы должны быть в состоянии получить более высокую производительность, избегая всех условных операторов.

Это Java, но порт для C# тривиален:

public class NumberUtil {
  final static String[] ORDINAL_SUFFIXES = {
    "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
  };

  public static String ordinalSuffix(int value) {
    int n = Math.abs(value);
    int lastTwoDigits = n % 100;
    int lastDigit = n % 10;
    int index = (lastTwoDigits >= 11 && lastTwoDigits <= 13) ? 0 : lastDigit;
    return ORDINAL_SUFFIXES[index];
  }

  public static String toOrdinal(int n) {
    return new StringBuffer().append(n).append(ordinalSuffix(n)).toString();
  }
}

Обратите внимание, что сокращение условных выражений и использование поиска в массиве должно ускорить производительность, если генерировать много ординалов в узком цикле. Тем не менее, я также признаю, что это не так легко читается, как решение для случая.

Я использую этот класс расширения:

public static class Int32Extensions
{
    public static string ToOrdinal(this int i)
    {
        return (i + "th")
            .Replace("1th", "1st")
            .Replace("2th", "2nd")
            .Replace("3th", "3rd");
    }
}
        private static string GetOrd(int num) => $"{num}{(!(Range(11, 3).Any(n => n == num % 100) ^ Range(1, 3).All(n => n != num % 10)) ? new[] { "ˢᵗ", "ⁿᵈ", "ʳᵈ" }[num % 10 - 1] : "ᵗʰ")}";

Если кто ищет один лайнер:p

Подобно решению Райана, но даже более простому, я просто использую простой массив и использую день, чтобы найти правильный порядковый номер:

private string[] ordinals = new string[] {"","st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st" };
DateTime D = DateTime.Now;
String date = "Today's day is: "+ D.Day.ToString() + ordinals[D.Day];

У меня не было необходимости, но я бы предположил, что вы могли бы использовать многомерный массив, если вы хотите иметь поддержку нескольких языков.

Из того, что я помню из моих дней Uni, этот метод требует минимальных усилий со стороны сервера.

Запрашиваемая версия "Самоудсона" с "меньшей избыточностью"...

public static string AddOrdinal(int number)
{
    if (number <= 0) return number.ToString();

    string GetIndicator(int num)
    {
        switch (num % 100)
        {
            case 11:
            case 12:
            case 13:
                return "th";
        }

        switch (num % 10)
        {
            case 1:
                return "st";
            case 2:
                return "nd";
            case 3:
                return "rd";
            default:
                return "th";
        }
    }

    return number + GetIndicator(number);
}

Пакет Nuget Humanizer предоставит вам вспомогательные методы. Отказ от ответственности, я являюсь участником этого проекта.

превращает число в порядковую строку, используемую для обозначения позиции в упорядоченной последовательности, такой как 1-я, 2-я, 3-я, 4-я:

      1.Ordinalize() => "1st"
5.Ordinalize() => "5th"

Вы также можете вызвать числовую строку и получить тот же результат: "21".Ordinalize()=> "21st"

также поддерживает грамматический род для обеих форм. Вы можете передать аргумент, чтобы указать, к какому полу должно быть выведено число. Возможные значения: GrammaticalGender.Masculine, GrammaticalGender.Feminineа также GrammaticalGender.Neuter:

      // for Brazilian Portuguese locale
1.Ordinalize(GrammaticalGender.Masculine) => "1º"
1.Ordinalize(GrammaticalGender.Feminine) => "1ª"
1.Ordinalize(GrammaticalGender.Neuter) => "1º"
"2".Ordinalize(GrammaticalGender.Masculine) => "2º"
"2".Ordinalize(GrammaticalGender.Feminine) => "2ª"
"2".Ordinalize(GrammaticalGender.Neuter) => "2º"

Очевидно, это относится только к некоторым культурам. Для других передача пола или не передача вообще не имеет никакого значения в результате.

Кроме того, Ordinalizeподдерживает варианты, которые применяются в некоторых культурах в зависимости от положения порядкового числа в предложении. Используйте аргумент, чтобы получить тот или иной результат. Возможные значения WordForm.Abbreviationа также WordForm.Normal. Вы можете комбинировать wordFormаргумент с полом, но передача этого аргумента, когда он неприменим, не повлияет на результат.

      // Spanish locale
1.Ordinalize(WordForm.Abbreviation) => "1.er" // As in "Vivo en el 1.er piso"
1.Ordinalize(WordForm.Normal) => "1.º" // As in "He llegado el 1º"
"3".Ordinalize(GrammaticalGender.Feminine, WordForm.Abbreviation) => "3.ª"
"3".Ordinalize(GrammaticalGender.Feminine, WordForm.Normal) => "3.ª"
"3".Ordinalize(GrammaticalGender.Masculine, WordForm.Abbreviation) => "3.er"
"3".Ordinalize(GrammaticalGender.Masculine, WordForm.Normal) => "3.º"

Если вы хотите углубиться, проверьте эти тестовые случаи: OrdinalizeTests.cs

РЕДАКТИРОВАТЬ: Как отмечает YM_Industries в комментарии, ответ Самджудсона работает для чисел свыше 1000, комментарии Никфа, похоже, исчезли, и я не могу вспомнить, в чем заключалась проблема, с которой я столкнулся. Оставьте этот ответ здесь для сравнения времени.

Очень многие из них не работают для чисел> 999, как указал nickf в комментарии (EDIT: теперь отсутствует).

Вот версия, основанная на модифицированной версии принятого ответа samjudson.

public static String GetOrdinal(int i)
{
    String res = "";

    if (i > 0)
    {
        int j = (i - ((i / 100) * 100));

        if ((j == 11) || (j == 12) || (j == 13))
            res = "th";
        else
        {
            int k = i % 10;

            if (k == 1)
                res = "st";
            else if (k == 2)
                res = "nd";
            else if (k == 3)
                res = "rd";
            else
                res = "th";
        }
    }

    return i.ToString() + res;
}

Также ответ Shahzad Qureshi, использующий манипуляции со строками, работает нормально, однако у него есть снижение производительности. Для генерации многих из них пример программы LINQPad делает строковую версию в 6-7 раз медленнее, чем эта целочисленная (хотя вам придется генерировать много, чтобы заметить).

Пример LINQPad:

void Main()
{
    "Examples:".Dump();

    foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 10000013 })
        Stuff.GetOrdinal(i).Dump();

    String s;

    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();

    for(int iter = 0; iter < 100000; iter++)
        foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
            s = Stuff.GetOrdinal(i);

    "Integer manipulation".Dump();
    sw.Elapsed.Dump();

    sw.Restart();

    for(int iter = 0; iter < 100000; iter++)
        foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
            s = (i.ToString() + Stuff.GetOrdinalSuffix(i));

    "String manipulation".Dump();
    sw.Elapsed.Dump();
}

public class Stuff
{
        // Use integer manipulation
        public static String GetOrdinal(int i)
        {
                String res = "";

                if (i > 0)
                {
                        int j = (i - ((i / 100) * 100));

                        if ((j == 11) || (j == 12) || (j == 13))
                                res = "th";
                        else
                        {
                                int k = i % 10;

                                if (k == 1)
                                        res = "st";
                                else if (k == 2)
                                        res = "nd";
                                else if (k == 3)
                                        res = "rd";
                                else
                                        res = "th";
                        }
                }

                return i.ToString() + res;
        }

        // Use string manipulation
        public static string GetOrdinalSuffix(int num)
        {
                if (num.ToString().EndsWith("11")) return "th";
                if (num.ToString().EndsWith("12")) return "th";
                if (num.ToString().EndsWith("13")) return "th";
                if (num.ToString().EndsWith("1")) return "st";
                if (num.ToString().EndsWith("2")) return "nd";
                if (num.ToString().EndsWith("3")) return "rd";
                return "th";
        }
}
public static string OrdinalSuffix(int ordinal)
{
    //Because negatives won't work with modular division as expected:
    var abs = Math.Abs(ordinal); 

    var lastdigit = abs % 10; 

    return 
        //Catch 60% of cases (to infinity) in the first conditional:
        lastdigit > 3 || lastdigit == 0 || (abs % 100) - lastdigit == 10 ? "th" 
            : lastdigit == 1 ? "st" 
            : lastdigit == 2 ? "nd" 
            : "rd";
}

Исходя из других ответов:

public static string Ordinal(int n)
{   
    int     r = n % 100,     m = n % 10;

    return (r<4 || r>20) && (m>0 && m<4) ? n+"  stndrd".Substring(m*2,2) : n+"th";                                              
}

Принятый ответ с выражениями-переключателями и сопоставлением с образцом из С# 8 и 9.

Никаких ненужных преобразований или выделений строк.

      string.Concat(number, number < 0 ? "" : (number % 100) switch 
{   
    11 or 12 or 13 => "th", 
    int n => (n % 10) switch 
    { 
        1 => "st", 
        2 => "nd", 
        3 => "rd", 
        _ => "th", 
    }
})

Или как недружественный однострочник:

      $"{number}{(number < 0 ? "" : (number % 100) switch { 11 or 12 or 13 => "th", int n => (n % 10) switch { 1 => "st", 2 => "nd", 3 => "rd", _ => "th" }})}"

В то время как есть много хороших ответов здесь, я думаю, есть место для еще одного, на этот раз на основе сопоставления с образцом, если не для чего-то еще, то, по крайней мере, для дискуссионной читаемости

public static string Ordinals1(this int number)
{
    switch (number)
    {
        case int p when p % 100 == 11:
        case int q when q % 100 == 12:
        case int r when r % 100 == 13:
            return $"{number}th";
        case int p when p % 10 == 1:
            return $"{number}st";
        case int p when p % 10 == 2:
            return $"{number}nd";
        case int p when p % 10 == 3:
            return $"{number}rd";
        default:
            return $"{number}th";
    }
}

и что делает это решение особенным? ничего, кроме того факта, что я добавляю некоторые соображения по производительности для различных других решений

честно говоря, я сомневаюсь, что производительность действительно имеет значение для этого конкретного сценария (кому действительно нужны порядковые числа миллионов чисел), но, по крайней мере, он вызывает некоторые сравнения, которые следует принимать во внимание...

1 миллион наименований для справки (разумеется, размер может варьироваться в зависимости от технических характеристик машины)

с сопоставлением с образцом и делениями (этот ответ)

~622 мс

с сопоставлением с образцом и строками (этот ответ)

~1967 мс

с двумя переключателями и делениями (принятый ответ)

~637 мс

с одним переключателем и делениями (другой ответ)

~725 мс

void Main()
{
    var timer = new Stopwatch();
    var numbers = Enumerable.Range(1, 1000000).ToList();

    // 1
    timer.Reset();
    timer.Start();
    var results1 = numbers.Select(p => p.Ordinals1()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and divisions");

    // 2
    timer.Reset();
    timer.Start();
    var results2 = numbers.Select(p => p.Ordinals2()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and strings");

    // 3
    timer.Reset();
    timer.Start();
    var results3 = numbers.Select(p => p.Ordinals3()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with two switches and divisons");
    
    // 4
    timer.Reset();
    timer.Start();
    var results4 = numbers.Select(p => p.Ordinals4()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with one switche and divisons");
}

public static class Extensions
{
    public static string Ordinals1(this int number)
    {
        switch (number)
        {
            case int p when p % 100 == 11:
            case int q when q % 100 == 12:
            case int r when r % 100 == 13:
                return $"{number}th";
            case int p when p % 10 == 1:
                return $"{number}st";
            case int p when p % 10 == 2:
                return $"{number}nd";
            case int p when p % 10 == 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals2(this int number)
    {
        var text = number.ToString();
        switch (text)
        {
            case string p when p.EndsWith("11"):
                return $"{number}th";
            case string p when p.EndsWith("12"):
                return $"{number}th";
            case string p when p.EndsWith("13"):
                return $"{number}th";
            case string p when p.EndsWith("1"):
                return $"{number}st";
            case string p when p.EndsWith("2"):
                return $"{number}nd";
            case string p when p.EndsWith("3"):
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals3(this int number)
    {
        switch (number % 100)
        {
            case 11:
            case 12:
            case 13:
                return $"{number}th";
        }

        switch (number % 10)
        {
            case 1:
                return $"{number}st";
            case 2:
                return $"{number}nd";
            case 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals4(this int number)
    {
        var ones = number % 10;
        var tens = Math.Floor(number / 10f) % 10;
        if (tens == 1)
        {
            return $"{number}th";
        }

        switch (ones)
        {
            case 1:
                return $"{number}th";
            case 2:
                return $"{number}nd";
            case 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }
}

FWIW, для MS-SQL это выражение сделает работу. Держи первое КОГДА (WHEN num % 100 IN (11, 12, 13) THEN 'th') в качестве первого в списке, поскольку это зависит от того, будет ли судить других.

CASE
  WHEN num % 100 IN (11, 12, 13) THEN 'th' -- must be tried first
  WHEN num % 10 = 1 THEN 'st'
  WHEN num % 10 = 2 THEN 'nd'
  WHEN num % 10 = 3 THEN 'rd'
  ELSE 'th'
END AS Ordinal

Для Excel:

=MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2)

Выражение (MOD(A1-11,100)>2) TRUE (1) для всех чисел, кроме любого, заканчивающегося на 11,12,13 (ЛОЖЬ = 0). Так 2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1) заканчивается как 1 для 11/12/13, в противном случае:
1 оценивает до 3
От 2 до 5,
От 3 до 7
другие: 9
- и необходимые 2 символа выбраны из "thstndrdth" начиная с этой позиции.

Если вы действительно хотите преобразовать это непосредственно в SQL, это сработало для меня для нескольких тестовых значений:

DECLARE @n as int
SET @n=13
SELECT SubString(  'thstndrdth'
                 , (SELECT MIN(value) FROM
                     (SELECT 9 as value UNION
                      SELECT 1+ (2* (ABS(@n) % 10)  *  CASE WHEN ((ABS(@n)+89) % 100)>2 THEN 1 ELSE 0 END)
                     ) AS Mins
                   )
                 , 2
                )

Еще один однострочный, но без сравнений, только индексируя результат регулярного выражения в массив.

public static string GetOrdinalSuffix(int input)
{
    return new []{"th", "st", "nd", "rd"}[Convert.ToInt32("0" + Regex.Match(input.ToString(), "(?<!1)[1-3]$").Value)];
}

Версия PowerShell может быть сокращена еще больше:

function ord($num) { return ('th','st','nd','rd')[[int]($num -match '(?<!1)[1-3]$') * $matches[0]] }

Another 1 liner.

public static string Ordinal(this int n)
{    
 return n + (new [] {"st","nd","rd" }.ElementAtOrDefault((((n + 90) % 100 - 10) % 10 - 1)) ?? "th");
}

Это реализация в dart и может быть изменено в зависимости от языка.

String getOrdinalSuffix(int num){
    if (num.toString().endsWith("11")) return "th";
    if (num.toString().endsWith("12")) return "th";
    if (num.toString().endsWith("13")) return "th";
    if (num.toString().endsWith("1")) return "st";
    if (num.toString().endsWith("2")) return "nd";
    if (num.toString().endsWith("3")) return "rd";
    return "th";
}

Вот класс Расширения DateTime. Скопируйте, вставьте и наслаждайтесь

открытый статический класс DateTimeExtensions {

    public static string ToStringWithOrdinal(this DateTime d)
    {
        var result = "";
        bool bReturn = false;            

        switch (d.Day % 100)
        {
            case 11:
            case 12:
            case 13:
                result = d.ToString("dd'th' MMMM yyyy");
                bReturn = true;
                break;
        }

        if (!bReturn)
        {
            switch (d.Day % 10)
            {
                case 1:
                    result = d.ToString("dd'st' MMMM yyyy");
                    break;
                case 2:
                    result = d.ToString("dd'nd' MMMM yyyy");
                    break;
                case 3:
                    result = d.ToString("dd'rd' MMMM yyyy");
                    break;
                default:
                    result = d.ToString("dd'th' MMMM yyyy");
                    break;
            }

        }

        if (result.StartsWith("0")) result = result.Substring(1);
        return result;
    }
}

Результат:

9 октября 2014 г.

Другая альтернатива, которую я использовал, основана на всех других предложениях, но не требует специального корпуса:

    public static string DateSuffix(int day)
    {
        if (day == 11 | day == 12 | day == 13) return "th";
        Math.DivRem(day, 10, out day);
        switch (day)
        {
            case 1:
                return "st";
            case 2:
                return "nd";
            case 3:
                return "rd";
            default:
                return "th";
        }
    }
Другие вопросы по тегам