11

I want to be able to capture facts like Bob was born in 2000 and Bill's birthday is May 7th.

In both examples we know only part of the person's birth date. In one case we know only the year; in the other case we know the month and day, but not the year.

How do I capture this information?

A few examples of how this might work:

Imagine a library like datetime which allowed None in the fields to represent unknowns. I might have code like the following:

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

This is just an example of how it might behave. I don't necessarily want this precise behavior.

  • In general, the way you deal with things like this is to use, for example, the year 0001 in .NET for dates that don't have a year, and January 1st for years without a month and day. – Robert Harvey May 18 '15 at 17:02
  • I edited your question to remove requests for a library. Such questions are off-topic on this site. – user22815 May 18 '15 at 17:10
  • @RobertHarvey I cannot use your suggestion. If we see that Bob was born Jan 1, 2000, we do not know what this means exactly. We cannot tell if he was born on the first day of 2000, or if he as born any day of 2000. We need to know the difference. – Buttons840 May 18 '15 at 17:18
  • @RobertHarvey I know that is common, but I have seen many bad failures because of bad choices of such signal values. (Plus, I don't think it answers the question as the OP needs to deal with only some dates being unknown. Setting to January 1st in such cases doesn't allow you to differentiate real Jan 1st dates from unknowns. – Steven Burnap May 18 '15 at 17:21
  • 5
    @Buttons840: Then you'll have to write a class that encapsulates the behaviors you want. You should wrap the existing date class and add your desired behaviors. – Robert Harvey May 18 '15 at 17:24
3

First of all, once you start decomposing dates into their constituent components, they are no longer dates.

In the same way that it is not possible to remove functionality via subclasses without breaking OOP, it is not possible to mix dates and date fractions without causing confusion (or worse) making them compatible as in your code example without breaking something else.

If you want to capture a year, what is wrong with an object containing a simple integer? If you want to capture a month and day, why not capture a month enumeration and an integer day? Maybe even store them internally in a date object so you get proper bounds checking (e.g. February 31 makes no sense). Expose a different interface, however.

Why would you want to compare a date to a year to see if they are the same, greater, or lesser? It makes no sense: there is not sufficient information to make that comparison. However, there are other comparisons that might make sense (this is pseudocode):

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

Robert Harvey's second comment contains the right answer, but let me expand on it a bit.

People's year of birth and people's birthdates are completely different entities, so you do not need to (and actually you should not) use the same mechanism for both.

For birthdates, you can devise a BirthDate data type (or possibly a YearlyRecurringDate though I cannot come up with a decent name right now) which would only contain a date with a constant year, like 2000 by convention. Year 2000 is a good choice because it was leap, so it will not fail people whose birthday is on Feb 28th.

For birth years, you can devise a BirthYear data type (or possibly an ApproximateDate data type) which would contain a date, and an indicator of the precision: Year, Month, Full.

The benefit of these approaches is that at the heart of things you still maintain a date so you can still perform date arithmetic.

1

I believe what you are describing would be a drop-in replacement for the datetime module that implements the datetime.datetime attributes (year, month, etc) as values with an uncertainty measurement (rather than just values).

Python packages exist to help with uncertain numbers (eg: the uncertainties package), and perhaps it wouldn't be too hard to make a fork of datetime that uses uncertainty on each attribute. I too would like to see one and might even have use for it. An argument could certainly be made for the inclusion of a udatetime into the afore-linked uncertainties package.

Your examples would be something like:

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??:??:??

"Signal values" have lots of issues, but in addition you can represent things with uncertainty that signal values cannot:

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

Another consideration is that to be more precise the uncertainties here should actually be of type timedelta. I leave it as an exercise for the reader to figure out a concise and complete constructor for udatetime using timedelta uncertainties.

So ultimately I would say that what you describe is "easily" modeled with uncertainties, but implementation of a udatetime is practically quite difficult. Most will take the "easy" route and break the datetime into components and track uncertainty on them independently, but if you are feeling ambitious the uncertainties package (or another) might be interested in a pull request for udatetime.

0

Why not create an "period" class which implements a from to structure.

"Bob was born in 2000" ->

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

You can then implement various search methods such by bracketing the from to dates. The fuzz attribute gives a usefull indication of how accurate the date is so you can specify fuzz == 1 for exact matches, or fuzz == 31 for within a month or so.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.