lib/amd/src/user_date.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Fetch and render dates from timestamps.
  17. *
  18. * @module core/user_date
  19. * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
  23. function($, Ajax, Storage, Config) {
  24. var SECONDS_IN_DAY = 86400;
  25. /** @var {object} promisesCache Store all promises we've seen so far. */
  26. var promisesCache = {};
  27. /**
  28. * Generate a cache key for the given request. The request should
  29. * have a timestamp and format key.
  30. *
  31. * @param {object} request
  32. * @return {string}
  33. */
  34. var getKey = function(request) {
  35. return 'core_user_date/' +
  36. Config.language + '/' +
  37. Config.usertimezone + '/' +
  38. request.timestamp + '/' +
  39. request.format;
  40. };
  41. /**
  42. * Retrieve a transformed date from the browser's storage.
  43. *
  44. * @param {string} key
  45. * @return {string}
  46. */
  47. var getFromLocalStorage = function(key) {
  48. return Storage.get(key);
  49. };
  50. /**
  51. * Save the transformed date in the browser's storage.
  52. *
  53. * @param {string} key
  54. * @param {string} value
  55. */
  56. var addToLocalStorage = function(key, value) {
  57. Storage.set(key, value);
  58. };
  59. /**
  60. * Check if a key is in the module's cache.
  61. *
  62. * @param {string} key
  63. * @return {bool}
  64. */
  65. var inPromisesCache = function(key) {
  66. return (typeof promisesCache[key] !== 'undefined');
  67. };
  68. /**
  69. * Retrieve a promise from the module's cache.
  70. *
  71. * @param {string} key
  72. * @return {object} jQuery promise
  73. */
  74. var getFromPromisesCache = function(key) {
  75. return promisesCache[key];
  76. };
  77. /**
  78. * Save the given promise in the module's cache.
  79. *
  80. * @param {string} key
  81. * @param {object} promise
  82. */
  83. var addToPromisesCache = function(key, promise) {
  84. promisesCache[key] = promise;
  85. };
  86. /**
  87. * Send a request to the server for each of the required timestamp
  88. * and format combinations.
  89. *
  90. * Resolves the date's deferred with the values returned from the
  91. * server and saves the value in local storage.
  92. *
  93. * @param {array} dates
  94. * @return {object} jQuery promise
  95. */
  96. var loadDatesFromServer = function(dates) {
  97. var args = dates.map(function(data) {
  98. var fixDay = data.hasOwnProperty('fixday') ? data.fixday : 1;
  99. var fixHour = data.hasOwnProperty('fixhour') ? data.fixhour : 1;
  100. return {
  101. timestamp: data.timestamp,
  102. format: data.format,
  103. type: data.type || null,
  104. fixday: fixDay,
  105. fixhour: fixHour
  106. };
  107. });
  108. var request = {
  109. methodname: 'core_get_user_dates',
  110. args: {
  111. contextid: Config.contextid,
  112. timestamps: args
  113. }
  114. };
  115. return Ajax.call([request], true, true)[0].then(function(results) {
  116. results.dates.forEach(function(value, index) {
  117. var date = dates[index];
  118. var key = getKey(date);
  119. addToLocalStorage(key, value);
  120. date.deferred.resolve(value);
  121. });
  122. return;
  123. })
  124. .catch(function(ex) {
  125. // If we failed to retrieve the dates then reject the date's
  126. // deferred objects to make sure they don't hang.
  127. dates.forEach(function(date) {
  128. date.deferred.reject(ex);
  129. });
  130. });
  131. };
  132. /**
  133. * Takes an array of request objects and returns a promise that
  134. * is resolved with an array of formatted dates.
  135. *
  136. * The values in the returned array will be ordered the same as
  137. * the request array.
  138. *
  139. * This function will check both the module's static promises cache
  140. * and the browser's session storage to see if the user dates have
  141. * already been loaded in order to avoid sending a network request
  142. * if possible.
  143. *
  144. * Only dates not found in either cache will be sent to the server
  145. * for transforming.
  146. *
  147. * A request object must have a timestamp key and a format key and
  148. * optionally may have a type key.
  149. *
  150. * E.g.
  151. * var request = [
  152. * {
  153. * timestamp: 1293876000,
  154. * format: '%d %B %Y'
  155. * },
  156. * {
  157. * timestamp: 1293876000,
  158. * format: '%A, %d %B %Y, %I:%M %p',
  159. * type: 'gregorian',
  160. * fixday: false,
  161. * fixhour: false
  162. * }
  163. * ];
  164. *
  165. * UserDate.get(request).done(function(dates) {
  166. * console.log(dates[0]); // prints "1 January 2011".
  167. * console.log(dates[1]); // prints "Saturday, 1 January 2011, 10:00 AM".
  168. * });
  169. *
  170. * @param {array} requests
  171. * @return {object} jQuery promise
  172. */
  173. var get = function(requests) {
  174. var ajaxRequests = [];
  175. var promises = [];
  176. // Loop over each of the requested timestamp/format combos
  177. // and add a promise to the promises array for them.
  178. requests.forEach(function(request) {
  179. var key = getKey(request);
  180. // If we've already got a promise then use it.
  181. if (inPromisesCache(key)) {
  182. promises.push(getFromPromisesCache(key));
  183. } else {
  184. var deferred = $.Deferred();
  185. var cached = getFromLocalStorage(key);
  186. if (cached) {
  187. // If we were able to get the value from session storage
  188. // then we can resolve the deferred with that value. No
  189. // need to ask the server to transform it for us.
  190. deferred.resolve(cached);
  191. } else {
  192. // Add this request to the list of ones we need to load
  193. // from the server. Include the deferred so that it can
  194. // be resolved when the server has responded with the
  195. // transformed values.
  196. request.deferred = deferred;
  197. ajaxRequests.push(request);
  198. }
  199. // Remember this promise for next time so that we can
  200. // bail out early if it is requested again.
  201. addToPromisesCache(key, deferred.promise());
  202. promises.push(deferred.promise());
  203. }
  204. });
  205. // If we have any requests that we couldn't resolve from the caches
  206. // then let's ask the server to get them for us.
  207. if (ajaxRequests.length) {
  208. loadDatesFromServer(ajaxRequests);
  209. }
  210. // Wait for all of the promises to resolve. Some of them may be waiting
  211. // for a response from the server.
  212. return $.when.apply($, promises).then(function() {
  213. // This looks complicated but it's just converting an unknown
  214. // length of arguments into an array for the promise to resolve
  215. // with.
  216. return arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments);
  217. });
  218. };
  219. /**
  220. * For a given timestamp get the midnight value in the user's timezone.
  221. *
  222. * The calculation is performed relative to the user's midnight timestamp
  223. * for today to ensure that timezones are preserved.
  224. *
  225. * E.g.
  226. * Input:
  227. * timestamp: 1514836800 (01/01/2018 8pm GMT)(02/01/2018 4am GMT+8)
  228. * midnight: 1514851200 (02/01/2018 midnight GMT)
  229. * Output:
  230. * 1514764800 (01/01/2018 midnight GMT)
  231. *
  232. * Input:
  233. * timestamp: 1514836800 (01/01/2018 8pm GMT)(02/01/2018 4am GMT+8)
  234. * midnight: 1514822400 (02/01/2018 midnight GMT+8)
  235. * Output:
  236. * 1514822400 (02/01/2018 midnight GMT+8)
  237. *
  238. * @param {Number} timestamp The timestamp to calculate from
  239. * @param {Number} todayMidnight The user's midnight timestamp
  240. * @return {Number} The midnight value of the user's timestamp
  241. */
  242. var getUserMidnightForTimestamp = function(timestamp, todayMidnight) {
  243. var future = timestamp > todayMidnight;
  244. var diffSeconds = Math.abs(timestamp - todayMidnight);
  245. var diffDays = future ? Math.floor(diffSeconds / SECONDS_IN_DAY) : Math.ceil(diffSeconds / SECONDS_IN_DAY);
  246. var diffDaysInSeconds = diffDays * SECONDS_IN_DAY;
  247. // Is the timestamp in the future or past?
  248. var dayTimestamp = future ? todayMidnight + diffDaysInSeconds : todayMidnight - diffDaysInSeconds;
  249. return dayTimestamp;
  250. };
  251. return {
  252. get: get,
  253. getUserMidnightForTimestamp: getUserMidnightForTimestamp
  254. };
  255. });