Pregunta

Tanto Java como Oracle tienen una marca de tiempo escriba llamado Fecha.Los desarrolladores tienden a manipularlos como si fueran calendario fechas, que he visto que causan errores únicos y desagradables.

  1. Para una cantidad de fecha básica, simplemente puede cortar la porción de tiempo al ingresarla, es decir, reducir la precisión.Pero si haces eso 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 rangos requieren 1) una operación de truncamiento: start < trunc(now) <= end, o 2) aritmética: start < now < (end + 24hrs).No es horrible, pero no SECO.

  2. Una alternativa es utilizar marcas de tiempo verdaderas: 9/29 00:00:00 - 10/1 00:00:00.(de medianoche a medianoche, por lo que no incluye ninguna parte de octubre).Ahora las duraciones son intrínsecamente correctas y las comparaciones de rangos son más sencillas: start <= now < end.Ciertamente más limpio para el procesamiento interno, sin embargo, las fechas de finalización deben convertirse en la entrada inicial (+1) y en la salida (-1), suponiendo una metáfora de fecha del calendario a nivel de usuario.

¿Cómo maneja los rangos de fechas en su proyecto?¿Existen otras alternativas?Estoy particularmente interesado en cómo maneja esto tanto en el lado de la ecuación de Java como de Oracle.

¿Fue útil?

Solución

Así es como lo hacemos.

  1. Utilice marcas de tiempo.

  2. Utilice intervalos semiabiertos para comparar: start <= now < end.

Ignore a los quejosos que insisten en que BETWEEN es de alguna manera esencial para un SQL exitoso.

Con esto, es realmente fácil auditar una serie de rangos de fechas.El valor de la base de datos para 9/30 to 10/1 abarcar un día (30/9).El inicio del siguiente intervalo debe ser igual al final del intervalo anterior.Eso interval[n-1].end == interval[n].start La regla es útil para la auditoría.

Cuando visualiza, si lo desea, puede mostrar el formato start y end-1.Resulta que tu poder educar a la gente para que comprenda que el "fin" es en realidad el primer día en que la regla ya no es cierta.Entonces, "30/9 al 1/10" significa "válido a partir del 30/9, ya no válido a partir del 1/10".

Otros consejos

Oracle tiene el tipo de datos TIMESTAMP . Almacena el año, mes y 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 aritmética de fechas.

Secundo lo que explicó S.Lott. Tenemos un conjunto de productos que hace un amplio uso de los rangos de fecha y hora y ha sido una de nuestras lecciones aprendidas trabajar con rangos como ese. Por cierto, llamamos a la fecha de finalización exclusiva 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, lo que solo tiene sentido si no hay una porción de tiempo.

Los usuarios generalmente esperan entradas / salidas de rangos de fechas inclusivos. En cualquier caso, convierta la entrada del usuario lo antes posible en rangos de fecha de finalización exclusivos, y convierta cualquier rango de fecha lo más tarde posible cuando deba mostrarse al usuario.

En la base de datos, siempre almacene rangos de fecha de finalización exclusivos. Si hay datos heredados con rangos de fecha de finalización incluidos, migre en la base de datos si es posible o conviértalos a un rango de fecha de finalización tan pronto como sea posible cuando se lean los datos.

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 la 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 dice al optimizador que ninguno de los valores tiene un componente de tiempo.

Por ejemplo, la restricción CHECK (MY_DATE = TRUNC (MY_DATE)) evita que un valor con un tiempo diferente a 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á verdadero y, por lo tanto, no se devolverá ninguna fila de la tabla porque se puede expandir 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 y 01-ene-2008? También permite ingresar valores ilegales, como 20070100.

Entonces, si tiene fechas sin componentes de tiempo, entonces definir 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 realizar una de las siguientes acciones:

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 (24/1/60/60) en lugar de un número mágico. Es bastante común en Oracle realizar aritmética de fechas agregando 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, así que:

select 27/24/60 from dual;

... da 0.01875, no 0.01874999999999 o lo que sea.

Todavía no veo los tipos de datos de intervalo publicados.

Oracle también tiene tipos de datos para su escenario exacto. También hay tipos de datos AÑO DE INTERVALO AL MES e DÍA DE INTERVALO AL SEGUNDO en Oracle.

De los documentos 10gR2.

  

AÑO DE INTERVALO AL MES almacena un período   de tiempo usando el AÑO y el MES   campos de fecha y hora. 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   significativo.

     

AÑO DE INTERVALO [(año_precisión)] TO   MES

     

donde year_precision es el número de   dígitos en el campo de fecha y hora AÑO. los   el valor predeterminado de year_precision es 2.

     

DÍA DE INTERVALO AL SEGUNDO TIPO DE DATOS

     

DÍAS DE INTERVALO AL SEGUNDO almacena un período   de tiempo en términos de días, horas,   minutos y segundos. Este tipo de datos es   útil para representar lo preciso   diferencia entre dos fecha y hora   valores.

     

