Pergunta

Java & A Oracle ambos têm um timestamp tipo chamado Data. Os desenvolvedores tendem a manipular os como se fossem calendário datas, que eu vi causa desagradável one-off bugs.

  1. Para uma quantidade data básica você pode simplesmente cortar a parte do tempo em cima de entrada, ou seja, reduzir a precisão. Mas se você fizer isso com um intervalo de datas, (por exemplo: 9 / 29-9 / 30 ), a diferença entre estes dois valores é de 1 dia, em vez de 2. Além disso, comparações alcance exigir quer 1 ) uma operação de truncamento: start < trunc(now) <= end, ou 2) aritmética: start < now < (end + 24hrs). Não Horrível, mas não DRY .

  2. Uma alternativa é usar verdadeiros timestamps: 9/29 00:00:00 - 10/1 0:00:00. (Meia-noite a meia-noite, por isso não inclui qualquer parte de outubro). Agora durações são intrinsecamente correta, e as comparações alcance são mais simples: start <= now < end. Certamente limpo para processamento interno, no entanto datas terminar não precisam de ser convertidos em cima de entrada inicial (1), e para a saída (-1), presumindo uma metáfora data de calendário ao nível do utilizador.

Como você lida com intervalos de datas sobre o seu projeto? Existem outras alternativas? Estou particularmente interessado em como você lida com isso tanto no Java e os lados da Oracle da equação.

Foi útil?

Solução

Veja como fazê-lo.

  1. Use timestamps.

  2. intervalos Use semi-aberto para comparação:. start <= now < end

Ignorar os chorões que insistem que ENTRE é algo essencial para SQL bem sucedido.

Com este uma série de intervalos de datas é realmente fácil de auditoria. O valor de base de dados para 9/30 to 10/1 englobam um dia (30/09). início do próximo intervalo deve ser igual final do intervalo anterior. Essa regra interval[n-1].end == interval[n].start é útil para auditoria.

Quando você exibir, se você quiser, você pode exibir a start formatado e end-1. Acontece que, você pode educar as pessoas a compreender que o "fim" é realmente o primeiro dia em que a regra já não é verdade. Assim, "9/30 a 10/1" significa "válido começando 9/30, já não é válida a partir 10/1".

Outras dicas

A Oracle tem o TIMESTAMP tipo de dados . Ele armazena o ano, mês e dia do tipo de dados DATE, além de hora, minuto, segundo e fracionários valores segundo.

Aqui está um fio em asktom.oracle.com sobre data aritmética.

Eu segundo o S.Lott explicou. Temos um conjunto de produtos que faz uso extensivo de intervalos de tempo de data e tem sido um dos nossos lições aprendidas para trabalhar com faixas como esse. By the way, chamamos a data final exclusivo data final, se não é parte da gama mais (IOW, um intervalo semi-aberto). Em contraste, é um inclusiva data final, se ele conta como parte da gama que só faz sentido se não houver nenhuma parte do tempo.

Os usuários tipicamente esperam de entrada / saída de intervalos de datas inclusivas. A qualquer entrada do usuário taxa, convertido assim que possível exclusivos intervalos de data final, e converter qualquer intervalo de datas o mais tarde possível quando se tem que ser mostrado para o usuário.

No banco de dados, sempre loja exclusiva intervalos de data final. Se houver dados legados com intervalos de datas final inclusiva, seja migrá-los sobre o DB se possível, ou convertido ao exclusivo gama data final o mais cedo possível, quando os dados são lidos.

Eu uso o tipo de dados data da Oracle e educar os desenvolvedores sobre a emissão de componentes de tempo que afectam as condições de contorno.

Uma restrição base de dados também vai impedir que a especificação acidental de um componente em vez de uma coluna que deve ter nenhum e também diz ao optimizador que nenhum dos valores de ter um componente de tempo.

