24

I'm so ridiculously frustrated with having to use DateTime values for data sets that are truly "just a day." Birthdays being the most-common example, but this comes up in business applications all the time.

I've gotten used to just setting the Time part of "date-only" records to "noon" (which avoids the date ever changing no matter the timezone). This seems like a hack, and I'm forever finding bugs from junior devs that trip over this issue.

Time is always relative to a fixed point. 4PM is 4 hours post meridian, or noon. The sun's highest point in transit is observable, and allows us to set up a coordinate system. 3 hours before noon (Ante Meridian), 2 hours after noon, 1441899402938 milliseconds since January 1, 1970. To people raised in a Cartesian world, this is second nature.

But our Calendar predates Descartes. My argument is that it is more properly thought of as a enumeration over which a modulo function is applied. Monday follows Sunday, and so on until you get to the fact that Sunday follows Saturday. There is no positive and negative, it is a modulus, or absolute value.

Similarly with years repeating. Every 365 days (or so) there are several special days for me: birthdays, anniversaries, childrens' birthdays, etc. etc. Business scheduling applications are rife with examples of meetings every seven days, first Tuesday of the month, etc. Just because we CAN map this onto a floating point number, and in truth mapping it onto said number solves a LOT of problems that are really difficult the old way, but that doesn't mean it's the only way to do it.

Awareness and understanding of the "square peg in a round hole" nature of using DateTimes to store Dates makes you a better programmer in my opinion.

Is there value in an application explicitly intended as a scheduling application in defining a Date class, or is "set all times to noon" the best approach? What issues might there be with using DateTime and setting the Time component to Noon? Can timezone shifting be accounted for in such an approach? I've used MomentJS, but I think that's just a better Date class.

