saber - rango de fechas en java
¿Cómo se almacenan los intervalos de fechas, que en realidad son marcas de tiempo? (11)
Alan tiene razón, el tiempo de Joda es genial. java.util.Date y Calendar son una lástima.
Si necesita marcas de tiempo, utilice el tipo de fecha del oráculo con la hora, asigne un nombre a la columna con algún tipo de sufijo como _tmst. Cuando lees los datos en Java, obténgalos en un objeto joda Time Date Time. para asegurarse de que la zona horaria sea correcta, considere que hay tipos de datos específicos en Oracle que almacenarán las marcas de tiempo con la zona horaria. O puede crear otra columna en la tabla para almacenar la ID de la zona horaria. Los valores para la ID de la zona horaria deben ser ID de nombre completo estándar para las zonas horarias. Consulte http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang.String%. 29 . Si utiliza otra columna para el TZ dta, cuando lea los datos en Java, utilice el objeto DateTime, pero configure la zona horaria en el objeto DateTime utilizando .withZoneRetainFields para establecer la zona horaria.
Si solo necesita los datos de fecha (sin fecha y hora), utilice el tipo de fecha en la base de datos sin tiempo. de nuevo llámalo bien. en este caso use el objeto DateMidnight de jodatime.
línea inferior: aproveche el sistema de tipos de la base de datos y el idioma que está utilizando. Aprende y aprovecha los beneficios de tener una sintaxis expresiva de API y lenguaje para lidiar con tu problema.
Java y Oracle tienen un tipo de marca de tiempo llamado Fecha. Los desarrolladores tienden a manipular estos como si fueran fechas de calendario , lo que he visto causar desagradables errores únicos.
Para una cantidad de fecha básica, simplemente puede cortar la porción de tiempo en la entrada, es decir, reducir la precisión. Pero si lo hace con un rango de fechas (por ejemplo: 9 / 29-9 / 30 ), la diferencia entre estos dos valores es de 1 día, en lugar de 2. Además, las comparaciones de rango requieren 1) una operación truncada:
start < trunc(now) <= end
, o 2) aritmética:start < now < (end + 24hrs)
. No es horrible, pero no SECO .Una alternativa es usar marcas de tiempo verdaderas: 9/29 00:00:00 - 10/1 00:00:00. (medianoche a medianoche, por lo que no incluye ninguna parte de Oct). Ahora las duraciones son intrínsecamente correctas, y las comparaciones de rango son más simples:
start <= now < end
. Ciertamente más limpio para el procesamiento interno, sin embargo, las fechas de finalización deben convertirse con la entrada inicial (+1) y la salida (-1), suponiendo una metáfora de fecha del calendario en el nivel de usuario.
¿Cómo manejas los rangos de fechas en tu proyecto? ¿Hay otras alternativas? Estoy particularmente interesado en cómo manejas esto en los lados de la ecuación de Java y de Oracle.
Así es como lo hacemos.
Usa marcas de tiempo
Use Intervalos a medio abrir para comparar:
start <= now < end
.
Ignore a los llorones que insisten en que BETWEEN es de alguna manera esencial para el SQL exitoso.
Con esto, una serie de intervalos de fechas es realmente fácil de auditar. El valor de la base de datos para 9/30 to 10/1
abarca un día (9/30). El inicio del próximo intervalo debe ser igual al final del intervalo anterior. Esa regla de interval[n-1].end == interval[n].start
es útil para la auditoría.
Cuando lo visualiza, si lo desea, puede mostrar el start
y el end
formateados -1. Resulta que puedes educar a las personas para que entiendan que el "final" es en realidad el primer día en que la regla ya no es verdadera. Por lo tanto, "9/30 a 10/1" significa "válido a partir del 9/30, ya no es válido a partir de 10/1".
En función de su primera oración, está tropezando con una de las "características" ocultas (es decir, errores) de Java: java.util.Date
debería haber sido inmutable, pero no lo es. (Java 7 promete arreglar esto con una nueva API de fecha / hora.) Casi todas las aplicaciones de la empresa cuentan con varios patrones temporales , y en algún momento deberá realizar operaciones aritméticas de fecha y hora.
Idealmente, podría usar el tiempo Joda , que es utilizado por Google Calendar. Si no puede hacer esto, supongo que una API que consiste en un contenedor alrededor de java.util.Date
con métodos computacionales similares a Grails / Rails, y de un rango de su contenedor (es decir, un par ordenado que indica el inicio y el final de un período de tiempo) será suficiente.
En mi proyecto actual (una aplicación de cronometraje HR) intentamos normalizar todas nuestras fechas en la misma zona horaria para Oracle y Java. Afortunadamente, nuestros requisitos de localización son livianos (= 1 zona horaria es suficiente). Cuando un objeto persistente no necesita una precisión mayor que un día, usamos la marca de tiempo a partir de la medianoche. Yo iría más lejos e insistiría en tirar los mili segundos adicionales a la granularidad más grosera que un objeto persistente puede tolerar (simplificará el procesamiento).
En segundo lugar lo que S.Lott explicó. Tenemos un paquete de productos que hace un amplio uso de los rangos de fechas y ha sido una de nuestras lecciones aprendidas para trabajar con rangos como ese. Por cierto, llamamos a la fecha de finalización exclusiva de la fecha de finalización si ya no forma parte del rango (IOW, un intervalo medio abierto). Por el contrario, es una fecha de finalización inclusiva si cuenta como parte del rango que solo tiene sentido si no hay una porción de tiempo.
Los usuarios generalmente esperan entrada / salida de rangos de fechas inclusivas. En cualquier caso, convierta la entrada del usuario tan pronto como sea posible a rangos de fechas de finalización exclusivos, y convierta cualquier intervalo de fechas lo más tarde posible cuando deba mostrarse al usuario.
En la base de datos, siempre almacene rangos exclusivos de fechas de finalización. Si hay datos heredados con rangos de fecha de finalización inclusivos, migrelos en el DB si es posible o conviértelos en un rango de fecha de finalización exclusivo tan pronto como sea posible cuando se lean los datos.
Estoy almacenando todas las fechas en milisegundos. No utilizo los campos timestamps / datetime en absoluto.
Entonces, tengo que manipularlo todo lo posible. Significa que no utilizo las palabras clave ''antes'', ''después'', ''ahora'' en mis consultas SQL.
No veo los tipos de datos Interval publicados todavía.
Oracle también tiene tipos de datos para su escenario exacto. También hay INTERVALO A MES y DÍA DE INTERVALO A SEGUNDO tipo de datos en Oracle.
De los documentos 10gR2.
INTERVAL AÑO AL MES almacena un período de tiempo usando los campos de fecha y hora de AÑO y MES. Este tipo de datos es útil para representar la diferencia entre dos valores de fecha y hora cuando solo los valores de año y mes son significativos.
AÑO INTERVALO [(año_precisión)] AL MES
donde año_precisión es la cantidad de dígitos en el campo de fecha y hora de AÑO. El valor predeterminado de year_precision es 2.
DÍA DE INTERVALO A SEGUNDO Tipo de datos
INTERVAL DAY to SECOND almacena un período de tiempo en términos de días, horas, minutos y segundos. Este tipo de datos es útil para representar la diferencia precisa entre dos valores de fecha y hora.
Especifique este tipo de datos de la siguiente manera:
DÍA DE INTERVALO [(day_precision)] A SECOND [(fractional_seconds_precision)]
dónde
day_precision es la cantidad de dígitos en el campo DAY datetime. Los valores aceptados son de 0 a 9. El valor predeterminado es 2.
fractional_seconds_precision es la cantidad de dígitos en la parte fraccional del SEGUNDO campo de fecha y hora. Los valores aceptados son de 0 a 9. El valor predeterminado es 6.
Tiene una gran flexibilidad al especificar valores de intervalo como literales. Consulte "Interval Literals" para obtener información detallada sobre especificar valores de intervalo como literales. Consulte también "Ejemplos de fecha y hora e intervalo" para ver un ejemplo con intervalos.
Oracle tiene el tipo de datos TIMESTAMP . Almacena el año, el mes y el día del tipo de datos DATE, más los valores de hora, minuto, segundo y segundo fraccionario.
Aquí hay un hilo en asktom.oracle.com sobre la aritmética de la fecha.
Para el lado de Java, use cualquiera de los siguientes:
- Biblioteca Joda-Time
- paquete java.time (en Java 8)
Ambos pueden convertir de ida y vuelta a java.sql.Timestamp.
Joda-Time
Joda-Time ofrece 3 clases para representar un lapso de tiempo: Intervalo, Duración y Período.
El estándar ISO 8601 especifica cómo formatear cadenas que representan una Duración y un Intervalo . Joda-Time analiza y genera tales cadenas.
La zona horaria es una consideración crucial. Su base de datos debe almacenar sus valores de fecha y hora en UTC. Pero su lógica comercial puede necesitar considerar zonas horarias. El comienzo de un "día" depende de la zona horaria. Por cierto, use nombres de zona horaria adecuados en lugar de códigos de 3 o 4 letras.
La respuesta correcta de S.Lott aconseja sabiamente usar la lógica de Half-Open, ya que generalmente funciona mejor para el trabajo de fecha y hora. El comienzo de un lapso de tiempo es inclusivo, mientras que el final es exclusivo . Joda-Time usa lógica medio abierta en sus métodos.
DateTimeZone timeZone_NewYork = DateTimeZone.forID( "America/New_York" );
DateTime start = new DateTime( 2014, 9, 29, 15, 16, 17, timeZone_NewYork );
DateTime stop = new DateTime( 2014, 9, 30, 1, 2, 3, timeZone_NewYork );
int daysBetween = Days.daysBetween( start, stop ).getDays();
Period period = new Period( start, stop );
Interval interval = new Interval( start, stop );
Interval intervalWholeDays = new Interval( start.withTimeAtStartOfDay(), stop.plusDays( 1 ).withTimeAtStartOfDay() );
DateTime lateNight29th = new DateTime( 2014, 9, 29, 23, 0, 0, timeZone_NewYork );
boolean containsLateNight29th = interval.contains( lateNight29th );
Volcado a la consola ...
System.out.println( "start: " + start );
System.out.println( "stop: " + stop );
System.out.println( "daysBetween: " + daysBetween );
System.out.println( "period: " + period ); // Uses format: PnYnMnDTnHnMnS
System.out.println( "interval: " + interval );
System.out.println( "intervalWholeDays: " + intervalWholeDays );
System.out.println( "lateNight29th: " + lateNight29th );
System.out.println( "containsLateNight29th: " + containsLateNight29th );
Cuando se ejecuta ...
start: 2014-09-29T15:16:17.000-04:00
stop: 2014-09-30T01:02:03.000-04:00
daysBetween: 0
period: PT9H45M46S
interval: 2014-09-29T15:16:17.000-04:00/2014-09-30T01:02:03.000-04:00
intervalWholeDays: 2014-09-29T00:00:00.000-04:00/2014-10-01T00:00:00.000-04:00
lateNight29th: 2014-09-29T23:00:00.000-04:00
containsLateNight29th: true
Según mis experiencias, existen cuatro formas principales de hacerlo:
1) Convierta la fecha en un entero de época (segundos desde el 1 de enero de 1970) y guárdela en la base de datos como un número entero.
2) Convierta la fecha en un entero YYYYMMDDHHMMSS y guárdelo en la base de datos como un número entero.
3) Almacenarlo como una fecha
4) Almacenarlo como una cadena
Siempre me he quedado con 1 y 2, porque le permite realizar operaciones aritméticas rápidas y simples con la fecha y no depender de la funcionalidad de la base de datos subyacente.
Todas las fechas se pueden almacenar sin ambigüedad como marcas de tiempo GMT (es decir, sin zona horaria o dolores de cabeza) guardando el resultado de getTime () como un número entero largo.
En los casos en que se necesitan manipulaciones de día, semana, mes, etc., cuando el rendimiento de la consulta es primordial, las marcas de tiempo (normalizadas a una granularidad mayor que milisegundos) se pueden vincular a una tabla de desglose de fechas que tiene columnas para el día , semana, mes, etc. valores para que las costosas funciones de fecha / hora no tengan que ser utilizadas en las consultas.
Utilizo el tipo de datos de fecha de Oracle y educo a los desarrolladores sobre el tema de los componentes de tiempo que afectan las condiciones de contorno.
Una restricción de base de datos también evitará la especificación accidental de un componente de tiempo en una columna que no debería tener ninguno y también le indica al optimizador que ninguno de los valores tiene un componente de tiempo.
Por ejemplo, la restricción CHECK (MY_DATE = TRUNC (MY_DATE)) impide que un valor con un tiempo distinto de 00:00:00 se coloque en la columna my_date, y también permite a Oracle inferir que un predicado como MY_DATE = TO_DATE ('' 2008-09-12 15:00:00 '') nunca será cierto, y por lo tanto no se devolverán filas de la tabla porque se puede ampliar a:
MY_DATE = TO_DATE(''2008-09-12 15:00:00'') AND
TO_DATE(''2008-09-12 15:00:00'') = TRUNC(TO_DATE(''2008-09-12 15:00:00''))
Esto es automáticamente falso, por supuesto.
Aunque a veces es tentador almacenar fechas como números como 20080915, esto puede causar problemas de optimización de consultas. Por ejemplo, ¿cuántos valores legales hay entre 20,071,231 y 20,070,101? ¿Qué tal entre las fechas 31-Dic-2007 abnd 01-Jan-2008? También permite ingresar valores ilegales, como 20070100.
Entonces, si tiene fechas sin componentes de tiempo, entonces la definición de un rango se vuelve fácil:
select ...
from ...
where my_date Between date ''2008-01-01'' and date ''2008-01-05''
Cuando hay un componente de tiempo, puede hacer una de las siguientes cosas:
select ...
from ...
where my_date >= date ''2008-01-01'' and
my_date < date ''2008-01-06''
o
select ...
from ...
where my_date Between date ''2008-01-01''
and date ''2008-01-05''-(1/24/60/60)
Tenga en cuenta el uso de (1/24/60/60) en lugar de un número mágico. Es bastante común en Oracle realizar la aritmética de fechas al agregar fracciones definidas de un día ... 3/24 durante tres horas, 27/24/60 durante 27 minutos. Las matemáticas de Oracle de este tipo son exactas y no sufren errores de redondeo, por lo que:
select 27/24/60 from dual;
... da 0.01875, no 0.01874999999999 o lo que sea.