dateintervalformat.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. // Copyright 2017 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview DateIntervalFormat provides methods to format a date interval
  16. * into a string in a user friendly way and a locale sensitive manner.
  17. *
  18. * Similar to the ICU4J class com/ibm/icu/text/DateIntervalFormat:
  19. * http://icu-project.org/apiref/icu4j/com/ibm/icu/text/DateIntervalFormat.html
  20. *
  21. * Example usage:
  22. * var DateIntervalFormat = goog.require('goog.i18n.DateIntervalFormat');
  23. * var DateRange = goog.require('goog.date.DateRange');
  24. * var DateTime = goog.require('goog.date.DateTime');
  25. * var DateTimeFormat = goog.require('goog.i18n.DateTimeFormat');
  26. * var GDate = goog.require('goog.date.Date');
  27. * var Interval = goog.require('goog.date.Interval');
  28. *
  29. * // Formatter.
  30. * var dtIntFmt = new DateIntervalFormat(DateTimeFormat.Format.MEDIUM_DATE);
  31. *
  32. * // Format a date range.
  33. * var dt1 = new GDate(2016, 8, 23);
  34. * var dt2 = new GDate(2016, 8, 24);
  35. * var dtRng = new DateRange(dt1, dt2);
  36. * dtIntFmt.formatRange(dtRng); // --> 'Sep 23 – 24, 2016'
  37. *
  38. * // Format two dates.
  39. * var dt3 = new DateTime(2016, 8, 23, 14, 53, 0);
  40. * var dt4 = new DateTime(2016, 8, 23, 14, 54, 0);
  41. * dtIntFmt.format(dt3, dt4); // --> 'Sep 23, 2016'
  42. *
  43. * // Format a date and an interval.
  44. * var dt5 = new DateTime(2016, 8, 23, 14, 53, 0);
  45. * var itv = new Interval(0, 1); // One month.
  46. * dtIntFmt.format(dt5, itv); // --> 'Sep 23 – Oct 23, 2016'
  47. *
  48. */
  49. goog.module('goog.i18n.DateIntervalFormat');
  50. var DateLike = goog.require('goog.date.DateLike');
  51. var DateRange = goog.require('goog.date.DateRange');
  52. var DateTime = goog.require('goog.date.DateTime');
  53. var DateTimeFormat = goog.require('goog.i18n.DateTimeFormat');
  54. var DateTimeSymbols = goog.require('goog.i18n.DateTimeSymbols');
  55. var DateTimeSymbolsType = goog.require('goog.i18n.DateTimeSymbolsType');
  56. var Interval = goog.require('goog.date.Interval');
  57. var TimeZone = goog.require('goog.i18n.TimeZone');
  58. var array = goog.require('goog.array');
  59. var asserts = goog.require('goog.asserts');
  60. var dateIntervalSymbols = goog.require('goog.i18n.dateIntervalSymbols');
  61. var object = goog.require('goog.object');
  62. /**
  63. * Constructs a DateIntervalFormat object based on the current locale.
  64. *
  65. * @param {number|!dateIntervalSymbols.DateIntervalPatternMap} pattern Pattern
  66. * specification or pattern object.
  67. * @param {!dateIntervalSymbols.DateIntervalSymbols=} opt_dateIntervalSymbols
  68. * Optional DateIntervalSymbols to use for this instance rather than the
  69. * global symbols.
  70. * @param {!DateTimeSymbolsType=} opt_dateTimeSymbols Optional DateTimeSymbols
  71. * to use for this instance rather than the global symbols.
  72. * @constructor
  73. * @struct
  74. * @final
  75. */
  76. var DateIntervalFormat = function(
  77. pattern, opt_dateIntervalSymbols, opt_dateTimeSymbols) {
  78. asserts.assert(goog.isDef(pattern), 'Pattern must be defined.');
  79. asserts.assert(
  80. goog.isDef(opt_dateIntervalSymbols) ||
  81. goog.isDef(dateIntervalSymbols.getDateIntervalSymbols()),
  82. 'goog.i18n.DateIntervalSymbols or explicit symbols must be defined');
  83. asserts.assert(
  84. goog.isDef(opt_dateTimeSymbols) || goog.isDef(DateTimeSymbols),
  85. 'goog.i18n.DateTimeSymbols or explicit symbols must be defined');
  86. /**
  87. * DateIntervalSymbols object that contains locale data required by the
  88. * formatter.
  89. * @private @const {!dateIntervalSymbols.DateIntervalSymbols}
  90. */
  91. this.dateIntervalSymbols_ =
  92. opt_dateIntervalSymbols || dateIntervalSymbols.getDateIntervalSymbols();
  93. /**
  94. * DateTimeSymbols object that contain locale data required by the formatter.
  95. * @private @const {!DateTimeSymbolsType}
  96. */
  97. this.dateTimeSymbols_ = opt_dateTimeSymbols || DateTimeSymbols;
  98. /**
  99. * Date interval pattern to use.
  100. * @private @const {!dateIntervalSymbols.DateIntervalPatternMap}
  101. */
  102. this.intervalPattern_ = this.getIntervalPattern_(pattern);
  103. /**
  104. * Keys of the available date interval patterns. Used to lookup the key that
  105. * contains a specific pattern letter (e.g. for ['Myd', 'hms'], the key that
  106. * contains 'y' is 'Myd').
  107. * @private @const {!Array<string>}
  108. */
  109. this.intervalPatternKeys_ = object.getKeys(this.intervalPattern_);
  110. // Remove the default pattern's key ('_') from intervalPatternKeys_. Is not
  111. // necesary when looking up for a key: when no key is found it will always
  112. // default to the default pattern.
  113. array.remove(this.intervalPatternKeys_, DEFAULT_PATTERN_KEY_);
  114. /**
  115. * Default fallback pattern to use.
  116. * @private @const {string}
  117. */
  118. this.fallbackPattern_ =
  119. this.dateIntervalSymbols_.FALLBACK || DEFAULT_FALLBACK_PATTERN_;
  120. // Determine which date should be used with each part of the interval
  121. // pattern.
  122. var indexOfFirstDate = this.fallbackPattern_.indexOf(FIRST_DATE_PLACEHOLDER_);
  123. var indexOfSecondDate =
  124. this.fallbackPattern_.indexOf(SECOND_DATE_PLACEHOLDER_);
  125. if (indexOfFirstDate < 0 || indexOfSecondDate < 0) {
  126. throw new Error('Malformed fallback interval pattern');
  127. }
  128. /**
  129. * True if the first date provided should be formatted with the first pattern
  130. * of the interval pattern.
  131. * @private @const {boolean}
  132. */
  133. this.useFirstDateOnFirstPattern_ = indexOfFirstDate <= indexOfSecondDate;
  134. /**
  135. * Map that stores a Formatter_ object per calendar field. Formatters will be
  136. * instanced on demand and stored on this map until required again.
  137. * @private @const {!Object<string, !Formatter_>}
  138. */
  139. this.formatterMap_ = {};
  140. };
  141. /**
  142. * Default fallback interval pattern.
  143. * @private @const {string}
  144. */
  145. var DEFAULT_FALLBACK_PATTERN_ = '{0} – {1}';
  146. /**
  147. * Interval pattern placeholder for the first date.
  148. * @private @const {string}
  149. */
  150. var FIRST_DATE_PLACEHOLDER_ = '{0}';
  151. /**
  152. * Interval pattern placeholder for the second date.
  153. * @private @const {string}
  154. */
  155. var SECOND_DATE_PLACEHOLDER_ = '{1}';
  156. /**
  157. * Key used by the default datetime pattern.
  158. * @private @const {string}
  159. */
  160. var DEFAULT_PATTERN_KEY_ = '_';
  161. /**
  162. * Gregorian calendar Eras.
  163. * @private @enum {number}
  164. */
  165. var Era_ = {BC: 0, AD: 1};
  166. /**
  167. * Am Pm markers.
  168. * @private @enum {number}
  169. */
  170. var AmPm_ = {AM: 0, PM: 1};
  171. /**
  172. * String of all pattern letters representing the relevant calendar fields.
  173. * Sorted according to the length of the datetime unit they represent.
  174. * @private @const {string}
  175. */
  176. var RELEVANT_CALENDAR_FIELDS_ = 'GyMdahms';
  177. /**
  178. * Regex that matches all possible pattern letters.
  179. * @private @const {!RegExp}
  180. */
  181. var ALL_PATTERN_LETTERS_ = /[a-zA-Z]/;
  182. /**
  183. * Returns the interval pattern from a pattern specification or from the pattern
  184. * object.
  185. * @param {number|!dateIntervalSymbols.DateIntervalPatternMap} pattern Pattern
  186. * specification or pattern object.
  187. * @return {!dateIntervalSymbols.DateIntervalPatternMap}
  188. * @private
  189. */
  190. DateIntervalFormat.prototype.getIntervalPattern_ = function(pattern) {
  191. if (goog.isNumber(pattern)) {
  192. switch (pattern) {
  193. case DateTimeFormat.Format.FULL_DATE:
  194. return this.dateIntervalSymbols_.FULL_DATE;
  195. case DateTimeFormat.Format.LONG_DATE:
  196. return this.dateIntervalSymbols_.LONG_DATE;
  197. case DateTimeFormat.Format.MEDIUM_DATE:
  198. return this.dateIntervalSymbols_.MEDIUM_DATE;
  199. case DateTimeFormat.Format.SHORT_DATE:
  200. return this.dateIntervalSymbols_.SHORT_DATE;
  201. case DateTimeFormat.Format.FULL_TIME:
  202. return this.dateIntervalSymbols_.FULL_TIME;
  203. case DateTimeFormat.Format.LONG_TIME:
  204. return this.dateIntervalSymbols_.LONG_TIME;
  205. case DateTimeFormat.Format.MEDIUM_TIME:
  206. return this.dateIntervalSymbols_.MEDIUM_TIME;
  207. case DateTimeFormat.Format.SHORT_TIME:
  208. return this.dateIntervalSymbols_.SHORT_TIME;
  209. case DateTimeFormat.Format.FULL_DATETIME:
  210. return this.dateIntervalSymbols_.FULL_DATETIME;
  211. case DateTimeFormat.Format.LONG_DATETIME:
  212. return this.dateIntervalSymbols_.LONG_DATETIME;
  213. case DateTimeFormat.Format.MEDIUM_DATETIME:
  214. return this.dateIntervalSymbols_.MEDIUM_DATETIME;
  215. case DateTimeFormat.Format.SHORT_DATETIME:
  216. return this.dateIntervalSymbols_.SHORT_DATETIME;
  217. default:
  218. return this.dateIntervalSymbols_.MEDIUM_DATETIME;
  219. }
  220. } else {
  221. return pattern;
  222. }
  223. };
  224. /**
  225. * Formats the given date or date interval objects according to the present
  226. * pattern and current locale.
  227. *
  228. * Parameter combinations:
  229. * * StartDate: {@link goog.date.DateLike}, EndDate: {@link goog.date.DateLike}
  230. * * StartDate: {@link goog.date.DateLike}, Interval: {@link goog.date.Interval}
  231. *
  232. * @param {!DateLike} startDate Start date of the date range.
  233. * @param {!DateLike|!Interval} endDate End date of the date range or an
  234. * interval object.
  235. * @param {!TimeZone=} opt_timeZone Timezone to be used in the target
  236. * representation.
  237. * @return {string} Formatted date interval.
  238. */
  239. DateIntervalFormat.prototype.format = function(
  240. startDate, endDate, opt_timeZone) {
  241. asserts.assert(
  242. startDate != null,
  243. 'The startDate parameter should be defined and not-null.');
  244. asserts.assert(
  245. endDate != null, 'The endDate parameter should be defined and not-null.');
  246. // Convert input to DateLike.
  247. var endDt;
  248. if (goog.isDateLike(endDate)) {
  249. endDt = /** @type {!DateLike} */ (endDate);
  250. } else {
  251. asserts.assertInstanceof(
  252. endDate, Interval,
  253. 'endDate parameter should be a goog.date.DateLike or ' +
  254. 'goog.date.Interval');
  255. endDt = new DateTime(startDate);
  256. endDt.add(endDate);
  257. }
  258. // Obtain the largest different calendar field between the two dates.
  259. var largestDifferentCalendarField =
  260. DateIntervalFormat.getLargestDifferentCalendarField_(
  261. startDate, endDt, opt_timeZone);
  262. // Get the Formatter_ required to format the specified calendar field and use
  263. // it to format the dates.
  264. var formatter =
  265. this.getFormatterForCalendarField_(largestDifferentCalendarField);
  266. return formatter.format(
  267. startDate, endDt, largestDifferentCalendarField, opt_timeZone);
  268. };
  269. /**
  270. * Formats the given date range object according to the present pattern and
  271. * current locale.
  272. *
  273. * @param {!DateRange} dateRange
  274. * @param {!TimeZone=} opt_timeZone Timezone to be used in the target
  275. * representation.
  276. * @return {string} Formatted date interval.
  277. */
  278. DateIntervalFormat.prototype.formatRange = function(dateRange, opt_timeZone) {
  279. asserts.assert(
  280. dateRange != null,
  281. 'The dateRange parameter should be defined and non-null.');
  282. var startDate = dateRange.getStartDate();
  283. var endDate = dateRange.getEndDate();
  284. if (startDate == null) {
  285. throw Error('The dateRange\'s startDate should be defined and non-null.');
  286. }
  287. if (endDate == null) {
  288. throw Error('The dateRange\'s endDate should be defined and non-null.');
  289. }
  290. return this.format(startDate, endDate, opt_timeZone);
  291. };
  292. /**
  293. * Returns the Formatter_ to be used to format two dates for the given calendar
  294. * field.
  295. * @param {string} calendarField Pattern letter representing the calendar field.
  296. * @return {!Formatter_}
  297. * @private
  298. */
  299. DateIntervalFormat.prototype.getFormatterForCalendarField_ = function(
  300. calendarField) {
  301. if (calendarField != '') {
  302. for (var i = 0; i < this.intervalPatternKeys_.length; i++) {
  303. if (this.intervalPatternKeys_[i].indexOf(calendarField) >= 0) {
  304. return this.getOrCreateFormatterForKey_(this.intervalPatternKeys_[i]);
  305. }
  306. }
  307. }
  308. return this.getOrCreateFormatterForKey_(DEFAULT_PATTERN_KEY_);
  309. };
  310. /**
  311. * Returns and creates (if necessary) a formatter for the specified key.
  312. * @param {string} key
  313. * @return {!Formatter_}
  314. * @private
  315. */
  316. DateIntervalFormat.prototype.getOrCreateFormatterForKey_ = function(key) {
  317. var fmt = this;
  318. return object.setWithReturnValueIfNotSet(this.formatterMap_, key, function() {
  319. var patternParts =
  320. DateIntervalFormat.divideIntervalPattern_(fmt.intervalPattern_[key]);
  321. if (patternParts === null) {
  322. return new DateTimeFormatter_(
  323. fmt.intervalPattern_[key], fmt.fallbackPattern_,
  324. fmt.dateTimeSymbols_);
  325. }
  326. return new IntervalFormatter_(
  327. patternParts.firstPart, patternParts.secondPart, fmt.dateTimeSymbols_,
  328. fmt.useFirstDateOnFirstPattern_);
  329. });
  330. };
  331. /**
  332. * Divides the interval pattern string into its two parts. Will return null if
  333. * the pattern can't be divided (e.g. it's a datetime pattern).
  334. * @param {string} intervalPattern
  335. * @return {?{firstPart:string, secondPart:string}} Record containing the two
  336. * parts of the interval pattern. Null if the pattern can't be divided.
  337. * @private
  338. */
  339. DateIntervalFormat.divideIntervalPattern_ = function(intervalPattern) {
  340. var foundKeys = {};
  341. var patternParts = null;
  342. // Iterate over the pattern until a repeated calendar field is found.
  343. DateIntervalFormat.executeForEveryCalendarField_(
  344. intervalPattern, function(char, index) {
  345. if (object.containsKey(foundKeys, char)) {
  346. patternParts = {
  347. firstPart: intervalPattern.substring(0, index),
  348. secondPart: intervalPattern.substring(index)
  349. };
  350. return false;
  351. }
  352. object.set(foundKeys, char, true);
  353. return true;
  354. });
  355. return patternParts;
  356. };
  357. /**
  358. * Iterates over a pattern string and executes a function for every
  359. * calendar field. The function will be executed once, independent of the width
  360. * of the calendar field (number of repeated pattern letters). It will ignore
  361. * all literal text (enclosed by quotes).
  362. *
  363. * For example, on: "H 'h' mm – H 'h' mm" it will call the function for:
  364. * H (pos:0), m (pos:6), H (pos:11), m (pos:17).
  365. *
  366. * @param {string} pattern
  367. * @param {function(string, number):boolean} func Function which accepts as
  368. * parameters the current calendar field and the index of its first pattern
  369. * letter; and returns a boolean which indicates if the iteration should
  370. * continue.
  371. * @private
  372. */
  373. DateIntervalFormat.executeForEveryCalendarField_ = function(pattern, func) {
  374. var inQuote = false;
  375. var previousChar = '';
  376. for (var i = 0; i < pattern.length; i++) {
  377. var char = pattern.charAt(i);
  378. if (inQuote) {
  379. if (char == '\'') {
  380. if (i + 1 < pattern.length && pattern.charAt(i + 1) == '\'') {
  381. i++; // Literal quotation mark: ignore and advance.
  382. } else {
  383. inQuote = false;
  384. }
  385. }
  386. } else {
  387. if (char == '\'') {
  388. inQuote = true;
  389. } else if (char != previousChar && ALL_PATTERN_LETTERS_.test(char)) {
  390. if (!func(char, i)) {
  391. break;
  392. }
  393. }
  394. }
  395. previousChar = char;
  396. }
  397. };
  398. /**
  399. * Returns a pattern letter representing the largest different calendar field
  400. * between the two dates. This is calculated using the timezone used in the
  401. * target representation.
  402. * @param {!DateLike} startDate Start date of the date range.
  403. * @param {!DateLike} endDate End date of the date range.
  404. * @param {!TimeZone=} opt_timeZone Timezone to be used in the target
  405. * representation.
  406. * @return {string} Pattern letter representing the largest different calendar
  407. * field or an empty string if all relevant fields for these dates are equal.
  408. * @private
  409. */
  410. DateIntervalFormat.getLargestDifferentCalendarField_ = function(
  411. startDate, endDate, opt_timeZone) {
  412. // Before comparing them, dates have to be adjusted by the target timezone's
  413. // offset.
  414. var startDiff = 0;
  415. var endDiff = 0;
  416. if (opt_timeZone != null) {
  417. startDiff =
  418. (startDate.getTimezoneOffset() - opt_timeZone.getOffset(startDate)) *
  419. 60000;
  420. endDiff =
  421. (endDate.getTimezoneOffset() - opt_timeZone.getOffset(endDate)) * 60000;
  422. }
  423. var startDt = new Date(startDate.getTime() + startDiff);
  424. var endDt = new Date(endDate.getTime() + endDiff);
  425. if (DateIntervalFormat.getEra_(startDt) !=
  426. DateIntervalFormat.getEra_(endDt)) {
  427. return 'G';
  428. } else if (startDt.getFullYear() != endDt.getFullYear()) {
  429. return 'y';
  430. } else if (startDt.getMonth() != endDt.getMonth()) {
  431. return 'M';
  432. } else if (startDt.getDate() != endDt.getDate()) {
  433. return 'd';
  434. } else if (
  435. DateIntervalFormat.getAmPm_(startDt) !=
  436. DateIntervalFormat.getAmPm_(endDt)) {
  437. return 'a';
  438. } else if (startDt.getHours() != endDt.getHours()) {
  439. return 'h';
  440. } else if (startDt.getMinutes() != endDt.getMinutes()) {
  441. return 'm';
  442. } else if (startDt.getSeconds() != endDt.getSeconds()) {
  443. return 's';
  444. }
  445. return '';
  446. };
  447. /**
  448. * Returns the Era of a given DateLike object.
  449. * @param {!Date} date
  450. * @return {number}
  451. * @private
  452. */
  453. DateIntervalFormat.getEra_ = function(date) {
  454. return date.getFullYear() > 0 ? Era_.AD : Era_.BC;
  455. };
  456. /**
  457. * Returns if the given date is in AM or PM.
  458. * @param {!Date} date
  459. * @return {number}
  460. * @private
  461. */
  462. DateIntervalFormat.getAmPm_ = function(date) {
  463. var hours = date.getHours();
  464. return (12 <= hours && hours < 24) ? AmPm_.PM : AmPm_.AM;
  465. };
  466. /**
  467. * Returns true if the calendar field field1 is a larger or equal than field2.
  468. * Assumes that both string parameters have just one character. Field1 has to
  469. * be part of the relevant calendar fields set.
  470. * @param {string} field1
  471. * @param {string} field2
  472. * @return {boolean}
  473. * @private
  474. */
  475. DateIntervalFormat.isCalendarFieldLargerOrEqualThan_ = function(
  476. field1, field2) {
  477. return RELEVANT_CALENDAR_FIELDS_.indexOf(field1) <=
  478. RELEVANT_CALENDAR_FIELDS_.indexOf(field2);
  479. };
  480. /**
  481. * Interface implemented by internal date interval formatters.
  482. * @interface
  483. * @private
  484. */
  485. var Formatter_ = function() {};
  486. /**
  487. * Formats two dates with the two parts of the date interval and returns the
  488. * formatted string.
  489. * @param {!DateLike} firstDate
  490. * @param {!DateLike} secondDate
  491. * @param {string} largestDifferentCalendarField
  492. * @param {!TimeZone=} opt_timeZone Target timezone in which to format the
  493. * dates.
  494. * @return {string} String with the formatted date interval.
  495. */
  496. Formatter_.prototype.format = function(
  497. firstDate, secondDate, largestDifferentCalendarField, opt_timeZone) {};
  498. /**
  499. * Constructs an IntervalFormatter_ object which implements the Formatter_
  500. * interface.
  501. *
  502. * Internal object to construct and store a goog.i18n.DateTimeFormat for each
  503. * part of the date interval pattern.
  504. *
  505. * @param {string} firstPattern First part of the date interval pattern.
  506. * @param {string} secondPattern Second part of the date interval pattern.
  507. * @param {!DateTimeSymbolsType} dateTimeSymbols Symbols to use with the
  508. * datetime formatters.
  509. * @param {boolean} useFirstDateOnFirstPattern Indicates if the first or the
  510. * second date should be formatted with the first or second part of the date
  511. * interval pattern.
  512. * @constructor
  513. * @implements {Formatter_}
  514. * @private
  515. */
  516. var IntervalFormatter_ = function(
  517. firstPattern, secondPattern, dateTimeSymbols, useFirstDateOnFirstPattern) {
  518. /**
  519. * Formatter_ to format the first part of the date interval.
  520. * @private {!DateTimeFormat}
  521. */
  522. this.firstPartFormatter_ = new DateTimeFormat(firstPattern, dateTimeSymbols);
  523. /**
  524. * Formatter_ to format the second part of the date interval.
  525. * @private {!DateTimeFormat}
  526. */
  527. this.secondPartFormatter_ =
  528. new DateTimeFormat(secondPattern, dateTimeSymbols);
  529. /**
  530. * Specifies if the first or the second date should be formatted by the
  531. * formatter of the first or second part of the date interval.
  532. * @private {boolean}
  533. */
  534. this.useFirstDateOnFirstPattern_ = useFirstDateOnFirstPattern;
  535. };
  536. /** @override */
  537. IntervalFormatter_.prototype.format = function(
  538. firstDate, secondDate, largestDifferentCalendarField, opt_timeZone) {
  539. if (this.useFirstDateOnFirstPattern_) {
  540. return this.firstPartFormatter_.format(firstDate, opt_timeZone) +
  541. this.secondPartFormatter_.format(secondDate, opt_timeZone);
  542. } else {
  543. return this.firstPartFormatter_.format(secondDate, opt_timeZone) +
  544. this.secondPartFormatter_.format(firstDate, opt_timeZone);
  545. }
  546. };
  547. /**
  548. * Constructs a DateTimeFormatter_ object which implements the Formatter_
  549. * interface.
  550. *
  551. * Internal object to construct and store a goog.i18n.DateTimeFormat for the
  552. * a datetime pattern and formats dates using the fallback interval pattern
  553. * (e.g. '{0} – {1}').
  554. *
  555. * @param {string} dateTimePattern Datetime pattern used to format the dates.
  556. * @param {string} fallbackPattern Fallback interval pattern to be used with the
  557. * datetime pattern.
  558. * @param {!DateTimeSymbolsType} dateTimeSymbols Symbols to use with
  559. * the datetime format.
  560. * @constructor
  561. * @implements {Formatter_}
  562. * @private
  563. */
  564. var DateTimeFormatter_ = function(
  565. dateTimePattern, fallbackPattern, dateTimeSymbols) {
  566. /**
  567. * Date time pattern used to format the dates.
  568. * @private {string}
  569. */
  570. this.dateTimePattern_ = dateTimePattern;
  571. /**
  572. * Date time formatter used to format the dates.
  573. * @private {!DateTimeFormat}
  574. */
  575. this.dateTimeFormatter_ =
  576. new DateTimeFormat(dateTimePattern, dateTimeSymbols);
  577. /**
  578. * Fallback interval pattern.
  579. * @private {string}
  580. */
  581. this.fallbackPattern_ = fallbackPattern;
  582. };
  583. /** @override */
  584. DateTimeFormatter_.prototype.format = function(
  585. firstDate, secondDate, largestDifferentCalendarField, opt_timeZone) {
  586. // Check if the largest different calendar field between the two dates is
  587. // larger or equal than any calendar field in the datetime pattern. If true,
  588. // format the string using the datetime pattern and the fallback interval
  589. // pattern.
  590. var shouldFormatWithFallbackPattern = false;
  591. if (largestDifferentCalendarField != '') {
  592. DateIntervalFormat.executeForEveryCalendarField_(
  593. this.dateTimePattern_, function(char, index) {
  594. if (DateIntervalFormat.isCalendarFieldLargerOrEqualThan_(
  595. largestDifferentCalendarField, char)) {
  596. shouldFormatWithFallbackPattern = true;
  597. return false;
  598. }
  599. return true;
  600. });
  601. }
  602. if (shouldFormatWithFallbackPattern) {
  603. return this.fallbackPattern_
  604. .replace(
  605. FIRST_DATE_PLACEHOLDER_,
  606. this.dateTimeFormatter_.format(firstDate, opt_timeZone))
  607. .replace(
  608. SECOND_DATE_PLACEHOLDER_,
  609. this.dateTimeFormatter_.format(secondDate, opt_timeZone));
  610. }
  611. // If not, format the first date using the datetime pattern.
  612. return this.dateTimeFormatter_.format(firstDate, opt_timeZone);
  613. };
  614. exports = DateIntervalFormat;