Por exemplo, a restrição CHECK (MY_DATE = TRUNCAR (MY_DATE)) impede um valor com um tempo diferente de 00:00:00 ser colocado na coluna my_date, e também permite que o Oracle inferir que um predicado, tais como MY_DATE = TO_DATE ( '2008-09-12 15:00:00') nunca vai ser verdade, e, portanto, nenhuma linha será devolvido a partir da tabela porque ela pode ser expandida para:

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

Este é automaticamente falsa é claro.

Embora às vezes é tentador para armazenar datas como números, como 20080915 isso pode causar problemas de otimização de consulta. Por exemplo, quantos valores legal existem entre 20071231 e 20070101? Como cerca de entre as datas 31-Dez-2007 abnd 01-Jan-2008? Ele também permite que valores ilegais para ser inserido, como 20070100.

Então, se você tem datas sem componentes de tempo, em seguida, definindo uma gama torna-se fácil:

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

Quando há um componente de tempo que você pode fazer uma das seguintes opções:

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

ou

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

Observe o uso de (1/24/60/60) em vez de um número mágico. É muito comum no Oracle para executar data aritmética adicionando as fracções definidas de um dia ... 3/24 por três horas, 27/24/60 por 27 minutos. A Oracle matemática deste tipo é exata e não sofre erros de arredondamento, da seguinte forma:

select 27/24/60 from dual;

... dá 0,01875, não ,01874999999999 ou o que quer.

Eu não ver os tipos de dados Intervalo postado ainda.

A Oracle também tem tipos de dados para o seu cenário exato. Há INTERVAL YEAR TO mês e INTERVAL DAY TO SECOND tipos de dados no Oracle também.

De docs 10gR2.

INTERVALO YEAR TO MONTH armazena um período de tempo usando o ano e mês campos de data e hora. Este tipo de dados é útil para representar a diferença entre dois valores de data e hora, quando apenas os valores do ano e mês são significativa.

ANO intervalo [(year_precision)] para MÊS

onde year_precision é o número de dígitos no campo datetime ano. o valor padrão de year_precision é 2.

INTERVALO DIA PARA SEGUNDA tipo de dados

INTERVALO DAY TO SECOND armazena um período do tempo em termos de dias, horas, minutos e segundos. Este tipo de dados é útil para representar o preciso diferença entre duas datetime valores.

Especifique este tipo de dados da seguinte forma:

INTERVALO DIA [(day_precision)] para SEGUNDO [(Fractional_seconds_precision)]

onde

day_precision é o número de dígitos no campo DIA data e hora. Aceitaram Os valores vão de 0 a 9. O padrão é 2.

fractional_seconds_precision é o número de dígitos no fraccionada parte do campo datetime segundo. Os valores aceitáveis ??são 0 a 9. A padrão é 6.

Você tem uma grande flexibilidade ao especificar valores de intervalo quanto literais. Por favor, consulte "Intervalo Literais" para obter informações detalhadas sobre especificar valores do intervalo como literais. Veja também "Datetime e Interval Exemplos para" um exemplo usando intervalos.

Com base na minha experiência, existem quatro principais maneiras de fazer isso:

1) converter a data em um inteiro época (segundos desde 1 de janeiro de 1970) e armazená-lo no banco de dados como um número inteiro.

2) converter a data em um inteiro YYYYMMDDHHMMSS e armazená-lo no banco de dados como um número inteiro.

3) Guarde-o como uma data

4) Guarde-o como uma string

Eu sempre preso com 1 e 2, porque permite realizar operações aritméticas simples e rápida com a data e não contam com a funcionalidade de banco de dados subjacente.

Com base em sua primeira frase, você tropeçar em um dos "recursos" escondidos (ou seja, erros) do Java: java.util.Date deveria ter sido imutável, mas não é. (Java 7 promete corrigir isso com uma nova data / API tempo.) Quase todas as empresas contagem de aplicativos em vários temporais padrões , e em algum momento você vai precisar para fazer aritmética de data e hora.

Idealmente, você poderia usar Joda tempo, que é usado pelo Google Calendar. Se você não pode fazer isso, eu acho que uma API que consiste de um invólucro em torno java.util.Date com métodos computacionais semelhantes a Grails / Rails, e de uma gama de sua embalagem (isto é um par ordenado indicando o início eo fim de um período de tempo) será suficiente.

