Frage

Java & Oracle beide haben eine Zeitstempel Typ namens Datum. Entwickler neigen dazu, diese zu manipulieren, als ob sie Kalender Termine, die ich Ursache böse Einmal Bugs gesehen haben.

  1. Für eine Ecktermin Menge, die Sie einfach den Zeitanteil bei Eingabe abhacken können, das heißt, die Genauigkeit verringern. Aber wenn Sie das tun, mit einem Datumsbereich (zB: 9 / 29-9 / 30 ), wird die Differenz zwischen diesen beiden Werten 1 Tag, anstatt 2. Auch Bereichsvergleiche erfordern entweder 1 ) eine Abschneideoperation: start < trunc(now) <= end oder 2) arithmetischer: start < now < (end + 24hrs). Nicht schrecklich, aber nicht DRY .

  2. Eine Alternative ist wahr Zeitstempel zu verwenden: 29.9 00.00.00 - 01.10 00.00.00. (Mitternacht zu Mitternacht, enthält also keinen Teil Okt). Jetzt sind Dauern sich richtigen und Bereichsvergleiche sind einfacher: start <= now < end. Sicherlich für die interne Verarbeitung saubere, aber Termine tun müssen, um bei der anfänglichen Eingabe umgewandelt werden (+1), und für die Ausgabe (-1), ein Kalenderdatum Metapher auf Benutzerebene vorausgesetzt.

Wie Sie Datumsbereiche an Ihrem Projekt umgehen? Gibt es andere Alternativen? Ich bin besonders daran interessiert, wie Sie damit umgehen sowohl auf der Java und Oracle Seiten der Gleichung.

War es hilfreich?

Lösung

Hier ist, wie wir es tun.

  1. Verwenden Sie Zeitstempel.

  2. Mit halboffene Intervalle zum Vergleich:. start <= now < end

Ignorieren Sie die Nörgler, die darauf bestehen, daß zwischen irgendwie essentiell für eine erfolgreiche SQL ist.

diese mit einer Reihe von Datumsbereichen ist wirklich einfach zu prüfen. Der Datenbankwert für 9/30 to 10/1 umfasst einen Tag (30.09). Das Start der nächsten Intervalls muss das vorherige Intervall Ende gleich. Die interval[n-1].end == interval[n].start Regel ist praktisch für die Prüfung.

Wenn Sie sich anzeigen lassen, wenn Sie möchten, können Sie die formatierte start und end-1 angezeigt werden soll. Es stellte sich heraus, Sie können Menschen erziehen zu verstehen, dass das „Ende“ tatsächlich der erste Tag ist die Regel nicht länger wahr. So „9/30 bis 10/1“ bedeutet „gültig 30.09 starten, nicht mehr gültig 01.10 Start“.

Andere Tipps

Oracle hat die Datentyp timestamp rel="nofollow . Es speichert das Jahr, den Monat und den Tag des DATE-Datentypen sowie Stunde, Minute, Sekunde und Sekundenbruchteile Werte.

Hier ist ein Faden auf asktom.oracle.com über Datum Arithmetik.

Ich zweite, was S.Lott erklärt. Wir haben eine Produktsuite, die umfangreiche Verwendung von Datum Zeitbereiche macht und es hat sich zu einem unserer Lektionen gelernt wurden mit Bereichen, so zu arbeiten. By the way, nennen wir das Enddatum exklusiv Enddatum, wenn es nicht Teil des Bereichs ist mehr (IOW, ein halboffenes Intervall). Im Gegensatz dazu ist es ein inklusive Enddatum, wenn es als Teil des Bereichs zählt, den Sinn macht, nur dann, wenn es keinen Zeitteil ist.

Benutzer in der Regel der Eingabe / Ausgabe inklusive Datumsbereichen erwarten. Auf jeden Fall eine Benutzereingabe umwandeln so schnell wie möglich zu exklusiven Enddatum Bereiche und wandeln jeden Datumsbereich so spät wie möglich, wenn es dem Benutzer angezeigt werden muss.

Auf der Datenbank speichert immer exklusive Enddatum Bereiche. Wenn es Altdaten mit inklusive Enddatum Bereichen liegt, entweder wandern sie auf der DB, wenn möglich oder so schnell wie möglich zu exklusivem Enddatum Bereich umwandeln, wenn die Daten gelesen werden.

Ich benutze Oracle Datum Datentyp und erziehen Entwickler auf die Frage der Zeitkomponenten zu beeinflussen Randbedingungen.

Eine Datenbank Einschränkung wird auch die zufällige Angabe einer Zeitkomponente in einer Spalte verhindern, die keine haben sollte und erzählt auch die Optimierer, dass keiner der Werte eine Zeitkomponente haben.