16
  • 7
    this is a pretty interesting question I think. Take the birthday example, it only really makes sense in human, I'm living in one place and everyone wakes up at the sameish time, kind of way. as soon as you need to express it mathematically it becomes very complex. Same with stuff like work shift start/end times etc they arent 'times' they are 'hours of the day in the timezone in which you are working'
    – Ewan
    Sep 10, 2015 at 14:54
  • 3
    NodaTime (a C# library for Date&Time) has a type of dates. Sep 10, 2015 at 17:47
  • 5
    Store your times as UTC, no date issues, no time zone issues. Sep 11, 2015 at 3:49
  • 3
    "But God this seems like a hack, and I'm forever finding bugs from junior devs that trip over this issue." - why don't you just create your own DateOnly class or whatever and call it a day.
    – Brandin
    Sep 11, 2015 at 11:38
  • 5
    @MichaelBlackburn I'm serious. Your explanation sounds exactly the situation for an own data type which enforces the business rule (date only, e.g. you can store it as a "DateTime" internally with the time part set to noon), but then only expose the parts you want (month, day etc. year/no year).
    – Brandin
    Sep 11, 2015 at 14:55

9 Answers 9

15

First of all, let's get one thing out of the way: birthdays are one thing, dates-of-birth are another. A Birthday is an exotic data type because it lacks not only the components of hours, minutes etc but it also lacks the year component. If you really want to deal with birthdays, I would recommend inventing your own data type which contains nothing but a month number and a day number, and is not related to any of the built-in date-time data types.

On the other hand, if you want to also keep track of the year of birth, then what you have is not birthdays, it is dates-of-birth. So, the question now becomes why is there no date-only data type, so that you can conveniently represent dates-of-birth, and instead popular languages seem to force you to use some type that also includes a time component.

Let me briefly mention that it is not true that all programming languages only offer temporal data types which include a time component. I have come across date-only data types in RDBMSes and in their corresponding SQL dialects. But that's irrelevant: the fact that these data types exist does not mean that they are a good thing to have, and RDBMSes have a long history of confusing storage with representation.

You will understand why it is a bad idea to have such date-only data types the moment you realize that time is a coordinate. Most people have a very vague idea of what time is, and this idea contains arcane cultural notions such as years, months and days, without realizing that these notions are exclusively representational: They are only useful for representing time to a human, and to receive time as input from a human. At any layer below the actual time-entry GUI control, time should be, and usually is, represented as a time coordinate, which is a single number of time units since some origin.

For example, in the DateTime data type of Microsoft Dotnet, the time unit is 100 nanoseconds, and the origin of time is 12:00 midnight, January 1, 0001 C.E.

Another example of an arcane, exclusively representational notation is angle measurements using degrees, minutes of a degree, and seconds of a degree. Of course, in order to get any useful calculation done, you have to internally use radians, and if need be, convert to and from degrees when interacting with a human user.

So, do not confuse the human-readable representation of a measurement with the actual nature of the measurement. Quite often the ideal method for realizing a measurement, which most closely matches the nature of the measurement, is very different from the human-readable representation of that measurement.

In light of all this, your request for a temporal data type that represents only dates is akin to a request for an angular data type that would only be capable of representing degrees, explicitly preventing any greater precision. Such a data type would be quite limited, and ultimately useless, because you would have to convert it to and from radians anyway in order to get anything useful done with it.

Your problem with the date-of-birth is that you have an imprecise time coordinate: the person was, of course, born at a specific moment in time, but the hour and minute were either not recorded by the hospital, or we do not care about them. So, what is really happening is that your date-and-time-of-birth time coordinate has a margin of error, a tolerance or uncertainty if you wish, and it is best to treat it as such: put it at exactly the middle of the day, and consider an implied +12 -12 hour uncertainty. And that's precisely the solution that you have intuitively arrived to.

24
  • 4
    This is not true for "Days" which are a concept even older than Time. They are most properly thought of as an enumeration, or a modulus. Sunday follows Monday, etc until we get back to Sunday, which follows Saturday. It's really a different thing. Sep 10, 2015 at 15:25
  • 11
    Well of course the hospital wrote down the time of birth -- that's standard practice in most hospitals. That's not the issue at all. The issue is that for the purposes of commemorating events (birthdays, holidays, anniversaries, etc.) people don't use or want resolution that's finer than 1 day. Go ask the first person you see when their birthday is, and they'll give you a month and a day, but never an hour and minute. Also: we use units that include scale all the time: millimeter, kilogram, megawatt, and indeed, microfarad.
    – Caleb
    Sep 14, 2015 at 18:44
  • 4
    -1: Not all uses of time are to represent a single moment in time. A birthday is a different idea, represented by a month and day in the Gregorian calendar. It makes sense to ask "is it Kevin's birthday today?" The answer is location-dependent. If I am in Sydney it could be my birthday, but if I were in Honolulu instead, it wouldn't be for some hours yet. Sep 14, 2015 at 20:00
  • 6
    @MikeNakis The result would be microamps, not nonsense. More important: when you use a date rather than a second or millisecond, you're generally talking about the entire day -- a time period -- rather than a specific time, and depending on context it may be a repeating event.
    – Caleb
    Sep 14, 2015 at 20:08
  • 4
    The OP was talking about birthdays, which are exactly represented by java.util.MonthDay. A birthday is not a measurement, it's the human notion of a day that repeats yearly. There are lots of human ideas of time that are not represented by a single point in time. Sep 14, 2015 at 23:00
9

Dates and times are many different things depending on context, and you need many separate types to cover all use cases.

The DateTime type present in many languages represent a precise point in time ("instantaneous time"). Beyond this we have a number of relative or "human" time and timespan concepts like calendar days, recurring dates, months, years etc. which are in many cases ambiguous and context dependent. These types are not as universally useful, but necessary in specific application domains like calendars, scheduling tools and other applications which interact with human concepts of time.

If you are writing something like a calendar app you would definitely benefit from using a library like Joda-time which provides a richer set of time types. For example LocalDate, a date without time. This has different semantics than an ordinary DateTime with the time part set to zero, since the DateTime still indicates a specific point in time (midnight in a specific timezone), while LocalDate indicates the whole day, and is not tied to a specific timezone. This also means you cannot directly translate one to the other.

LocalDate is certainly simpler than DateTime because it doesn't have to take timezones into consideration, but you should be aware of the other issues, e.g. that the current date may actually go backwards when you are crossing a timezone, and that the same instant in time may correspond to different dates in different timezones. If you are using local dates in networked or web apps you should be very careful about these issues. Removing the time part from a date does not solve the fundamental problem of timezones! And if you take historical dates and different cultures into consideration it gets even trickier, since the same date may correspond to wildly different instances in time in say the Julian versus the Gregorian calendar.

Now you ask why languages does not have something like LocalDate built-in. Well first off, some languages like SQL and Visual Basic do have a date type without time part. And Java have also added a LocalDate in the recent version. But other platforms like .Net does not. Only the language designers can really answer why this is not included in the standard library, but my guess would be that "instantaneous time" is conceptually simple and universally useful, while the other time concepts are only useful for specific application domains (like calendars etc.). So it makes sense to let the application developer write custom types to handle the more complex use cases, or let it be handled by a third-party library (like Joda-time).

5

I'm so ridiculously frustrated with having to use DateTime values for data sets that are truly "just a day." Birthdays being the most-common example, but this comes up in business applications all the time.

This is probably because calendar are complicated and used in so many different ways that nobody has been able to figure a class which is simple yet general enough to be useful in many fields.

The date type commonly found in programming languages can be used to accurately date transactions in a computer system. Other use-cases are likely to require a custom library.

Here is a short lists of facts about calendars, demonstrating their complexity – most of them are historical, so if you are restricting your attention to dates after 1.1.1970, you will not be affected by this. However if your application needs to work with dates occurring before the late 19th century, then these facts would matter. Possible use-cases are historical databases of all sorts (books, genealogy) but also assets of large companies or organisation still in activity today.

All these facts are quoted from the excellent FAQ found in the calendar library for OCaml written by Julien Signolles.

  1. The Julian calendar was introduced by Julius Caesar in 45 BC. It was in common use until the 1500s, when countries started changing to the Gregorian calendar (section 2.2). However, some countries (for example, Greece and Russia) used it into the 1900s, and the Orthodox church in Russia still uses it, as do some other Orthodox churches.

  2. Switching from the Julian to the Gregorian calendar did not occur uniformly, and depending from the year of change, 10 to 13 days has been dropped. For instance in France 9 Dec 1582 was followed by 20 Dec 1582 and in Greece 9 Mar 1924 was followed by 23 Mar 1924.

  3. Even in the modern era, many different calendars are used (Gregorian, Orthodox, Islamic and Chinese) to quote a few, which all use different ways to compute years and anniversaries or the date religious celebrations.

Now you hope for a date type bundled with operations useful for general business operations. I guess there is no such a thing as general business operations. For instance, in the finance world, we need to compute:

  1. Year fractions (like “6 months” corresponds to “0.5”), which are used in conjunction with an interest rate to compute the actual interest on a loan for a given term. There is 6 to 10 recipes to compute these fractions, each differing in the ways they handle the length of a leap year, the position of the period with respect to the last day of February and the duration of a month.

  2. Date rolling, when computing anniversaries, we use a business calendar and a rule (picked from a set of more than 6 different rules) to shift an anniversary from an holiday to a business day.

For people working in the finance industry, any calendar type not implementing all these functions and rules is useless. It is likely that many other industries have other types of habits and conventions requiring custom computations on calendar.

Is there value in an application explicitly intended as a scheduling application in defining a Date class, or is "set all times to noon" the best approach? What issues might there be with using DateTime and setting the Time component to Noon? Can timezone shifting be accounted for in such an approach? I've used MomentJS, but I think that's just a better Date class.

If you need to keep track of a single calendar day, the best way is probably to use a large integer representing the Julian day of that calendar day. Algorithms to convert back and forth from the Julian day to the calendar day – described with year, month and calendar – are widely known and thoroughly tested, so that you can easily implement them in your application – and figure out whatever rule is pertinent in your case to compute the anniversary of an event occurring on some 29th Februray.

2

I think Mike Nakis in his answer above does a better job than I can of explaining how time in general is an absolute measured coordinate and any other communication, supposed state or persistence of that time coordinate is merely an abstracted representation of said time coordinate.

You speak to such representations when referring to Day of the Week as merely some modulus kind of representation of an actual point in time. In reality it is somewhat more complicated than that. If you were tasked to write a function that will return the Day of the Week for a given point in time, consider the following information that you will need as an input to such an algorithm. You will require the point in time, the Calendar, the time-zone to consider (Keep in mind, timezones change ALL THE TIME so you need to know when that effective timezone began to when it ended in specific time coordinates. North Korea recently just changed theirs for example!), and if Daylight Savings time is in effect, this changes over time as well. Now consider if you were given a DateTime in local timezone, you also have to convert this into a time coordinate for your algorithm.

You can see how complicated this seemingly simple question can truly be.

I know the pain you are feeling as I was in your shoes at one point fixing all the bugs in a visitation scheduling application for a product that was written by inexperienced developers. The whole thing had to be scrapped.

Time is indeed a coordinate, but besides a mere Date, consider other time sensitive data that might be needed like:

Duration: A span of milliseconds that might occur that signifies a length or passage of time with no specific time coordinates specified. A use case might be,

As a user, I would like this task to be performed 15 seconds after the completion of the midnight job every other Wednesday.

Interval: A range of time between two specific time coordinates. A use case where you may want to consider an interval.

As a user, I need to see every Day of the Month wholly contained within a specified interval of time.

Another quick point I wanted to make is that you made a comment about Floating Point numbers for time based data and I advise against it. Floating point arithmetic inevitably leads to rounding errors that may not give you as precise a measurement as necessary for time.

So in conclusion, all of this information inevitably leads to the following design considerations:

  • Specific points or ranges in time should be persisted in UTC or some datatype that contains enough information to be easily abstracted back to UTC where needed
  • Application logic should be written to format UTC or a range of UTC coordinates into a representational data state that is more digestible for the end user.
  • Important durations should be persisted in milliseconds
  • Locale specific or end user preferences on the display of any data regarding time should be persisted as additional data and treated like a display option. (Eg. Kiosk A, Central Time Zone, Military Time format, or User B preferences, Standard Time in Tel Aviv (GMT+7:00) timezone, etc...)
  • Avoid FP numbers
6
  • 1
    None of this is "inevitable". It may be common for many applications, but the Large Hadron Collider deals with events on the nanosecond scale. System times have moved to microseconds because milliseconds do not have enough precision. Calendar apps need to have a concept of a date which happens on a local calendar day whether or not you are in the same time zone you created the event. Sep 14, 2015 at 13:08
  • 1
    @AlanShutko And yes that "additional data" that is needed are things like the local time zone, and Days, and any other important data that must be captured. These are all just abstractions however to a single point in time, even if the point in time is not important to your algorithm. As far as nanosecond and even microsecond scale, my answer is geared more towards web and LOB type software. I doubt that the LHC language of choice is C# or Javascript.
    – maple_shaft
    Sep 14, 2015 at 13:14
  • 1
    Milliseconds are great for repeatable physical processes. For human activity, sometimes the appropriate unit is nominal days, e.g. if shipped at the end of the day it will be available for use at the destination three days later. Sep 14, 2015 at 23:16
  • In the past I have considered using 64-bit floating point numbers for time coordinates, and more importantly for time spans, because they allow you to express tiny quantities with great precision, as well as huge quantities, where loss of precision does not really matter. (So what if the Dinosaurs died 65 million years ago give or take a few years?) So, why do you say that they should be avoided?
    – Mike Nakis
    Oct 6, 2015 at 22:00
  • Mike, I think his concern is with FP math inevitably rounding as it's converting from decimal to binary values. For example, 0.3 is a repeating decimal when expressed as a binary FP number, and therefore cannot be represented exactly. It's not that you're missing the capacity, but you're missing the precision. Mar 18, 2016 at 16:16
2

In short, because most computer based time types are focused on handling the time and time zone issue properly.

There are 2 edge cases that are not served well by the usual approach. Setting a point in time that is on the other side of a daylight saving change using local time, which then gets converted in the UTC by a lower abstraction layer, and then makes you 1 hour early / late for your meeting.

The other one is (as per the question) modelling arbitrary date information, such as recording a person's date of birth. Imagine the case where two people are born at the same time, one in New Zealand, the other in Hawaii. The probability is that they will have different dates of birth on their passports, and if the person born in Hawaii moves to New Zealand, they will be considered a day older than the New Zealand born person, despite having lived for the exact same time.

The suggestion in the question as setting the date to have a time of midday, UTC will work, ALMOST everywhere. UTC offsets range from -12 to +14, so there are a few places in the pacific where this approach will fail. I tend to treat these data types as strings, in a yyyymmdd format, and if I have to do comparisons calculations between two dates, this can be safely done as a string comparison. When doing delta comparisons (e.g. between date and now, or how long till they reach age X), you need to ensure that all dates are created in the same UTC offset, and can then use the standard time functions to do the work.

2
  • 3
    "I tend to treat these data types as strings, in a yyyymmdd format, and if I have to do comparisons calculations between two dates, this can be safely done as a string comparison." That sure sounds like a Date datatype, just being carried in a String Sep 15, 2015 at 1:10
  • Just as you say, its a string representation of a date, and unlike the original poster, the date does not get changed by being in a UTC+13 timezone. Sep 15, 2015 at 14:20
2

Why isn't there a true “Date-Only” data type?

For the same reasons, I think, that DateTime values are generally specified in UTC: simplicity and reliability. The point of a DateTime value is to specify a single point in time that's unaffected by time zone, daylight savings, calendar, and other local adjustments. DateTime values specify one instant (up to the limit of the type's resolution), not a time period or a set of times. These limitations make it possible to compare DateTime values in a reliable, predictable, uncomplicated way.

