Exibir números com sufixo ordinal em PHP

148

Quero exibir os números da seguinte maneira

  • 1 como 1º,
  • 2 como segundo,
  • ...,
  • 150 como 150th.

Como devo encontrar o sufixo ordinal correto (st, nd, rd ou th) para cada número no meu código?

Arca
fonte
2
Veja minha resposta aqui. stackoverflow.com/questions/69262/… Essa pergunta era para .NET, mas eu respondi com uma solução PHP, por isso deve ajudá-lo.
nickf
A única maneira de pensar em fazer isso é ter uma instrução if subindo para cada número que você possa ter, IE, se (1) então "st" elseif (2) então "nd" etc etc if (23000) then " nd ". É um problema se você tiver grandes números, mas puder escrever um programa para escrever o código para você, ele poderá fazer um loop em todos os números que imprimem os ifs para você copiar + colar no seu código.
Tom Gullen
2
@ Tom, uma tabela de pesquisa pode ser melhor, apenas inicialize-a com os valores 23000 e obtenha o valor no índice n, onde n é o número que você deseja que o ordinal.
precisa
2
Coronel Shrapnel. você pode brilhante, mas não todos. qualquer forma obrigado por mostrar interesse na minha pergunta
Arca
@ John, idéia muito, muito inteligente, seria muito rápido para acessar, bem como cada índice representa o número que você está procurando.
Tom Gullen

Respostas:

282

da wikipedia :

$ends = array('th','st','nd','rd','th','th','th','th','th','th');
if (($number %100) >= 11 && ($number%100) <= 13)
   $abbreviation = $number. 'th';
else
   $abbreviation = $number. $ends[$number % 10];

Onde $numberestá o número que você deseja escrever. Funciona com qualquer número natural.

Como uma função:

function ordinal($number) {
    $ends = array('th','st','nd','rd','th','th','th','th','th','th');
    if ((($number % 100) >= 11) && (($number%100) <= 13))
        return $number. 'th';
    else
        return $number. $ends[$number % 10];
}
//Example Usage
echo ordinal(100);
Iacopo
fonte
Embora um pouco difícil de entender a princípio, agora acho que melhor representa como o sistema de sufixos ordinais funciona para o inglês.
erisco
5
Eu gosto da sua solução. Além disso, se você preferir não para gerar 0 modificar a última linha a ser$abbreviation = ($number)? $number. $ends[$number % 10] : $number;
Gavin Jackson
1
@GavinJackson sua adição a esta excelente solução realmente me ajudou (+1). Você poderia me explicar o que está acontecendo no cálculo? Eu quero entender. Felicidades! EDIT: Encontrado uma resposta: operador condicional
Andrew Fox
Desculpe, tive que quebrar os 111 votos a 112: D Tornou uma função no Delphi junto com um aplicativo de demonstração: pastebin.com/wvmz1CHY
Jerry Dodge
1
@HafezDivandari - você tem certeza? Apenas testado com 7.1.19 e parece funcionar bem
Iacopo
141

O PHP possui funcionalidade incorporada para isso . Ele até lida com internacionalização!

$locale = 'en_US';
$nf = new NumberFormatter($locale, NumberFormatter::ORDINAL);
echo $nf->format($number);

Note que esta funcionalidade está disponível apenas no PHP 5.3.0 e posterior.

Jeremy Kauffman
fonte
15
Observe que isso também requer PECL intl> = 1.0.0
rymo
4
Você sabe se é possível obter o número ordinal em forma de palavra? ou seja, primeiro, segundo, terceiro, etc. em vez de primeiro, segundo, terceiro ...
its_me 16/10
@jeremy Quando tentei usar o NumberFormatter, ele sempre gera um erro NumberFomatter file not found. Como você resolveu isso?
Jhnferraris
1
@Jhn você tem que instalar a extensãoapt-get install php5-intl
Aley
@Aley eu vejo. O Yii possui um formatador incorporado que estamos usando no momento. heh
jhnferraris
20

Isso pode ser realizado em uma única linha, aproveitando a funcionalidade semelhante nas funções de data / hora internas do PHP. Eu humildemente submeto:

Solução:

