You can find it here: https://github.com/DataDink/client-time.js
What It Be
So now we have client-time.js. When I say my goal was to make it "better" realize that this term is subjective. In this case I was not trying to best the performance of the other solutions I found. I wanted to make a plugin that would translate dates and times from a server-rendered page to the timezone of the client rendering the page that had the following improvements:
- Easier to read code (this is also subjective)
- Adaptable format strings to match the server-side strings
The idea behind this is that the same string that is used to format a date will ideally be the same string required to parse a date. The process we are trying to support here is rendering a page with all dates and times in UTC, but having them seen by the end-user in his/her own timezone.
My Solution
Keep in mind that I wanted to make this easy to adapt to other format string schemas out there. So the first thing I did was try to break down the process of both parsing and formatting a date. What I came up with was 2 steps consisting of 2 parts each:
- pattern & reader
- parser & mapper
Writing this now in a blog makes me realize my naming conventions are off and should be fixed at some point...
The first step is to read a UTC time based on a formatter. For example if I am given a format string looking something like this: "yyyy MM dd HH:mm" I expect the time I am given to look something like this: "1999 12 20 15:32".
To make this possible in the plugin I've paired the pattern-item for reading (yyyy) with a regex pattern for parsing (/\d{4}/) and now I am able to convert a format string into a regex statement for reading. What I end up with is something like this: /(\d{4}) (\d{2}) (\d{2}) (\d{2}):(\d{2})/.
Once this is done all I have to do is take the information from the regex groups in the match and map it to the appropriate UTC setters on a javascript Date object. Unfortunately in javascript there are no named groups so prior to converting the format string to a regular expression I must make an index map letting me know what regex groups correspond with which pattern items. Anyways, to accomplish this I have grouped a function I am calling a "mapper" with each "pattern"/"parser" pair.
Finally all that is left to do is read the LOCAL values from the Date object and replace each part of the format string with the appropriate values. With this my formatter group grows to 4 items, the final being a function that takes a value from the Date object and returns it in the appropriate format.
What we get is something like this:
- var formatters = [
- { pattern: 'yyyy', parser: '\\\\d{4}', reader: function () { return leadZeros(this.year, 4); }, mapper: function (value) { this.setUTCFullYear(parseInt(value)); } },
- { pattern: 'yy', parser: '\\\\d{2}', reader: function () { return leadZeros(this.year, 2); }, mapper: function (value) { this.setUTCFullYear(parseInt(getCentury() + value)); } },
- { pattern: 'MMMM', parser: longMonths.join('|'), reader: function () { return longMonths[this.month]; }, mapper: function (value) { this.setUTCMonth(indexOf(longMonths, value)); } },
- { pattern: 'MMM', parser: shortMonths.join('|'), reader: function () { return shortMonths[this.month]; }, mapper: function (value) { this.setUTCMonth(indexOf(shortMonths, value)); } },
- { pattern: 'MM', parser: '\\\\d{2}', reader: function () { return leadZeros(this.month, 2); }, mapper: function (value) { this.setUTCMonth(parseInt(value) - 1); } },
- { pattern: 'M', parser: '\\\\d{1,2}', reader: function () { return this.month; }, mapper: function (value) { this.setUTCMonth(parseInt(value) - 1); } },
- { pattern: 'dddd', parser: longDays.join('|'), reader: function () { return longDays[this.dayOfWeek]; }, mapper: function (value) { } },
- { pattern: 'ddd', parser: shortDays.join('|'), reader: function () { return shortDays[this.dayOfWeek]; }, mapper: function (value) { } },
- { pattern: 'dd', parser: '\\\\d{2}', reader: function () { return leadZeros(this.day, 2); }, mapper: function (value) { this.setUTCDate(parseInt(value)); } },
- { pattern: 'd', parser: '\\\\d{1,2}', reader: function () { return this.day; }, mapper: function (value) { this.setUTCDate(parseInt(value)); } },
- { pattern: 'HH', parser: '\\\\d{2}', reader: function () { return leadZeros(this.hour24, 2); }, mapper: function (value) { this.setUTCHours(parseInt(value)); } },
- { pattern: 'H', parser: '\\\\d{1,2}', reader: function () { return this.hour24; }, mapper: function (value) { this.setUTCHours(parseInt(value)); } },
- { pattern: 'hh', parser: '\\\\d{2}', reader: function () { return leadZeros(this.hour12, 2); }, mapper: function (value) { this.setUTCHours(parseInt(value) % 12); } },
- { pattern: 'h', parser: '\\\\d{1,2}', reader: function () { return this.hour12; }, mapper: function (value) { this.setUTCHours(parseInt(value) % 12); } },
- { pattern: 'mm', parser: '\\\\d{2}', reader: function () { return leadZeros(this.minute, 2); }, mapper: function (value) { this.setUTCMinutes(parseInt(value)); } },
- { pattern: 'm', parser: '\\\\d{1,2}', reader: function () { return this.minute; }, mapper: function (value) { this.setUTCMinutes(parseInt(value)); } },
- { pattern: 'ss', parser: '\\\\d{2}', reader: function () { return leadZeros(this.second, 2); }, mapper: function (value) { this.setUTCSeconds(parseInt(value)); } },
- { pattern: 's', parser: '\\\\d{1,2}', reader: function () { return this.second; }, mapper: function (value) { this.setUTCSeconds(parseInt(value)); } },
- { pattern: 'o', parser: 'th|st|nd|rd', reader: function () { return this.ordinal; }, mapper: function (value) { } },
- { pattern: 'TT', parser: 'AM|PM', reader: function () { return this.ampm.toUpperCase(); }, mapper: function (value) { pmAdjust(this, value); } },
- { pattern: 'T', parser: 'A|P', reader: function () { return this.ampm.toUpperCase().substr(0, 1); }, mapper: function (value) { pmAdjust(this, value); } },
- { pattern: 'tt', parser: 'am|pm', reader: function () { return this.ampm.toLowerCase(); }, mapper: function (value) { pmAdjust(this, value); } },
- { pattern: 't', parser: 'a|p', reader: function () { return this.ampm.toLowerCase().substr(0, 1); }, mapper: function (value) { pmAdjust(this, value); } }
- ];
Instructions For Use
Here is how to use it:
First you will need to have both jQuery.js and client-time.js referenced by your page.
- <head>
- <meta charset="utf-8" />
- <title></title>
- <script src="scripts/jquery.js"></script>
- <script src="scripts/client-time.js"></script>
- </head>
Once that is done anywhere you want to display a date/time on your page make sure to wrap it in its own element. A span, or div would suffice.
- <p>
- A long time ago it was <span>1999 April 11, 12:00 pm</span>
- </p>
Next we need to inform "client-time.js" what format string we are using. I have wired this up to use both a config object or data attributes. You will most likely only ever want the data attributes. So now the previous markup should look something like this:
- <p>
- A long time ago it was <span data-time-format="yyyy MMMM d, hh:mm tt">1999 April 11, 12:00 pm</span>
- </p>
Finally we just need to let client-time.js know which elements need localizing
- $('[data-time-format]').clientTime();
My Disclaimers
- I do not recommend using this plugin (it was an experiment and is untested)
- I do not make any claims to the performance or quality of this plugin (it was an experiment and is untested)
- I DO think you should go here for a much more tested and simpler solution: http://stackoverflow.com/questions/3830418/is-there-a-jquery-plugin-to-convert-utc-datetimes-to-local-user-timezone
- I DO hope you will let me know if you do decide to use this plugin and tell me what you think.
No comments:
Post a Comment