date-formatting.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Single Date Formatting
  2. // -------------------------------------------------------------------------------------------------
  3. // call this if you want Moment's original format method to be used
  4. function momentFormat(mom, formatStr) {
  5. return moment.fn.format.call(mom, formatStr);
  6. }
  7. // Formats `date` with a Moment formatting string, but allow our non-zero areas and
  8. // additional token.
  9. function formatDate(date, formatStr) {
  10. return formatDateWithChunks(date, getFormatStringChunks(formatStr));
  11. }
  12. function formatDateWithChunks(date, chunks) {
  13. var s = '';
  14. var i;
  15. for (i=0; i<chunks.length; i++) {
  16. s += formatDateWithChunk(date, chunks[i]);
  17. }
  18. return s;
  19. }
  20. // addition formatting tokens we want recognized
  21. var tokenOverrides = {
  22. t: function(date) { // "a" or "p"
  23. return momentFormat(date, 'a').charAt(0);
  24. },
  25. T: function(date) { // "A" or "P"
  26. return momentFormat(date, 'A').charAt(0);
  27. }
  28. };
  29. function formatDateWithChunk(date, chunk) {
  30. var token;
  31. var maybeStr;
  32. if (typeof chunk === 'string') { // a literal string
  33. return chunk;
  34. }
  35. else if ((token = chunk.token)) { // a token, like "YYYY"
  36. if (tokenOverrides[token]) {
  37. return tokenOverrides[token](date); // use our custom token
  38. }
  39. return momentFormat(date, token);
  40. }
  41. else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
  42. maybeStr = formatDateWithChunks(date, chunk.maybe);
  43. if (maybeStr.match(/[1-9]/)) {
  44. return maybeStr;
  45. }
  46. }
  47. return '';
  48. }
  49. // Date Range Formatting
  50. // -------------------------------------------------------------------------------------------------
  51. // TODO: make it work with timezone offset
  52. // Using a formatting string meant for a single date, generate a range string, like
  53. // "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
  54. // If the dates are the same as far as the format string is concerned, just return a single
  55. // rendering of one date, without any separator.
  56. function formatRange(date1, date2, formatStr, separator, isRTL) {
  57. date1 = fc.moment.parseZone(date1);
  58. date2 = fc.moment.parseZone(date2);
  59. // Expand localized format strings, like "LL" -> "MMMM D YYYY"
  60. formatStr = date1.lang().longDateFormat(formatStr) || formatStr;
  61. // BTW, this is not important for `formatDate` because it is impossible to put custom tokens
  62. // or non-zero areas in Moment's localized format strings.
  63. separator = separator || ' - ';
  64. return formatRangeWithChunks(
  65. date1,
  66. date2,
  67. getFormatStringChunks(formatStr),
  68. separator,
  69. isRTL
  70. );
  71. }
  72. fc.formatRange = formatRange; // expose
  73. function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
  74. var chunkStr; // the rendering of the chunk
  75. var leftI;
  76. var leftStr = '';
  77. var rightI;
  78. var rightStr = '';
  79. var middleI;
  80. var middleStr1 = '';
  81. var middleStr2 = '';
  82. var middleStr = '';
  83. // Start at the leftmost side of the formatting string and continue until you hit a token
  84. // that is not the same between dates.
  85. for (leftI=0; leftI<chunks.length; leftI++) {
  86. chunkStr = formatSimilarChunk(date1, date2, chunks[leftI]);
  87. if (chunkStr === false) {
  88. break;
  89. }
  90. leftStr += chunkStr;
  91. }
  92. // Similarly, start at the rightmost side of the formatting string and move left
  93. for (rightI=chunks.length-1; rightI>leftI; rightI--) {
  94. chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);
  95. if (chunkStr === false) {
  96. break;
  97. }
  98. rightStr = chunkStr + rightStr;
  99. }
  100. // The area in the middle is different for both of the dates.
  101. // Collect them distinctly so we can jam them together later.
  102. for (middleI=leftI; middleI<=rightI; middleI++) {
  103. middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
  104. middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
  105. }
  106. if (middleStr1 || middleStr2) {
  107. if (isRTL) {
  108. middleStr = middleStr2 + separator + middleStr1;
  109. }
  110. else {
  111. middleStr = middleStr1 + separator + middleStr2;
  112. }
  113. }
  114. return leftStr + middleStr + rightStr;
  115. }
  116. var similarUnitMap = {
  117. Y: 'year',
  118. M: 'month',
  119. D: 'day', // day of month
  120. d: 'day', // day of week
  121. // prevents a separator between anything time-related...
  122. A: 'second', // AM/PM
  123. a: 'second', // am/pm
  124. T: 'second', // A/P
  125. t: 'second', // a/p
  126. H: 'second', // hour (24)
  127. h: 'second', // hour (12)
  128. m: 'second', // minute
  129. s: 'second' // second
  130. };
  131. // TODO: week maybe?
  132. // Given a formatting chunk, and given that both dates are similar in the regard the
  133. // formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
  134. function formatSimilarChunk(date1, date2, chunk) {
  135. var token;
  136. var unit;
  137. if (typeof chunk === 'string') { // a literal string
  138. return chunk;
  139. }
  140. else if ((token = chunk.token)) {
  141. unit = similarUnitMap[token.charAt(0)];
  142. // are the dates the same for this unit of measurement?
  143. if (unit && date1.isSame(date2, unit)) {
  144. return momentFormat(date1, token); // would be the same if we used `date2`
  145. // BTW, don't support custom tokens
  146. }
  147. }
  148. return false; // the chunk is NOT the same for the two dates
  149. // BTW, don't support splitting on non-zero areas
  150. }
  151. // Chunking Utils
  152. // -------------------------------------------------------------------------------------------------
  153. var formatStringChunkCache = {};
  154. function getFormatStringChunks(formatStr) {
  155. if (formatStr in formatStringChunkCache) {
  156. return formatStringChunkCache[formatStr];
  157. }
  158. return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
  159. }
  160. // Break the formatting string into an array of chunks
  161. function chunkFormatString(formatStr) {
  162. var chunks = [];
  163. var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination
  164. var match;
  165. while ((match = chunker.exec(formatStr))) {
  166. if (match[1]) { // a literal string inside [ ... ]
  167. chunks.push(match[1]);
  168. }
  169. else if (match[2]) { // non-zero formatting inside ( ... )
  170. chunks.push({ maybe: chunkFormatString(match[2]) });
  171. }
  172. else if (match[3]) { // a formatting token
  173. chunks.push({ token: match[3] });
  174. }
  175. else if (match[5]) { // an unenclosed literal string
  176. chunks.push(match[5]);
  177. }
  178. }
  179. return chunks;
  180. }