1/* This reusable script is copyrighted. 2 Copyright (c) 2004,2005,2006 Claude Ostyn 3This script is free for use with attribution 4under the Creative Commons Attribution-ShareAlike 2.5 License. 5To view a copy of this license, visit 6http://creativecommons.org/licenses/by-sa/2.5/ 7or send a letter to 8Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. 9 10For any other use, contact Claude Ostyn via [email protected]. 11 12USE AT YOUR OWN RISK! 13THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER 17BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20SOFTWARE. 21*/ 22 23function centisecsToISODuration(n, bPrecise) 24{ 25 // Note: SCORM and IEEE 1484.11.1 require centisec precision 26 // Parameters: 27 // n = number of centiseconds 28 // bPrecise = optional parameter; if true, duration will 29 // be expressed without using year and/or month fields.
30 // If bPrecise is not true, and the duration is long, 31 // months are calculated by approximation based on average number 32 // of days over 4 years (365*4+1), not counting the extra days 33 // for leap years. If a reference date was available, 34 // the calculation could be more precise, but becomes complex, 35 // since the exact result depends on where the reference date 36 // falls within the period (e.g. beginning, end or ???) 37 // 1 year ~ (365*4+1)/4*60*60*24*100 = 3155760000 centiseconds 38 // 1 month ~ (365*4+1)/48*60*60*24*100 = 262980000 centiseconds 39 // 1 day = 8640000 centiseconds 40 // 1 hour = 360000 centiseconds 41 // 1 minute = 6000 centiseconds 42 var str = "P"; 43 var nCs=n; 44 var nY=0, nM=0, nD=0, nH=0, nMin=0, nS=0; 45 n = Math.max(n,0); // there is no such thing as a negative duration 46 var nCs = n; 47 // Next set of operations uses whole seconds 48 with (Math) 49 { 50 nCs = round(nCs); 51 if (bPrecise == true) 52 { 53 nD = floor(nCs / 8640000); 54 } 55 else 56 { 57 nY = floor(nCs / 3155760000); 58 nCs -= nY * 3155760000; 59 nM = floor(nCs / 262980000); 60 nCs -= nM * 262980000; 61 nD = floor(nCs / 8640000); 62 } 63 nCs -= nD * 8640000; 64 nH = floor(nCs / 360000); 65 nCs -= nH * 360000; 66 var nMin = floor(nCs /6000); 67 nCs -= nMin * 6000 68 } 69 // Now we can construct string 70 if (nY > 0) str += nY + "Y"; 71 if (nM > 0) str += nM + "M"; 72 if (nD > 0) str += nD + "D"; 73 if ((nH > 0) || (nMin > 0) || (nCs > 0)) 74 { 75 str += "T"; 76 if (nH > 0) str += nH + "H"; 77 if (nMin > 0) str += nMin + "M"; 78 if (nCs > 0) str += (nCs / 100) + "S"; 79 } 80 if (str == "P") str = "PT0H0M0S"; 81 // technically PT0S should do but SCORM test suite assumes longer form. 82 return str; 83} 84 85 86function ISODurationToCentisec(str) 87{ 88 // Only gross syntax check is performed here 89 // Months calculated by approximation based on average number 90 // of days over 4 years (365*4+1), not counting the extra days 91 // in leap years. If a reference date was available, 92 // the calculation could be more precise, but becomes complex, 93 // since the exact result depends on where the reference date 94 // falls within the period (e.g. beginning, end or ???) 95 // 1 year ~ (365*4+1)/4*60*60*24*100 = 3155760000 centiseconds 96 // 1 month ~ (365*4+1)/48*60*60*24*100 = 262980000 centiseconds 97 // 1 day = 8640000 centiseconds 98 // 1 hour = 360000 centiseconds 99 // 1 minute = 6000 centiseconds 100 var aV = new Array(0,0,0,0,0,0); 101 var bErr = false; 102 var bTFound = false; 103 if (str.indexOf("P") != 0) bErr = true; 104 if (!bErr) 105 { 106 var aT = new Array("Y","M","D","H","M","S") 107 var p=0, i=0; 108 str = str.substr(1); //get past the P 109 for (i = 0 ; i < aT.length; i++) 110 { 111 if (str.indexOf("T") == 0) 112 { 113 str = str.substr(1); 114 i = Math.max(i,3); 115 bTFound = true; 116 } 117 p = str.indexOf(aT[i]); 118 //alert("Checking for " + aT[i] + "\nstr = " + str); 119 if (p > -1) 120 { 121 // Is this a M before or after T? 122 if ((i == 1) && (str.indexOf("T") > -1) && (str.indexOf("T") < p)) continue; 123 if (aT[i] == "S") 124 { 125 aV[i] = parseFloat(str.substr(0,p)) 126 } 127 else 128 { 129 aV[i] = parseInt(str.substr(0,p)) 130 } 131 if (isNaN(aV[i])) 132 { 133 bErr = true; 134 break; 135 } 136 else if ((i > 2) && (!bTFound)) 137 { 138 bErr = true; 139 break; 140 } 141 str = str.substr(p+1); 142 } 143 } 144 if ((!bErr) && (str.length != 0)) bErr = true; 145 //alert(aV.toString()) 146 } 147 if (bErr) 148 { 149 //alert("Bad format: " + str) 150 return 151 } 152 return aV[0]*3155760000 + aV[1]*262980000 153 + aV[2]*8640000 + aV[3]*360000 + aV[4]*6000 154 + Math.round(aV[5]*100) 155} 156 157// Legacy functions to translate to/from SCORM 1.2 format 158 159function SCORM12DurationToCs(str) 160{ 161 // Format is [HH]HH:MM:SS[.SS] or maybe sometimes MM:SS[.SS] 162 // Does not catch all possible errors 163 // First convert to centisecs 164 var a=str.split(":"); 165 var nS=0, n=0; 166 var nMult = 1; 167 var bErr = ((a.length < 2) || (a.length > 3)); 168 if (!bErr) 169 { 170 for (i=a.length-1;i >= 0; i--) 171 { 172 n = parseFloat(a[i]); 173 if (isNaN(n)) 174 { 175 bErr = true; 176 break; 177 }
178 nS += n * nMult; 179 nMult *= 60; 180 } 181 } 182 if (bErr) 183 { 184 alert ("Incorrect format: " + str + "\n\nFormat must be [HH]HH:MM:SS[.SS]"); 185 return NaN; 186 } 187 return Math.round(nS * 100); 188} 189 190function centisecsToSCORM12Duration(n) 191{ 192 // Format is [HH]HH:MM:SS[.SS] 193 var bTruncated = false; 194 with (Math) 195 { 196 n = round(n); 197 var nH = floor(n / 360000); 198 var nCs = n - nH * 360000; 199 var nM = floor(nCs / 6000); 200 nCs = nCs - nM * 6000; 201 var nS = floor(nCs / 100); 202 nCs = nCs - nS * 100; 203 } 204 if (nH > 9999) 205 { 206 nH = 9999; 207 bTruncated = true; 208 } 209 var str = "0000" + nH + ":"; 210 str = str.substr(str.length-5,5); 211 if (nM < 10) str += "0"; 212 str += nM + ":"; 213 if (nS < 10) str += "0"; 214 str += nS; 215 if (nCs > 0) 216 { 217 str += "."; 218 if (nCs < 10) str += "0"; 219 str += nCs; 220 } 221 //if (bTruncated) alert ("Hours truncated to 9999 to fit HHHH:MM:SS.SS format") 222 return str; 223} 224 225 226/*** time stamp helper function. Returns a time stamp in ISO format ***/ 227 228function MakeISOtimeStamp(objSrcDate, bRelative, nResolution) 229{ 230 // Make an ISO 8601 time stamp string as specified for SCORM 2004 231 // * objDate is an optional ECMAScript Date object; 232 // if objDate is null, "this instant" is assumed. 233 // * bRelative is optional; if bRelative is true, 234 // the time stamp will show local time with a time offset from UTC; 235 // otherwise the time stamp will show UTC (a.k.a. Zulu) time. 236 // * nResolution is optional; it specifies max decimal digits 237 // for fractions of second; it can be null, 0 or 2. If null, 2 is assumed. 238 var s = ""; 239 var nY=0, nM=0, nD=0, nH=0, nMin=0, nS=0, nMs=0, nCs = 0; 240 var bCentisecs = ((isNaN(nResolution)) || (nResolution != 0)); 241 // Need to make a copy of the source date object because we will 242 // tweak it if we need to round up to next second 243 objDate = new Date(); 244 with (objDate) 245 { 246 setTime(objSrcDate.getTime()); 247 ((bRelative)? nMs = getMilliseconds(): nMs = getUTCMilliseconds()); 248 249 if (bCentisecs) 250 { 251 // Default precision is centisecond. Let us see whether we need to add 252 // a rounding up adjustment 253 if (nMs > 994) 254 { 255 ((bRelative)? setMilliseconds(1000): setUTCMilliseconds(1000)); 256 } 257 else 258 { 259 nCs = Math.floor(nMs / 10); 260 } 261 } 262 else 263 { 264 // Precision is whole seconds; round up if necessary 265 if (nMs > 499) 266 { 267 ((bRelative)? setMilliseconds(1000): setUTCMilliseconds(1000)); 268 } 269 } 270 if (bRelative) 271 { 272 nY = getFullYear(); 273 nM = getMonth(); 274 nD = getDate(); 275 nH = getHours(); 276 nMin = getMinutes(); 277 nS = getSeconds(); 278 } 279 else 280 { 281 nY = getUTCFullYear(); 282 nM = getUTCMonth(); 283 nD = getUTCDate(); 284 nH = getUTCHours(); 285 nMin = getUTCMinutes(); 286 nS = getUTCSeconds(); 287 } 288 } 289 // Note: Date.Month() and Date.UTCMonth() are base 0 not 1 290 s = nY + "-" + 291 ZeroPad(nM+1, 2) + "-" + 292 ZeroPad(nD, 2) + "T" + 293 ZeroPad(nH, 2) + ":" + 294 ZeroPad(nMin, 2) + ":" + 295 ZeroPad(nS,2); 296 if (nCs > 0) 297 { 298 s += "." + ZeroPad(nCs,2); 299 } 300 if (bRelative) 301 { 302 // Need to flip the sign of the time zone offset 303 var nTZOff = -objDate.getTimezoneOffset(); 304 if (nTZOff >= 0) s += "+"; 305 s += ZeroPad(Math.round(nTZOff / 60), 2); 306 nTZOff = nTZOff % 60; 307 if (nTZOff > 0) s += ":" + ZeroPad(nTZOff, 2); 308 } 309 else 310 { 311 s += "Z"; 312 } 313 return s; 314} 315 316function ZeroPad(n, nLength) 317{ 318 // Takes a number and pads it with leading 0 to the length specified. 319 // The padded length does not include negative sign if present. 320 var bNeg = (n < 0); 321 var s = n.toString(); 322 if (bNeg) s = s.substr(1,s.length); 323 while (s.length < nLength) s = "0" + s; 324 if (bNeg) s = "-" + s; 325 return s 326} 327 328var gsParseErr = ""; 329function DateFromISOString(strDate) 330{ 331 // Convert an ISO 8601 formatted string to a local date 332 // Returns an ECMAScript Date object or null if an error was detected 333 // Assumes that the string is well formed and SCORM conformant 334 // otherwise a runtime error may occur in this function. 335 // In practice the data range is limited to the date range supported 336 // by the ECMAScript Date object. See the ECMAScript standard for details. 337 var sDate = strDate; // The date part of the input, after a little massaging 338 var sTime = null; // The time part of the input, if it is included 339 var sTimeOffset = null; // UTC offset, if specified in the input string 340 var sUTCOffsetSign = ""; 341 var a = null;
341 // Will be reused for all kinds of string splits 342 var n=0, nY=0, nM=0, nD=1, nH=0, nMin=0, nS=0, nMs = 0; 343 344 gsParseErr = ""; 345 346 // If this is "Zulu" time, it will make things a little easier 347 var bZulu = (sDate.indexOf("Z") > -1); 348 if (bZulu) sDate = sDate.substr(0, sDate.length - 1); 349 350 // Parse the ISO string into date and time 351 if (sDate.indexOf("T") > -1) 352 { 353 var a = sDate.split("T"); 354 sDate = a[0]; 355 var sTime = a[1]; 356 } 357 // Parse the date part 358 a = sDate.split("-"); 359 nY = parseInt(a[0],10); 360 if ((isNaN(nY)) || (nY > 9999) || (nY < 0000)) 361 { 362 gsParseErr = "Invalid year value:\n" + strDate; 363 return null; 364 } 365 if (a.length > 1) 366 { 367 nM = parseInt(a[1],10) - 1; // months are in base 0 368 if (nM < 0) alert("a[1] =" + a[1] + " from " + strDate); 369 if (a.length > 2) 370 { 371 nD = parseInt(a[2],10); // days are in base 1 372 } 373 } 374 // Done with the date. If there is a time part, parse it out. 375 if (sTime) 376 { 377 if (sTime.indexOf("-") > -1) sUTCOffsetSign = "-"; 378 if (sTime.indexOf("+") > -1) sUTCOffsetSign = "+"; 379 if (sUTCOffsetSign != "") 380 { 381 if (bZulu) 382 { 383 gsParseErr = "You can't have both UTC offset and Zulu in ISO time stamp:\n" + strDate; 384 return null; 385 } 386 a = sTime.split(sUTCOffsetSign); 387 sTime = a[0]; 388 sTimeOffset = a[1]; 389 } 390 a = sTime.split(":"); 391 nH = parseInt(a[0],10); 392 if (a.length > 1) 393 { 394 nMin = parseInt(a[1],10); 395 if (a.length > 2) 396 { 397 (a[2].indexOf(".")<0?nS = parseInt(a[2],10) : nS = parseFloat(a[2])); 398 if (isNaN(nS)) 399 { 400 gsParseErr = "Error parsing seconds: " + a[2] + "\n" + strDate; 401 return null; 402 } 403 nMs = Math.round((nS % 1) * 1000); 404 nS = Math.floor(nS); 405 } 406 } 407 } 408 else if (bZulu) 409 { 410 gsParseErr = "UTC not allowed in time stamp unless there is a Time part:\n" + 411 strDate; 412 return null; 413 } 414 var objDate = new Date(); 415 if (bZulu) 416 { 417 objDate.setUTCFullYear(nY,nM,nD); 418 objDate.setUTCHours(nH,nMin,nS,nMs); 419 } 420 else 421 { 422 // Calculate and apply the time offset for local time 423 if (sTimeOffset) 424 { 425 var nOffset = 0; 426 a = sTimeOffset.split(":"); 427 nOffset = parseInt(a[0]); 428 if (isNaN(nOffset)) 429 { 430 gsParseErr = "Found UTC time offset but offset value is NaN:\n" + strDate; 431 return null; 432 } 433 nOffset = nOffset * 60 434 if (a.length > 1) 435 { 436 n = parseInt(a[1]); 437 if (isNaN(n)) 438 { 439 gsParseErr = "Found UTC time offset minutes but minute value is NaN:\n" + 440 strDate; 441 return null; 442 } 443 nOffset += n; 444 } 445 nOffset = nOffset * 60; // minutes to milliseconds 446 if (sUTCOffsetSign == "-") nOffset = -nOffset; 447 objDate.setTime(objDate.getTime() + nOffset); 448 } 449 objDate.setFullYear(nY,nM,nD); 450 objDate.setHours(nH,nMin,nS,nMs); 451 } 452 return objDate 453} 454
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.