Zum Beispiel kann das Einschränkungs CHECK (my_date = TRUNC (my_date)) verhindert, dass ein Wert mit einem anderen Zeitpunkt als 00.00.00 in die my_date Spalte angeordnet ist, und erlaubt auch Oracle zu folgern, dass ein Prädikat wie my_date = TO_DATE ( '2008-09-12 15.00.00) wird niemals wahr sein, und daher werden keine Zeilen aus der Tabelle zurückgegeben werden, da es erweitert werden kann:

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

Dies ist automatisch falsch natürlich.

Auch wenn es manchmal verlockend, zum Speichern von Datumsangaben als Zahlen wie 20080915 diese Abfrage Optimierungsprobleme verursachen kann. Zum Beispiel, wie viele legale Werte gibt es zwischen 20.071.231 und 20.070.101? Wie wäre es zwischen den Terminen 31-Dec-2007 abnd 01-Jan-2008? Es ermöglicht auch unzulässige Werte eingegeben werden, wie 20.070.100.

Also, wenn Sie Termine ohne Zeitkomponenten haben dann einen Bereich definieren, wird einfach:

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

Wenn es eine Zeitkomponente ist Sie eine der folgenden Möglichkeiten:

select ...
from   ...
where  my_date >= date '2008-01-01' and
       my_date  < date '2008-01-06'

oder

select ...
from   ...
where  my_date Between date '2008-01-01'
                   and date '2008-01-05'-(1/24/60/60)

Beachten Sie die Verwendung von (1/24/60/60) anstelle einer magischen Zahl. Es ist ziemlich üblich, in Oracle Datumsberechnungen durchzuführen, indem definierte Fraktionen eines Tages Hinzufügen ... 3/24 für 3 Stunden, 27/24/60 für 27 Minuten. Oracle math dieser Art ist genau und leidet nicht Rundungsfehler, so:

select 27/24/60 from dual;

... gibt 0,01875, nicht ,01874999999999 oder was auch immer.

Ich sehe nicht die Intervall-Datentypen noch nicht veröffentlicht.

Oracle hat auch Datentypen für Ihr genaues Szenario. Es gibt INTERVAL YEAR TO MONTH und INTERVAL DAY TO SECOND-Datentypen in Oracle als auch.

Von der 10gR2 docs.

  

INTERVAL YEAR TO MONTH speichert einen Zeitraum   Zeit mit Jahr und Monat   Datetime-Felder. Dieser Datentyp ist   nützlich, um die Differenz für die Darstellung von   zwischen zwei Werten Datumzeit, wenn nur   die Jahres- und Monatswerte   signifikant.

     

INTERVAL YEAR [(genauigkeit_in_jahren)] TO   MONAT

     

wo genauigkeit_in_jahren ist die Anzahl der   Ziffern im Jahr Datetime-Feld. Das   Standardwert von genauigkeit_in_jahren 2.

     

INTERVAL DAY TO SECOND Datentyp

     

INTERVAL DAY TO SECOND speichert einen Zeitraum   Zeit in Tagen, Stunden,   Minuten und Sekunden. Dieser Datentyp ist   nützlich, um die präzisen zum Repräsentieren   Differenz zwischen zwei Datumzeit   Werte.

     

Geben Sie diesen Datentyp wie folgt:

     

INTERVAL DAY [(day_precision)] TO   ZWEITE   [(Genauigkeit_in_bruchteilen_von_sekunden)]

     

Dabei steht

     

day_precision ist die Anzahl der Stellen   im TAG Datetime-Feld. Akzeptiert   Werte sind 0 bis 9. Der Standard 2 ist.

     

genauigkeit_in_bruchteilen_von_sekunden ist die   Anzahl der Stellen in der fraktionierten   Teil des zweiten Datetime-Feld.   Zulässige Werte sind 0 bis 9   Standard ist 6.

     

Sie haben ein hohes Maß an Flexibilität   bei der Angabe von Intervallwerten als   Literale. Bitte beachten Sie „Interval   Literale“für detaillierte Informationen über   Intervallwerte angeben, als Literale.   Siehe auch „für Datum und Uhrzeit und Intervall   Beispiele“für ein Beispiel unter Verwendung von   Intervalle.

Basierend auf meinen Erfahrungen gibt es vier wichtigsten Möglichkeiten, es zu tun:

1) Wandeln Sie das Datum einer Epoche ganze Zahl (Sekunden seit dem 1. Januar 1970) und speichern sie in der Datenbank als Integer.

2) umrechnen Datum in einem YYYYMMDDHHMMSS integer und speichern sie in der Datenbank als Integer.

3) Bewahren Sie es als Datum

4) Bewahren Sie es als String

Ich habe immer fest mit 1 und 2, weil es Ihnen ermöglicht eine schnelle und einfache Arithmetik mit dem Datum durchzuführen und nicht auf der zugrunde liegenden Datenbank-Funktionalität zu verlassen.

Basierend auf dem ersten Satz, Sie stolpern auf einen der versteckten „Features“ (das heißt Bugs) von Java: java.util.Date unveränderlich sein sollte, aber es ist nicht. (Java 7 verspricht dies mit einem neuen Datum / Zeit-API zu beheben.) Fast alle Unternehmen App zählt auf verschiedene zeitlichen Muster , und irgendwann müssen Sie Arithmetik auf Datum und Zeit tun.