Trying to specify a date with a DateTime value is like trying to use a point to specify an area. You can make that work by using a convention, like "this point represents the center of a circle with a 100m radius," but there are a lot of problems there: everybody needs to use the same convention, you need to write a bunch of supporting code to make working with the wrong type less painful, and it's pretty much guaranteed that at some point you'll need to specify an area that's larger or smaller than the conventional area. So it is with dates: you can use 'noon' as the conventional time for specifying dates, but then you get into time zones because people expect to specify dates in their local time rather than UTC. And even if you come up with a satisfactory way to use a DateTime to specify a date, you'll need more information to know whether it's an absolute date or a relative one: is it July 4, 1776 or every July 4? What if you want to repeat using some other period? And calendars have all sorts of crazy issues: some months are longer than others, some years are longer than others, some days are even longer than others, and some calendars have gaps in them. You wouldn't want to solve these problems just for whole days, because the same issues come up for shorter periods: you'd probably like to be able to write code that expresses "take 1 pill every 4 hours" as readily as "the group meets on every third Friday."

So, there's lots of complication involved in working with dates. It's relatively (no pun intended) easy to provide a type that specifies a point in time and work with it as you would a number, but extremely difficult to provide a type that addresses all the ways that dates are used.

As others have pointed out, there are languages and libraries that provide good support for dates, and it's often a good idea to use them given it's quite difficult to get date-related code exactly right.