No meu projeto atual (um aplicativo de RH cronometragem) tentamos normalizar todas as nossas datas para o mesmo fuso horário para Oracle e Java. Felizmente, os nossos requisitos de localização são leves (= 1 do fuso horário é suficiente). Quando um objeto persistente não precisa mais fina precisão do que um dia, usamos o timestamp a partir de meia-noite. Eu iria mais longe e insistem em jogar fora os mili-segundos extras para a granularidade grosseira que um objeto persistente pode tolerar (ele vai fazer o seu processamento mais simples).

Todas as datas podem ser inequivocamente armazenado como GMT timestamps (isto é, não de fuso horário ou horário de verão dores de cabeça), armazenando o resultado de getTime () como um inteiro longo.

Nos casos em que dia, semana, mês, etc. manipulações são necessários em consultas de banco de dados, e quando consulta de desempenho é fundamental, a data e hora (normalizados para uma granularidade maior que milissegundos) pode ser ligada a uma tabela data colapso que tem colunas para o dia, semana, mês, etc. valores para que data caro / funções de tempo não tem que ser usado em consultas.

Alan é hora Joda direita é grande. java.util.Date e calendário são apenas uma vergonha.

Se precisar de timestamps usar o tipo de data da Oracle com o tempo, o nome da coluna com algum tipo de sufixo como _tmst. Quando você lê os dados em java obtê-lo em um objeto DateTime tempo Joda. para garantir que o fuso horário é certo considerar que existem tipos de dados específicos em Oracle que irá armazenar a data e hora com o fuso horário. Ou você pode criar uma outra coluna na tabela para armazenar o ID de fuso horário. Os valores para o ID de fuso horário deve ser padrão nome ID completo para Timezones ver http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang.String% 29 . Se você usar outra coluna para a dta TZ, em seguida, quando você lê os dados em uso java objeto DateTime, mas definir o fuso horário no objeto DateTime usando as .withZoneRetainFields para definir o fuso horário.

Se você só precisa os dados de data (sem timestamp), em seguida, usar o tipo de data no banco de dados sem tempo. novamente nomeá-lo bem. neste caso de uso objeto DateMidnight de JodaTime.

linha de fundo: a alavancagem do sistema tipo de banco de dados e o idioma que você está usando. Aprendê-los e colher os benefícios de ter expressiva api e linguagem sintaxe para lidar com seu problema.

UPDATE: O projeto Joda-Time agora está em modo de manutenção. Sua equipe aconselha a migração para o java.time aulas construído em Java.

Joda-Time

Joda-Time oferece 3 classes para representar um espaço de tempo:. Interval, Duração e Período

As ISO 8601 especifica padrão como seqüências de formato que representam um Duração e um < a href = "https://en.wikipedia.org/wiki/ISO_8601#Time_intervals" rel = "nofollow noreferrer"> Interval . Joda-Time ambos parses e gera essas strings.

Fuso é uma consideração crucial. Seu banco de dados deve estar armazenando seus valores de data e hora em UTC. Mas sua lógica de negócios pode precisar de considerar fusos horários. O início de um "dia" depende de fuso horário. By the way, o uso momento adequado nomes de zona em vez de 3 ou 4 códigos de letras.

A resposta correta por S.Lott sabiamente aconselha a usar a lógica semi-aberto, como que normalmente funciona melhor para data -hora do trabalho. O início de um período de tempo é inclusiva , enquanto o final é exclusivo . Joda-Time usa a lógica semi-aberto em seus métodos.

diagrama que define uma semana como maior do que ou igual a 1 dia e menos de Dia 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 para consolar ...

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 run ...

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 armazenar todas as datas em milissegundos. Não uso timestamps / data e hora campos em tudo.

Então, eu tenho que manipulá-lo como longs. Isso significa que eu não uso 'antes', 'depois', 'agora' palavras-chave em minhas consultas SQL.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top