viernes, 19 de mayo de 2017

Conversión de números a texto

Hace bastante tiempo necesité un conversor para representar un importe en euros en texto; principalmente para imprimir importes de cheques, recibos, etc.

Como no encontré ninguno que se adaptara a lo que necesitaba, partiendo de uno que tenía hecho en C, lo hice en C#.

Luego decidí "traducirlo" a PHP. Partiendo de .NET es relativamente sencillo, ya que básicamente se trata de analizar un número y traducirlo.

Tienes las dos versiones disponibles en GitHub y en PHP Classes.

Se trata de una sola clase con un método estático que realiza la conversión.

Su uso es muy sencillo:

C#

string txt1 = Alp3476.A2Num2Txt.ToString("3528.25");
Console.WriteLine(txt1);
// tres mil quinientos veintiocho con veinticinco

string txt2 = Alp3476.A2Num2Txt.ToString(123456);
Console.WriteLine(txt2);
// ciento veintitres mil cuatrocientos cincuenta y seis

En la versión de C#, el método está sobrecargado, por lo que admite como parámetro un double, o un string.

PHP

include 'Num2Txt.php';

$o = new  \Alp3476\Num2Txt();
echo $o->toString(3527.25) . PHP_EOL;
// tres mil quinientos veintisiete con veinticinco

echo $o->toString(12342245281.890) . PHP_EOL;
// doce mil trescientos cuarenta y dos millones doscientos cuarenta y cinco mil ...
// ... doscientos ochenta y uno con ochenta y nueve

En PHP, el parámetro se convierte a float utilizando floatval(), por lo que admite cualquier parámetro que se pueda convertir a un número de punto flotante.

Los números se redondean a dos dígitos.
Como he comentado, su uso inicial es para imprimir importes en euros (de ahí que se utlicen dos decimales)


Por supuesto que hay muchas clases similares y seguramente más optimizadas, pero si crees que te pueden servir, ahí las tienes.

Son muy sencillas y las podrás adaptar a tus necesidades.



lunes, 15 de mayo de 2017

Cálculos con fechas

Todos los lenguajes de programación (o casi todos) disponen de un tipo de datos para representar fechas y/o  funciones para trabajar con ellas.
Sin embargo, no es raro que nos encontremos con código, (a veces antiguo, a veces no) que trata las fechas "a mano".

Este es un un ejemplo real de una función que ví en una aplicación PHP:

function sumaMes($fecha,$mesesSumar,$formatoDMA=false)
{
  $dia = $this->separaFecha($fecha,"D");
  $mes = $this->separaFecha($fecha,"M");
  $ano = $this->separaFecha($fecha,"A");
  $mesResult = $mes;

  if (!empty($mes))
  {
    for ($n = $mes; $n<($mes + $mesesSumar); $n++)
    {
      if ($n == 12)
      {
        $mesResult = 1;
        $ano++;
      }
      else
        $mesResult++;
    }
  }

  $mes = $this->cerosLeft($mesResult, 2);

  if ($formatoDMA)
    $resp = "$dia-$mes-$ano";
  else
    $resp = "$ano-$mes-$dia";

  return $resp;
}

Esta función está tal y como la encontré. En apariencia, el código parece correcto, y funciona bien hasta que hacemos operaciones como sumar un mes a un 30 de enero, lo que nos devuelve un 30 de febrero.
Este tipo de errores se detectan cuando utilizamos una base de datos y protesta cuando intentamos grabar una fecha no válida.
Si estamos creando un fichero tipo XML o similar, tenemos un problema; tarde o temprano lo descubriremos, o más bien, lo descubrirán los clientes por nosotros.

Esto de despotricar del código de otros es fácil; hay gente a la que le parece divertido.
Sin embargo, cuando heredas un código como éste y te toca mantenerlo, o tienes que hacer un traspaso de datos que ha pasado por este código, créeme, no es divertido.

La función sumaMes() lá encontré en una aplicación que utilizaba una versión antigua de MySQL que, sorprendentemente, se tragaba fechas como el 31 de abril. El traspaso de esos datos a PostgreSQL fue toda una aventura.

El tratar las fechas como texto, descomponerlas y hacer operaciones, no es lo más adecuado.
Una cosa es analizar y descomponer un texto que representa una fecha, y otra hacer operaciones con la fecha.

Vamos a ver un par de ejemplos de tratamiento de fechas:
  • Añadir / restar intervalos
  • Cálculo del último día del mes

Añadir / Restar intervalos

Vamos a ver varios ejemplos de cómo añadir 30 días, y restar un año en fechas en PHP, C#, JavaScript y PostgreSQL:

PHP