Im Idealfall könnte man verwenden Joda Zeit , die von Google Kalender verwendet wird. Wenn Sie dies nicht tun können, ich denke, eine API, um java.util.Date mit Rechenverfahren ähnlich wie Grails / Rails und eine Reichweite Ihres Wrapper (dh ein geordnetes Paar, die den Beginn und das Ende einer Zeitperiode) eine Hülle besteht wird ausreichend.

Auf meinem aktuelles Projekt (eine HR-Zeitnehmung Anwendung) versuchen wir alle unsere Daten auf die gleiche Zeitzone zu normalisieren für Oracle und Java. Glücklicherweise sind unsere Lokalisierungsanforderungen leicht (= 1 Zeitzone ist genug). Wenn ein persistentes Objekt als ein Tag nicht feine Präzision benötigt, verwenden wir den Zeitstempel ab Mitternacht. Ich würde noch weiter gehen und darauf bestehen, wirft die zusätzliche milli-Sekunden auf die gröbsten Granularität entfernt, dass ein persistentes Objekt tolerieren kann (es wird Ihre Bearbeitung einfacher machen).

Alle Daten können eindeutig als GMT Zeitstempel gespeichert werden (das heißt keine Zeitzone und Sommer Kopfschmerzen) durch Speichern des Ergebnisses von getTime () als long integer.

In Fällen, in denen Tag, Woche, Monat usw. Manipulationen in Datenbankabfragen benötigt werden, und wenn die Abfrageleistung im Vordergrund steht, können die Zeitstempel (normiert auf eine höhere Granularität als Millisekunden) zu einem Zeitpunkt Bruchstabelle verknüpft werden, die Spalten für den Tag, Woche, Monat usw. Werte, so dass teures Datum / Zeit-Funktionen müssen in Abfragen nicht verwendet werden.

Alan ist rechts- Joda Zeit groß ist. java.util.Date und Kalender sind nur schade.

Wenn Sie Zeitstempel müssen das Orakel Datumsart mit der Zeit verwenden, benennen Sie die Spalte mit irgendeiner Art von Suffix wie _tmst. Wenn Sie die Daten in Java zu lesen bekommt es in ein Datetime-Objekt joda Zeit. um sicherzustellen, dass die Zeitzone richtig ist die Ansicht, dass es bestimmte Datentypen in Oracle ist, die die Zeitstempel mit der Zeitzone gespeichert werden. Oder Sie können eine andere Spalte in der Tabelle erstellen, die Zeitzone ID zu speichern. Die Werte für die Zeitzone-ID sollte Standard vollständigen Namen ID sein für Zeitzonen http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang.String% 29 . Wenn Sie eine weitere Spalte für die TZ dta verwenden dann, wenn Sie die Daten in Java-Anwendung Datetime-Objekt lesen, aber stellen Sie die Zeitzone auf dem Datetime-Objekt die .withZoneRetainFields mit der Zeitzone einzustellen.

Wenn Sie nur die aktuellen Daten benötigen (kein Zeitstempel) verwendet dann den Datumstyp in der Datenbank ohne Zeit. nennen Sie es wieder gut. in diesem Fall DateMidnight Objekt aus jodatime verwenden.

Unterm Strich: das Typsystem der Datenbank nutzen, und die Sprache, die Sie verwenden. Lernen Sie sie und erntet die Vorteile, ausdruck api und Sprachsyntax mit dem Problem fertig zu werden.

UPDATE: Das Joda-Time Projekt ist jetzt in dem Wartungsmodus. Sein Team berät Migration auf die java.time gebaut Klassen in Java.

Joda-Time

Joda-Time bietet drei Klassen für eine Zeitspanne darstellt. Intervall, Dauer und Periode

Die ISO 8601-Standard legt fest, wie Strings zu formatieren, ein Dauer und ein darstellen < a href = "https://en.wikipedia.org/wiki/ISO_8601#Time_intervals" rel = "nofollow noreferrer"> Intervall . Joda-Time beide Parsen und erzeugt solche Strings.

Zeitzone ist eine entscheidende Überlegung. Ihre Datenbank sollte das Datum Zeitwert in UTC werden zu speichern. Aber Ihre Business-Logik müssen Zeitzonen berücksichtigen. Der Anfang eines „Tag“ ist abhängig von Zeitzone. By the way, verwenden richtige Zeitzone Namen anstatt 3 oder 4-Buchstaben-Codes.

Die richtige Antwort von S.Lott rät weise Halboffen Logik zu verwenden, da dies in der Regel am besten für Datum funktioniert -Arbeitszeit. Der Beginn einer Zeitspanne ist inklusive , während das Ende ist exklusiv . Joda-Time verwendet halboffene Logik in ihren Methoden.

Diagramm, die eine Woche als größer oder gleich zu Tag 1 und weniger als Tag 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 );

Dump zu trösten ...

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

Wenn er gestartet wird ...

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

Im alle Daten in Millisekunden speichern. Ich benutze keinen Zeitstempel / Datetime-Felder überhaupt.

Also, muss ich es als Long-Positionen manipulieren. Es bedeutet, dass ich nicht verwenden ‚vor‘, ‚nach‘, ‚jetzt‘ Keywords in meinen SQL-Abfragen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top