function ordinalSuffix( $n )
{
  return date('S',mktime(1,1,1,1,( (($n>=10)+($n>=20)+($n==0))*10 + $n%10) ));
}

Explicação detalhada:

A date()função interna possui lógica de sufixo para lidar com os cálculos do enésimo dia do mês. O sufixo é retornado quando Sé fornecido na string de formato:

date( 'S' , ? );

Desde date()requer um timestamp (para ?cima), nós vamos passar o nosso inteiro $ncomo o dayparâmetro para mktime()e uso manequim valores de 1para o hour, minute, second, e month:

date( 'S' , mktime( 1 , 1 , 1 , 1 , $n ) );

Na verdade, isso falha normalmente nos valores fora do intervalo de um dia do mês (ou seja $n > 31), mas podemos adicionar uma lógica embutida simples ao limite de $n29:

date( 'S', mktime( 1, 1, 1, 1, ( (($n>=10)+($n>=20))*10 + $n%10) ));

O único valor positivo( Maio de 2017 ), isso falha $n == 0, mas isso é facilmente corrigido adicionando 10 nesse caso especial:

date( 'S', mktime( 1, 1, 1, 1, ( (($n>=10)+($n>=20)+($n==0))*10 + $n%10) ));

Atualização, maio de 2017

Conforme observado por @donatJ, o acima falhou acima de 100 (por exemplo, "111st"), pois as >=20verificações estão sempre retornando verdadeiras. Para redefini-las a cada século, adicionamos um filtro à comparação:

date( 'S', mktime( 1, 1, 1, 1, ( (($n>=10)+($n%100>=20)+($n==0))*10 + $n%10) ));

Basta envolvê-lo em uma função por conveniência e pronto!

Andrew Kozak
fonte
14

Aqui está uma frase:

$a = <yournumber>;
echo $a.substr(date('jS', mktime(0,0,0,1,($a%10==0?9:($a%100>20?$a%10:$a%100)),2000)),-2);

Provavelmente a solução mais curta. Obviamente, pode ser envolvido por uma função:

function ordinal($a) {
  // return English ordinal number
  return $a.substr(date('jS', mktime(0,0,0,1,($a%10==0?9:($a%100>20?$a%10:$a%100)),2000)),-2);
}

Atenciosamente, Paul

EDIT1: Correção do código de 11 a 13.

EDIT2: Correção de código para 111, 211, ...

EDIT3: Agora também funciona corretamente para múltiplos de 10.

Paulo
fonte
Eu gosto dessa abordagem, mas, infelizmente, ela não funciona :-( o 30º sai como o 30º. O 40º sai como o 40º etc. etc.
Flukey
1
Sim, desculpe-me. Quando li a pergunta que pensei, ei, isso deve ser possível por uma única linha de código. E eu apenas digitei. Como você vê nas minhas edições, estou melhorando. Após a terceira edição agora, acho que está bem feito. Pelo menos todos os números de 1 a 150 são impressos na tela.
Paulo
Parece bom até 500! (ainda não o testei). Bom trabalho! :-)
Flukey
13

de http://www.phpro.org/examples/Ordinal-Suffix.html

<?php

/**
 *
 * @return number with ordinal suffix
 *
 * @param int $number
 *
 * @param int $ss Turn super script on/off
 *
 * @return string
 *
 */
function ordinalSuffix($number, $ss=0)
{

    /*** check for 11, 12, 13 ***/
    if ($number % 100 > 10 && $number %100 < 14)
    {
        $os = 'th';
    }
    /*** check if number is zero ***/
    elseif($number == 0)
    {
        $os = '';
    }
    else
    {
        /*** get the last digit ***/
        $last = substr($number, -1, 1);

        switch($last)
        {
            case "1":
            $os = 'st';
            break;

            case "2":
            $os = 'nd';
            break;

            case "3":
            $os = 'rd';
            break;

            default:
            $os = 'th';
        }
    }

    /*** add super script ***/
    $os = $ss==0 ? $os : '<sup>'.$os.'</sup>';

    /*** return ***/
    return $number.$os;
}
?> 
John Boker
fonte
9

Resposta simples e fácil será:

$Day = 3; 
echo date("S", mktime(0, 0, 0, 0, $Day, 0));