/* Sumar 30 días */
$f = new DateTime();
$f->add(new DateInterval('P30D'));
echo $f->format('d-M-Y');

/* Restar 1 año */
$f = new DateTime();
$f->sub(new DateInterval('P1Y'));
echo $f->format('d-M-Y'); 

La clase DateInterval es la que nos permite definir el intervalo con el que operamos sobre la fecha actual. Hay que tener en cuenta que se modifica el objeto original, lo que puede ser una ventaja o un inconveniente.

C#

/* Sumar 30 días */
var f= DateTime.Today;
var f2 = f.AddDays(30);
Console.WriteLine($"Original {f}  Nueva: {f2}");

/* Restar 1 año */
var f= DateTime.Today;
var f2 =f.AddYears(-1);
Console.WriteLine($"Original {f}  Nueva: {f2}");

En C#, la clase Datetime nos proporciona métodos para operar con la fecha.
A diferencia de PHP, estos métodos devuelven un nuevo objeto de tipo DateTime, manteniendo el objeto original intacto.

JavaScript

/* Sumar 30 días */
var f= new Date();
f.setDate(f.getDate() + 30);
console.log(f);

/* Restar 1 año */
var f= new Date();
f.setFullYear(f.getFullYear() - 1);
console.log(f);

En JavaScript lo que hacemos, es modificar las propiedades del objeto Date().

PostgreSQL

-- Sumar 30 días
SELECT current_date + 30
SELECT cast(current_date + INTERVAL '1 day' AS date)

-- Restar 1 año
SELECT cast(current_date - INTERVAL '1 year' AS date)

En PostgreSQL, podemos sumar o restar días directamente con un entero. También podemos utilizar INTERVAL
Al utilizar INTERVAL, nos devuelve un timestamp. Si lo que queremos es un tipo 'date', hacemos el casting.

Último día del mes

Esta es una operación muy frecuente.
Seguramente que habrás visto código en el que tenemos un array de 12 elementos que contiene los días de los meses; luego selecciona el elemento correspondiente al mes, y si es febrero, se comprueba si el año es bisiesto y devuelve 29 en lugar de 28.

Dependiendo del lenguaje, hay formas más o menos sencillas de calcular el último día del mes.

PHP

echo date('t'); // último día del mes actual

/* Ultimo día del mes de una fecha concreta */
$fecha = new DateTime('2016-02-10');
echo  $fecha->format('t'); // 29
echo $día;

En PHP es muy sencillo,podemos pasar el parámetro 't' la la función date() (utiliza la fecha del sistema), o bien, utilizar el método format() en un objeto DateTime.

C#

class Fechas {
 public static DateTime UltimoDiaMes(DateTime fechaOrigen)
 {
   DateTime fecha = new DateTime(fechaOrigen.Year, fechaOrigen.Month, 1);
   return fecha.AddMonths(1).AddDays(-1);
 }
}

En C#, lo más sencillo es crear un objeto DateTime con el primer día del mes; Luego, sumamos un mes (la fecha se "posiciona" en el día 1 del mes siguiente) y restamos un día.
Esta función de ejemplo recibe como parámetro un objeto DateTime del que partimos para hacer el cálculo.
Si queremos calcular el último día del mes actual llamamos a esta función así:

DateTime fechaUDM = Fechas.UltimoDiaMes(new DateTime());
La variable fechaUDM es un objeto DateTime que almacena al último día del mes actual.

Si sólo queremos el día, lo extraemos del objeto DateTime.
int día = Fechas.UltimoDiaMes(new DateTime()).Day;

JavaScript

var fecha = new Date();
fecha.setMonth(fecha.getMonth() + 1);
fecha.setDate(0);

En JavaScript, el método es similar a C#:
Primero, incrementamos el mes, y luego llamamos a setDate() con el valor 0; esto nos sitúa en el último día del mes anterior. Si el valor es -1 pasaríamos al penúltimo día del mes anterior, y así.

PostgreSQL

SELECT cast(date_trunc('month', current_date) + interval '1 month - 1 day' AS date)

En PostgreSQL, la función date_trunc() con el parámetro 'month' devuelve una fecha correspondiente al primer día del mes. En realidad, lo que hace es recortar la fecha al nivel / precisión que indicamos. En este caso, recorta a nivel de mes, con lo que mantiene el año y el mes del segundo parámetro. Este cálculo lo encontré aquí: PostgreSQL: Date LastDay

Una aclaración: estos ejemplos de código no me los he inventado yo, los podréis encontrar en foros o en la documentación de los distintos lenguajes, más o menos con la misma apariencia.

Coalesce C#

En programación es bastante común que se compruebe si una variable "tiene valor" y si es así, se utiliza, y en caso contrario se u...