JENS MALMGREN I create, that is my hobby.

Porting my blog for the second time, localize date and time

This is post #38 of my series about how I port this blog from Blogengine.NET 2.5 ASPX on a Windows Server 2003 to a Linux Ubuntu server, Apache2, MySQL and PHP. A so called LAMP. The introduction to this project can be found in this blog post https://www.malmgren.nl/post/Porting-my-blog-for-the-second-time-Project-can-start.aspx.

In my previous post I created the search routine of the right sidebar. The search result displays date and time. Displaying Date and Time is not a trivial task. We cannot just present the date and time as you are used to because there are several ways to present date and time. In the US they start with month then day and finally year. In Europe it is often so that a date starts with day then month and finally year. Then we have the ISO countries displaying year then month and finally day. That is so that you can sort by date textually. Clever! Several clock cycles per year are saved by making sorting simple.

Sweden is very much an ISO country. If there exists an international standard for something then the social convention in Sweden imply that you are supposed to follow that standard, otherwise..., in Sweden. Then imagine that the Metric Act was signed by the US President on 28 July 1866. Since then the metric system has been ignored for 149 years. Haha, Americans are slightly more ignorant about standards than Sweeds! You could argue that the Metric Act only allows those Metric people to trade things in the US so that the Metric and the Imperial people could live happily next to each other forever and the act does not imply that anyone should use anything. As I said, in Sweden everyone and his mother knows when something is better and if you don't abide to what is better then you are kindly requested to change your habit. I was driving around in Sweden last summer and nudging the traffic laws just slightly here and there and I was promptly honked at. An old man came to me and informed me that the combination of lights I had turned on on my car would render me a fine. I have endless examples like these.

Back to the date and time. My solution will be to present the date in the page in such a way that Google can understand the dates when indexing the pages. For this I will use the HTML5 tag

A blog like this is a client - server solution. The client is the browser and the server is LAMP, Linux + Apache2 + MySQL and PHP. How the data is stored and all that should obviously be solved on the server. How things are presented on the client should if possible be resolved on the client. For this to work we need to add functionality to the page as it is being rendered. I like to use the jQuery library and JavaScript for that. When the HTML page loads also the jQuery library need to be loaded and to do this I add this line to the head element of my PHP page:

≺script type="text/javascript" src="/jquery-1.11.3.min.js"≻≺/script≻

I host this library locally because I think that is safer. When using libraries hosted at other domains I think it is theoretically possible with man-in-the-middle attack. If I for example pointed to an external domain for loading the jQuery library then without knowing it download and run a compromised version of the library. That is not possible when the library is hosted at the same domain because then the whole site would not work. But how can a fake domain be set up? That can be done in for example public WIFI hot-spots. If there was a fake hot-spot setup with the same credentials as the public hot-spot and then in that hot-spot the domains of common libraries, such as jQuery for example, were replaced so that people with automatic login automatically gets logged on to the fake WIFI and starts browsing and get to load compromised libraries. Perhaps I understood it wrong, anyway I have the library locally.

For testing I create myself a simple test case so I create a variable for storing the date time from the database, at the server, for the current post.

# http://www.jens.malmgren.nl/post/Porting-my-blog-for-the-second-time-localize-date-and-time.aspx
$dtPublishedOn = null;

Nothing fancy here just an empty variable. Then I set this variable when I load the data from the database. Like this:

# http://www.jens.malmgren.nl/post/Porting-my-blog-for-the-second-time-localize-date-and-time.aspx
$dtPublishedOn = new DateTime($row["PublishedON"]);

When the PHP is rendering the page the variable $dtPublishedOn will hold an object with the date and time time instead of a string representing the date time. This object got built in functionality for presenting itself in various ways. I will use two ways, atom and rfc850 here below:

≺p≻≺time datetime = '≺?php
# http://www.jens.malmgren.nl/post/Porting-my-blog-for-the-second-time-localize-date-and-time.aspx
echo $dtPublishedOn-≻format(DateTime::ATOM); ?≻'≻≺?php echo $dtPublishedOn-≻format(DateTime::RFC850); ?≻≺/time≻≺/p≻

When requesting the page this code here gives me this in the HTML provided to the client:

≺p≻≺time datetime = '2015-04-06T20:57:00+02:00'≻Monday, 06-Apr-15 20:57:00 CEST≺/time≻≺/p≻

Now in the browser client we can pick up this text and do things with it so that the display changes depending on the settings of the client.

Before I continue I would like to stress one very important little detail here. This blog engine is minimalistic. I know it is something I inflicted upon myself. It is a curse and a blessing. When I keep it simple I know it fails because of me and not because of someone else. So I decided upon using jQuery but not moments.js or react or wordpress. So I could use moments. But I don't. Moments.js is probably fine but I decided I will not use it. So if you don't like this then you are free to continue to another page where they download all sorts of things, filling the browser memory with yet another library, and another, and another, and another, and another, and another, and another, and another.

Picking up the user preferred language setting is easier said than done. I'll explain why: It is a complete mess. Some developers claims that this works:

≺span≻≺span class="pl-k"≻var≺/span≻ language ≺span class="pl-k"≻=≺/span≻ (≺span class="pl-smi"≻navigator≺/span≻.≺span class="pl-c1"≻language≺/span≻ ≺span class="pl-k"≻||≺/span≻ ≺span class="pl-smi"≻navigator≺/span≻.≺span class="pl-smi"≻browserLanguage≺/span≻).≺span class="pl-c1"≻split≺/span≻(≺span class="pl-s"≻≺span class="pl-pds"≻'≺/span≻-≺span class="pl-pds"≻'≺/span≻≺/span≻)[≺span class="pl-c1"≻0≺/span≻];≺/span≻

