/**
* @module duratiform
*/
/**
* Separate time duration into parts.
*
* @param {Integer} nDuration
* Time duration in milliseconds.
* @param {Integer} [nPartQty]
* Parts quantity. The default value is 3.
* The following values are allowed:
* <ul>
* <li><code>1</code> - return seconds quantity (<code>second</code> field)
* <li><code>2</code> - return quantity of minutes and seconds (<code>minute</code> and <code>second</code> fields)
* <li><code>3</code> - return quantity of hours, minutes and seconds (<code>hour</code>, <code>minute</code> and <code>second</code> fields)
* <li><code>4</code> - return quantity of days, hours, minutes and seconds (<code>day</code>, <code>hour</code>, <code>minute</code> and <code>second</code> fields)
* <li><code>5</code> - return quantity of weeks, days, hours, minutes and seconds (<code>week</code>, <code>day</code>, <code>hour</code>, <code>minute</code> and <code>second</code> fields)
* </ul>
* @param {Boolean} [bAddStrings]
* Specifies whether additional string fields should be included into result object.
* An additional field represents a value of calculated part that is converted into string
* and is prefixed with "0" if it is necessary (i.e. values from 0 to 9 are converted to "00"-"09").
* The default value is <code>false</code>.
* @return {Object}
* Object representing the requested parts of time duration. Can contain the following fields.
* <table class="params">
* <tr>
* <th>Name</th>
* <th>Type</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>day</td>
* <td>Integer</td>
* <td>Quantity of full days</td>
* </tr>
* <tr>
* <td>day2</td>
* <td>String</td>
* <td>
* Quantity of full days. String contains at least 2 characters.
* This field is included only when <code>bAddStrings</code> has <code>true</code> value.
* </td>
* </tr>
* <tr>
* <td>hour</td>
* <td>Integer</td>
* <td>Quantity of full hours</td>
* </tr>
* <tr>
* <td>hour2</td>
* <td>String</td>
* <td>
* Quantity of full hours. String contains at least 2 characters.
* This field is included only when <code>bAddStrings</code> has <code>true</code> value.
* </td>
* </tr>
* <tr>
* <td>minute</td>
* <td>Integer</td>
* <td>Quantity of full minutes</td>
* </tr>
* <tr>
* <td>minute2</td>
* <td>String</td>
* <td>
* Quantity of full minutes. String contains at least 2 characters.
* This field is included only when <code>bAddStrings</code> has <code>true</code> value.
* </td>
* </tr>
* <tr>
* <td>second</td>
* <td>Integer</td>
* <td>Quantity of full seconds</td>
* </tr>
* <tr>
* <td>second2</td>
* <td>String</td>
* <td>
* Quantity of full seconds. String contains at least 2 characters.
* This field is included only when <code>bAddStrings</code> has <code>true</code> value.
* </td>
* </tr>
* <tr>
* <td>week</td>
* <td>Integer</td>
* <td>Quantity of full weeks</td>
* </tr>
* <tr>
* <td>week2</td>
* <td>String</td>
* <td>
* Quantity of full weeks. String contains at least 2 characters.
* This field is included only when <code>bAddStrings</code> has <code>true</code> value.
* </td>
* </tr>
* </table>
* @alias module:duratiform.divide
*/
function divide(nDuration, nPartQty, bAddStrings) {
var result = {};
function getPart(sField, nDivisor) {
var nV;
if (nDuration >= nDivisor) {
nV = result[sField] = Math.floor(nDuration / nDivisor);
nDuration = nDuration % nDivisor;
}
else {
nV = result[sField] = 0;
}
if (bAddStrings) {
result[sField + "2"] = nV < 10 ? "0" + nV : String(nV);
}
}
// Convert duration to seconds
nDuration = nDuration * 0.001;
if (! nPartQty) {
nPartQty = 3;
}
// Extract weeks
if (nPartQty > 4) {
getPart("week", 604800);
}
// Extract days
if (nPartQty > 3) {
getPart("day", 86400);
}
// Extract hours
if (nPartQty > 2) {
getPart("hour", 3600);
}
// Extract minutes
if (nPartQty > 1) {
getPart("minute", 60);
}
// Extract seconds
if (nPartQty > 0) {
getPart("second", 1);
}
return result;
}
/**
* Convert time duration into string.
*
* @param {Integer} nDuration
* Time duration in milliseconds.
* @param {String} [sFormat]
* Format of the returned value. The default value is <code>hh:mm:ss</code>.<br>
* The following tokens are interpreted in special way:
* <ul>
* <li><code>\x</code> - replaced by <code>x</code>, where <code>x</code> is any character
* <li><code>d</code> - quantity of days (1 or more characters)
* <li><code>dd</code> - quantity of days (2 or more characters)
* <li><code>h</code> - quantity of hours (1 or more characters)
* <li><code>hh</code> - quantity of hours (2 or more characters)
* <li><code>m</code> - quantity of minutes (1 or more characters)
* <li><code>mm</code> - quantity of minutes (2 or more characters)
* <li><code>s</code> - quantity of seconds (1 or more characters)
* <li><code>ss</code> - quantity of seconds (2 or more characters)
* <li><code>w</code> - quantity of weeks (1 or more characters)
* <li><code>ww</code> - quantity of weeks (2 or more characters)
* <li><code>[</code> - cancel special processing of the following characters (except <code>\x</code> and <code>]</code>);
* this character won't be included into the result
* <li><code>]</code> - restore special processing that was previously cancelled by <code>[</code> character;
* this character won't be included into the result;
* thus any sequence of characters that is surrounded by square brackets (except <code>\x</code> and <code>]</code>)
* will be included into the result as is but without brackets
* <li><code>(x:</code> - where <code>x</code> is one of <code>w</code> (weeks), <code>d</code> (days),
* <code>h</code> (hours), <code>m</code> (minutes) or <code>s</code> (seconds),
* begin group of characters that will be included in the result
* only when the corresponding part of duration is present (above 0)
* <li><code>(!x:</code> - where <code>x</code> is one of <code>w</code> (weeks), <code>d</code> (days),
* <code>h</code> (hours), <code>m</code> (minutes) or <code>s</code> (seconds),
* begin group of characters that will be included in the result
* only when the corresponding part of duration is not present (equals to 0)
* <li><code>)</code> - end of previous group; thus by using format <code>(h:h:)mm:ss</code> hours part
* will be in result only when duration is greater than 60 minutes
* </ul>
* All other characters will be included into the result as is.
* @return {String}
* String representing time duration depending of format.
* @alias module:duratiform.format
*/
function format(nDuration, sFormat) {
/*jshint boss:true, laxbreak:true*/
var result = [],
bReplace = true,
group = null,
groupList = [],
nP = 0,
specialChar = {
w: ["week", 5],
d: ["day", 4],
h: ["hour", 3],
m: ["minute", 2],
s: ["second", 1]
},
sSlash = "\\",
bNegative, nI, nK, nL, sChar, sNextChar, part, struct;
function getGroupList() {
return group && groupList.concat(group);
}
function getPart() {
var groupSet = this.g,
bNot, nEnd, nNum, sGroupExpr;
if (groupSet) {
for (nNum = 0, nEnd = groupSet.length; nNum < nEnd; nNum++) {
sGroupExpr = groupSet[nNum];
bNot = sGroupExpr.charAt(0) === "!";
if ((bNot && struct[sGroupExpr.substring(1)]) || (! bNot && ! struct[sGroupExpr])) {
groupSet = false;
break;
}
}
}
else {
groupSet = true;
}
return groupSet
? this.c || String(struct[this.p])
: "";
}
if (! sFormat) {
sFormat = "hh:mm:ss";
}
// Scan format string and find positions for replacement
for (nI = 0, nL = sFormat.length, nK = nL - 1; nI < nL; nI++) {
sChar = sFormat.charAt(nI);
sNextChar = sFormat.charAt(nI + 1);
// Special character
if (bReplace && sChar in specialChar) {
struct = specialChar[sChar];
// Save value that will be replaced:
// 2 or more characters
if (sNextChar === sChar) {
part = struct[0] + "2";
nI++;
}
// 1 or more characters
else {
part = struct[0];
}
result.push({p: part, g: getGroupList(), toString: getPart});
// Change quantity of time duration parts if it is necessary
if (struct[1] > nP) {
nP = struct[1];
}
}
// Cancel special processing
else if (bReplace && sChar === "[") {
bReplace = false;
}
// Restore special processing
else if (! bReplace && sChar === "]") {
bReplace = true;
}
// Start of a group
else if (bReplace && sChar === "("
&& ((bNegative = sNextChar === "!") || true)
&& sFormat.charAt(nI + (bNegative ? 3 : 2)) === ":"
&& (part = (bNegative ? sFormat.charAt(nI + 2) : sNextChar).match(/d|h|m|s|w/))) {
if (group) {
groupList.push(group);
}
group = (bNegative ? "!" : "") + specialChar[part[0]][0];
nI += bNegative ? 3 : 2;
}
// End of a group
else if (bReplace && group && sChar === ")") {
group = groupList.length
? groupList.pop()
: null;
}
// Any other character or escaped character
else if (sChar !== sSlash || nI < nK) {
// Escaped character
if (sChar === sSlash) {
sChar = sNextChar;
nI++;
}
result.push(
group
? {g: getGroupList(), c: sChar, toString: getPart}
: sChar
);
}
}
// Get parts of duration if it is necessary
if (nP) {
struct = divide(nDuration, nP, true);
}
return result.join("");
}
//Exports
module.exports = {
divide: divide,
format: format
};