Como eu modelo datas parciais no Python? Como um ano desconhecido, ou dia desconhecido do mês?

11

Eu quero ser capaz de capturar fatos como Bob was born in 2000e Bill's birthday is May 7th.

Nos dois exemplos, conhecemos apenas parte da data de nascimento da pessoa. Em um caso, sabemos apenas o ano; no outro caso, sabemos o mês e o dia, mas não o ano.

Como capturo essas informações?

Alguns exemplos de como isso pode funcionar:

Imagine uma biblioteca como datetime que permita que None nos campos represente incógnitas. Eu posso ter um código como o seguinte:

date_a = date(2000, 5, None)
date_b = date(2000, 6, None)
difference = date_b - date_a
assert difference.min.days == 1
assert difference.max.days == 60  # Or something close to 60.
assert equal(date_a, date_b) == False

date_c = date(2000, 5, None)
assert equal(date_a, date_c) == Maybe

Este é apenas um exemplo de como ele pode se comportar. Eu não necessariamente quero esse comportamento preciso.

Botões840
fonte
Em geral, a maneira como você lida com coisas desse tipo é usar, por exemplo, o ano 0001 no .NET para datas que não têm um ano e 1º de janeiro para anos sem um mês e dia.
Robert Harvey
Editei sua pergunta para remover solicitações de biblioteca. Essas perguntas não são abordadas neste site.
@RobertHarvey Não posso usar sua sugestão. Se vemos que Bob nasceu em 1º de janeiro de 2000, não sabemos exatamente o que isso significa. Não podemos dizer se ele nasceu no primeiro dia de 2000 ou se ele nasceu em um dia de 2000. Precisamos saber a diferença.
Buttons840
@RobertHarvey Eu sei que isso é comum, mas tenho visto muitas falhas ruins por causa de más escolhas de tais valores de sinal. (Além disso, não acho que ele responda à pergunta, pois o OP precisa lidar apenas com algumas datas desconhecidas. Definir para 1º de janeiro nesses casos não permite diferenciar datas reais de 1º de janeiro de desconhecidas.
Gort the Robot
5
@ Buttons840: Então você terá que escrever uma classe que encapsule os comportamentos que você deseja. Você deve agrupar a classe de data existente e adicionar os comportamentos desejados.
Robert Harvey

Respostas:

3

Primeiro de tudo, quando você começa a decompor as datas em seus componentes constituintes, elas não são mais datas.

Da mesma forma que não é possível remover a funcionalidade via subclasses sem interromper o OOP, não é possível misturar datas e frações de datas sem causar confusão (ou pior), tornando-as compatíveis como no seu exemplo de código sem interromper outra coisa.

Se você deseja capturar um ano, o que há de errado com um objeto que contém um número inteiro simples? Se você deseja capturar um mês e um dia, por que não capturar uma enumeração de mês e um dia inteiro? Talvez até armazene-os internamente em um objeto de data para obter uma verificação adequada dos limites (por exemplo, 31 de fevereiro não faz sentido). Expor uma interface diferente, no entanto.

Por que você deseja comparar uma data com um ano para ver se são iguais, maiores ou menores? Não faz sentido: não há informações suficientes para fazer essa comparação. No entanto, existem outras comparações que podem fazer sentido (este é o pseudocódigo):

Year y = Year(2015)
Date d = Date(2015, 01, 01)
assert y.contains(d) == True

fonte
2

O segundo comentário de Robert Harvey contém a resposta certa, mas deixe-me expandir um pouco.

O ano de nascimento e as datas de nascimento das pessoas são entidades completamente diferentes, portanto você não precisa (e realmente não deve) usar o mesmo mecanismo para ambos.

Para datas de nascimento, você pode criar um BirthDatetipo de dados (ou possivelmente um nome YearlyRecurringDateque eu não consiga inventar agora) que conteria apenas dateum ano constante, como 2000 por convenção. O ano de 2000 é uma boa escolha, porque foi um salto; portanto, não falhará as pessoas cujo aniversário é em 28 de fevereiro.

Durante anos de nascimento, você pode inventar um BirthYeartipo de dados (ou possivelmente um ApproximateDatetipo de dados) que conterá uma date, e um indicador da precisão: Year, Month, Full.

O benefício dessas abordagens é que, no centro das coisas, você ainda mantém um datepara poder executar a aritmética de datas.

Mike Nakis
fonte
1

Acredito que o que você está descrevendo seria um substituto para o datetimemódulo que implementa os datetime.datetimeatributos (ano, mês etc.) como valores com uma medição de incerteza (em vez de apenas valores).

Existem pacotes Python para ajudar com números incertos (por exemplo, o pacote de incertezas ), e talvez não seja muito difícil criar uma bifurcação datetimeque use incerteza em cada atributo. Eu também gostaria de ver um e pode até ter utilidade para ele. Certamente, poderia ser argumentado para a inclusão de um udatetimeno pacote de incertezas vinculado anteriormente.

Seus exemplos seriam algo como:

bob_bday = udatetime(2000, (6,6))  # 2000-06 +/- 6mo
>>> 2000-??-?? T??:??:??
bil_bday = udatetime((1970, 50), 3, 7)  # assume bill is ~40 +/- 40 
>>> [1970+/-40]-03-07 T??:??:??

"Valores de sinal" têm muitos problemas, mas além disso, você pode representar coisas com incerteza que os valores de sinal não podem:

# ali was born in spring
ali_bday = udatetime((), (4.5, 1.5))
>>> [1970+/-40]-[4.5+/-1.5]-?? T??:??:??

Outra consideração é que, para ser mais preciso, as incertezas aqui devem realmente ser do tipo timedelta. Deixo como exercício para o leitor descobrir um construtor conciso e completo para o udatetimeuso de timedeltaincertezas.

Então, em última análise, eu diria que o que você descreve é ​​"facilmente" modelado com incertezas, mas a implementação de a udatetimeé praticamente bastante difícil. A maioria seguirá a rota "fácil" e dividirá a data e hora em componentes e rastreará a incerteza sobre eles de forma independente, mas se você estiver se sentindo ambicioso, o uncertaintiespacote (ou outro) pode estar interessado em uma solicitação de recebimento udatetime.

7yl4r
fonte
0

Por que não criar uma classe "período" que implemente um de para a estrutura.

"Bob nasceu em 2000" ->

period {
   from  {
      yy = 2000;
      mm = 01;
      dd = 01; 
   }
   to {
     yy = 2000;
     mm = 12;
     dd = 31;
   }
   fuzz = 365;
}

Você pode implementar vários métodos de pesquisa, colocando entre as datas de e até. O atributo fuzz fornece uma indicação útil da precisão da data, para que você possa especificar fuzz == 1 para correspondências exatas ou fuzz == 31 para dentro de um mês ou mais.

James Anderson
fonte