Domanda

Java e Oracle hanno entrambi una timestamp tipo di detta Data.Gli sviluppatori tendono a manipolare questi come se fossero calendario date, che io ho visto causa brutto one-off bug.

  1. Per una data quantità che si può semplicemente tagliare la parte del tempo su di input, cioè, ridurre la precisione.Ma se lo fai con un intervallo di date, ad esempio: 9/29-9/30), la differenza tra questi due valori è di 1 giorno, piuttosto che 2.Inoltre, la gamma confronti richiedono: 1) un troncamento di funzionamento: start < trunc(now) <= end, o 2) aritmetica: start < now < (end + 24hrs).Non orribile, ma non LAVAGGIO.

  2. Un'alternativa è usare vero timestamp: 9/29 00:00:00 - 10/1 00:00:00.(da mezzanotte a mezzanotte, quindi non include alcuna parte di Ottobre).Ora le durate sono intrinsecamente corretto, e la gamma confronti sono di più semplice: start <= now < end.Certamente cleaner per l'elaborazione interna, tuttavia la data di fine non hanno bisogno di essere convertiti su input iniziale (+1), e per l'uscita (-1), presumendo una data di calendario metafora a livello di utente.

Come si fa a gestire intervalli di date sul vostro progetto?Ci sono altre alternative?Io sono particolarmente interessato a come gestire questo sia il Java e Oracle i lati dell'equazione.

È stato utile?

Soluzione

Ecco come lo facciamo.

  1. Usa timestamp.

  2. Usa intervalli a metà apertura per il confronto: start <= now < end.

Ignora i piagnucoloni che insistono sul fatto che TRA è in qualche modo essenziale per il successo di SQL.

Con questo una serie di intervalli di date è davvero facile da controllare. Il valore del database per 9/30 to 10/1 comprende un giorno (9/30). L'inizio dell'intervallo successivo deve essere uguale alla fine dell'intervallo precedente. Quella interval[n-1].end == interval[n].start regola è utile per l'audit.

Quando si visualizza, se lo si desidera, è possibile visualizzare i formati start e end - 1. Si scopre che puoi educare le persone a capire che & Quot; end & Quot; è in realtà il primo giorno in cui la regola non è più vera. Quindi & Quot; dal 9/30 al 10/1 & Quot; significa " valido a partire dal 9/30, non più valido a partire dal 10/1 " ;.

Altri suggerimenti

Oracle ha il TIMESTAMP datatype . Memorizza l'anno, il mese e il giorno del tipo di dati DATE, più i valori di ora, minuto, secondo e secondo frazionario.

Ecco una thread su asktom.oracle.com sull'aritmetica della data.

Secondo ciò che ha spiegato S.Lott. Abbiamo una suite di prodotti che fa ampio uso degli intervalli di date ed è stata una delle nostre lezioni apprese a lavorare con intervalli di questo tipo. A proposito, chiamiamo la data di fine esclusiva data di fine se non fa più parte dell'intervallo (IOW, un intervallo semiaperto). Al contrario, è una data di fine inclusiva se conta come parte dell'intervallo che ha senso solo se non vi è alcuna porzione di tempo.

Gli utenti generalmente si aspettano input / output di intervalli di date inclusivi. In ogni caso, converti l'input dell'utente il prima possibile in intervalli di date di fine esclusivi e converti qualsiasi intervallo di date il più tardi possibile quando deve essere mostrato all'utente.

Nel database, archiviare sempre intervalli di date di fine esclusivi. Se sono presenti dati legacy con intervalli di date di fine inclusivi, migrarli sul DB se possibile o convertirli in un intervallo di date di fine esclusivo il più presto possibile quando i dati vengono letti.

Uso il tipo di dati della data di Oracle e istruisco gli sviluppatori sul problema dei componenti dell'ora che incidono sulle condizioni al contorno.

Un vincolo del database impedirà anche la specifica accidentale di un componente temporale in una colonna che non dovrebbe avere nessuno e inoltre dice all'ottimizzatore che nessuno dei valori ha un componente temporale.

Ad esempio, il vincolo CHECK (MY_DATE = TRUNC (MY_DATE)) impedisce che un valore con un orario diverso da 00:00:00 venga inserito nella colonna my_date e consente inoltre a Oracle di dedurre che un predicato come MY_DATE = TO_DATE ('2008-09-12 15:00:00') non sarà mai vero, e quindi nessuna tabella verrà restituita dalla tabella perché può essere espansa in:

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'))