//OUTPUT - rd
Cavalo branco
fonte
Não diz nada sobre datas na pergunta tho. I upvoted qualquer maneira - é uma solução rápida e simples quando você sabe que o intervalo de números vai ser <32
cronoklee
8

Eu escrevi isso para PHP4. Está funcionando bem e é bem econômico.

function getOrdinalSuffix($number) {
    $number = abs($number) % 100;
    $lastChar = substr($number, -1, 1);
    switch ($lastChar) {
        case '1' : return ($number == '11') ? 'th' : 'st';
        case '2' : return ($number == '12') ? 'th' : 'nd';
        case '3' : return ($number == '13') ? 'th' : 'rd'; 
    }
    return 'th';  
}
iletras
fonte
Vishal Kumar, você poderia expandir / expor / explicar isso um pouco mais? Tks!
Iletras 4/04
4

você só precisa aplicar uma determinada função.

function addOrdinalNumberSuffix($num) {
  if (!in_array(($num % 100),array(11,12,13))){
    switch ($num % 10) {
      // Handle 1st, 2nd, 3rd
      case 1:  return $num.'st';
      case 2:  return $num.'nd';
      case 3:  return $num.'rd';
    }
  }
  return $num.'th';
}
Chintan Thummar
fonte
3

Genericamente, você pode usar isso e chamar echo get_placing_string (100);

<?php
function get_placing_string($placing){
    $i=intval($placing%10);
    $place=substr($placing,-2); //For 11,12,13 places

    if($i==1 && $place!='11'){
        return $placing.'st';
    }
    else if($i==2 && $place!='12'){
        return $placing.'nd';
    }

    else if($i==3 && $place!='13'){
        return $placing.'rd';
    }
    return $placing.'th';
}
?>
Uzair Bin Nisar
fonte
2

Eu criei uma função que não depende da date();função do PHP, pois ela não é necessária, mas também a tornei tão compacta e tão curta quanto eu acho que é atualmente possível.

O código : (121 bytes no total)

function ordinal($i) { // PHP 5.2 and later
  return($i.(($j=abs($i)%100)>10&&$j<14?'th':(($j%=10)>0&&$j<4?['st', 'nd', 'rd'][$j-1]:'th')));
}

Código mais compacto abaixo.

Funciona da seguinte maneira :

printf("The %s hour.\n",    ordinal(0));   // The 0th hour.
printf("The %s ossicle.\n", ordinal(1));   // The 1st ossicle.
printf("The %s cat.\n",     ordinal(12));  // The 12th cat.
printf("The %s item.\n",    ordinal(-23)); // The -23rd item.

Coisas a saber sobre esta função :

  • Ele lida com números inteiros negativos da mesma forma que números inteiros positivos e mantém o sinal.
  • Retorna 11º, 12º, 13º, 811º, 812º, 813º etc. para os números -14 , conforme o esperado.
  • Ele não verifica decimais, mas vai deixá-los no lugar (uso floor($i), round($i)ou ceil($i)no início da instrução de retorno final).
  • Você também pode adicionar format_number($i)no início da declaração de retorno final para obter um número inteiro separado por vírgula (se estiver exibindo milhares, milhões, etc.).
  • Você pode remover o $iinício da declaração de retorno apenas se desejar retornar o sufixo ordinal sem o que você inseriu.

Esta função funciona a partir do PHP 5.2, lançado em novembro de 2006, apenas por causa da sintaxe curta da matriz. Se você possui uma versão anterior a essa atualização, atualize-a porque está quase uma década desatualizada! Caso contrário, basta substituir a linha ['st', 'nd', 'rd']por uma variável temporária contendoarray('st', 'nd', 'rd'); .

A mesma função (sem retornar a entrada), mas uma visão explodida da minha função curta para melhor entendimento:

function ordinal($i) {
  $j = abs($i); // make negatives into positives
  $j = $j%100; // modulo 100; deal only with ones and tens; 0 through 99

  if($j>10 && $j<14) // if $j is over 10, but below 14 (so we deal with 11 to 13)
    return('th'); // always return 'th' for 11th, 13th, 62912th, etc.

  $j = $j%10; // modulo 10; deal only with ones; 0 through 9

  if($j==1) // 1st, 21st, 31st, 971st
    return('st');

  if($j==2) // 2nd, 22nd, 32nd, 582nd
    return('nd'); // 

  if($j==3) // 3rd, 23rd, 33rd, 253rd
    return('rd');

  return('th'); // everything else will suffixed with 'th' including 0th
}

