zotero/chrome/content/zotero/xpcom/date.js
2011-03-18 08:26:55 +00:00

725 lines
No EOL
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.Date = new function(){
this.sqlToDate = sqlToDate;
this.dateToSQL = dateToSQL;
this.strToDate = strToDate;
this.formatDate = formatDate;
this.strToISO = strToISO;
this.strToMultipart = strToMultipart;
this.isMultipart = isMultipart;
this.multipartToSQL = multipartToSQL;
this.multipartToStr = multipartToStr;
this.isSQLDate = isSQLDate;
this.isSQLDateTime = isSQLDateTime;
this.sqlHasYear = sqlHasYear;
this.sqlHasMonth = sqlHasMonth;
this.sqlHasDay = sqlHasDay;
this.getUnixTimestamp = getUnixTimestamp;
this.toUnixTimestamp = toUnixTimestamp;
this.getFileDateString = getFileDateString;
this.getFileTimeString = getFileTimeString;
this.getLocaleDateOrder = getLocaleDateOrder;
var _localeDateOrder = null;
var _months = null;
/**
* Load dateFormat bundle into _dateFormatsBundle
*/
function _loadDateFormatsBundle() {
var src = 'chrome://global/locale/dateFormat.properties';
var localeService = Components.classes['@mozilla.org/intl/nslocaleservice;1'].
getService(Components.interfaces.nsILocaleService);
var appLocale = localeService.getApplicationLocale();
var bundle =
Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService).createBundle(src, appLocale);
_months = {"short":[], "long":[]};
for(let i=1; i<=12; i++) {
_months.short.push(bundle.GetStringFromName("month."+i+".Mmm"));
_months.long.push(bundle.GetStringFromName("month."+i+".name"));
}
}
/**
* Lazy getter for reading month strings from dateFormat.properties
*/
this.__defineGetter__("months", function() {
if(!_months) _loadDateFormatsBundle();
return _months;
});
/**
* Convert an SQL date in the form '2006-06-13 11:03:05' into a JS Date object
*
* Can also accept just the date part (e.g. '2006-06-13')
**/
function sqlToDate(sqldate, isUTC){
try {
var datetime = sqldate.split(' ');
var dateparts = datetime[0].split('-');
if (datetime[1]){
var timeparts = datetime[1].split(':');
}
else {
timeparts = [false, false, false];
}
// Invalid date part
if (dateparts.length==1){
return false;
}
if (isUTC){
return new Date(Date.UTC(dateparts[0], dateparts[1]-1, dateparts[2],
timeparts[0], timeparts[1], timeparts[2]));
}
return new Date(dateparts[0], dateparts[1]-1, dateparts[2],
timeparts[0], timeparts[1], timeparts[2]);
}
catch (e){
Zotero.debug(sqldate + ' is not a valid SQL date', 2)
return false;
}
}
/**
* Convert a JS Date object to an SQL date in the form '2006-06-13 11:03:05'
*
* If _toUTC_ is true, creates a UTC date
**/
function dateToSQL(date, toUTC)
{
try {
if (toUTC){
var year = date.getUTCFullYear();
var month = date.getUTCMonth();
var day = date.getUTCDate();
var hours = date.getUTCHours();
var minutes = date.getUTCMinutes();
var seconds = date.getUTCSeconds();
}
else {
return date.toLocaleFormat('%Y-%m-%d %H:%M:%S');
}
year = Zotero.Utilities.lpad(year, '0', 4);
month = Zotero.Utilities.lpad(month + 1, '0', 2);
day = Zotero.Utilities.lpad(day, '0', 2);
hours = Zotero.Utilities.lpad(hours, '0', 2);
minutes = Zotero.Utilities.lpad(minutes, '0', 2);
seconds = Zotero.Utilities.lpad(seconds, '0', 2);
return year + '-' + month + '-' + day + ' '
+ hours + ':' + minutes + ':' + seconds;
}
catch (e){
Zotero.debug(date + ' is not a valid JS date', 2);
return '';
}
}
/**
* Convert a JS Date object to an ISO 8601 UTC date/time
*
* @param {Date} date JS Date object
* @return {String} ISO 8601 UTC date/time
* e.g. 2008-08-15T20:00:00Z
*/
this.dateToISO = function (date) {
var year = date.getUTCFullYear();
var month = date.getUTCMonth();
var day = date.getUTCDate();
var hours = date.getUTCHours();
var minutes = date.getUTCMinutes();
var seconds = date.getUTCSeconds();
year = Zotero.Utilities.lpad(year, '0', 4);
month = Zotero.Utilities.lpad(month + 1, '0', 2);
day = Zotero.Utilities.lpad(day, '0', 2);
hours = Zotero.Utilities.lpad(hours, '0', 2);
minutes = Zotero.Utilities.lpad(minutes, '0', 2);
seconds = Zotero.Utilities.lpad(seconds, '0', 2);
return year + '-' + month + '-' + day + 'T'
+ hours + ':' + minutes + ':' + seconds + 'Z';
}
/**
* Convert an ISO 8601formatted UTC date/time to a JS Date
*
* Adapted from http://delete.me.uk/2005/03/iso8601.html (AFL-licensed)
*
* @param {String} isoDate ISO 8601 date
* @return {Date} JS Date
*/
this.isoToDate = function (isoDate) {
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})))?)?)?)?/;
var d = isoDate.match(re8601);
var offset = 0;
var date = new Date(d[1], 0, 1);
if (d[3]) { date.setMonth(d[3] - 1); }
if (d[5]) { date.setDate(d[5]); }
if (d[7]) { date.setHours(d[7]); }
if (d[8]) { date.setMinutes(d[8]); }
if (d[10]) { date.setSeconds(d[10]); }
if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] == '-') ? 1 : -1);
}
offset -= date.getTimezoneOffset();
var time = (Number(date) + (offset * 60 * 1000));
return new Date(time);
}
/*
* converts a string to an object containing:
* day: integer form of the day
* month: integer form of the month (indexed from 0, not 1)
* year: 4 digit year (or, year + BC/AD/etc.)
* part: anything that does not fall under any of the above categories
* (e.g., "Summer," etc.)
*
* Note: the returned object is *not* a JS Date object
*/
var _slashRe = /^(.*?)\b([0-9]{1,4})(?:([\-\/\.\u5e74])([0-9]{1,2}))?(?:([\-\/\.\u6708])([0-9]{1,4}))?((?:\b|[^0-9]).*?)$/
var _yearRe = /^(.*?)\b((?:circa |around |about |c\.? ?)?[0-9]{1,4}(?: ?B\.? ?C\.?(?: ?E\.?)?| ?C\.? ?E\.?| ?A\.? ?D\.?)|[0-9]{3,4})\b(.*?)$/i;
var _monthRe = null;
var _dayRe = null;
function strToDate(string) {
// Parse 'yesterday'/'today'/'tomorrow'
var lc = (string + '').toLowerCase();
if (lc == 'yesterday' || lc == Zotero.getString('date.yesterday')) {
string = Zotero.Date.dateToSQL(new Date(new Date().getTime() - 86400000)).substr(0, 10);
}
else if (lc == 'today' || lc == Zotero.getString('date.today')) {
string = Zotero.Date.dateToSQL(new Date()).substr(0, 10);
}
else if (lc == 'tomorrow' || lc == Zotero.getString('date.tomorrow')) {
string = Zotero.Date.dateToSQL(new Date(new Date().getTime() + 86400000)).substr(0, 10);
}
var date = new Object();
// skip empty things
if(!string) {
return date;
}
string = string.toString().replace(/^\s+/, "").replace(/\s+$/, "").replace(/\s+/, " ");
// first, directly inspect the string
var m = _slashRe.exec(string);
if(m &&
(!m[5] || m[3] == m[5] || (m[3] == "\u5e74" && m[5] == "\u6708")) && // require sane separators
((m[2] && m[4] && m[6]) || (!m[1] && !m[7]))) { // require that either all parts are found,
// or else this is the entire date field
// figure out date based on parts
if(m[2].length == 3 || m[2].length == 4 || m[3] == "\u5e74") {
// ISO 8601 style date (big endian)
date.year = m[2];
date.month = m[4];
date.day = m[6];
} else {
// local style date (middle or little endian)
date.year = m[6];
var country = Zotero.locale.substr(3);
if(country == "US" || // The United States
country == "FM" || // The Federated States of Micronesia
country == "PW" || // Palau
country == "PH") { // The Philippines
date.month = m[2];
date.day = m[4];
} else {
date.month = m[4];
date.day = m[2];
}
}
if(date.year) date.year = parseInt(date.year, 10);
if(date.day) date.day = parseInt(date.day, 10);
if(date.month) {
date.month = parseInt(date.month, 10);
if(date.month > 12) {
// swap day and month
var tmp = date.day;
date.day = date.month
date.month = tmp;
}
}
if((!date.month || date.month <= 12) && (!date.day || date.day <= 31)) {
if(date.year && date.year < 100) { // for two digit years, determine proper
// four digit year
var today = new Date();
var year = today.getFullYear();
var twoDigitYear = year % 100;
var century = year - twoDigitYear;
if(date.year <= twoDigitYear) {
// assume this date is from our century
date.year = century + date.year;
} else {
// assume this date is from the previous century
date.year = century - 100 + date.year;
}
}
if(date.month) date.month--; // subtract one for JS style
Zotero.debug("DATE: retrieved with algorithms: "+date.toSource());
date.part = m[1]+m[7];
} else {
// give up; we failed the sanity check
Zotero.debug("DATE: algorithms failed sanity check");
date = {"part":string};
}
} else {
Zotero.debug("DATE: could not apply algorithms");
date.part = string;
}
// couldn't find something with the algorithms; use regexp
// YEAR
if(!date.year) {
var m = _yearRe.exec(date.part);
if(m) {
date.year = m[2];
date.part = m[1]+m[3];
Zotero.debug("DATE: got year ("+date.year+", "+date.part+")");
}
}
// MONTH
if(!date.month) {
// compile month regular expression
var months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
'aug', 'sep', 'oct', 'nov', 'dec'];
// If using a non-English bibliography locale, try those too
if (Zotero.locale != 'en-US') {
months = months.concat(Zotero.Date.months.short).concat(Zotero.Date.months.long);
for(var i in months) months[i] = months[i].toLowerCase();
}
if(!_monthRe) {
_monthRe = new RegExp("^(.*)\\b("+months.join("|")+")[^ ]*(?: (.*)$|$)", "i");
}
var m = _monthRe.exec(date.part);
if(m) {
// Modulo 12 in case we have multiple languages
date.month = months.indexOf(m[2].toLowerCase()) % 12;
date.part = m[1]+m[3];
Zotero.debug("DATE: got month ("+date.month+", "+date.part+")");
}
}
// DAY
if(!date.day) {
// compile day regular expression
if(!_dayRe) {
var daySuffixes = Zotero.getString("date.daySuffixes").replace(/, ?/g, "|");
_dayRe = new RegExp("\\b([0-9]{1,2})(?:"+daySuffixes+")?\\b(.*)", "i");
}
var m = _dayRe.exec(date.part);
if(m) {
var day = parseInt(m[1], 10);
// Sanity check
if (day <= 31) {
date.day = day;
if(m.index > 0) {
date.part = date.part.substr(0, m.index);
if(m[2]) {
date.part += " "+m[2];;
}
} else {
date.part = m[2];
}
Zotero.debug("DATE: got day ("+date.day+", "+date.part+")");
}
}
}
// clean up date part
if(date.part) {
date.part = date.part.replace(/^[^A-Za-z0-9]+/, "").replace(/[^A-Za-z0-9]+$/, "");
}
if(date.part === "" || date.part == undefined) {
delete date.part;
}
return date;
}
/**
* does pretty formatting of a date object returned by strToDate()
*
* @param {Object} date A date object, as returned from strToDate()
* @param {Boolean} shortFormat Whether to return a short (12/1/95) date
* @return A formatted date string
* @type String
**/
function formatDate(date, shortFormat) {
if(shortFormat) {
var localeDateOrder = getLocaleDateOrder();
var string = localeDateOrder[0]+"/"+localeDateOrder[1]+"/"+localeDateOrder[2];
return string.replace("y", (date.year !== undefined ? date.year : "00"))
.replace("m", (date.month !== undefined ? 1+date.month : "0"))
.replace("d", (date.day !== undefined ? date.day : "0"));
} else {
var string = "";
if(date.part) {
string += date.part+" ";
}
var months = Zotero.Date.months.long;
if(date.month != undefined && months[date.month]) {
// get short month strings from CSL interpreter
string += months[date.month];
if(date.day) {
string += " "+date.day+", ";
} else {
string += " ";
}
}
if(date.year) {
string += date.year;
}
}
return string;
}
function strToISO(str) {
var date = Zotero.Date.strToDate(str);
if(date.year) {
var dateString = Zotero.Utilities.lpad(date.year, "0", 4);
if(date.month) {
dateString += "-"+Zotero.Utilities.lpad(date.month+1, "0", 2);
if(date.day) {
dateString += "-"+Zotero.Utilities.lpad(date.day, "0", 2);
}
}
return dateString;
}
return false;
}
function strToMultipart(str){
if (!str){
return '';
}
var parts = strToDate(str);
// FIXME: Until we have a better BCE date solution,
// remove year value if not between 1 and 9999
if (parts.year) {
var year = parts.year + '';
if (!year.match(/^[0-9]{1,4}$/)) {
delete parts.year;
}
}
parts.month = typeof parts.month != "undefined" ? parts.month + 1 : '';
var multi = (parts.year ? Zotero.Utilities.lpad(parts.year, '0', 4) : '0000') + '-'
+ Zotero.Utilities.lpad(parts.month, '0', 2) + '-'
+ (parts.day ? Zotero.Utilities.lpad(parts.day, '0', 2) : '00')
+ ' '
+ str;
return multi;
}
// Regexes for multipart and SQL dates
// Allow zeroes in multipart dates
// TODO: Allow negative multipart in DB and here with \-?
var _multipartRE = /^[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31) /;
var _sqldateRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31)$/;
var _sqldateWithZeroesRE = /^\-?[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31)$/;
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])$/;
/**
* Tests if a string is a multipart date string
* e.g. '2006-11-03 November 3rd, 2006'
*/
function isMultipart(str){
if (isSQLDateTime(str)) {
return false;
}
return _multipartRE.test(str);
}
/**
* Returns the SQL part of a multipart date string
* (e.g. '2006-11-03 November 3rd, 2006' returns '2006-11-03')
*/
function multipartToSQL(multi){
if (!multi){
return '';
}
if (!isMultipart(multi)){
return '0000-00-00';
}
return multi.substr(0, 10);
}
/**
* Returns the user part of a multipart date string
* (e.g. '2006-11-03 November 3rd, 2006' returns 'November 3rd, 2006')
*/
function multipartToStr(multi){
if (!multi){
return '';
}
if (!isMultipart(multi)){
return multi;
}
return multi.substr(11);
}
function isSQLDate(str, allowZeroes) {
if (allowZeroes) {
return _sqldateWithZeroesRE.test(str);
}
return _sqldateRE.test(str);
}
function isSQLDateTime(str){
return _sqldatetimeRE.test(str);
}
function sqlHasYear(sqldate){
return isSQLDate(sqldate, true) && sqldate.substr(0,4)!='0000';
}
function sqlHasMonth(sqldate){
return isSQLDate(sqldate, true) && sqldate.substr(5,2)!='00';
}
function sqlHasDay(sqldate){
return isSQLDate(sqldate, true) && sqldate.substr(8,2)!='00';
}
function getUnixTimestamp() {
return Math.round(Date.now() / 1000);
}
function toUnixTimestamp(date) {
if (date === null || typeof date != 'object' ||
date.constructor.name != 'Date') {
throw ('Not a valid date in Zotero.Date.toUnixTimestamp()');
}
return Math.round(date.getTime() / 1000);
}
/**
* Convert a JS Date to a relative date (e.g., "5 minutes ago")
*
* Adapted from http://snipplr.com/view/10290/javascript-parse-relative-date/
*
* @param {Date} date
* @return {String}
*/
this.toRelativeDate = function (date) {
var str;
var now = new Date();
var timeSince = now.getTime() - date;
var inSeconds = timeSince / 1000;
var inMinutes = timeSince / 1000 / 60;
var inHours = timeSince / 1000 / 60 / 60;
var inDays = timeSince / 1000 / 60 / 60 / 24;
var inYears = timeSince / 1000 / 60 / 60 / 24 / 365;
var n;
// in seconds
if (Math.round(inSeconds) == 1) {
var key = "secondsAgo";
}
else if (inMinutes < 1.01) {
var key = "secondsAgo";
n = Math.round(inSeconds);
}
// in minutes
else if (Math.round(inMinutes) == 1) {
var key = "minutesAgo";
}
else if (inHours < 1.01) {
var key = "minutesAgo";
n = Math.round(inMinutes);
}
// in hours
else if (Math.round(inHours) == 1) {
var key = "hoursAgo";
}
else if (inDays < 1.01) {
var key = "hoursAgo";
n = Math.round(inHours);
}
// in days
else if (Math.round(inDays) == 1) {
var key = "daysAgo";
}
else if (inYears < 1.01) {
var key = "daysAgo";
n = Math.round(inDays);
}
// in years
else if (Math.round(inYears) == 1) {
var key = "yearsAgo";
}
else {
var key = "yearsAgo";
var n = Math.round(inYears);
}
return Zotero.getString("date.relative." + key + "." + (n ? "multiple" : "one"), n);
}
function getFileDateString(file){
var date = new Date();
date.setTime(file.lastModifiedTime);
return date.toLocaleDateString();
}
function getFileTimeString(file){
var date = new Date();
date.setTime(file.lastModifiedTime);
return date.toLocaleTimeString();
}
/**
* Figure out the date order from the output of toLocaleDateString()
*
* Returns a string with y, m, and d (e.g. 'ymd', 'mdy')
*/
function getLocaleDateOrder(){
if (_localeDateOrder) {
return _localeDateOrder;
}
var date = new Date("October 5, 2006");
var parts = date.toLocaleDateString().match(/([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)/);
// The above only works on OS X and Linux,
// where toLocaleDateString() produces "10/05/2006"
if (!parts) {
var country = Zotero.locale.substr(3);
switch (country) {
// I don't know where this country list came from, but these
// are little-endian in Zotero.strToDate()
case 'US': // The United States
case 'FM': // The Federated States of Micronesia
case 'PW': // Palau
case 'PH': // The Philippines
return 'mdy';
break;
default:
return 'dmy';
}
}
switch (parseInt(parts[1])){
case 2006:
var order = 'y';
break;
case 10:
var order = 'm';
break;
case 5:
var order = 'd';
break;
}
switch (parseInt(parts[2])){
case 2006:
order += 'y';
break;
case 10:
order += 'm';
break;
case 5:
order += 'd';
break;
}
switch (parseInt(parts[3])){
case 2006:
order += 'y';
break;
case 10:
order += 'm';
break;
case 5:
order += 'd';
break;
}
_localeDateOrder = order;
return order;
}
}