Especifique este tipo de datos de la siguiente manera:

     

DÍA DE INTERVALO [(día_precisión)] TO   SEGUNDO   [(fraccional_segundos_precisión)]

     

donde

     

day_precision es el número de dígitos   en el campo DAY datetime. Aceptado   los valores son de 0 a 9. El valor predeterminado es 2.

     

fraccional_segundos_precisión es el   número de dígitos en el fraccional   parte del SEGUNDO campo de fecha y hora.   Los valores aceptados son de 0 a 9. El   el valor predeterminado es 6.

     

Tienes mucha flexibilidad   al especificar valores de intervalo como   literales Consulte & Quot; Intervalo   Literales & Quot; para obtener información detallada sobre   especificar valores de intervalo como literales.   Ver también & Quot; Fecha y hora e intervalo   Ejemplos & Quot; para un ejemplo usando   intervalos.

Según mis experiencias, hay cuatro formas principales de hacerlo:

1) Convierta la fecha en un entero de época (segundos desde el 1 de enero de 1970) y almacénelo en la base de datos como un entero.

2) Convierta la fecha a un número entero AAAAMMDDHHMMSS y almacénelo en la base de datos como un número entero.

3) Almacénelo como una fecha

4) Almacénelo 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 confiar en la funcionalidad de la base de datos subyacente.

Según su primera oración, se topa 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 empresariales cuentan con varias temporal patrones , y en algún momento tendrá que hacer operaciones aritméticas en fecha y hora.

Idealmente, podría usar Joda time , que es utilizado por Google Calendar. Si no puede hacer esto, supongo que una API que consiste en un contenedor alrededor de <=> 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 tiempo período) será suficiente.

En mi proyecto actual (una aplicación de cronometraje de recursos humanos) tratamos de normalizar todas nuestras fechas a la misma zona horaria tanto para Oracle como para Java. Afortunadamente, nuestros requisitos de localización son livianos (= 1 zona horaria es suficiente). Cuando un objeto persistente no necesita una precisión más fina que un día, usamos la marca de tiempo a partir de la medianoche. Iría más allá e insistiría en tirar los milisegundos adicionales a la granularidad más gruesa que un objeto persistente puede tolerar (hará que su procesamiento sea más simple).

Todas las fechas se pueden almacenar inequívocamente como marcas de tiempo GMT (es decir, sin zona horaria o dolores de cabeza que ahorran luz del día) almacenando el resultado de getTime () como un entero largo.

En los casos en que se necesitan manipulaciones de día, semana, mes, etc. en las consultas de la base de datos, y 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 fecha que tiene columnas para los valores de día, semana, mes, etc., de modo que las costosas funciones de fecha / hora no tengan que usarse en consultas.

Alan tiene razón: el tiempo de Joda es genial. java.util.Date y Calendar son una pena.

Si necesita marcas de tiempo, use el tipo de fecha oracle con la hora, nombre la columna con algún tipo de sufijo como _tmst. Cuando lea los datos en Java, póngalos en un objeto DateTime de tiempo joda. 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 zona horaria. Los valores para la ID de zona horaria deben ser la 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 usa otra columna para el TZ dta, cuando lea los datos en Java use el objeto DateTime pero configure la zona horaria en el objeto DateTime usando .withZoneRetainFields para configurar la zona horaria.

Si solo necesita los datos de fecha (sin marca de tiempo), use el tipo de fecha en la base de datos sin hora. otra vez nombrarlo bien. en este caso, use el objeto DateMidnight de jodatime.

conclusión: aproveche el sistema de tipos de la base de datos y el idioma que está utilizando. Apréndalos y aproveche los beneficios de tener una API expresiva y una sintaxis de lenguaje para resolver su problema.

ACTUALIZACIÓN: El proyecto Joda-Time ahora está en modo de mantenimiento. Su equipo aconseja la migración a las clases java.time integradas en Java.

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 una < a href = "https://en.wikipedia.org/wiki/ISO_8601#Time_intervals" rel = "nofollow noreferrer"> 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 de negocios puede necesitar considerar zonas horarias. El comienzo de un & Quot; día & Quot; 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 utilizar la lógica de apertura media, ya que generalmente funciona mejor para la fecha -hora de trabajar. El comienzo de un período de tiempo es inclusivo mientras que el final es exclusivo . Joda-Time utiliza la lógica medio abierta en sus métodos.

diagrama que define una semana como mayor o igual que el Día 1 y menor que el Día 8

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 & # 8230;

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 & # 8230;

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

Estoy almacenando todas las fechas en milisegundos. No uso campos de fecha / hora en absoluto.

Entonces, tengo que manipularlo tanto tiempo. Significa que no uso las palabras clave 'antes', 'después', 'ahora' en mis consultas sql.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top