Atualização de código :

Aqui está uma versão modificada que é 14 bytes inteiros mais curtos (total de 107 bytes):

function ordinal($i) {
  return $i.(($j=abs($i)%100)>10&&$j<14?'th':@['th','st','nd','rd'][$j%10]?:'th');
}

Ou, o mais curto possível, sendo 25 bytes mais curtos (total de 96 bytes):

function o($i){return $i.(($j=abs($i)%100)>10&&$j<14?'th':@['th','st','nd','rd'][$j%10]?:'th');}

Com esta última função, basta chamar o(121);e ele fará exatamente o mesmo que as outras funções que listei.

Atualização de código # 2 :

Ben e eu trabalhamos juntos e reduzimos em 38 bytes (83 bytes no total):

function o($i){return$i.@(($j=abs($i)%100)>10&&$j<14?th:[th,st,nd,rd][$j%10]?:th);}

Não achamos que possa ficar mais curto que isso! Disposto a ser provado errado, no entanto. :)

Espero que todos gostem.

nxasdf
fonte
você não precisa usar abs()com módulo%
99 Problemas - Sintaxe não é uma
Confiar apenas no módulo ainda deixará o sinal negativo no lugar. abs();remove o sinal negativo que é o que eu precisava.
Nxasdf 5/09/16
1

Uma versão ainda mais curta para datas no mês (até 31) em vez de usar mktime () e não exigir pecl intl:

function ordinal($n) {
    return (new DateTime('Jan '.$n))->format('jS');
}

ou processualmente:

echo date_format(date_create('Jan '.$n), 'jS');

Isso funciona, é claro, porque o mês padrão que escolhi (janeiro) tem 31 dias.

Curiosamente, se você tentar com fevereiro (ou outro mês sem 31 dias), ele será reiniciado antes do final:

...clip...
31st
1st
2nd
3rd

para que você possa contar até os dias deste mês com o especificador de data tem seu loop: número de dias no mês.

Dan Dart
fonte
1
function ordinal($number){

    $last=substr($number,-1);
    if( $last>3 || $last==0 || ( $number >= 11 && $number <= 19 ) ){
      $ext='th';
    }else if( $last==3 ){
      $ext='rd';
    }else if( $last==2 ){
      $ext='nd';
    }else{
      $ext='st';
    }
    return $number.$ext;
  }
xyz
fonte
0

Encontrei uma resposta no PHP.net

<?php
function ordinal($num)
{
    // Special case "teenth"
    if ( ($num / 10) % 10 != 1 )
    {
        // Handle 1st, 2nd, 3rd
        switch( $num % 10 )
        {
            case 1: return $num . 'st';
            case 2: return $num . 'nd';
            case 3: return $num . 'rd';  
        }
    }
    // Everything else is "nth"
    return $num . 'th';
}
?>
mysticmo
fonte
0

Aqui está outra versão muito curta, usando as funções de data. Funciona para qualquer número (sem restrição de dias do mês) e leva em consideração que * 11 * 12 * 13 não segue o formato * 1 * 2 * 3.

function getOrdinal($n)
{
    return $n . date_format(date_create('Jan ' . ($n % 100 < 20 ? $n % 20 : $n % 10)), 'S');
}
Claire Matthews
fonte
-1

Eu gosto deste pequeno trecho

<?php

  function addOrdinalNumberSuffix($num) {
    if (!in_array(($num % 100),array(11,12,13))){
      switch ($num % 10) {
        // Handle 1st, 2nd, 3rd
        case 1:  return $num.'st';
        case 2:  return $num.'nd';
        case 3:  return $num.'rd';
      }
    }
    return $num.'th';
  }

?>

AQUI

niksmac
fonte
1
Não tenho muita certeza do que essa resposta oferece, além da resposta de ChintanThummar. Bem, isso sugere que ChintanThummar fez uma violação de direitos autorais, a menos que ele escreveu o código na sua fonte ...
Andreas Rejbrand