/* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ /** * 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) { 'use strict'; 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 }; };