They both look the same, have roughly the same use. Nevertheless, they differ in many respects. I'll note the difference, and note a few pitfalls and peculiarities.
TIMESTAMP starts with the epoch, '1970-01-01 00:00:01' UTC and ends with '2038-01-19 03:14:07' UTC. This is all very nice today, and may actually hold up till our retirement, but the younger readers may yet have to deal with the Bug2K+38™, certain to arrive.
DATETIME starts with '1000-01-01 00:00:00' and lasts up to '9999-12-31 23:59:59'. More on that later.
In respect of range, your current event logs may well use a TIMESTAMP value, while your grandfather's and granddaughter's birth dates may require DATETIME.
In general I would suggest that anything that relates to now, can be a TIMESTAMP. A new entry is added? It is added now. It can be represented by a TIMESTAMP. Anything has an expiry time of a few minutes, perhaps a few days or a month? You'll be safe using it up till late 2037. Anything else had better use a DATETIME. In particular, dates relating to issues such as birth, insurance, the market etc. fall into this category.
History, however, does not even hold up with DATETIME. Rome fell long before MIN(DATETIME). You will have to manage your own. Not even YEAR will help you out.
TIMESTAMP makes for 4 bytes. DATETIME makes for 8 bytes. Now that we have this behind us, let's see why.
A TIMESTAMP is merely the number of elapsed seconds since the epoch. It is a number. Not only is this a number, it is an incrementing number, and without gaps. Ever ascending. It actually equals the UNIX_TIMESTAMP() function.
A DATETIME is more of an oddity. From the manual:
DATETIME: Eight bytes:
- A four-byte integer packed as
- A four-byte integer packed as
There's nothing sequential about a DATETIME. The value which follows 20110307095159 is 20110307095200. It's like the string representation without any delimiters. See the following (ignore the fraction, it's not really there):
firstname.lastname@example.org> SELECT NOW()+0; +-----------------------+ | NOW()+0 | +-----------------------+ | 20110307095238.000000 | +-----------------------+
The implication of this internal representation is that conversions must be made back and forth. When you want to add 5 seconds to a DATETIME, MySQL cannot simply add 5 to some number, but must make for a more complicated calculation.
Now here's a question: why does DATETIME start with '1000-01-01 00:00:00'? Can you provide with a convincing argument? The only argument I can find is hardly convincing.
Did you ever need to type in a password with your phone? Say, for your Credit Card company, or your Bank? Did you get recorded instructions saying "You must type six digits. You must not repeat the same digits three successive times"? That's somewhat nice, and probably a good advice. Did you ever get told "The first digit must not be 0"? That's just stupid. This means we're giving up on 100,000 valid passwords, thereby reducing our search space by 10%!
I suspect in both cases the reasoning is the same: if you start with a 0, and we represent it as a number, it won't make up for the same number of digits we intended it to.
In the case of your Credit Card company, there is no excuse. What's the problem with padding with zeros till you make those six digits?
In the case of MySQL's DATETIME, I can see a weak reasoning. Read this page on the docs to find out that a DATETIME can be represented in many forms, and can be automatically deduced from a string in many formats. Both strings '110307095100' and '20110307095100' make for the same DATETIME value. It is based on the number of characters in your text, that the parsing decides how to act.
The fact the first year is 1000 makes the number of digits predictable.
I don't see why I should care about automatically converting texts of different formats to a DATETIME. As a programmer, I'm perfectly content with strict typing. I don't mind passing around only texts of the form 'YYYY-MM-DD HH:MM:SS' (or, better yet, passing date objects and letting my connector do the translation). Moreover, I prefer it that way! It makes me feel safer, that I haven't passed a wrong text by mistake, to be silently accepted.
For this reason I don't like the idea of losing the ability to use DATETIME on 1,000 lost years. As far as I can tell, this is a MySQL specific issue; there is no ANSI SQL for DATETIME.