How to store very old dates in database? - mysql

It's not actually a problem I'm having, but imagine someone's building a website about the medieval times and wants to store dates, how would they go about it?
The spec for MySQLs DATE says it won't go below the year 1000. Which makes sense when the format is YYYY-MM-DD. How can you store information about the death of Kenneth II of Scotland in 995? Of course you can store it as a string, but are there real date-type options?

Actually, you can store dates below year 1000 in MySQL despite even documentation clarification:
mysql> describe test;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| birth | date | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
-you still need to input year in YYYY format:
mysql> insert into test values (1, '0995-03-05');
Query OK, 1 row affected (0.02 sec)
mysql> select * from test;
+------+------------+
| id | birth |
+------+------------+
| 1 | 0995-03-05 |
+------+------------+
1 row in set (0.00 sec)
-and you'll be able to operate with this as a date:
mysql> select birth + interval 5 day from test;
+------------------------+
| birth + interval 5 day |
+------------------------+
| 0995-03-10 |
+------------------------+
1 row in set (0.03 sec)
As for safety. I've never faced a case when this will not work in MySQL 5.x (that, of cause, does not mean that it will 100% work, but at least it is reliable with certain probability)
About BC dates (below Christ). I think that is simple - in MySQL there's no way to store negative dates as well. I.e. you will need to store year separately as a signed integer field:
mysql> select '0001-05-04' - interval 1 year as above_bc, '0001-05-04' - interval 2 year as below_bc;
+------------+----------+
| above_bc | below_bc |
+------------+----------+
| 0000-05-04 | NULL |
+------------+----------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1441 | Datetime function: datetime field overflow |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)
But I think, in any case (below/above year 0) it's better to store date parts as integers in that case - this will not rely to undocumented feature. However, you will need to operate with those 3 fields not as the dates (so, in some sense that is not a solution to your problem)

Choose a dbms that supports what you want to do. Among other free database management systems, PostgreSQL supports a timestamp range from 4713 BC to 294276 AD.
If you break up the date into separate columns for year, month, and day, you also need more tables and constraints to guarantee that values in those columns represent actual dates. If those columns let you store the value {2013, 2, 29}, your table is broken. A dbms that supports dates in your range entirely avoids this kind of problem.
Other problems you might run into
Incorrect date arithmetic on dates that are out of range.
Incorrect locale-specific formatting on dates that are out of range.
Surprising behavior from date and time functions on dates that are out of range.
Gregorian calendar weirdness.
Gregorian calendar weirdness? In Great Britain, the day after Sep 2, 1752 is Sep 14, 1752. PostgreSQL documents their rationale for ignoring that as follows.
PostgreSQL uses Julian dates for all date/time calculations. This has
the useful property of correctly calculating dates from 4713 BC to far
into the future, using the assumption that the length of the year is
365.2425 days.
Date conventions before the 19th century make for interesting reading,
but are not consistent enough to warrant coding into a date/time
handler.

Sadly, I think that currently the easiest option is to store year, month and day in separate fields with year as smallint.

To quote from http://dev.mysql.com/doc/refman/5.6/en/datetime.html
For the DATE and DATETIME range descriptions, “supported” means that although earlier values might work, there is no guarantee.
So there's a good change that a wider range will work given a sufficiently configured MySQL installation.
Make sure not to use TIMESTAMP, which seems to have a non-negative range.
The TIMESTAMP data type is used for values that contain both date and time parts. TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.
Here is a JavaScript example how far before the UNIX epoch(1) you can get with 2^36 seconds * -1000 (to get to milliseconds for Javascript).
d = new Date((Math.pow(2, 36) - 1) * -1000)
Sun May 13 -208 18:27:45 GMT+0200 (Westeuropäische Sommerzeit)
So I would suggest to store historical dates as BIGINT relative to the epoch.
See http://dev.mysql.com/doc/refman/5.6/en/integer-types.html for MxSQL 5.6.
(1)
epoch = new Date(0)
Thu Jan 01 1970 01:00:00 GMT+0100 (Westeuropäische Normalzeit)
epoch.toUTCString()
"Thu, 01 Jan 1970 00:00:00 GMT"

Related

Calculate age with decimals from date of birth