While others are saying that from now on you have to use this:

≺span class="nx"≻var language = navigator.languages[0];≺/span≻

And finally some are saying that there are browsers (IE11) where you cannot get the user preferred language through JavaScript at all whatever you are doing. So instead of proving this right or wrong I gave up this entire idea. When the page is requested by PHP all browsers are providing user language preferences to the server. When I process the page on the server I can pick up that setting and "expose" the value to the page to be used in the client. This is a much more robust solution than all those changing navigator language things. Here is how I do this in PHP:

# http://www.jens.malmgren.nl/post/Porting-my-blog-for-the-second-time-localize-date-and-time.aspx
$strPreferredLanguage = "en";
if (isset ($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
{
	$arrayClientLanguages = preg_split("/,/", $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
	for ($i = 0; $i ≺ count($arrayClientLanguages); $i++)
	{
		$arrayClientLanguages[$i] = strtolower(preg_replace("/[;-].*?$/", "", $arrayClientLanguages[$i]));
	}
	$strPreferredLanguage = $arrayClientLanguages[0];
}

When the page is rendered the list of preferred languages are parsed by my PHP routine. I keep the first language after cleaning it from things I don't need.

Then in the next step I expose the $strPreferredLanguage in the rendering of the script.

≺script≻
// http://www.jens.malmgren.nl/post/Porting-my-blog-for-the-second-time-localize-date-and-time.aspx
$( document ).ready(function() {
	$('time').html(function()
	{
		var strDate = $( this ).attr('datetime');
		var reDate = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2}):(\d{2})$/;
		var arrayComponents = [];
		var iZoneOffset = 0;
		strDate.replace(reDate, function(match,y,m,d,h,min,sec,zs,zh,zm)
		{
			for(var bits = [y,m,d,h,min,sec,zh,zm], i = 0; i ≺ 8; i ++)
			{
				arrayComponents[i] = parseInt(bits[i], 10);
			}
			arrayComponents[1]--;
			iZoneOffset = (arrayComponents[6] * 3600 + arrayComponents[7] * 60) * (zs == '-' ? 1000 : -1000);
		});

		var d = new Date(Date.UTC.apply(Date, arrayComponents) + iZoneOffset);
		var strUserPreferredLang = '≺?php echo $strPreferredLanguage ?≻';
		$( this ).text( d.toLocaleDateString(strUserPreferredLang) + " at " + d.toLocaleTimeString(strUserPreferredLang) );
	});
});
≺/script≻

On line 21 you can see the exposed $strPreferredLanguage injected into the script.

I made this routine based on the excellent example provided by James Edwards. Before I explain what I am doing here I have to point out that IE11 cannot parse iso 8601 dates. This is because the iso 8601 standard had not been finalized at the moment or the Date parsing routine standard had not been finalized when IE11 was released. At first when I found out about this flaw I got so flabbergasted I wrote long pages about how bad IE11 is but it is just very bad luck. They were moments before, that can happen. So I deleted all my angry talks and implemented a routine that can parse iso 8601 dates.

Here below I explain every row of the jQuery routine parsing an iso 8601 date produced by PHPs DateTime format(DateTime::ATOM)

  1. Here starts the script section. It ends at line 25.
  2. This is a comment I inserted so that later I know what blogpost I was talking about this routine.
  3. On this line you find the standard way of launching jQuery functionality when the page finished loading. The functionality is an anonymous funtion that currently just defines one other function on the next line.
  4. This is a selector that activates on all time tags in the page. This anonymous function ends on line 23.
  5. Here begins the time tage parsing routine.
  6. Here we define the variable strDate. We assign it with the text found in the datetime attribute of the time tag. This routine will walk over all time tags of the page.
  7. Here we define a regular expression. Parenthesis is referring to a part of the date we are parsing. The year has 4 digits. Then comes month and day. Then a 'T'. Then comes hours, minutes and seconds. This is follewed by either a plus or a minus. then comes zone hours followed by zone minutes.
  8. On this line we define an array for components we are parsing.
  9. The iZoneOffset is used to offset the local time compared to the absolute time. This is the clever part that no matter what place on earth you will see the time correctly.
  10. Here we start parsing strDate with the replace function providing the regular expression reDate and an anonymous function where each parenthesis got its own local variable.
  11. This is the start of the anonymous function that will parse the parts of the date. This ends at line 18.
  12. Here is a loop that walks over all the parenthesis in the regular expression.  We walk over all but zs because that is the zone sign. We also don't convert the match. Remaining to be parsed are eight strings to convert to decemal numbers.
  13. This is the loop block. It ends on line 15.
  14. Each arrayComponent is converted to an integer.
  15. End of the loop parsing arrayComponents.
  16. The month components is zero based. Here we adjust for that.
  17. Here we calculate the zone offset. We take the zone hour multiplied by 3600 plus the zone minutes multiplied by 60 and then we multiplie all of this with positive thousand or negative thousand based on the zone sign.
  18. Here ends the zone replace function.
  19. This is an empty line.
  20. Here is the components provided to the UTC function for calculating the number of milliseconds between January 1, 1970. Then we add our zone offset. This give a giant big integer that given to a new date object calculates the date.
  21. Here we get the $strUserPreferredLang from the server.
  22. On line 22 we convert the date object and inject the result into the time tag we are processing. We inject it according to the preferred language setting.

Here are some examples of how it looks like in FireFox:

 

I was born 1967 in Stockholm, Sweden. I grew up in the small village Vågdalen in north Sweden. 1989 I moved to Umeå to study Computer Science at University of Umeå. 1995 I moved to the Netherlands where I live in Almere not far from Amsterdam.

Here on this site I let you see my creations.

I create, that is my hobby.