/**
* 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;
})();