I have a dob field in my MySQL table that's of type date. Just a small, trivial example is something like this:
mysql> select dob from players limit 5;
+------------+
| dob |
+------------+
| 1983-12-02 |
| 1979-01-01 |
| 1989-05-11 |
| 1976-03-24 |
| 1989-09-12 |
+------------+
I am trying to calculate ages with decimal points by using today's date. So technically if your birthday is June 1, 1981 or 1981-06-01, that makes you 33 and today is June 7.. you'd be 33.(6/365) or 33.02 years old. What's the easiest way to calculate this using SQL?
Usually DOB calculation is pretty easy in mysql when you want to calculate the years without any fraction something as
mysql> select timestampdiff(YEAR,'1981-06-01',now());
+----------------------------------------+
| timestampdiff(YEAR,'1981-06-01',now()) |
+----------------------------------------+
| 33 |
+----------------------------------------+
But since you need the fraction also then this should do the trick
mysql> select format(datediff(curdate(),'1981-06-01') / 365.25,2);
+-----------------------------------------------------+
| format(datediff(curdate(),'1981-06-01') / 365.25,2) |
+-----------------------------------------------------+
| 33.02 |
+-----------------------------------------------------+
Year is considered as 365.25 days.
So in your case you may have the query as
select
format(datediff(curdate(),dob) / 365.25,2) as dob
from players limit 5;
You can use the to_days function to calculate the days between the year zero and someone's birthday. Then subtract from today that number of days. That should give you the birthday as if someone was born in the year zero:
select year(subdate(now(), to_days(dob)))
Example at SQL Fiddle.
There are a couple of issues. you are using 365 as the number of days in a year and ignoring leap years.
The easiest way to do this would be
select datediff( now(), '1981-06-01' )/365.25;
When you use 365.25 it will spread the leap year and you should be fine. MySQL says this value is 33.0157. when you round it off to two decimals you should be fine.
Your best bet is use of DATEDIFF() as follows:
select datediff(NOW(),date("1983-12-02"))/365;
where the quoted date is your date field, so your example would translate to:
select datediff(NOW(),date(dob))/365 from players limit 5;
The result is returned would be the number of years since the birth date to 4 decimal places.
If you prefer two decimal places use truncate() as follows:
truncate(datediff(NOW(),date("1983-12-02"))/365,2);

What data type to be used for storing dates like '01-05'?

I want to store the list of all public holidays in a year.
Then the employees can avail the leaves for these days. The leave details will be stored in another table.
My issue is that I need to have a table for listing all the public holidays and the corresponding leaves.
Ex- 1st May must be listed for May Day.
However, I can't give date here (01-05-2014) because the same dates (01-05) will occur in each year.
So, how can I store these dates in a mysql table.
My current table structure is:
mysql> desc table;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(10) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| leaveCode | text | NO | | NULL | |
| date | date | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
Here, I use date as the data type for the date.
But, It's not working.
When I tried inserting the values, the dates field is not getting populated.
<pre>
INSERT INTO table VALUES ('', 'May Day', 'MD', '01-05');
</pre>
I need a method to store these dates in the format - DD-MM in the table (Ex: 01-05).
Can someone please help me.
I'd rather prefer to store the day and the month in separate numeric fields, just because if you store a date, you must have to choose an arbitrary year with 366 days...
The concept of Date is a moment in time. You're not storing a date, although you could use the Date representation to store your infomation.
BUT, if you really want to use the Date column, you can choose some arbitrary leap year and set your "date" as YYYY-MM-DD
You can't store a holiday list as just month and day, because holidays vary from year-to-year on the calendars most commonly used for business. Religious holidays are notorious for wandering around the calendar. Consider:
Easter
Passover
Ramadan
Add to that holidays such as the Chinese New Year, American Thanksgiving, and American President's Day and you might start to think that calendar days have nothing to do with holidays. Okay, there are a few -- New Year's Day, May Day, Fourth of July, and Bastille Day. In the United States, there is a tendency to move national holidays onto a Monday, when the holiday would occur on the weekend.
You need a holiday table or calendar table with holiday information. When I've needed such a beast in the past, I've just used the holidays list provided with gmacs. I'm pretty sure there are other sources on the web for this information.

manipulate string to extract date-time

I have MeasureDateTime (nvarchar(50)) column in my SQL Server table.
I need to get
|measureFilePath | MeasureDateTime | MeasureName |
| 12Nc121 |Thu Jun 19 15:00:05 2011| Annulus 4th RMS (Waves) |
| 12NB121 |Thu Jul 19 15:38:05 2012| 3.0mm 4th RMS (Waves) |
| 12NXc121 |Tue May 15 12:13:02 2012| BC (mm) |
| 12NA121 |Tue May 15 12:13:02 2012| CT (mm) |
| 12Nc111 |Tue May 15 12:13:02 2012| Reference Angle (deg.) |
| 12Nc231 |Wed May 15 12:03:02 2013| Temperature (C) |
I want to get last 6 months of data using the MeasureDateTime column, for example.
But the problem is MeasureDateTime is of nvarchar type.
Anyone know how to do this? Is it even possible?
Try
CONVERT(datetime,SUBSTRING(MeasureDateTime,4,100),101)
to convert the varchar column into a datetime format.
SUBSTRING(MeasureDateTime,4,100) removes the weekday part of the date string and the CONVERT() call with format 101 will accept the US-type date format.
The select could look like
SELECT * FROM table WHERE DATEADD(month, 6, CONVERT(datetime,SUBSTRING(MeasureDateTime,4,100),101)) > getdate()
I ignored the inserted time information before. Here another attempt on the date conversion:
convert(datetime,substring(stuff(dt,11,0,right(dt,5)),4,21),101)
This approach relies on equal string lengths. I don't know for certain whether that is a given here.
This is a bit convoluted but may work:
WHERE DATEDIFF(Month, CAST(RIGHT(MeasureDateTime, LEN(MeasureDateTime) - 4) as datetime), GETDATE()) <= 6
Try using a trigger for this table when inserting. Try the solution provided by #cars10 to get the date and store it in a datetime field. Always keep datetime fields as datetime rather than varchar fields. Later on you my have to retrieve records based on date and this datetime field will prove to be useful.