Questo è automaticamente falso, ovviamente.

Anche se a volte è allettante memorizzare le date come numeri come 20080915, ciò può causare problemi di ottimizzazione delle query. Ad esempio, quanti valori legali ci sono tra 20.071.231 e 20.070.101? Che ne dite tra le date 31-dic-2007 e 01-gen-2008? Consente inoltre di immettere valori illegali, come 20070100.

Quindi, se si hanno date senza componenti temporali, la definizione di un intervallo diventa semplice:

select ...
from   ...
where  my_date Between date '2008-01-01' and date '2008-01-05'

Quando è presente un componente temporale, è possibile effettuare una delle seguenti operazioni:

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)

Nota l'uso di (1/24/60/60) invece di un numero magico. È abbastanza comune in Oracle eseguire l'aritmetica della data aggiungendo frazioni definite di un giorno ... 3/24 per tre ore, 27/24/60 per 27 minuti. La matematica Oracle di questo tipo è esatta e non presenta errori di arrotondamento, quindi:

select 27/24/60 from dual;

... dà 0.01875, non 0.01874999999999 o altro.

Non vedo ancora i tipi di dati Interval pubblicati.

Oracle ha anche tipi di dati per lo scenario esatto. Ci sono tipi di dati INTERVAL YEAR TO MONTH e INTERVAL DAY TO SECOND in Oracle.

Dai documenti 10gR2.

  

L'INTERVALLO ANNO AL MESE memorizza un periodo   di tempo usando ANNO e MESE   campi datetime. Questo tipo di dati è   utile per rappresentare la differenza   tra due valori datetime solo quando   i valori di anno e mese sono   significativa.

     

INTERVAL YEAR [(year_precision)] TO   MESE

     

dove year_precision è il numero di   cifre nel campo ANNO datetime. Il   il valore predefinito di year_precision è 2.

     

INTERVAL DAY TO SECOND Tipo di dati

     

INTERVAL DAY TO SECOND memorizza un periodo   di tempo in termini di giorni, ore,   minuti e secondi. Questo tipo di dati è   utile per rappresentare il preciso   differenza tra due datetime   valori.

     

Specificare questo tipo di dati come segue:

     

INTERVAL DAY [(day_precision)] TO   SECONDO   [(Fractional_seconds_precision)]

     

dove

     

day_precision è il numero di cifre   nel campo DAY datetime. Accettato   i valori sono compresi tra 0 e 9. Il valore predefinito è 2.

     

fractional_seconds_precision è il   numero di cifre in frazionario   parte del campo SECONDO datetime.   I valori accettati sono da 0 a 9. Il   il valore predefinito è 6.

     

Hai una grande flessibilità   quando si specificano i valori di intervallo come   letterali. Fare riferimento a & Quot; Intervallo   & Letterali quot; per informazioni dettagliate su   specifica i valori dell'intervallo come valori letterali.   Vedi anche & Quot; Datetime e Interval   & Esempi quot; per un esempio usando   intervalli.

Sulla base delle mie esperienze, ci sono quattro modi principali per farlo:

1) Converti la data in un numero intero di epoca (secondi dal 1 ° gennaio 1970) e memorizzala nel database come numero intero.

2) Converti la data in un intero AAAAMMGGHHMMSS e memorizzala nel database come intero.

3) Conservalo come una data

4) Conservalo come una stringa

Mi sono sempre bloccato con 1 e 2, perché ti consente di eseguire un'aritmetica rapida e semplice con la data e non fare affidamento sulla funzionalità del database sottostante.

In base alla tua prima frase, stai inciampando in una delle " nascoste; caratteristiche " (ad esempio bug) di Java: java.util.Date avrebbe dovuto essere immutabile ma non lo è. (Java 7 promette di risolvere questo problema con una nuova API data / ora.) Quasi tutte le app aziendali contano su diversi temporali modelli , e ad un certo punto dovrai fare l'aritmetica su data e ora.

Idealmente, potresti utilizzare Joda time , utilizzato da Google Calendar. Se non puoi farlo, immagino un'API che consiste in un wrapper intorno a <=> con metodi computazionali simili a Grails / Rails e in un intervallo del tuo wrapper (ovvero una coppia ordinata che indica l'inizio e la fine di un tempo periodo) sarà sufficiente.

