| /* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */ |
| /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
| |
| 'use strict'; |
| |
| /** |
| * This lib relies on `l10n.js' to implement localizable date/time strings. |
| * |
| * The proposed `DateTimeFormat' object should provide all the features that are |
| * planned for the `Intl.DateTimeFormat' constructor, but the API does not match |
| * exactly the ES-i18n draft. |
| * - https://bugzilla.mozilla.org/show_bug.cgi?id=769872 |
| * - http://wiki.ecmascript.org/doku.php?id=globalization:specification_drafts |
| * |
| * Besides, this `DateTimeFormat' object provides two features that aren't |
| * planned in the ES-i18n spec: |
| * - a `toLocaleFormat()' that really works (i.e. fully translated); |
| * - a `fromNow()' method to handle relative dates ("pretty dates"). |
| * |
| * WARNING: this library relies on the non-standard `toLocaleFormat()' method, |
| * which is specific to Firefox -- no other browser is supported. |
| */ |
| |
| navigator.mozL10n.DateTimeFormat = function(locales, options) { |
| var _ = navigator.mozL10n.get; |
| |
| // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleFormat |
| function localeFormat(d, format) { |
| var tokens = format.match(/(%E.|%O.|%.)/g); |
| |
| for (var i = 0; tokens && i < tokens.length; i++) { |
| var value = ''; |
| |
| // http://pubs.opengroup.org/onlinepubs/007908799/xsh/strftime.html |
| switch (tokens[i]) { |
| // localized day/month names |
| case '%a': |
| value = _('weekday-' + d.getDay() + '-short'); |
| break; |
| case '%A': |
| value = _('weekday-' + d.getDay() + '-long'); |
| break; |
| case '%b': |
| case '%h': |
| value = _('month-' + d.getMonth() + '-short'); |
| break; |
| case '%B': |
| value = _('month-' + d.getMonth() + '-long'); |
| break; |
| case '%Eb': |
| value = _('month-' + d.getMonth() + '-genitive'); |
| break; |
| |
| // like %H, but in 12-hour format and without any leading zero |
| case '%I': |
| value = d.getHours() % 12 || 12; |
| break; |
| |
| // like %d, without any leading zero |
| case '%e': |
| value = d.getDate(); |
| break; |
| |
| // %p: 12 hours format (AM/PM) |
| case '%p': |
| value = d.getHours() < 12 ? _('time_am') : _('time_pm'); |
| break; |
| |
| // localized date/time strings |
| case '%c': |
| case '%x': |
| case '%X': |
| // ensure the localized format string doesn't contain any %c|%x|%X |
| var tmp = _('dateTimeFormat_' + tokens[i]); |
| if (tmp && !(/(%c|%x|%X)/).test(tmp)) { |
| value = localeFormat(d, tmp); |
| } |
| break; |
| |
| // other tokens don't require any localization |
| } |
| |
| format = format.replace(tokens[i], value || d.toLocaleFormat(tokens[i])); |
| } |
| |
| return format; |
| } |
| |
| /** |
| * Returns the parts of a number of seconds |
| */ |
| function relativeParts(seconds) { |
| seconds = Math.abs(seconds); |
| var descriptors = {}; |
| var units = [ |
| 'years', 86400 * 365, |
| 'months', 86400 * 30, |
| 'weeks', 86400 * 7, |
| 'days', 86400, |
| 'hours', 3600, |
| 'minutes', 60 |
| ]; |
| |
| if (seconds < 60) { |
| return { |
| minutes: Math.round(seconds / 60) |
| }; |
| } |
| |
| for (var i = 0, uLen = units.length; i < uLen; i += 2) { |
| var value = units[i + 1]; |
| if (seconds >= value) { |
| descriptors[units[i]] = Math.floor(seconds / value); |
| seconds -= descriptors[units[i]] * value; |
| } |
| } |
| return descriptors; |
| } |
| |
| /** |
| * Returns a translated string which respresents the |
| * relative time before or after a date. |
| * @param {String|Date} time before/after the currentDate. |
| * @param {String} useCompactFormat whether to use a compact display format. |
| * @param {Number} maxDiff returns a formatted date if the diff is greater. |
| */ |
| function prettyDate(time, useCompactFormat, maxDiff) { |
| maxDiff = maxDiff || 86400 * 10; // default = 10 days |
| |
| switch (time.constructor) { |
| case String: // timestamp |
| time = parseInt(time); |
| break; |
| case Date: |
| time = time.getTime(); |
| break; |
| } |
| |
| var secDiff = (Date.now() - time) / 1000; |
| if (isNaN(secDiff)) { |
| return _('incorrectDate'); |
| } |
| |
| if (Math.abs(secDiff) > 60) { |
| // round milliseconds up if difference is over 1 minute so the result is |
| // closer to what the user would expect (1h59m59s300ms diff should return |
| // "in 2 hours" instead of "in an hour") |
| secDiff = secDiff > 0 ? Math.ceil(secDiff) : Math.floor(secDiff); |
| } |
| |
| if (secDiff > maxDiff) { |
| return localeFormat(new Date(time), '%x'); |
| } |
| |
| var f = useCompactFormat ? '-short' : '-long'; |
| var parts = relativeParts(secDiff); |
| |
| var affix = secDiff >= 0 ? '-ago' : '-until'; |
| for (var i in parts) { |
| return _(i + affix + f, { value: parts[i]}); |
| } |
| } |
| |
| // API |
| return { |
| localeDateString: function localeDateString(d) { |
| return localeFormat(d, '%x'); |
| }, |
| localeTimeString: function localeTimeString(d) { |
| return localeFormat(d, '%X'); |
| }, |
| localeString: function localeString(d) { |
| return localeFormat(d, '%c'); |
| }, |
| localeFormat: localeFormat, |
| fromNow: prettyDate, |
| relativeParts: relativeParts |
| }; |
| }; |