l10n_date.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. *
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. *
  20. */
  21. /**
  22. * This lib relies on `l10n.js' to implement localizable date/time strings.
  23. *
  24. * The proposed `DateTimeFormat' object should provide all the features that are
  25. * planned for the `Intl.DateTimeFormat' constructor, but the API does not match
  26. * exactly the ES-i18n draft.
  27. * - https://bugzilla.mozilla.org/show_bug.cgi?id=769872
  28. * - http://wiki.ecmascript.org/doku.php?id=globalization:specification_drafts
  29. *
  30. * Besides, this `DateTimeFormat' object provides two features that aren't
  31. * planned in the ES-i18n spec:
  32. * - a `toLocaleFormat()' that really works (i.e. fully translated);
  33. * - a `fromNow()' method to handle relative dates ("pretty dates").
  34. *
  35. * WARNING: this library relies on the non-standard `toLocaleFormat()' method,
  36. * which is specific to Firefox -- no other browser is supported.
  37. */
  38. navigator.mozL10n.DateTimeFormat = function(locales, options) {
  39. 'use strict';
  40. var _ = navigator.mozL10n.get;
  41. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleFormat
  42. function localeFormat(d, format) {
  43. var tokens = format.match(/(%E.|%O.|%.)/g);
  44. for (var i = 0; tokens && i < tokens.length; i++) {
  45. var value = '';
  46. // http://pubs.opengroup.org/onlinepubs/007908799/xsh/strftime.html
  47. switch (tokens[i]) {
  48. // localized day/month names
  49. case '%a':
  50. value = _('weekday-' + d.getDay() + '-short');
  51. break;
  52. case '%A':
  53. value = _('weekday-' + d.getDay() + '-long');
  54. break;
  55. case '%b':
  56. case '%h':
  57. value = _('month-' + d.getMonth() + '-short');
  58. break;
  59. case '%B':
  60. value = _('month-' + d.getMonth() + '-long');
  61. break;
  62. case '%Eb':
  63. value = _('month-' + d.getMonth() + '-genitive');
  64. break;
  65. // like %H, but in 12-hour format and without any leading zero
  66. case '%I':
  67. value = d.getHours() % 12 || 12;
  68. break;
  69. // like %d, without any leading zero
  70. case '%e':
  71. value = d.getDate();
  72. break;
  73. // %p: 12 hours format (AM/PM)
  74. case '%p':
  75. value = d.getHours() < 12 ? _('time_am') : _('time_pm');
  76. break;
  77. // localized date/time strings
  78. case '%c':
  79. case '%x':
  80. case '%X':
  81. // ensure the localized format string doesn't contain any %c|%x|%X
  82. var tmp = _('dateTimeFormat_' + tokens[i]);
  83. if (tmp && !(/(%c|%x|%X)/).test(tmp)) {
  84. value = localeFormat(d, tmp);
  85. }
  86. break;
  87. // other tokens don't require any localization
  88. }
  89. format = format.replace(tokens[i], value || d.toLocaleFormat(tokens[i]));
  90. }
  91. return format;
  92. }
  93. /**
  94. * Returns the parts of a number of seconds
  95. */
  96. function relativeParts(seconds) {
  97. seconds = Math.abs(seconds);
  98. var descriptors = {};
  99. var units = [
  100. 'years', 86400 * 365,
  101. 'months', 86400 * 30,
  102. 'weeks', 86400 * 7,
  103. 'days', 86400,
  104. 'hours', 3600,
  105. 'minutes', 60
  106. ];
  107. if (seconds < 60) {
  108. return {
  109. minutes: Math.round(seconds / 60)
  110. };
  111. }
  112. for (var i = 0, uLen = units.length; i < uLen; i += 2) {
  113. var value = units[i + 1];
  114. if (seconds >= value) {
  115. descriptors[units[i]] = Math.floor(seconds / value);
  116. seconds -= descriptors[units[i]] * value;
  117. }
  118. }
  119. return descriptors;
  120. }
  121. /**
  122. * Returns a translated string which respresents the
  123. * relative time before or after a date.
  124. * @param {String|Date} time before/after the currentDate.
  125. * @param {String} useCompactFormat whether to use a compact display format.
  126. * @param {Number} maxDiff returns a formatted date if the diff is greater.
  127. */
  128. function prettyDate(time, useCompactFormat, maxDiff) {
  129. maxDiff = maxDiff || 86400 * 10; // default = 10 days
  130. switch (time.constructor) {
  131. case String: // timestamp
  132. time = parseInt(time);
  133. break;
  134. case Date:
  135. time = time.getTime();
  136. break;
  137. }
  138. var secDiff = (Date.now() - time) / 1000;
  139. if (isNaN(secDiff)) {
  140. return _('incorrectDate');
  141. }
  142. if (Math.abs(secDiff) > 60) {
  143. // round milliseconds up if difference is over 1 minute so the result is
  144. // closer to what the user would expect (1h59m59s300ms diff should return
  145. // "in 2 hours" instead of "in an hour")
  146. secDiff = secDiff > 0 ? Math.ceil(secDiff) : Math.floor(secDiff);
  147. }
  148. if (secDiff > maxDiff) {
  149. return localeFormat(new Date(time), '%x');
  150. }
  151. var f = useCompactFormat ? '-short' : '-long';
  152. var parts = relativeParts(secDiff);
  153. var affix = secDiff >= 0 ? '-ago' : '-until';
  154. for (var i in parts) {
  155. return _(i + affix + f, { value: parts[i]});
  156. }
  157. }
  158. // API
  159. return {
  160. localeDateString: function localeDateString(d) {
  161. return localeFormat(d, '%x');
  162. },
  163. localeTimeString: function localeTimeString(d) {
  164. return localeFormat(d, '%X');
  165. },
  166. localeString: function localeString(d) {
  167. return localeFormat(d, '%c');
  168. },
  169. localeFormat: localeFormat,
  170. fromNow: prettyDate,
  171. relativeParts: relativeParts
  172. };
  173. };