MYSQL Convert timestamp to Month

I have this certain problem about mysql date functions.
I'm trying to compare the value of THIS MONTH to the given timestamp in database.
For example, month today is june, and the timestamp is 1369967316
And I'm trying to determine if that timestamp is in month of june.
$query = db_query("SELECT * FROM users WHERE MONTH(FROM_UNIXTIME(CURDATE())) = MONTH(1369967316)");
//count total members this mont
$members_month = $query->rowCount();
so if I used the rowCount, the $members_month should have the value of 1.
Unfortunately it doesn't work.
Any help would be appreciated.
Well I saw some answers that some kind of relevant to mine but it doesn't hit the spot or I didn't applied it well.
mysql get month from timestamp not working
how to use curdate() in where clause against unixtimestamp (bigint) column
This works for me:
mysql> SELECT MONTH(FROM_UNIXTIME(1369967316));
+----------------------------------+
| MONTH(FROM_UNIXTIME(1369967316)) |
+----------------------------------+
| 5 |
+----------------------------------+
Your issue is likely coming from the fact that 1369967316 is May 30th, not June (as you expect), thus resulting in an inequality with MONTH(CURDATE()).
mysql> SELECT FROM_UNIXTIME(1369967316);
+---------------------------+
| FROM_UNIXTIME(1369967316) |
+---------------------------+
| 2013-05-30 22:28:36 |
+---------------------------+

Using Time datatype in MySQL without seconds

I'm trying to store a 12/24hr (ie; 00:00) clock time in a MySQL database. At the moment I am using the time datatype. This works ok but it insists on adding the seconds to the column. So you enter 09:20 and it is stored as 09:20:00. Is there any way I can limit it in MySQL to just 00:00?
That doesn't look possible. The TIME data type is defined to represent the time of the day (or elapsed time) with a 1 second resolution. However, you can always use the DATE_FORMAT() function to format your field as HH:MM in a SELECT query:
SELECT DATE_FORMAT(NOW(), '%k:%i');
+-----------------------------+
| DATE_FORMAT(NOW(), '%k:%i') |
+-----------------------------+
| 4:09 |
+-----------------------------+
1 row in set (0.00 sec)
SELECT DATE_FORMAT(NOW(), '%H:%i');
+-----------------------------+
| DATE_FORMAT(NOW(), '%H:%i') |
+-----------------------------+
| 04:09 |
+-----------------------------+
1 row in set (0.00 sec)
The TIME column type does not accept any parameter or modifier to define range or precision. You can, however, omit seconds on insert if you are careful:
Be careful about assigning abbreviated values to a TIME column. MySQL
interprets abbreviated TIME values with colons as time of the day.
That is, '11:12' means '11:12:00', not '00:11:12'. MySQL interprets
abbreviated values without colons using the assumption that the two
rightmost digits represent seconds (that is, as elapsed time rather
than as time of day). For example, you might think of '1112' and 1112
as meaning '11:12:00' (12 minutes after 11 o'clock), but MySQL
interprets them as '00:11:12' (11 minutes, 12 seconds). Similarly,
'12' and 12 are interpreted as '00:00:12'.
CREATE TABLE example (
example_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
elapsed TIME NOT NULL,
PRIMARY KEY (example_id)
);
INSERT INTO example (elapsed) VALUES ('123:45:00'), ('123:45');
SELECT * FROM example;
+------------+-----------+
| example_id | elapsed |
+------------+-----------+
| 1 | 123:45:00 |
| 2 | 123:45:00 |
+------------+-----------+
... and you can remove them on read (if necessary) by applying a proper TIME_FORMAT(), noting that:
If the time value contains an hour part that is greater than 23, the
%H and %k hour format specifiers produce a value larger than the usual
range of 0..23. The other hour format specifiers produce the hour
value modulo 12.
INSERT INTO example (elapsed) VALUES ('2:00');
SELECT example_id, TIME_FORMAT(elapsed, '%k:%i') AS elapsed
FROM example;
+------------+---------+
| example_id | elapsed |
+------------+---------+
| 1 | 123:45 |
| 2 | 123:45 |
| 3 | 2:00 |
+------------+---------+
Since MySQL/5.7.5 you can also use a generated column to get a display value automatically:
-- Completely untested, I don't have 5.7 yet
CREATE TABLE example (
example_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
elapsed TIME NOT NULL,
-- Size to accomodate for '-838:59:59'
elapsed_display VARCHAR(10) AS (TIME_FORMAT(elapsed, '%k:%i')) VIRTUAL NOT NULL,
PRIMARY KEY (example_id)
);