/** * jintervals -- JavaScript library for interval formatting * * Copyright (c) 2009 Rene Saarsoo * * jintervals is cross-licensed under MIT and LGPL. * * ==== * * jintervals is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * jintervals 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with jintervals. If not, see * . * * ==== * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var jintervals = (function() { function jintervals(seconds, format) { return Interpreter.evaluate(new Time(seconds), Parser.parse(format)); }; /** * Parses format string into data structure, * that can be interpreted later. */ var Parser = { parse: function(format) { var unparsed = format; var result = []; while ( unparsed.length > 0 ) { // leave plain text untouched var textMatch = /^([^\\{]+)([\\{].*|)$/.exec(unparsed); if (textMatch) { result.push(textMatch[1]); unparsed = textMatch[2]; } // parse jintervals {Code} separately var match = /^([{].*?(?:[}]|$))(.*)$/i.exec(unparsed); if (match) { result.push(this.parseCode(match[1])); unparsed = match[2]; } // backslash escapes next character // transform \{ --> { // transform \\ --> \ if (unparsed.charAt(0) === "\\") { result.push(unparsed.charAt(1)); unparsed = unparsed.slice(2); } } return result; }, // parses single {Code} in format string // Returns object representing the code or false when incorrect format string parseCode: function(code) { var re = /^[{]([smhdg])([smhdg]*)?(ays?|ours?|inutes?|econds?|reatests?|\.)?(\?(.*))?[}]$/i; var matches = re.exec(code); if (!matches) { return false; } return { // single-letter uppercase name of the type type: matches[1].toUpperCase(), // when code begins with lowercase letter, then set showing limited amount to true limited: (matches[1].toLowerCase() == matches[1]), paddingLength: (matches[2] || "").length + 1, format: (matches[3]||"") == "" ? false : (matches[3] == "." ? "letter" : "full"), optional: !!matches[4], optionalSuffix: matches[5] || "" }; } }; /** * Evaluates parse tree in the context of given time object */ var Interpreter = { evaluate: function(time, parseTree) { var smallestUnit = this.smallestUnit(parseTree); var result = ""; while ( parseTree.length > 0 ) { var code = parseTree.shift(); // leave plain text untouched if (typeof code === "string") { result += code; } // evaluate the code else if (typeof code === "object") { var unit = (code.type == "G") ? time.getGreatestUnit() : code.type; var smallest = (code.type == "G") ? unit : smallestUnit; var value = time.get(unit, code.limited, smallest); var suffix = code.format ? Localization.translate(code.format, unit, value) : ""; // show when not optional or totalvalue is non-zero if (!code.optional || time.get(unit) != 0) { result += this.zeropad(value, code.paddingLength) + suffix + code.optionalSuffix; } } // otherwise we have error else { result += "?"; } } return result; }, /** * Finds the smallest unit from parse tree. * * For example when parse tree contains "d", "m", "h" then returns "m" */ smallestUnit: function(parseTree) { var unitOrder = { "S": 0, "M": 1, "H": 2, "D": 3 }; var smallest = "D"; for (var i = 0; i < parseTree.length; i++) { if (typeof parseTree[i] === "object") { var type = parseTree[i].type; if (type !== "G" && unitOrder[type] < unitOrder[smallest]) { smallest = type; } } } return smallest; }, // utility function to pad number with leading zeros zeropad: function(nr, decimals) { var padLength = decimals - (""+nr).length; return (padLength > 0) ? this.repeat("0", padLength) + nr : nr; }, // utility function to repeat string repeat: function(string, times) { var result = ""; for (var i=0; i < times; i++) { result += string; } return result; } }; /** * Time class that deals with the actual computation of time units. */ var Time = function(s) { this.seconds = s; }; Time.prototype = { nextUnit: {D: "H", H: "M", M: "S", S: "S"}, /** * Returns the value of time in given unit * * @param {String} unit Either "S", "M", "H" or "D" * @param {Boolean} limited When true 67 seconds will become just 7 seconds (defaults to false) * @param {String} smallest The name of smallest unit. Defaults to next unit. * For example for "D" it will be "H", for "H" it will be "M" and so on... */ get: function(unit, limited, smallest) { if (!this[unit]) { return "?"; } smallest = smallest || this.nextUnit[unit]; return this[unit](limited, smallest); }, // functions for each unit S: function(limited, smallest) { return limited ? this.seconds - this.M(false, smallest) * 60 : this.seconds; }, M: function(limited, smallest) { var minutes = this.seconds / 60; minutes = (smallest === "M") ? Math.round(minutes): Math.floor(minutes); if (limited) { minutes = minutes - this.H(false, smallest) * 60; } return minutes; }, H: function(limited, smallest) { var hours = this.M(false, smallest) / 60; hours = (smallest === "H") ? Math.round(hours): Math.floor(hours); if (limited) { hours = hours - this.D(false, smallest) * 24; } return hours; }, D: function(limited, smallest) { var days = this.H(false, smallest) / 24; return (smallest === "D") ? Math.round(days): Math.floor(days); }, /** * Returns the name of greatest time unit. * * For example when we have 2 hours, 30 minutes, and 7 seconds, * then the greatest unit is hour and "H" is returned. */ getGreatestUnit: function() { if (this.seconds < 60) { return "S"; } else if (this.M(false, "M") < 60) { return "M"; } else if (this.H(false, "H") < 24) { return "H"; } else { return "D"; } } }; var Localization = { translate: function(format, lcType, value) { var loc = this.locales[this.currentLocale]; var translation = loc[format][lcType]; if (typeof translation === "string") { return translation; } else { return translation[loc.plural(value)]; } }, locale: function(loc) { if (loc) { this.currentLocale = loc; } return this.currentLocale; }, currentLocale: "en_US", locales: { en_US: { letter: { D: "d", H: "h", M: "m", S: "s" }, full: { D: [" day", " days"], H: [" hour", " hours"], M: [" minute", " minutes"], S: [" second", " seconds"] }, plural: function(nr) { return (nr == 1) ? 0 : 1; } }, et_EE: { letter: { D: "p", H: "h", M: "m", S: "s" }, full: { D: [" p\u00E4ev", " p\u00E4eva"], H: [" tund", " tundi"], M: [" minut", " minutit"], S: [" sekund", " sekundit"] }, plural: function(nr) { return (nr == 1) ? 0 : 1; } }, lt_LT: { letter: { D: "d", H: "h", M: "m", S: "s" }, full: { D: [" dieną", " dienas", " dienų"], H: [" valandą", " valandas", " valandų"], M: [" minutę", " minutes", " minučių"], S: [" sekundę", " sekundes", " sekundžų"] }, plural: function(n) { return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); } }, ru_RU: { letter: { D: "д", H: "ч", M: "м", S: "с" }, full: { D: [" день", " дня", " дней"], H: [" час", " часа", " часов"], M: [" минута", " минуты", " минут"], S: [" секунда", " секунды", " секунд"] }, plural: function(n) { return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } }, uk_UA: { letter: { D: "д", H: "г", M: "х", S: "с" }, full: { D: [" день", " дні", " днів"], H: [" година", " години", " годин"], M: [" хвилина", " хвилини", " хвилин"], S: [" секунда", " секунди", " секунд"] }, plural: function(n) { return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); } }, fi_FI: { letter: { D: "p", H: "h", M: "m", S: "s" }, full: { D: [" päivä", " päivää"], H: [" tunti", " tuntia"], M: [" minuutti", " minuuttia"], S: [" sekunti", " sekunttia"] }, plural: function(nr) { return (nr == 1) ? 0 : 1; } } } }; // Changing and getting current locale jintervals.locale = function(loc) { return Localization.locale(loc); }; jintervals.Time = Time; return jintervals; })();