Sul mio progetto attuale (un'applicazione di cronometraggio delle risorse umane) proviamo a normalizzare tutte le nostre date allo stesso fuso orario sia per Oracle che per Java. Fortunatamente, i nostri requisiti di localizzazione sono leggeri (= 1 fuso orario è sufficiente). Quando un oggetto persistente non richiede una precisione più fine di un giorno, usiamo il timestamp a partire da mezzanotte. Vorrei andare oltre e insistere nel buttare via i milli-secondi extra alla granularità più grossolana che un oggetto persistente può tollerare (renderà la tua elaborazione più semplice).

Tutte le date possono essere memorizzate in modo inequivocabile come timestamp GMT (ovvero nessun fuso orario o mal di testa ora legale) memorizzando il risultato di getTime () come intero lungo.

Nei casi in cui sono necessarie manipolazioni di giorno, settimana, mese, ecc. nelle query del database e quando le prestazioni della query sono di primaria importanza, i timestamp (normalizzati a una granularità superiore rispetto ai millisecondi) possono essere collegati a una tabella di suddivisione della data con colonne per i valori di giorno, settimana, mese ecc. in modo che le costose funzioni data / ora non debbano essere utilizzate nelle query.

Alan è giusto - Joda tempo è grande.java.util.La data e il Calendario sono solo una vergogna.

Se avete bisogno di timestamp utilizzare oracle tipo di data con l'ora, il nome della colonna con un qualche tipo di suffisso come _tmst.Quando si leggono i dati in java ottenere in un joda tempo oggetto DateTime.per assicurarsi che il fuso orario è giusto considerare che ci sono tipi specifici di dati in oracle che memorizza data e ora con il fuso orario.Oppure si può creare un'altra colonna della tabella per memorizzare l'ID di fuso orario.I valori per il fuso orario di ID dovrebbe essere standard con il nome di ID per vedere Fusi orari http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang.String%29 .Se si utilizza un'altra colonna per TZ cdi poi quando si leggono i dati in java oggetto DateTime, ma impostare il fuso orario su oggetto DateTime utilizzando il .withZoneRetainFields per impostare il fuso orario.

Se avete solo bisogno di dati date (no timestamp) e poi utilizzare il tipo di data nel database con il tempo.nuovo nome del bene.in questo caso utilizzare DateMidnight oggetto da jodatime.

linea di fondo:leva il tipo di sistema di database e il linguaggio che si sta utilizzando.Imparare e sfruttare i vantaggi di avere espressiva api e la sintassi del linguaggio per trattare il problema.

AGGIORNAMENTO: il progetto Joda-Time è ora in modalità manutenzione. Il suo team consiglia la migrazione alle classi java.time integrate in Java.

Joda Time

Joda-Time offre 3 classi per rappresentare un arco di tempo: intervallo, durata e periodo.

Lo standard ISO 8601 specifica come formattare le stringhe che rappresentano una Duration e una < a href = "https://en.wikipedia.org/wiki/ISO_8601#Time_intervals" rel = "nofollow noreferrer"> Intervallo . Joda-Time analizza e genera tali stringhe.

Il fuso orario è una considerazione cruciale. Il database dovrebbe archiviare i suoi valori di data e ora in UTC. Ma la tua logica aziendale potrebbe dover considerare i fusi orari. L'inizio di un & Quot; giorno & Quot; dipende dal fuso orario. A proposito, usa nomi di fuso orario corretti piuttosto che codici di 3 o 4 lettere.

La risposta corretta di S.Lott consiglia saggiamente di utilizzare la logica semiaperta, poiché di solito funziona meglio per data lavoro a tempo. L'inizio di un arco di tempo è inclusivo mentre la fine è esclusivo . Joda-Time utilizza la logica semiaperta nei suoi metodi.

diagramma che definisce una settimana maggiore o uguale al Giorno 1 e inferiore al Giorno 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 );

Scarica su console & # 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 );

Quando eseguito & # 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

Sto memorizzando tutte le date in millisecondi. Non uso affatto campi data / ora / data / ora.

Quindi, devo manipolarlo a lungo. Significa che non uso le parole chiave "before", "after", "now" nelle mie query sql.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top