¿Hay una manera fácil de crear ordinales en C #?

202

¿Hay una manera fácil en C # de crear ordinales para un número? Por ejemplo:

  • 1 vuelve primero
  • 2 regresa 2do
  • 3 vuelve tercero
  • ... etc.

¿Se puede hacer esto String.Format()o hay alguna función disponible para hacerlo?

GateKiller
fuente

Respuestas:

311

Esta página le ofrece una lista completa de todas las reglas de formato numérico personalizado:

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

Como puede ver, no hay nada allí sobre ordinales, por lo que no se puede hacer usando String.Format. Sin embargo, no es realmente tan difícil escribir una función para hacerlo.

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";
    }
}

Actualización: Técnicamente, los ordinales no existen para <= 0, por lo que he actualizado el código anterior. También eliminó los ToString()métodos redundantes .

También tenga en cuenta que esto no está internacionalizado. No tengo idea de cómo se ven los ordinales en otros idiomas.

samjudson
fuente
2
Assert.AreEqual ("0", AddOrdinal (0)); Ver wisegeek.com/what-is-an-ordinal-number.htm
si618
2
Usar un método de extensión (o como se llame, vea la respuesta de @ Stu) funcionaría muy bien aquí. @ Si, agregar esa condición sería muy fácil si fuera necesario.
extraño
12
Olvidé '11, 12 13' ... debería ser una pregunta de entrevista. :-)
Holf
2
Sí, bueno, los programadores son raros;)
samjudson
2
@IanWarburton No hay redundancia ya que solo se afectará una sola declaración de devolución. Si no está satisfecho con la respuesta, proporcione la suya, mostrándonos la forma "adecuada" de hacerlo y por qué es importante.
B2K
73

¡Recuerda la internacionalización!

Las soluciones aquí solo funcionan para inglés. Las cosas se vuelven mucho más complejas si necesita admitir otros idiomas.

Por ejemplo, en español "1st" se escribiría como "1.o", "1.a", "1.os" o "1.as" dependiendo de si lo que está contando es masculino, femenino o plural !

Entonces, si su software necesita admitir diferentes idiomas, intente evitar los ordinales.

roomaroo
fuente
77
@ Andomar: "Los primeros 2 lectores" => en italiano (y en español también, supongo) "primero" está en plural aquí. Entonces tienes singular masculino, singular femenino, plural masculino, plural femenino; tal vez algo de lenguaje tiene también un caso neutro (distinguing cosas de los hombres / de los animales)
M.Turrini
2
Dicho esto, no tiene que evitar los ordinales: inclúyalos en la localización, una vez que sepa todo el caso que podría enfrentar, o (haga que su cliente) acepte algunas limitaciones.
M.Turrini
26
Esto explica por qué el equipo de .NET evitó agregarlo a los formateadores DateTime
Chris S
moment.js tiene una función de formato "ordinal" por configuración regional, por lo que parece factible, también desearía que lo hubieran hecho en .NET para DateTime
Guillaume86
55
Todo sería muy simple si todos usaran el "." carácter para ordinales, como lo hacemos en alemán)))) 1. 2. 3. 4. 5., etc. Aunque el algoritmo sería mucho más interesante si uno escribiera el número y tuviera que agregar inflexión en 4 casos gramaticales con 3 artículos diferentes, además de los casos singulares y plurales de las 12 combinaciones diferentes. Ahora que lo pienso, los rusos no tienen 2 más, más vocativ, y algunos idiomas nórdicos tienen 15, creo. Me hubiera encantado ver esa implementación en .NET.
Stefan Steiger
22

Mi versión de la versión de Jesse de las versiones de Stu y Samjudson :)

Prueba unitaria incluida para mostrar que la respuesta aceptada es incorrecta cuando el número <1

    /// <summary>
    /// Get the ordinal value of positive integers.
    /// </summary>
    /// <remarks>
    /// Only works for english-based cultures.
    /// Code from: http://stackoverflow.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());
    }
si618
fuente
15

Simple, limpio, rápido

    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";
    }

O mejor aún, como método de extensión

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";
    }
}

Ahora solo puedes llamar

int a = 1;
a.DisplayWithSuffix(); 

o incluso tan directo como

1.DisplayWithSuffix();
Shahzad Qureshi
fuente
14

