ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ.// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Fetch and render dates from timestamps. * * @module core/user_date * @copyright 2017 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'], function($, Ajax, Storage, Config) { var SECONDS_IN_DAY = 86400; /** @var {object} promisesCache Store all promises we've seen so far. */ var promisesCache = {}; /** * Generate a cache key for the given request. The request should * have a timestamp and format key. * * @param {object} request * @return {string} */ var getKey = function(request) { return 'core_user_date/' + Config.language + '/' + Config.usertimezone + '/' + request.timestamp + '/' + request.format; }; /** * Retrieve a transformed date from the browser's storage. * * @param {string} key * @return {string} */ var getFromLocalStorage = function(key) { return Storage.get(key); }; /** * Save the transformed date in the browser's storage. * * @param {string} key * @param {string} value */ var addToLocalStorage = function(key, value) { Storage.set(key, value); }; /** * Check if a key is in the module's cache. * * @param {string} key * @return {bool} */ var inPromisesCache = function(key) { return (typeof promisesCache[key] !== 'undefined'); }; /** * Retrieve a promise from the module's cache. * * @param {string} key * @return {object} jQuery promise */ var getFromPromisesCache = function(key) { return promisesCache[key]; }; /** * Save the given promise in the module's cache. * * @param {string} key * @param {object} promise */ var addToPromisesCache = function(key, promise) { promisesCache[key] = promise; }; /** * Send a request to the server for each of the required timestamp * and format combinations. * * Resolves the date's deferred with the values returned from the * server and saves the value in local storage. * * @param {array} dates * @return {object} jQuery promise */ var loadDatesFromServer = function(dates) { var args = dates.map(function(data) { var fixDay = data.hasOwnProperty('fixday') ? data.fixday : 1; var fixHour = data.hasOwnProperty('fixhour') ? data.fixhour : 1; return { timestamp: data.timestamp, format: data.format, type: data.type || null, fixday: fixDay, fixhour: fixHour }; }); var request = { methodname: 'core_get_user_dates', args: { contextid: Config.contextid, timestamps: args } }; return Ajax.call([request], true, true)[0].then(function(results) { results.dates.forEach(function(value, index) { var date = dates[index]; var key = getKey(date); addToLocalStorage(key, value); date.deferred.resolve(value); }); return; }) .catch(function(ex) { // If we failed to retrieve the dates then reject the date's // deferred objects to make sure they don't hang. dates.forEach(function(date) { date.deferred.reject(ex); }); }); }; /** * Takes an array of request objects and returns a promise that * is resolved with an array of formatted dates. * * The values in the returned array will be ordered the same as * the request array. * * This function will check both the module's static promises cache * and the browser's session storage to see if the user dates have * already been loaded in order to avoid sending a network request * if possible. * * Only dates not found in either cache will be sent to the server * for transforming. * * A request object must have a timestamp key and a format key and * optionally may have a type key. * * E.g. * var request = [ * { * timestamp: 1293876000, * format: '%d %B %Y' * }, * { * timestamp: 1293876000, * format: '%A, %d %B %Y, %I:%M %p', * type: 'gregorian', * fixday: false, * fixhour: false * } * ]; * * UserDate.get(request).done(function(dates) { * console.log(dates[0]); // prints "1 January 2011". * console.log(dates[1]); // prints "Saturday, 1 January 2011, 10:00 AM". * }); * * @param {array} requests * @return {object} jQuery promise */ var get = function(requests) { var ajaxRequests = []; var promises = []; // Loop over each of the requested timestamp/format combos // and add a promise to the promises array for them. requests.forEach(function(request) { var key = getKey(request); // If we've already got a promise then use it. if (inPromisesCache(key)) { promises.push(getFromPromisesCache(key)); } else { var deferred = $.Deferred(); var cached = getFromLocalStorage(key); if (cached) { // If we were able to get the value from session storage // then we can resolve the deferred with that value. No // need to ask the server to transform it for us. deferred.resolve(cached); } else { // Add this request to the list of ones we need to load // from the server. Include the deferred so that it can // be resolved when the server has responded with the // transformed values. request.deferred = deferred; ajaxRequests.push(request); } // Remember this promise for next time so that we can // bail out early if it is requested again. addToPromisesCache(key, deferred.promise()); promises.push(deferred.promise()); } }); // If we have any requests that we couldn't resolve from the caches // then let's ask the server to get them for us. if (ajaxRequests.length) { loadDatesFromServer(ajaxRequests); } // Wait for all of the promises to resolve. Some of them may be waiting // for a response from the server. return $.when.apply($, promises).then(function() { // This looks complicated but it's just converting an unknown // length of arguments into an array for the promise to resolve // with. return arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments); }); }; /** * For a given timestamp get the midnight value in the user's timezone. * * The calculation is performed relative to the user's midnight timestamp * for today to ensure that timezones are preserved. * * E.g. * Input: * timestamp: 1514836800 (01/01/2018 8pm GMT)(02/01/2018 4am GMT+8) * midnight: 1514851200 (02/01/2018 midnight GMT) * Output: * 1514764800 (01/01/2018 midnight GMT) * * Input: * timestamp: 1514836800 (01/01/2018 8pm GMT)(02/01/2018 4am GMT+8) * midnight: 1514822400 (02/01/2018 midnight GMT+8) * Output: * 1514822400 (02/01/2018 midnight GMT+8) * * @param {Number} timestamp The timestamp to calculate from * @param {Number} todayMidnight The user's midnight timestamp * @return {Number} The midnight value of the user's timestamp */ var getUserMidnightForTimestamp = function(timestamp, todayMidnight) { var future = timestamp > todayMidnight; var diffSeconds = Math.abs(timestamp - todayMidnight); var diffDays = future ? Math.floor(diffSeconds / SECONDS_IN_DAY) : Math.ceil(diffSeconds / SECONDS_IN_DAY); var diffDaysInSeconds = diffDays * SECONDS_IN_DAY; // Is the timestamp in the future or past? var dayTimestamp = future ? todayMidnight + diffDaysInSeconds : todayMidnight - diffDaysInSeconds; return dayTimestamp; }; return { get: get, getUserMidnightForTimestamp: getUserMidnightForTimestamp }; });