One of the issues I came across while working on one of our main projects is that of exporting date formats to code in a multilingual Drupal (D7) setup in an efficient and clean manner. Although this project, like many out there, relies heavily on Features, we found that the latter lacks the ability to export all the localised date formats: all that Features will do is to export the date formats used in a single language set up; these are stored as variables (date_format_<format-name>
), so Features uses Strongarm to export them. However, when you are in a multilingual setup and localise your date formats, these are stored in a database table named date_format_locale
, which is not exportable via Features. As a margin note, you should know that in a multilingual environment the date_format_<format-name>
variables are always synchronised to the values found in date_format_locale
for the default language. Whenever the latter change, the variables also change.
The path to the solution
You can skip to the next paragraph if you are not interested in understanding why implementing hook_date_formats()
is not enough, but you just want to get things done.
The first step was to find out about hook_date_formats()
. Although this works just fine to define date formats, which will then be correctly displayed in the regional settings, etc., that is also all it will do for you. If you are expecting that this hook will work like an export, you – like me at first – are misunderstanding what this hook does. And believe me: working on a big project where hardly any settings is ever allowed to be overridden at UI level will do that to you.
I had initially thought there was a flaw in Drupal as to how this hook is handled; so much so, that I even came up with a patch. The changes in the patch were based on the assumption that whatever is defined in hook_date_formats()
should always be restored whenever one flushes the caches, thus clearing any “database override”. I went through the trouble of “visiting” the call graph and inspecting all the functions involved. I eventually decided that there were issues with how both system_date_format_save()
and _system_date_formats_build()
were implemented.
The latter gets all the date formats defined via hook_date_formats()
, then merges them with those that have been saved directly to the database; however, it does that in such a way that, if what’s found in database is merely an override of what’s also found in code in the hook_date_formats()
implementations, the database values take precedence, thus ultimately overriding whatever it’s in code. I was convinced this was wrong behaviour, but I then understood that I was wrong. The behaviour is perfectly normal, as it allows CMS users to change what a module has defined. If flushing the caches were to restore the values specified in the hook_date_formats()
every time, the user would see their overrides lost each time.
Regarding the issue I thought I had found with system_date_format_save()
, when you compare this function to locale_date_format_save()
, you’ll notice that the former doesn’t do anything if the locale has already been previously saved, thus making it impossible to restore a value from code. But of course, I then realised it’s not its job to restore a value from code.
A solution
The solution I implemented is really simple, and I divided it in three steps.
The first step is actually optional, and it is simply a way to get a feature module where you can implement the second step. So, if you don’t and are not going to have any other feature module, then you might want to do this. Export the date formats for your default language into a feature module; like I said, this will use strongarm to actually store the data in code. Since I will be using actual code snippets, be aware that the default language in the examples below is Spanish (es
). Some of the relevant code that will go into your feature module will be lines like the following added to the .info
file:
and something like this added to the .strongarm.inc
file of the same feature module:
The second step is to implement your hook_date_formats()
in the .module
file of your feature module. You may have noticed from above that we also defined additional date format types, so our code below will also show the implementation of hook_date_format_types()
. I thought I’d leave it in there 😉
You may have noticed that we have repeated here the same date formats already exported above via Strongarm. That is, again, because the first step is optional and if you already have a feature module available, you can implement the second step there. While the implementation for hook_date_format_types()
will successfully add new types to your installation, as we said in our introduction, implementing hook_date_formats()
will not actually act as an “export”; it’s simply a definition.
The third and last step is to implement hook_post_features_revert()
in the same .module
file as above. Although I am not familiar with the internal details, this I do know: that when the hook is implemented in a feature module, it will only be invoked when components from that same feature module are reverted. Which is quite convenient for what we need to achieve, as we don’t want the following code to run once for every single feature being reverted.
The code above should be fairly self-explanatory, as all it does is to get all the date formats we defined in our implementation of hook_date_formats()
and save them all into the database.
The proper solution
Of course, the proper solution would be to add to Features the support to export the date_format_locale
database table. Since I have limited time, however, I was content with the solution outlined in this article. But if you do decide to implement proper Features support or you happen to know that it’s finally been done by the time you read this article, please do let me know in the comments!
Ad maiora.