Tendrás que rodar el tuyo. Desde lo alto de mi cabeza:

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;
}

Entonces puedes hacer

Console.WriteLine(432.Ordinal());

Editado para las excepciones del 11/12/13. Dije desde lo alto de mi cabeza :-)

Editado para 1011: otros ya lo han solucionado, solo quieren asegurarse de que otros no tomen esta versión incorrecta.

Stu
fuente
12

Más bien me gustaron los elementos de las soluciones de Stu y Samjudson y los trabajé juntos en lo que creo que es un combo utilizable:

    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;
        }
    }
Jesse C. Slicer
fuente
1
¿Cuál es la razón detrás del uso de una constante para "th"?
nickf
porque se usa dos veces en el código. Simplemente utilizando la antigua sabiduría de que no debes repetirlo :) En este caso, el tiempo de ejecución de .NET solo debe crear una copia de la cadena mientras que con dos "th" s en el código, se crearán dos cadenas y referenciado en la memoria.
Jesse C. Slicer
25
y también, si el valor de TH alguna vez cambia, se establecerá.
Eclipse
77
@Jesse: obtienes mi +1, pero no creo que .NET maneje las cadenas de esta manera, mira yoda.arachsys.com/csharp/strings.html#interning , mi lectura de eso es cada referencia al literal "th" haría referencia al mismo bit de memoria. Pero estoy de acuerdo con DRY :)
si618
44
Eliminar duplicaciones como esta solo dificulta la legibilidad, creo, de ahí la confusión "¿Por qué el TH?". No creo que DRY deba interpretarse como 'eliminar toda duplicación a cualquier costo'.
SeeNoWeevil
8

Si bien aún no he evaluado esto, debería poder obtener un mejor rendimiento evitando todas las declaraciones de casos condicionales.

Esto es Java, pero un puerto a C # es trivial:

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();
  }
}

Tenga en cuenta que la reducción de condicionales y el uso de la búsqueda de matriz deberían acelerar el rendimiento si se generan muchos ordinales en un ciclo cerrado. Sin embargo, también reconozco que esto no es tan legible como la solución de declaración de caso.

Ryan McGeary
fuente
Lamento haber evaluado esto en C #, su versión no es más rápida que la solución de si618.
GY_
revise esta respuesta stackoverflow.com/a/58378465/2583579 para algunos puntos de referencia
Dan Dohotaru
3

Similar a la solución de Ryan, pero aún más básico, solo uso una matriz simple y uso el día para buscar el ordinal correcto:

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];

No he tenido la necesidad, pero supongo que podría usar una matriz multidimensional si quisiera tener soporte para múltiples idiomas.

Por lo que puedo recordar de mis días de Uni, este método requiere un esfuerzo mínimo del servidor.

Shawad
fuente
2

Yo uso esta clase de extensión:

public static class Int32Extensions
{
    public static string ToOrdinal(this int i)
    {
        return (i + "th")
            .Replace("1th", "1st")
            .Replace("2th", "2nd")
            .Replace("3th", "3rd");
    }
}
Perry Tribolet
fuente
11, 12 y 13
Kcoder
2

Se solicitó la versión "menos redundante" de la respuesta de samjudson ...

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);
}
Ian Warburton
fuente
2
Expondría "GetIndicator" como a public staticy cambiaría el nombre a un nombre más mnemónico (es decir, "OrdinalSuffix"). La persona que llama puede querer la parte del número en diferentes formatos (es decir, con comas).
Tom
2
        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] : "ᵗʰ")}";

Si alguien busca un revestimiento: p

Ali Humayun
fuente
1
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";
}
Fausto
fuente
1

EDITAR : Como YM_Industries señala en el comentario, la respuesta de samjudson funciona para números superiores a 1000, el comentario de nickf parece haberse ido, y no puedo recordar cuál fue el problema que vi. Dejó esta respuesta aquí para los tiempos de comparación.

Muchos de estos no funcionan para números> 999, como nickf señaló en un comentario (EDITAR: ahora falta).

Aquí hay una versión basada en una versión modificada de la respuesta aceptada de samjudson que sí.

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;
}

También la respuesta de Shahzad Qureshi usando la manipulación de cuerdas funciona bien, sin embargo, tiene una penalización de rendimiento. Para generar muchos de estos, un programa de ejemplo de LINQPad hace que la versión de cadena sea 6-7 veces más lenta que esta entera (aunque tendría que generar mucho para notar).

