date.js (24698B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 Zotero.Date = new function(){ 27 this.isMultipart = isMultipart; 28 this.multipartToSQL = multipartToSQL; 29 this.multipartToStr = multipartToStr; 30 this.isSQLDate = isSQLDate; 31 this.isSQLDateTime = isSQLDateTime; 32 this.sqlHasYear = sqlHasYear; 33 this.sqlHasMonth = sqlHasMonth; 34 this.sqlHasDay = sqlHasDay; 35 this.getUnixTimestamp = getUnixTimestamp; 36 this.toUnixTimestamp = toUnixTimestamp; 37 this.getFileDateString = getFileDateString; 38 this.getFileTimeString = getFileTimeString; 39 this.getLocaleDateOrder = getLocaleDateOrder; 40 41 var _localeDateOrder = null; 42 var _months; 43 var _monthsWithEnglish; 44 45 this.init = function () { 46 if (!Zotero.isFx || Zotero.isBookmarklet) { 47 throw new Error("Unimplemented"); 48 } 49 50 return Zotero.HTTP.request( 51 'GET', 'resource://zotero/schema/dateFormats.json', { responseType: 'json' } 52 ).then(function(xmlhttp) { 53 var json = xmlhttp.response; 54 55 var locale = Zotero.locale; 56 var english = locale.startsWith('en'); 57 // If no exact match, try first two characters ('de') 58 if (!json[locale]) { 59 locale = locale.substr(0, 2); 60 } 61 // Try first two characters repeated ('de-DE') 62 if (!json[locale]) { 63 locale = locale + "-" + locale.toUpperCase(); 64 } 65 // Look for another locale with same first two characters 66 if (!json[locale]) { 67 let sameLang = Object.keys(json).filter(l => l.startsWith(locale.substr(0, 2))); 68 if (sameLang.length) { 69 locale = sameLang[0]; 70 } 71 } 72 // If all else fails, use English 73 if (!json[locale]) { 74 locale = 'en-US'; 75 english = true; 76 } 77 _months = json[locale]; 78 79 // Add English versions if not already added 80 if (english) { 81 _monthsWithEnglish = _months; 82 } 83 else { 84 _monthsWithEnglish = {}; 85 for (let key in _months) { 86 _monthsWithEnglish[key] = _months[key].concat(json['en-US'][key]); 87 } 88 } 89 }); 90 }; 91 92 93 /** 94 * @param {Boolean} [withEnglish = false] - Include English months 95 * @return {Object} - Object with 'short' and 'long' arrays 96 */ 97 this.getMonths = function (withEnglish) { 98 if (withEnglish) { 99 if (_monthsWithEnglish) return _monthsWithEnglish; 100 } 101 else { 102 if (_months) return _months; 103 } 104 105 if (Zotero.isFx && !Zotero.isBookmarklet) { 106 throw new Error("Months not cached"); 107 } 108 109 // TODO: Use JSON file for connectors 110 return _months = _monthsWithEnglish = { 111 short: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 112 long: ["January", "February", "March", "April", "May", "June", "July", "August", 113 "September", "October", "November", "December"]}; 114 } 115 116 /** 117 * Convert an SQL date in the form '2006-06-13 11:03:05' into a JS Date object 118 * 119 * Can also accept just the date part (e.g. '2006-06-13') 120 **/ 121 this.sqlToDate = function (sqldate, isUTC) { 122 try { 123 if (!this.isSQLDate(sqldate) && !this.isSQLDateTime(sqldate)) { 124 throw new Error("Invalid date"); 125 } 126 127 var datetime = sqldate.split(' '); 128 var dateparts = datetime[0].split('-'); 129 if (datetime[1]){ 130 var timeparts = datetime[1].split(':'); 131 } 132 else { 133 timeparts = [false, false, false]; 134 } 135 136 // Invalid date part 137 if (dateparts.length==1){ 138 throw new Error("Invalid date part"); 139 } 140 141 if (isUTC){ 142 return new Date(Date.UTC(dateparts[0], dateparts[1]-1, dateparts[2], 143 timeparts[0], timeparts[1], timeparts[2])); 144 } 145 146 return new Date(dateparts[0], dateparts[1]-1, dateparts[2], 147 timeparts[0], timeparts[1], timeparts[2]); 148 } 149 catch (e){ 150 Zotero.debug(sqldate + ' is not a valid SQL date', 2) 151 return false; 152 } 153 } 154 155 156 /** 157 * Convert a JS Date object to an SQL date in the form '2006-06-13 11:03:05' 158 * 159 * If _toUTC_ is true, creates a UTC date 160 **/ 161 this.dateToSQL = function (date, toUTC) { 162 try { 163 if (toUTC){ 164 var year = date.getUTCFullYear(); 165 var month = date.getUTCMonth(); 166 var day = date.getUTCDate(); 167 var hours = date.getUTCHours(); 168 var minutes = date.getUTCMinutes(); 169 var seconds = date.getUTCSeconds(); 170 } 171 else { 172 var year = date.getFullYear(); 173 var month = date.getMonth(); 174 var day = date.getDate(); 175 var hours = date.getHours(); 176 var minutes = date.getMinutes(); 177 var seconds = date.getSeconds(); 178 } 179 180 year = Zotero.Utilities.lpad(year, '0', 4); 181 month = Zotero.Utilities.lpad(month + 1, '0', 2); 182 day = Zotero.Utilities.lpad(day, '0', 2); 183 hours = Zotero.Utilities.lpad(hours, '0', 2); 184 minutes = Zotero.Utilities.lpad(minutes, '0', 2); 185 seconds = Zotero.Utilities.lpad(seconds, '0', 2); 186 187 return year + '-' + month + '-' + day + ' ' 188 + hours + ':' + minutes + ':' + seconds; 189 } 190 catch (e){ 191 Zotero.debug(date + ' is not a valid JS date', 2); 192 return ''; 193 } 194 } 195 196 197 /** 198 * Convert a JS Date object to an ISO 8601 UTC date/time 199 * 200 * @param {Date} date JS Date object 201 * @return {String} ISO 8601 UTC date/time 202 * e.g. 2008-08-15T20:00:00Z 203 */ 204 this.dateToISO = function (date) { 205 var year = date.getUTCFullYear(); 206 var month = date.getUTCMonth(); 207 var day = date.getUTCDate(); 208 var hours = date.getUTCHours(); 209 var minutes = date.getUTCMinutes(); 210 var seconds = date.getUTCSeconds(); 211 212 year = Zotero.Utilities.lpad(year, '0', 4); 213 month = Zotero.Utilities.lpad(month + 1, '0', 2); 214 day = Zotero.Utilities.lpad(day, '0', 2); 215 hours = Zotero.Utilities.lpad(hours, '0', 2); 216 minutes = Zotero.Utilities.lpad(minutes, '0', 2); 217 seconds = Zotero.Utilities.lpad(seconds, '0', 2); 218 219 return year + '-' + month + '-' + day + 'T' 220 + hours + ':' + minutes + ':' + seconds + 'Z'; 221 } 222 223 224 var _re8601 = /^([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/; 225 226 /** 227 * @return {Boolean} - True if string is an ISO 8601 date, false if not 228 */ 229 this.isISODate = function (str) { 230 return _re8601.test(str); 231 } 232 233 /** 234 * Convert an ISO 8601–formatted date/time to a JS Date 235 * 236 * Adapted from http://delete.me.uk/2005/03/iso8601.html (AFL-licensed) 237 * 238 * @param {String} isoDate ISO 8601 date 239 * @return {Date|False} - JS Date, or false if not a valid date 240 */ 241 this.isoToDate = function (isoDate) { 242 var d = isoDate.match(_re8601); 243 if (!d) return false; 244 245 var offset = 0; 246 var date = new Date(d[1], 0, 1); 247 248 if (d[3]) { date.setMonth(d[3] - 1); } 249 if (d[5]) { date.setDate(d[5]); } 250 if (d[7]) { date.setHours(d[7]); } 251 if (d[8]) { date.setMinutes(d[8]); } 252 if (d[10]) { date.setSeconds(d[10]); } 253 if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } 254 if (d[14]) { 255 offset = (Number(d[16]) * 60) + Number(d[17]); 256 offset *= ((d[15] == '-') ? 1 : -1); 257 } 258 259 offset -= date.getTimezoneOffset(); 260 var time = (Number(date) + (offset * 60 * 1000)); 261 return new Date(time); 262 } 263 264 265 this.isoToSQL = function (isoDate) { 266 return Zotero.Date.dateToSQL(Zotero.Date.isoToDate(isoDate), true); // no 'this' for translator sandbox 267 } 268 269 270 /* 271 * converts a string to an object containing: 272 * day: integer form of the day 273 * month: integer form of the month (indexed from 0, not 1) 274 * year: 4 digit year (or, year + BC/AD/etc.) 275 * part: anything that does not fall under any of the above categories 276 * (e.g., "Summer," etc.) 277 * 278 * Note: the returned object is *not* a JS Date object 279 */ 280 var _slashRe = /^(.*?)\b([0-9]{1,4})(?:([\-\/\.\u5e74])([0-9]{1,2}))?(?:([\-\/\.\u6708])([0-9]{1,4}))?((?:\b|[^0-9]).*?)$/ 281 var _yearRe = /^(.*?)\b((?:circa |around |about |c\.? ?)?[0-9]{1,4}(?: ?B\.? ?C\.?(?: ?E\.?)?| ?C\.? ?E\.?| ?A\.? ?D\.?)|[0-9]{3,4})\b(.*?)$/i; 282 var _monthRe = null; 283 var _dayRe = null; 284 285 this.strToDate = function (string) { 286 var date = { 287 order: '' 288 }; 289 290 // skip empty things 291 if(!string) { 292 return date; 293 } 294 295 var parts = []; 296 297 // Parse 'yesterday'/'today'/'tomorrow' 298 var lc = (string + '').toLowerCase(); 299 if (lc == 'yesterday' || (Zotero.isClient && lc === Zotero.getString('date.yesterday'))) { 300 string = Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60*60*24)).substr(0, 10); // no 'this' for translator sandbox 301 } 302 else if (lc == 'today' || (Zotero.isClient && lc == Zotero.getString('date.today'))) { 303 string = Zotero.Date.dateToSQL(new Date()).substr(0, 10); 304 } 305 else if (lc == 'tomorrow' || (Zotero.isClient && lc == Zotero.getString('date.tomorrow'))) { 306 string = Zotero.Date.dateToSQL(new Date(Date.now() + 1000*60*60*24)).substr(0, 10); 307 } 308 else { 309 string = string.toString().replace(/^\s+|\s+$/g, "").replace(/\s+/, " "); 310 } 311 312 // first, directly inspect the string 313 var m = _slashRe.exec(string); 314 if(m && 315 ((!m[5] || !m[3]) || m[3] == m[5] || (m[3] == "\u5e74" && m[5] == "\u6708")) && // require sane separators 316 ((m[2] && m[4] && m[6]) || (!m[1] && !m[7]))) { // require that either all parts are found, 317 // or else this is the entire date field 318 // figure out date based on parts 319 if(m[2].length == 3 || m[2].length == 4 || m[3] == "\u5e74") { 320 // ISO 8601 style date (big endian) 321 date.year = m[2]; 322 date.month = m[4]; 323 date.day = m[6]; 324 date.order += m[2] ? 'y' : ''; 325 date.order += m[4] ? 'm' : ''; 326 date.order += m[6] ? 'd' : ''; 327 } else if(m[2] && !m[4] && m[6]) { 328 date.month = m[2]; 329 date.year = m[6]; 330 date.order += m[2] ? 'm' : ''; 331 date.order += m[6] ? 'y' : ''; 332 } else { 333 // local style date (middle or little endian) 334 var country = Zotero.locale ? Zotero.locale.substr(3) : "US"; 335 if(country == "US" || // The United States 336 country == "FM" || // The Federated States of Micronesia 337 country == "PW" || // Palau 338 country == "PH") { // The Philippines 339 date.month = m[2]; 340 date.day = m[4]; 341 date.order += m[2] ? 'm' : ''; 342 date.order += m[4] ? 'd' : ''; 343 } else { 344 date.month = m[4]; 345 date.day = m[2]; 346 date.order += m[2] ? 'd' : ''; 347 date.order += m[4] ? 'm' : ''; 348 } 349 date.year = m[6]; 350 date.order += 'y'; 351 } 352 353 if(date.year) date.year = parseInt(date.year, 10); 354 if(date.day) date.day = parseInt(date.day, 10); 355 if(date.month) { 356 date.month = parseInt(date.month, 10); 357 358 if(date.month > 12) { 359 // swap day and month 360 var tmp = date.day; 361 date.day = date.month 362 date.month = tmp; 363 date.order = date.order.replace('m', 'D') 364 .replace('d', 'M') 365 .replace('D', 'd') 366 .replace('M', 'm'); 367 } 368 } 369 370 if((!date.month || date.month <= 12) && (!date.day || date.day <= 31)) { 371 if(date.year && date.year < 100) { // for two digit years, determine proper 372 // four digit year 373 var today = new Date(); 374 var year = today.getFullYear(); 375 var twoDigitYear = year % 100; 376 var century = year - twoDigitYear; 377 378 if(date.year <= twoDigitYear) { 379 // assume this date is from our century 380 date.year = century + date.year; 381 } else { 382 // assume this date is from the previous century 383 date.year = century - 100 + date.year; 384 } 385 } 386 387 if(date.month) date.month--; // subtract one for JS style 388 else delete date.month; 389 390 //Zotero.debug("DATE: retrieved with algorithms: "+JSON.stringify(date)); 391 392 parts.push( 393 { part: m[1], before: true }, 394 { part: m[7] } 395 ); 396 } else { 397 // give up; we failed the sanity check 398 Zotero.debug("DATE: algorithms failed sanity check"); 399 var date = { 400 order: '' 401 }; 402 parts.push({ part: string }); 403 } 404 } else { 405 //Zotero.debug("DATE: could not apply algorithms"); 406 parts.push({ part: string }); 407 } 408 409 // couldn't find something with the algorithms; use regexp 410 // YEAR 411 if(!date.year) { 412 for (var i in parts) { 413 var m = _yearRe.exec(parts[i].part); 414 if (m) { 415 date.year = m[2]; 416 date.order = _insertDateOrderPart(date.order, 'y', parts[i]); 417 parts.splice( 418 i, 1, 419 { part: m[1], before: true }, 420 { part: m[3] } 421 ); 422 //Zotero.debug("DATE: got year (" + date.year + ", " + JSON.stringify(parts) + ")"); 423 break; 424 } 425 } 426 } 427 428 // MONTH 429 if(date.month === undefined) { 430 // compile month regular expression 431 let months = Zotero.Date.getMonths(true); // no 'this' for translator sandbox 432 months = months.short.map(m => m.toLowerCase()) 433 .concat(months.long.map(m => m.toLowerCase())); 434 435 if(!_monthRe) { 436 _monthRe = new RegExp("^(.*)\\b("+months.join("|")+")[^ ]*(?: (.*)$|$)", "i"); 437 } 438 439 for (var i in parts) { 440 var m = _monthRe.exec(parts[i].part); 441 if (m) { 442 // Modulo 12 in case we have multiple languages 443 date.month = months.indexOf(m[2].toLowerCase()) % 12; 444 date.order = _insertDateOrderPart(date.order, 'm', parts[i]); 445 parts.splice( 446 i, 1, 447 { part: m[1], before: "m" }, 448 { part: m[3], after: "m" } 449 ); 450 //Zotero.debug("DATE: got month (" + date.month + ", " + JSON.stringify(parts) + ")"); 451 break; 452 } 453 } 454 } 455 456 // DAY 457 if(!date.day) { 458 // compile day regular expression 459 if(!_dayRe) { 460 var daySuffixes = Zotero.getString ? Zotero.getString("date.daySuffixes").replace(/, ?/g, "|") : ""; 461 _dayRe = new RegExp("\\b([0-9]{1,2})(?:"+daySuffixes+")?\\b(.*)", "i"); 462 } 463 464 for (var i in parts) { 465 var m = _dayRe.exec(parts[i].part); 466 if (m) { 467 var day = parseInt(m[1], 10); 468 // Sanity check 469 if (day <= 31) { 470 date.day = day; 471 date.order = _insertDateOrderPart(date.order, 'd', parts[i]); 472 if(m.index > 0) { 473 var part = parts[i].part.substr(0, m.index); 474 if(m[2]) { 475 part += " " + m[2];; 476 } 477 } else { 478 var part = m[2]; 479 } 480 parts.splice( 481 i, 1, 482 { part: part } 483 ); 484 //Zotero.debug("DATE: got day (" + date.day + ", " + JSON.stringify(parts) + ")"); 485 break; 486 } 487 } 488 } 489 } 490 491 // Concatenate date parts 492 date.part = ''; 493 for (var i in parts) { 494 date.part += parts[i].part + ' '; 495 } 496 497 // clean up date part 498 if(date.part) { 499 date.part = date.part.replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, ""); 500 } 501 502 if(date.part === "" || date.part == undefined) { 503 delete date.part; 504 } 505 506 //make sure year is always a string 507 if(date.year || date.year === 0) date.year += ''; 508 509 return date; 510 } 511 512 this.isHTTPDate = function(str) { 513 var dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; 514 var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", 515 "Oct", "Nov", "Dec"]; 516 str = str.trim(); 517 var temp = str.split(','); 518 if (temp.length > 1) { 519 var dayOfWeek = temp[0]; 520 if(dayNames.indexOf(dayOfWeek) == -1) { 521 return false; 522 } 523 524 str = temp[1].trim(); 525 } 526 temp = str.split(' '); 527 temp = temp.filter((t) => ! t.match(/^\s*$/)); 528 if (temp.length < 5) { 529 return false; 530 } 531 if (!temp[0].trim().match(/[0-3]\d/)) { 532 return false; 533 } 534 if (monthNames.indexOf(temp[1].trim()) == -1) { 535 return false; 536 } 537 if (!temp[2].trim().match(/\d\d\d\d/)) { 538 return false; 539 } 540 temp.splice(0, 3); 541 var time = temp[0].trim().split(':'); 542 if (time.length < 2) { 543 return false; 544 } 545 for (let t of time) { 546 if (!t.match(/\d\d/)) { 547 return false; 548 } 549 } 550 temp.splice(0, 1); 551 var zone = temp.join(' ').trim(); 552 return !!zone.match(/([+-]\d\d\d\d|UTC?|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT)/) 553 }; 554 555 556 function _insertDateOrderPart(dateOrder, part, partOrder) { 557 if (!dateOrder) { 558 return part; 559 } 560 if (partOrder.before === true) { 561 return part + dateOrder; 562 } 563 if (partOrder.after === true) { 564 return dateOrder + part; 565 } 566 if (partOrder.before) { 567 var pos = dateOrder.indexOf(partOrder.before); 568 if (pos == -1) { 569 return dateOrder; 570 } 571 return dateOrder.replace(new RegExp("(" + partOrder.before + ")"), part + '$1'); 572 } 573 if (partOrder.after) { 574 var pos = dateOrder.indexOf(partOrder.after); 575 if (pos == -1) { 576 return dateOrder + part; 577 } 578 return dateOrder.replace(new RegExp("(" + partOrder.after + ")"), '$1' + part); 579 } 580 return dateOrder + part; 581 } 582 583 584 585 /** 586 * does pretty formatting of a date object returned by strToDate() 587 * 588 * @param {Object} date A date object, as returned from strToDate() 589 * @param {Boolean} shortFormat Whether to return a short (12/1/95) date 590 * @return A formatted date string 591 * @type String 592 **/ 593 this.formatDate = function (date, shortFormat) { 594 if(shortFormat) { 595 var localeDateOrder = getLocaleDateOrder(); 596 var string = localeDateOrder[0]+"/"+localeDateOrder[1]+"/"+localeDateOrder[2]; 597 return string.replace("y", (date.year !== undefined ? date.year : "00")) 598 .replace("m", (date.month !== undefined ? 1+date.month : "0")) 599 .replace("d", (date.day !== undefined ? date.day : "0")); 600 } else { 601 var string = ""; 602 603 if(date.part) { 604 string += date.part+" "; 605 } 606 607 var months = Zotero.Date.getMonths().long; // no 'this' for translator sandbox 608 if(date.month != undefined && months[date.month]) { 609 // get short month strings from CSL interpreter 610 string += months[date.month]; 611 if(date.day) { 612 string += " "+date.day+", "; 613 } else { 614 string += " "; 615 } 616 } 617 618 if(date.year) { 619 string += date.year; 620 } 621 } 622 623 return string; 624 } 625 626 this.strToISO = function (str) { 627 var date = this.strToDate(str); 628 629 if(date.year) { 630 var dateString = Zotero.Utilities.lpad(date.year, "0", 4); 631 if (parseInt(date.month) == date.month) { 632 dateString += "-"+Zotero.Utilities.lpad(date.month+1, "0", 2); 633 if(date.day) { 634 dateString += "-"+Zotero.Utilities.lpad(date.day, "0", 2); 635 } 636 } 637 return dateString; 638 } 639 return false; 640 } 641 642 643 this.sqlToISO8601 = function (sqlDate) { 644 var date = sqlDate.substr(0, 10); 645 var matches = date.match(/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})/); 646 if (!matches) { 647 return false; 648 } 649 date = matches[1]; 650 // Drop parts for reduced precision 651 if (matches[2] !== "00") { 652 date += "-" + matches[2]; 653 if (matches[3] !== "00") { 654 date += "-" + matches[3]; 655 } 656 } 657 var time = sqlDate.substr(11); 658 // TODO: validate times 659 if (time) { 660 date += "T" + time + "Z"; 661 } 662 return date; 663 } 664 665 this.strToMultipart = function (str) { 666 if (!str){ 667 return ''; 668 } 669 670 var parts = this.strToDate(str); 671 672 // FIXME: Until we have a better BCE date solution, 673 // remove year value if not between 1 and 9999 674 if (parts.year) { 675 var year = parts.year + ''; 676 if (!year.match(/^[0-9]{1,4}$/)) { 677 delete parts.year; 678 } 679 } 680 681 parts.month = typeof parts.month != "undefined" ? parts.month + 1 : ''; 682 683 var multi = (parts.year ? Zotero.Utilities.lpad(parts.year, '0', 4) : '0000') + '-' 684 + Zotero.Utilities.lpad(parts.month, '0', 2) + '-' 685 + (parts.day ? Zotero.Utilities.lpad(parts.day, '0', 2) : '00') 686 + ' ' 687 + str; 688 return multi; 689 } 690 691 // Regexes for multipart and SQL dates 692 // Allow zeroes in multipart dates 693 // TODO: Allow negative multipart in DB and here with \-? 694 var _multipartRE = /^[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31) /; 695 var _sqldateRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31)$/; 696 var _sqldateWithZeroesRE = /^\-?[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31)$/; 697 var _sqldatetimeRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])$/; 698 699 /** 700 * Tests if a string is a multipart date string 701 * e.g. '2006-11-03 November 3rd, 2006' 702 */ 703 function isMultipart(str){ 704 if (isSQLDateTime(str)) { 705 return false; 706 } 707 return _multipartRE.test(str); 708 } 709 710 711 /** 712 * Returns the SQL part of a multipart date string 713 * (e.g. '2006-11-03 November 3rd, 2006' returns '2006-11-03') 714 */ 715 function multipartToSQL(multi){ 716 if (!multi){ 717 return ''; 718 } 719 720 if (!isMultipart(multi)){ 721 return '0000-00-00'; 722 } 723 724 return multi.substr(0, 10); 725 } 726 727 728 /** 729 * Returns the user part of a multipart date string 730 * (e.g. '2006-11-03 November 3rd, 2006' returns 'November 3rd, 2006') 731 */ 732 function multipartToStr(multi){ 733 if (!multi){ 734 return ''; 735 } 736 737 if (!isMultipart(multi)){ 738 return multi; 739 } 740 741 return multi.substr(11); 742 } 743 744 745 function isSQLDate(str, allowZeroes) { 746 if (allowZeroes) { 747 return _sqldateWithZeroesRE.test(str); 748 } 749 return _sqldateRE.test(str); 750 } 751 752 753 function isSQLDateTime(str){ 754 return _sqldatetimeRE.test(str); 755 } 756 757 758 function sqlHasYear(sqldate){ 759 return isSQLDate(sqldate, true) && sqldate.substr(0,4)!='0000'; 760 } 761 762 763 function sqlHasMonth(sqldate){ 764 return isSQLDate(sqldate, true) && sqldate.substr(5,2)!='00'; 765 } 766 767 768 function sqlHasDay(sqldate){ 769 return isSQLDate(sqldate, true) && sqldate.substr(8,2)!='00'; 770 } 771 772 773 function getUnixTimestamp() { 774 return Math.round(Date.now() / 1000); 775 } 776 777 778 function toUnixTimestamp(date) { 779 if (date === null || typeof date != 'object' || 780 date.constructor.name != 'Date') { 781 throw new Error(`'${date}' is not a valid date`); 782 } 783 return Math.round(date.getTime() / 1000); 784 } 785 786 787 /** 788 * Convert a JS Date to a relative date (e.g., "5 minutes ago") 789 * 790 * Adapted from http://snipplr.com/view/10290/javascript-parse-relative-date/ 791 * 792 * @param {Date} date 793 * @return {String} 794 */ 795 this.toRelativeDate = function (date) { 796 var str; 797 var now = new Date(); 798 var timeSince = now.getTime() - date; 799 var inSeconds = timeSince / 1000; 800 var inMinutes = timeSince / 1000 / 60; 801 var inHours = timeSince / 1000 / 60 / 60; 802 var inDays = timeSince / 1000 / 60 / 60 / 24; 803 var inYears = timeSince / 1000 / 60 / 60 / 24 / 365; 804 805 var n; 806 807 // in seconds 808 if (Math.round(inSeconds) == 1) { 809 var key = "secondsAgo"; 810 } 811 else if (inMinutes < 1.01) { 812 var key = "secondsAgo"; 813 n = Math.round(inSeconds); 814 } 815 816 // in minutes 817 else if (Math.round(inMinutes) == 1) { 818 var key = "minutesAgo"; 819 } 820 else if (inHours < 1.01) { 821 var key = "minutesAgo"; 822 n = Math.round(inMinutes); 823 } 824 825 // in hours 826 else if (Math.round(inHours) == 1) { 827 var key = "hoursAgo"; 828 } 829 else if (inDays < 1.01) { 830 var key = "hoursAgo"; 831 n = Math.round(inHours); 832 } 833 834 // in days 835 else if (Math.round(inDays) == 1) { 836 var key = "daysAgo"; 837 } 838 else if (inYears < 1.01) { 839 var key = "daysAgo"; 840 n = Math.round(inDays); 841 } 842 843 // in years 844 else if (Math.round(inYears) == 1) { 845 var key = "yearsAgo"; 846 } 847 else { 848 var key = "yearsAgo"; 849 var n = Math.round(inYears); 850 } 851 852 return Zotero.getString("date.relative." + key + "." + (n ? "multiple" : "one"), n); 853 } 854 855 856 function getFileDateString(file){ 857 var date = new Date(); 858 date.setTime(file.lastModifiedTime); 859 return date.toLocaleDateString(); 860 } 861 862 863 function getFileTimeString(file){ 864 var date = new Date(); 865 date.setTime(file.lastModifiedTime); 866 return date.toLocaleTimeString(); 867 } 868 869 /** 870 * Get the order of the date components based on the current locale 871 * 872 * Returns a string with y, m, and d (e.g. 'ymd', 'mdy') 873 */ 874 function getLocaleDateOrder(){ 875 if (!_localeDateOrder) { 876 switch (Zotero.locale ? Zotero.locale.substr(3) : "US") { 877 // middle-endian 878 case 'US': // The United States 879 case 'BZ': // Belize 880 case 'FM': // The Federated States of Micronesia 881 case 'PA': // Panama 882 case 'PH': // The Philippines 883 case 'PW': // Palau 884 case 'ZW': // Zimbabwe 885 _localeDateOrder = 'mdy'; 886 break; 887 888 // big-endian 889 case 'fa': // Persian 890 case 'AL': // Albania 891 case 'CA': // Canada 892 case 'CN': // China 893 case 'HU': // Hungary 894 case 'JP': // Japan 895 case 'KE': // Kenya 896 case 'KR': // Korea 897 case 'LT': // Lithuania 898 case 'LV': // Latvia 899 case 'MN': // Mongolia 900 case 'SE': // Sweden 901 case 'TW': // Taiwan 902 case 'ZA': // South Africa 903 _localeDateOrder = 'ymd'; 904 break; 905 906 // little-endian 907 default: 908 _localeDateOrder = 'dmy'; 909 } 910 } 911 return _localeDateOrder; 912 } 913 }