Date, Number y Currency en Java

Buenas tardes, en esta entrada vamos a ver un poco el uso de Date, Numbers y Currency en Java.


La API de Java provee un extenso set de clases para ayudarnos a la hora de trabajar con fechas, números y monedas. Cuando finalicemos esta sección deberíamos tener un sólido conocimiento en tareas como crear una nueva fecha y un objeto DateFormat, convertir Strings a Dates y de nuevo, realizar una función Calendar, imprimiendo apropiadamente los valores de moneda formateadas, y hacer todo esto para cualquier localización del globo.

  • Trabajando con Dates, Numbers y Currencies

    Si queremos trabajar con fechas en los diferentes lugares del mundo, tenemos que estar familiarizados con al menos 4 clases del package java.text y java.util. Aquí vamos a ver las 4 clases relacionadas con fechas que tenemos que entender:

    • java.util.Date: La mayoría de los métodos de esta clase ya están obsoletos, pero podemos usar esta clase para trabajar junto a las clases Calendar y DateFormat. Un ainstancia de un Date representa una fecha y el tiempo, hasta milisegundos.
    • java.util.Calendar: Esta clase provee una gran variedad de métodos que nos ayudan a convertir y manipular fechas y horas. Por ejemplo, si queremos añadir un mes a una fecha dada, o buscar que dia de la semana será el día 1 de Enero, los métodos de esta clase nos ayudarán.
    • java.text.DateFormat: Esta clase es usada para formatear fechas no solo de estilos como “01/01/70” o “January 1, 1970”, sino tambien como formatear fechas para diferentes localizaciones en el mundo.
    • java.text.NumberFormat: Esta clase es usada para formatear números y currencies para diferentes localizaciones en el mundo.
    • java.util.Locale: Esta clase es usada en conjunción con DateFormat y numberFormat para formatear fechas, números y monedas para una localización específica. Con la ayuda de la clase Locale seremos capaces de convertir una fecha como “10/10/2005” a “Segunda-feira, 10 de Outubro de 2005”. Si ueremos manipular fechas sin producir una salida formateada, podemos usar la clase Locale directamente con la clase Calendar.
    • Clases relacionadas con Date y Number

      Cuando trabajamos con fechas y números, a menudo usaremos clases entre ellas. Es importante entender como las clases que hemos vito encima se relacionan entre ellas, y cuando usarlas en combinacion las unas con las otras. Por ejemplo, vamos a necesitar saber que si queremos hacer un formato específico para una localización, tenemos que crear primero el objeto Locale antes de hacer el objeto DateFormat, ya que necesitamos el objeto Locale como argumento para el método de DateFormat. En la siguiente tabla vamos a proveer un pequeño resumen de los casos de uso relacionados con las fechas y los números usando estas clases. Esta tabla tambien nos traerá preguntas específicas sobre las clases individuales, y veremos casos específicos de cada clase después.

      Caso de uso Pasos
      Obtener la fecha y hora actual 1.Creamos un Date: Date d = new Date();
      2.Obtenemos el valor: String s = d.toString();
      Obtener un objeto que nos permita realizar calculos de fecha y hora en nuestra localización. 1.Creamos un objeto de la clase Calendar: Calendar c = Calendar.getInstance();
      2.Usamos c.add(…) y c.roll(…) para realizar manipulaciones de datos.
      Obtener un objeto que nos permita realizar calculos en una localización diferente. 1.Creamos un objeto Locale:
      Locale loc = new Locale(language);
      Locale loc = new Locale(language, country);
      2.Creamos un Calendar para ese Locale:
      Calendar c = Calendar.getInstance(loc);
      3.Usamos c.add(…) y c.roll(…) para realizar manipulaciones de fechas y horas
      Obtener un objeto que nos permita realizar calculos de fecha y hora, y entonces formatear la salida en diferentes localizaciones con diferentes estilos de fecha. 1.Creamos un Calendar:
      Calendar c = Calendar.getInstance();
      2.Creamos un Locale para cada localización:
      Locale loc = new Locale(…);
      3.Convertimos Calendar a Date:
      Date d = c.getTime();
      4.Creamos un DateFormat para cada Locale:
      DateFormat df = DateFormat.getDateInstance(style, loc);
      5.Usamos los métodos de format() para crear fechas formateadas:
      String s = df.format(d);
      Obtener un objeto que nos permita formatear numeros y monedas en los diferentes localizaciones: 1.Creamos un Locale para cada localizacion:
      Locale loc = new Locale(…);
      2.Creamos un NumberFormat:
      NumberFormat nf = NumberFormat.getInstance(loc); o
      NumberFormat nf = NumberFormat.getCurrencyInstance(loc);
      3.Usamos el método format() para crear una salida formateada:
      String s = nf.format(someNumber);
    • La Clase Date

      La clase Date tiene un pasado muy comprobado. El diseño de su API no hacía un buen trabajo del manejo de internacionalización y situaciones de localización. En su estado actual, la mayoría de sus métodos ya están obsoletos, y para la mayoría de los propósitos querremos usar la clase Calendar en vez de la clase Date.

      Como hemos mencionado anteriormente, una instancia de la clase Date representa una fecha y una hora. Internamente, la fecha y la hora es guardada en un tipo de dato primitivo long. Específicamente, el long almacena el número de milisegundos (1000 por cada segundo), entre la fecha dada y el 1 de Enero de 1970.

      Vamos a usar la clase Date para ver como de largo puede ser pasar un trillón de milisegundos, empezando desde el 1 de Enero de 1970:

      				import java.util.*;
      				public class TestDates {
      					public static void main(String[] args){
      						Date d1 = new Date(1000000000000L); // Un trillon
      						System.out.println("1st Date " + d1.toString());
      					}
      				}
      				

      En mi JVM, la salida es:

      				1st Date Sun Sep 09 03:46:40 CEST 2001
      				

      Aunque la mayoría de los métodos de Date están ya obsoletos, es aún aceptable usar los métodos getTime y setTime, aunque como veremos mas tarde, es un poco doloroso. Vamos a agregar una hora a nuestra instancia de Date d1, en el ejemplo anterior:

      				import java.util.*;
      				public class TestDates {
      					public static void main(String[] args){
      						Date d1 = new Date(1000000000000L); // Un trillon
      						System.out.println("1st Date " + d1.toString());
      						d1.setTime(d1.getTime() + 3600000); // 3600000 milisegundos / hora
      						System.out.println("new time " + d1.toString());
      					}
      				}
      				

      La salida en mi JVM es:

      				1st Date Sun Sep 09 03:46:40 CEST 2001
      				new time Sun Sep 09 04:46:40 CEST 2001
      				

      Vemos que tanto setTime() como getTime() usamos una escala de milisegundos. Si queremos manipular fechas con la clase Date, esta es nuestra única elección. Si esto ya era dificil, imaginemos cuando se tenían que agregar, por así decirlo, un año a una fecha dada.

      Volveremos a visitar la clase Date mas tarde, pero por ahora la otra cosa que necesitamos saber es que si queremos crear una instancia de Date para representar la hora y fecha actual, tenemos que usar el constructor sin argumentos de Date:

      				Hora actual Thu Jan 24 16:56:58 CET 2013
      				
    • La Clase Calendar

      Acabamos de ver como manipular fechas usando la clase Date, y que es bastante dificil. La clase Calendar está diseñada para hacer la manipulación de fechas mas fácil. Mientras que la clase Calendar tiene millones de campos y métodos, una vez que sepamos como manejar unos cuantos, el resto será fácil.

      Cuando intentamos hacer un primer uso de la clase Calendar podemos ver que es una clase abstracta. No podemos decir:

      				Calendar c = new Calendar(); 	// Ilegal, Calendar es abstract
      				

      Para crear una instancia de Calendar, tenemos que usar uno de los métodos sobrecargados getInstance():

      				Calendar cal = Calendar.getInstance();
      				

      Cuando obtenemos una referencia de Calendar como cal, nuestra referencia de Calendar está actualmente refiriendose a una instancia de una subclase concreta de Calendar. No podemos saber con certeza que subclase vamos a obtener (java.util.GregorianCalendar es lo que normalmente obtendremos), pero no nos importa. Estaremos usando la API de Calendar.

      Bien, ahora que tenemos una instancia de Calendar, vamos a ir a nuestro ejemplo anterior, y encontrar en que dia de la semana cae nuestro trillón de milisegundos, y vamos a agregar un mes a esa fecha:

      				public class TestDates {
      					public static void main(String[] args) {
      						Date d1 = new Date(1000000000000L);
      						System.out.println("1st date " + d1.toString());
      				
      						Calendar c = Calendar.getInstance();
      						c.setTime(d1);			// #1
      				
      						if (Calendar.MONDAY == c.getFirstDayOfWeek()) {		// #2
      							System.out.println("Monday is the first day of the week");
      						}
      						System.out.println("trillionth milli day of the week is "
      								+ c.get(Calendar.DAY_OF_WEEK));		// #3
      						
      						c.add(Calendar.MONTH, 1);		// #4
      						Date d2 = c.getTime();		// #5
      						System.out.println("new date " + d2.toString());
      					}
      				}
      				

      Vamos a echar un vistazo al programa, enfocandonos en las lineas comentadas:

      1. Asignamos el Date d1 a la instancia de Calendar c.
      2. Usamos el campo de MONDAY para determinar si en nuestra JVM, es considerado como el primer día de la semana. La clase Calendar provee campos similares para días de la semana, meses, el día del mes, el día del año, y así con el resto.
      3. Usamos el campo DAY_OF_THE_WEEK para encontrar el cia de la semana en el que cae nuestro trillón de milisegundos.
      4. Hemos usado métodos setter y getter para guiarnos de forma intuitiva. Ahora usamos el método de Calendar add(). Estos métodos son realmente potentes y nos permiten añadir o quitar unidades de tiempo apropiadamente para el campo de Calendar que hemos especificado. Por ejemplo:
        						c.add(Calendar.HOUR, -4);	// Extrae 4 horas del valor de C
        						c.add(Calendar.YEAR, 2);	// Añade 2 años al valor de c
        						c.add(Calendar.DAY_OF_WEEK, -2);	// Extrae 2 dias del valor de c
        						
      5. Convierte el valor de c de nuevo a una instancia de Date.

      Otro de los métodos de Calendar que tenemos que saber es el método roll(). Este método actúa como el método add(), excepto que cuando una parte de Date es incrementada o decrementada, las partes mayores de Date no serán incrementadas o decrementadas. Por ejemplo:

      				public class TestDates {
      					public static void main(String[] args) {
      						Calendar c = Calendar.getInstance();
      						c.roll(Calendar.MONTH, 13);
      						Date d4 = c.getTime();
      						System.out.println("new date " + d4.toString());
      					}
      				}
      				

      Vemos que el año no cambia, incluso aunque hayamos agregado 13 meses a una fecha de Enero. De una manera similar, invocar roll() con HOUR no cambiará la fecha, el mes, o el año.

    • La clase DateFormat

      Ya que hemos aprendido a crear fechas y manipularlas, vamos a ver como podemos formatearlas. Aquí vamos a ver un ejemplo de como una fecha puede ser formateada de diferentes maneras:

      				import java.text.DateFormat;
      				import java.util.Date;
      				
      				public class DateFormatTest {
      					public static void main (String[] args){
      						Date d1 = new Date(1000000000000L);
      						
      						DateFormat[] dfa = new DateFormat[6];
      						dfa[0] = DateFormat.getInstance();
      						dfa[1] = DateFormat.getDateInstance();
      						dfa[2] = DateFormat.getDateInstance(DateFormat.SHORT);
      						dfa[3] = DateFormat.getDateInstance(DateFormat.MEDIUM);
      						dfa[4] = DateFormat.getDateInstance(DateFormat.LONG);
      						dfa[5] = DateFormat.getDateInstance(DateFormat.FULL);
      						
      						for (DateFormat df : dfa){
      							System.out.println(df.format(d1));
      						}
      					}
      				}
      				

      En nuestra JVM produce la salida:

      				9/09/01 3:46
      				09-sep-2001
      				9/09/01
      				09-sep-2001
      				9 de septiembre de 2001
      				domingo 9 de septiembre de 2001
      				

      Examinando este código vemos un par de cosas. Lo primero, parece que DateFormat es otro tipo de clase abstract, por lo que no podemos usar new para crear instancias de DateFormat. En este caso hemos usado 2 métodos de factoría, getInstance() y getDateInstance(). Vemos que getDateInstance() está sobrecargado, cuando discutamos los Locale, veremos otra versión de getDateInstance() que tenemos que entender.

      Lo siguiente, hemos usado campos static de la clase DateFormat para personalizar nuestras diferentes instancias de DateFormat. Cada uno de estos campos static representan un estilo de formateo. En este cao parece que la versión sin argumentos de getDateInstance() nos dá el mismo estilo que la version de MEDIUM del método, pero no es una regla dificil y rápida. Finalmente, hemos usado el método format() para crear Strings que representan la versión formateada apropiadamente de Date con la que hemos estado trabajando.

      El último método con el que tendríamos que estar familiarizados es el método parse(). El método parse() coge un String formateado con un estilo de la instancia de DateFormat que está siendo usado, y convierte el String en un objeto Date. Como podemos imaginar, el método parse() tiene muchos riesgos ya que puede recibir un String mal formado.Por esto, el método parse() puede lanzar un ParseException. El siguiente código crea una instancia de Date, usando DateFormat.format() para convertirlo en un String, y entonces usa DateFormat.parse() para cambiarlo de nuevo a Date.

      				public class DateFormatTest {
      					public static void main (String[] args){
      						Date d1 = new Date(1000000000000L);
      						System.out.println("d1 = " + d1.toString());
      						
      						DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
      						
      						String s = df.format(d1);
      						System.out.println(s);
      						
      						try{
      							Date d2 = df.parse(s);
      							System.out.println("parsed = " + d2.toString());
      						}catch (ParseException pe){
      							System.out.println("Parse exc");
      						}
      					}
      				}
      				

      En mi JVM produce lo siguiente:

      				d1 = Sun Sep 09 03:46:40 CEST 2001
      				9/09/01
      				parsed = Sun Sep 09 00:00:00 CEST 2001				
      				
    • La Clase Locale

      Antes dijimos que una gran parte de esta parte consistía en la habilidad de hacer diferentes tareas de internacionalización. La clase Locale es nuestro ticket a la dominación del mundo. Ambas clases, Dateformat y NumberFormat pueden usar instancias de Locale para personalizar el formato de salida para que sea específico para una localización. ¿Como define Java un Locale?. La API dice que un Locale es “Una región específica geográficamente, politica o cultural”. Los 2 constructores de Locale que tenemos que entender para usar con mucha mas frecuencia son:

      				Locale (String language);
      				Locale(String language, String country);
      				

      El argumento language representa el Código de Lenguaje ISO 639, por ejemplo si queremos dar formato a nuestras fechas o números en Walloon (lengua usada algunas veces en el sur de Bélgica), usaríamos “wa” como argumento. Hay mas de 500 Códigos de Lenguaje ISO, incluyendo el Klingon (“tlh”), aunque por desgracia Java aún no soporta el Klingon.

      Vamos a ver como podemos usar estos códigos. Si queremos representar el Italiano básico en nuestra aplicación, todo lo que necesitamos es el código del lenguaje. Si, por otro lado, queremos representar el Italiano usado en Suiza, deberíamos indicar que el pais es Suiza (el código de pais es “CH”), pero el lenguaje es Italiano:

      				Locale locPT = new Locale("it");	// Italiano
      				Locale locBR = new Locale("it", "CH");	// Suiza
      				

      Usando estas 2 localizaciones en una fecha podríamos obtener la siguiente salida:

      				sabato 1 ottobre 2005
      				sabato, 1. ottobre 2005
      				

      Ahora vamos a poner todo esto en un objeto Calendar, establecemos una fecha, y entonces lo convertimos a Date. Después de esto tendremos el objeto Date y lo imprimiremos usando algunas localizaciones del mundo:

      				public class LocaleTest {
      					public static void main (String[] args){
      						Calendar c = Calendar.getInstance();
      						c.set(2013,  0, 24);	// 24 Enero de 2013
      						
      						Date d2 = c.getTime();
      						
      						Locale locIT = new Locale("it", "IT");		// Italia
      						Locale locPT = new Locale("pt");			// Portugal
      						Locale locBR = new Locale("pt", "BR");		// Brasil
      						Locale locIN = new Locale("hi", "IN");		// India
      						Locale locJA = new Locale("ja");			// Japon
      						
      						DateFormat dfUS = DateFormat.getInstance();
      						System.out.println("US		" + dfUS.format(d2));
      						
      						DateFormat dfUSfull = DateFormat.getDateInstance(DateFormat.FULL);
      						System.out.println("US full  " + dfUSfull.format(d2));
      						
      						DateFormat dfIT = DateFormat.getDateInstance(DateFormat.FULL, locIT);
      						System.out.println("Italy  " + dfIT.format(d2));
      						
      						DateFormat dfPT = DateFormat.getDateInstance(DateFormat.FULL, locPT);
      						System.out.println("Portugal    " + dfPT.format(d2));
      						
      						DateFormat dfBR = DateFormat.getDateInstance(DateFormat.FULL, locBR);
      						System.out.println("Brasil     " + dfBR.format(d2));
      						
      						DateFormat dfIN = DateFormat.getDateInstance(DateFormat.FULL, locIN);
      						System.out.println("India    " + dfIN.format(d2));
      						
      						DateFormat dfJA = DateFormat.getDateInstance(DateFormat.FULL, locJA);
      						System.out.println("Japon    " + dfJA.format(d2));
      					}
      				}
      				

      Esto nos dá la salida en mi JVM:

      				US		24/01/13 18:33
      				US full		jueves 24 de enero de 2013
      				Italy	giovedì 24 gennaio 2013
      				Portugal		Quinta-feira, 24 de Janeiro de 2013
      				Brasil		Quinta-feira, 24 de Janeiro de 2013
      				India		???????, ?? ?????, ????
      				Japon		2013?1?24?
      				

      Hay un par de métodos en Locale (getDisplayCountry() y getDisplayLanguage()) que tenemos que conocer. Estos métodos nos permiten crear Strings que representan un pais y un lenguaje en términos de localización por defecto y otra localización:

      				public class LocaleTest {
      					public static void main (String[] args){
      						Calendar c = Calendar.getInstance();
      						c.set(2013, 0, 24);
      						Date d2 = c.getTime();
      						
      						Locale locBR = new Locale("pt", "BR");	// Brasil
      						Locale locDK = new Locale("da", "DK");	// Danés
      						Locale locIT = new Locale("it", "IT");	// Italia
      						
      						System.out.println("def " + locBR.getDisplayCountry());
      						System.out.println("loc " + locBR.getDisplayCountry(locBR));
      						
      						System.out.println("def " + locDK.getDisplayLanguage());
      						System.out.println("loc " + locDK.getDisplayLanguage(locDK));
      						System.out.println("D>I " + locDK.getDisplayLanguage(locIT));
      					}
      				}
      				

      En mi JVM el resultado es:

      				def Brasil
      				loc Brasil
      				def danés
      				loc Dansk
      				D>I danese
      				
    • La Clase NumberFormat

      Vamos a terminar esta entrada con la clase NumberFormat. Como la clase DateFormat, NumberFormat es abstracta, por lo que podemos usar tanto getInstance() como getCurrencyInstance() para crear un objeto NumberFormat. Podemos usar esta clase para dar formato a numeros o valores de moneda:

      				public class NumberFormatTest {
      					public static void main (String[] args){
      						float f1 = 123.4567f;
      						Locale locFR = new Locale("fr");	// France
      						NumberFormat[] nfa = new NumberFormat[4];
      						
      						nfa[0] = NumberFormat.getInstance();
      						nfa[1] = NumberFormat.getInstance(locFR);
      						nfa[2] = NumberFormat.getCurrencyInstance();
      						nfa[3] = NumberFormat.getCurrencyInstance(locFR);
      						
      						for (NumberFormat nf : nfa){
      							System.out.println(nf.format(f1));
      						}
      					}
      				}
      				

      En mi JVM produce la salida:

      				123,457
      				123,457
      				123,46 €
      				123,46 €
      				

      No nos tenemos que preocupar si no se nos muestran los simbolos de los francos, las libras, los drachmas etc. No tenemos porque conocer los simbolos para cada moneda. Aquí hay un pequeño código que usa los métodos getMaximumFractionDigits(), setMaximumFractionDigits(), parse(), y setParseIntegerOnly():

      				public class NumberFormatTest {
      					public static void main (String[] args){
      						float f1 = 123.45678f;
      						NumberFormat nf = NumberFormat.getInstance();
      						System.out.print(nf.getMaximumFractionDigits() + " ");
      						System.out.print(nf.format(f1) + " ");
      						
      						nf.setMaximumFractionDigits(5);
      						System.out.println(nf.format(f1) + " ");
      						
      						try{
      							nf.setParseIntegerOnly(false);
      							System.out.println(nf.parse("1234,567"));
      							nf.setParseIntegerOnly(true);
      							System.out.println(nf.parse("1234.567"));
      						}catch(ParseException pe){
      							System.out.println("parse exc");
      						}
      					}
      				}
      				

      En mi JVM la salida es:

      				3 123,457 123,4567 
      				1234.567
      				1234567
      				

      Vemos que en este caso, el número inicial de los fígitos fraccionarios por defecto de NumberFormat es 3: y entonces el método format() redondea el valor de f1, no lo trunca.Despues de cambiar los digitos fraccionales de nf, el valor entero de f1 es mostrado. Luego, vemos que el método parse() debe ejecutarse en un bloque try/catch y el método setParseIntegerOnly() usa un boolean y en este caso, retorna solo la parte entera de Strings formateada como numeros punto-flotante.

    Como hemos visto, las diferentes clases que hemos cubierto en este objetivo son abstract. En adición, para todas estas clases, la funcionalidad de cada instancia es establecida en tiempo de creación. EN la tabla siguiente vamos a ver los métodos y constructores usados para crear instancias de todas las clases que hemos discutido en esta sección.

    Clase Opciones de Creacion Clave de la Instancia
    util.date new Date();
    new Date(long millisecondsSince010170);
    util.Calendar Calendar.getInstance();
    Calendar.getInstance(Locale);
    util.Locale Locale.getDefault();
    new Locale(String language);
    new Locale(String language, String country);
    text.DateFormat DateFormat.getInstance();
    DateFormat.getDateInstance();
    DateFormat.getDateInstance(style);
    DateFormat.getDateInstance(stryle, Locale);
    text.NumberFormat NumberFormat.getInstance();
    NumberFormat.getInstance(Locale);
    NumberFormat.getNumberInstance();
    NumberFormat.getNumberInstance(Locale)
    NumberFormat.getCurrencyInstance();
    NumberFormat.getCurrencyInstance(Locale);


Tras esta larga sección hemos visto el uso básico de las clases mas importantes para tratar con Fechas, el formato de fechas, formato de numeros, formato de moneda etc. Es algo importante saber como reaccionar en cada caso y como saber aplicarlo.

Sin mas, cualquier aporte o corrección es bienvenido.

Saludos!!!