Ejemplo de 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";
        }
}
El alcoholismo
fuente
No puedo encontrar el comentario de @ nickf, ¿qué tiene de malo la respuesta de samjudson? Me parece que maneja números superiores a 1000 bien mientras es mucho más legible que el tuyo.
Joshua Walsh
1
Es un comentario justo, acabo de ejecutar un conjunto de prueba y no puedo encontrar ningún problema. Parece que tampoco hubo modificaciones en la respuesta de Sam, así que solo puedo imaginar que me estaba volviendo loco. He editado mi respuesta para reflejar eso.
Whelkaholism
1
Jaja, todos tenemos momentos así ¿no? Volvemos al código antiguo y decimos "¿por qué demonios escribí esto?"
Joshua Walsh
1

Basado en las otras respuestas:

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";                                              
}
Dave Sumter
fuente
3
PRIMER LUGAR: La respuesta más innecesariamente críptica. "Innecesariamente": el tamaño del código / los beneficios de rendimiento no valen los costos de legibilidad. "Cryptic": se necesita una traducción significativa para mapear los requisitos de "Laico".
Tom
0

FWIW, para MS-SQL, esta expresión hará el trabajo. Mantenga el primer WHEN ( WHEN num % 100 IN (11, 12, 13) THEN 'th') como el primero en la lista, ya que esto se basa en ser probado antes que los demás.

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

Para Excel:

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

La expresión (MOD(A1-11,100)>2)es VERDADERO (1) para todos los números, excepto los que terminan en 11,12,13(FALSO = 0). Entonces 2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1)termina como 1 para el 12/11/13, de lo contrario:
1 evalúa a 3
2 a 5,
3 a 7
otros: 9
- y los 2 caracteres requeridos se seleccionan a "thstndrdth"partir de esa posición.

Si realmente desea convertir eso directamente a SQL, esto funcionó para mí para un puñado de valores de prueba:

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
                )
AjV Jsy
fuente
0

Esta es la implementación darty puede modificarse según el idioma.

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";
}
Phani Rithvij
fuente
0

Si bien hay muchas buenas respuestas aquí, supongo que hay espacio para otra, esta vez basada en la coincidencia de patrones, si no para otra cosa, al menos para una legibilidad discutible

    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";
        }
    }

¿Y qué hace que esta solución sea especial? nada más que el hecho de que estoy agregando algunas consideraciones de rendimiento para varias otras soluciones

francamente, dudo que el rendimiento realmente sea importante para este escenario en particular (que realmente necesita los ordinales de millones de números), pero al menos muestra algunas comparaciones a tener en cuenta ...

1 millón de artículos para referencia (su millaje puede variar según las especificaciones de la máquina, por supuesto)

con coincidencia de patrones y divisiones (esta respuesta)

~ 622 ms

con coincidencia de patrones y cadenas (esta respuesta)

~ 1967 ms

con dos interruptores y divisiones (respuesta aceptada)

~ 637 ms

con un interruptor y divisiones (otra respuesta)

~ 725 ms

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";
        }
    }
}
Dan Dohotaru
fuente
0

Otra línea, pero sin comparaciones al indexar solo el resultado de la expresión regular en una matriz.

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

La versión de PowerShell se puede acortar aún más:

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

Otro 1 trazador de líneas.

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

Aquí está la clase de extensión DateTime. Copiar, pegar y disfrutar

clase estática pública 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;
    }
}

Resultado:

9 de octubre de 2014

Maulik Patel
fuente
Está duplicando: a) la cadena de formato de fecha (X5) yb) todo el resto del método (cuando surge el caso de uso probable (si aún no lo ha hecho) de que se necesita un sufijo ordinal para otro día propósitos o incluso un día del mes con una cadena de formato de fecha diferente). Utilice el método "OrdinalSuffix" que sugerí que fuera expuesto en la respuesta de Ian Warburton del 6 de abril de 17 a las 16:32 ( stackoverflow.com/questions/20156/… ).
Tom
-3

Otra alternativa que utilicé en base a todas las otras sugerencias, pero no requiere una carcasa especial:

    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";
        }
    }
Rupert
fuente