Search code examples
javamysqldateinternationalizationhijri

How to perform 'between' in Arabic (hijri) calendar and save it as 'date' in MySQL?


My question is actually my ultimate aim. So far, I am having 2 issues.

  1. How to save arabic date as a 'date' in mysql? because, I have been converting Gregorian to Hijri and then, using preg_replace (php, for now, final is in Java) would change the numbers to arabic ascii hex... and then, save it in MySQL as varchar.

I know about collation cp1256_general_ci which allows us to store in arabic but, currently, for simplicity sake, I have put it aside. utf-8_general is doing fine too. So storing as varchar is not an issue, storing as 'date' is.

  1. Performing queries on it. I thought the requirements would end there but, now the task is to perform queries like date 'between' xyz and pqr... Also, the constraint is to 'store it in arabic only'.

Any inputs are much appreciated.


Solution

  • SQL dates

    I'd think about it like this: the server actually stores a date as a reference to a given day. How it does that is no concern of yours. When storing data to or reading data from such a date column, the server represents that date using a specific calendar, which is gregorian by convention. What I'm trying to say is, I wouldn't consider the stored value to be gregorian, although it may well be. I would rather consider the transferred date to be gregorian.

    So the best solution, in my opinion, is accepting that fact and converting between Gregorian and Hijri on the application side. That way, you could use normal between checks on that.

    Strings made up from numbers

    If this is not possible, due to the fact that the locale-dependent conversion is too complicated, or because the mapping betwen Hijri and Grogorian is not unique or not known in advance, then you will have to store the date in some other form. Possible forms that come to my mind are either a varchar containing strings of the form YYYY-MM-DD, with the letters signifying digits. This scheme ensures that strings would compare like the dates they represent, so you could still use between on them. Turning these strings back into spelled out dates would still be tricky, though.

    One or more numeric columns

    So I would actually suggest you use three columns., each containing a number signifying a date, You could then use 10000*year + 100*month + day_of_month to obtain a single number for each day, which you could use for comparisons and between. On the other hand, you could use the function ELT in your queries to turn the number for the month back into a name. If performance is an issue, you might be better of storing just a single number, and splitting it into parts upon selection. In a Gregorian calendar, this would look like this:

    CREATE TABLE tableName (myDate DECIMAL(8));
    
    SELECT myDate DIV 10000 AS year,
           ELT((myDate DIV 100) MOD 100, "Jan", "Feb", …) AS month,
           myDate MOD 100 AS day_of_month
    FROM tableName
    WHERE myDate BETWEN 20121021 AND 20121023;
    

    Compatibility and convenience

    If you have to maintain read-only compatibility with code that expects a single textual date column, you could use a VIEW to provide that. For example for a German Gregorian DD. MMMM YYYY format, you could use code like this:

    CREATE VIEW compatibleName AS
    SELECT CONCAT(myDate MOD 100, ". ",
                  ELT((myDate DIV 100) MOD 100, "Januar", "Februar", …), ". ",
                  myDate DIV 10000) as dateString,
           * -- or explicitely name other columns needed for compatibility
    FROM tableName
    

    Decoding strings

    If you need read-write access by another application using a string format, you'll have to parse those strings yourself. You can do that at the SQL level. Useful tools are SUBSTRING_INDEX to split the string into fields and FIELD to turn a month name into a number. You might want to add a trigger to the database which will ensure that your strings will always be in a valid format which you can decompose in this way. This question gives details on how to use triggers to enforce such checks.