4
  • 2
    "Difficult to provide a type that addresses all the ways that dates are used." True, which is why it's a horrible idea to try to provide a single type to do it all. The first Java releases provided only java.util.Date and the result was horrible. Then they added a complex java.util.Calendar hierarchy and it didn't get much better. Sep 14, 2015 at 20:12
  • 1
    @kevincline Fully agree. OP didn't specify a language, so I was aiming for generality.
    – Caleb
    Sep 14, 2015 at 20:39
  • "For the same reasons, I think, that DateTime values are generally specified in UTC" ... Oh, would that that were true. It's 2015, and there is still a lot of code being written using the author's home timezone :-( Sep 15, 2015 at 1:07
  • 1
    I was indeed wondering why there is not a generally-accepted convention surrounding dates. Your "using a point to represent an area" perfectly encapsulates my frustration. Sep 23, 2015 at 4:00
2

Why isn't there a true “Date-Only” data type?

There are many such types in various libraries for various languages. There is almost certainly one for your current language. The Java util package had a horrible API for time calculations, but the introduction of the java.time package has made life much better. See java.time.LocalDate, containing a year-month-day value, or java.time.MonthDay, containing only a month and day number.

2

Calendrical manipulation is one of the poorest-understood aspects of computing. Whole books have been written on the topic. @MichealBlackburn is absolutely right in asking for a date-only datatype, one that does not resolve down to a point on a timeline, subject to re-interpretation. Historically, there have been legitimate disputes about the meaning of a date. One has to look no further than the adoption of the Gregorian calendar to find how complex it can get. Furthermore, years have not always started on January 1st, even in western Europe and its colonies (e.g., Britain and British America started the year on March 25th).

1
  • 1
    This is right. The calendar FAQ I quote in my answer is an excellent resource to discover the complexities and subtletie4s of clandar manipulations Sep 16, 2015 at 8:29
-2

In answer to:

Well, I'd honestly like to know why almost every language only has one data type for this.

The most common reason might be "because it's not necessary". If you want a date time that doesn't care about hours, minutes, seconds, etc, then you'd just initialize it like this:

date = new DateTime(year, month, day, 0, 0, 0);

If you want you can extend DateTime yourself:

public class Date extends DateTime {
    ...
    public Date(int year, int month, int day) {
        this(year, month, day, 0, 0, 0);
    }
}

Note: I'm intentionally ignoring the default time and timezone. It doesn't really matter what you set them to, so long as they're the same for all Dates. You can make a case for UTC. You can make a case to use the timezone your server sits in - either way I don't think it's important for representing an imprecise value like Date. Same with the default time - you can make it 0, you can make it noon. It doesn't matter. If Facebook sends me a birthday notification at 00:01 but I was born at 23:59 I don't really care, and I won't be offended that they were more than 12 hours off.

The above is in Java but would work similarly in any language with a DateTime and inheritance. Java actually has numerous ways of solving this, and the above is deprecated(they want you to use Calendar now). But as others posted in comments, some languages actually do provide a Date class, presumably for just this reason.

In all likelihood, every Date implementation is probably just a wrapper for the language's DateTime and it zeroes out the time. Otherwise you would need duplicated code to solve problems like the number of days between two Dates/DateTimes, or whether two Dates are equal(What about Feb 29 and Mar 1?). Those kinds of things are usually solved in the DateTime class. It makes sense to reuse the same code for a Date.

6
  • 4
    -1: Use LocalDate if you want just year+month+day. Using DateTime for that purpose will lead to bugs when some other programmer sees DateTime and assumes that it represents a moment in time. Sep 14, 2015 at 20:09
  • @kevincline I didn't feel that's necessary because this question is language-agnostic. I would also think it's obvious that using code that's labeled "Deprecated" is a use-at-your-own-risk scenario. More importantly, I only used that as an example of something you can do in a scenario where a language doesn't have a LocalDate, or Date type class and you have to 'roll-your-own'.
    – Shaz
    Sep 14, 2015 at 22:01
  • 1
    But "it IS necessary" for clear concise and correct code involving business rules about time (as opposed to physical time). if the library doesn't provide types for nominal time it will be necessary for the programmer to invent them, and the way to do that does not start with "DateTime". Sep 14, 2015 at 23:55
  • @kevincline I don't see how the code would be any less clear or concise. What's the difference from a programmer's point of view between new Date(2000, 1, 1); and new Date(2000, 1, 1);? Can you tell which one has empty hours, minutes, and seconds underneath? If you're worried about extra functions showing up(like setMinutes()), then you could go the route of having Date wrap DateTime instead of inheriting from it, and then you only expose setYear, setMonth, setDay, etc. And it's certainly no less correct than the library's DateTime that you're using.
    – Shaz
    Sep 15, 2015 at 0:32
  • My background is largely C#, which doesn't have a construct analogous to LocalDate. We just just DateTime and muddle through, apparently. Sep 23, 2015 at 4:05

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