blob: 6479ff81e12f881aa888abe1aeba04ae9df6c15a [file] [log] [blame]
/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("layer.validation");
dojo.provide("dojo.experimental");
dojo.experimental = function(/* String */ moduleName, /* String? */ extra){
// summary: Marks code as experimental.
// description:
// This can be used to mark a function, file, or module as experimental.
// Experimental code is not ready to be used, and the APIs are subject
// to change without notice. Experimental code may be completed deleted
// without going through the normal deprecation process.
// moduleName: The name of a module, or the name of a module file or a specific function
// extra: some additional message for the user
// examples:
// dojo.experimental("dojo.data.Result");
// dojo.experimental("dojo.weather.toKelvin()", "PENDING approval from NOAA");
var message = "EXPERIMENTAL: " + moduleName;
message += " -- Not yet ready for use. APIs subject to change without notice.";
if(extra){ message += " " + extra; }
dojo.debug(message);
}
dojo.provide("dojo.regexp");
dojo.evalObjPath("dojo.regexp.us", true); // this file also defines stuff in the dojo.regexp.us module (TODO: move to separate file?)
// *** Regular Expression Generators ***
dojo.regexp.tld = function(/*Object?*/flags){
// summary: Builds a RE that matches a top-level domain
//
// flags:
// flags.allowCC Include 2 letter country code domains. Default is true.
// flags.allowGeneric Include the generic domains. Default is true.
// flags.allowInfra Include infrastructure domains. Default is true.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.allowCC != "boolean"){ flags.allowCC = true; }
if(typeof flags.allowInfra != "boolean"){ flags.allowInfra = true; }
if(typeof flags.allowGeneric != "boolean"){ flags.allowGeneric = true; }
// Infrastructure top-level domain - only one at present
var infraRE = "arpa";
// Generic top-level domains RE.
var genericRE =
"aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|xxx|jobs|mobi|post";
// Country Code top-level domains RE
var ccRE =
"ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|" +
"bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|" +
"ec|ee|eg|er|eu|es|et|fi|fj|fk|fm|fo|fr|ga|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|"
+
"gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kr|kw|ky|kz|" +
"la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|" +
"my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|" +
"re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sk|sl|sm|sn|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|" +
"tn|to|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw";
// Build top-level domain RE
var a = [];
if(flags.allowInfra){ a.push(infraRE); }
if(flags.allowGeneric){ a.push(genericRE); }
if(flags.allowCC){ a.push(ccRE); }
var tldRE = "";
if (a.length > 0) {
tldRE = "(" + a.join("|") + ")";
}
return tldRE; // String
}
dojo.regexp.ipAddress = function(/*Object?*/flags){
// summary: Builds a RE that matches an IP Address
//
// description:
// Supports 5 formats for IPv4: dotted decimal, dotted hex, dotted octal, decimal and hexadecimal.
// Supports 2 formats for Ipv6.
//
// flags An object. All flags are boolean with default = true.
// flags.allowDottedDecimal Example, 207.142.131.235. No zero padding.
// flags.allowDottedHex Example, 0x18.0x11.0x9b.0x28. Case insensitive. Zero padding allowed.
// flags.allowDottedOctal Example, 0030.0021.0233.0050. Zero padding allowed.
// flags.allowDecimal Example, 3482223595. A decimal number between 0-4294967295.
// flags.allowHex Example, 0xCF8E83EB. Hexadecimal number between 0x0-0xFFFFFFFF.
// Case insensitive. Zero padding allowed.
// flags.allowIPv6 IPv6 address written as eight groups of four hexadecimal digits.
// flags.allowHybrid IPv6 address written as six groups of four hexadecimal digits
// followed by the usual 4 dotted decimal digit notation of IPv4. x:x:x:x:x:x:d.d.d.d
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.allowDottedDecimal != "boolean"){ flags.allowDottedDecimal = true; }
if(typeof flags.allowDottedHex != "boolean"){ flags.allowDottedHex = true; }
if(typeof flags.allowDottedOctal != "boolean"){ flags.allowDottedOctal = true; }
if(typeof flags.allowDecimal != "boolean"){ flags.allowDecimal = true; }
if(typeof flags.allowHex != "boolean"){ flags.allowHex = true; }
if(typeof flags.allowIPv6 != "boolean"){ flags.allowIPv6 = true; }
if(typeof flags.allowHybrid != "boolean"){ flags.allowHybrid = true; }
// decimal-dotted IP address RE.
var dottedDecimalRE =
// Each number is between 0-255. Zero padding is not allowed.
"((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])";
// dotted hex IP address RE. Each number is between 0x0-0xff. Zero padding is allowed, e.g. 0x00.
var dottedHexRE = "(0[xX]0*[\\da-fA-F]?[\\da-fA-F]\\.){3}0[xX]0*[\\da-fA-F]?[\\da-fA-F]";
// dotted octal IP address RE. Each number is between 0000-0377.
// Zero padding is allowed, but each number must have at least 4 characters.
var dottedOctalRE = "(0+[0-3][0-7][0-7]\\.){3}0+[0-3][0-7][0-7]";
// decimal IP address RE. A decimal number between 0-4294967295.
var decimalRE = "(0|[1-9]\\d{0,8}|[1-3]\\d{9}|4[01]\\d{8}|42[0-8]\\d{7}|429[0-3]\\d{6}|" +
"4294[0-8]\\d{5}|42949[0-5]\\d{4}|429496[0-6]\\d{3}|4294967[01]\\d{2}|42949672[0-8]\\d|429496729[0-5])";
// hexadecimal IP address RE.
// A hexadecimal number between 0x0-0xFFFFFFFF. Case insensitive. Zero padding is allowed.
var hexRE = "0[xX]0*[\\da-fA-F]{1,8}";
// IPv6 address RE.
// The format is written as eight groups of four hexadecimal digits, x:x:x:x:x:x:x:x,
// where x is between 0000-ffff. Zero padding is optional. Case insensitive.
var ipv6RE = "([\\da-fA-F]{1,4}\\:){7}[\\da-fA-F]{1,4}";
// IPv6/IPv4 Hybrid address RE.
// The format is written as six groups of four hexadecimal digits,
// followed by the 4 dotted decimal IPv4 format. x:x:x:x:x:x:d.d.d.d
var hybridRE = "([\\da-fA-F]{1,4}\\:){6}" +
"((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])";
// Build IP Address RE
var a = [];
if(flags.allowDottedDecimal){ a.push(dottedDecimalRE); }
if(flags.allowDottedHex){ a.push(dottedHexRE); }
if(flags.allowDottedOctal){ a.push(dottedOctalRE); }
if(flags.allowDecimal){ a.push(decimalRE); }
if(flags.allowHex){ a.push(hexRE); }
if(flags.allowIPv6){ a.push(ipv6RE); }
if(flags.allowHybrid){ a.push(hybridRE); }
var ipAddressRE = "";
if(a.length > 0){
ipAddressRE = "(" + a.join("|") + ")";
}
return ipAddressRE; // String
}
dojo.regexp.host = function(/*Object?*/flags){
// summary: Builds a RE that matches a host
// description: A host is a domain name or an IP address, possibly followed by a port number.
// flags: An object.
// flags.allowIP Allow an IP address for hostname. Default is true.
// flags.allowLocal Allow the host to be "localhost". Default is false.
// flags.allowPort Allow a port number to be present. Default is true.
// flags in regexp.ipAddress can be applied.
// flags in regexp.tld can be applied.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.allowIP != "boolean"){ flags.allowIP = true; }
if(typeof flags.allowLocal != "boolean"){ flags.allowLocal = false; }
if(typeof flags.allowPort != "boolean"){ flags.allowPort = true; }
// Domain names can not end with a dash.
var domainNameRE = "([0-9a-zA-Z]([-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?\\.)+" + dojo.regexp.tld(flags);
// port number RE
var portRE = ( flags.allowPort ) ? "(\\:" + dojo.regexp.integer({signed: false}) + ")?" : "";
// build host RE
var hostNameRE = domainNameRE;
if(flags.allowIP){ hostNameRE += "|" + dojo.regexp.ipAddress(flags); }
if(flags.allowLocal){ hostNameRE += "|localhost"; }
return "(" + hostNameRE + ")" + portRE; // String
}
dojo.regexp.url = function(/*Object?*/flags){
// summary: Builds a regular expression that matches a URL
//
// flags: An object
// flags.scheme Can be true, false, or [true, false].
// This means: required, not allowed, or match either one.
// flags in regexp.host can be applied.
// flags in regexp.ipAddress can be applied.
// flags in regexp.tld can be applied.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.scheme == "undefined"){ flags.scheme = [true, false]; }
// Scheme RE
var protocolRE = dojo.regexp.buildGroupRE(flags.scheme,
function(q){ if(q){ return "(https?|ftps?)\\://"; } return ""; }
);
// Path and query and anchor RE
var pathRE = "(/([^?#\\s/]+/)*)?([^?#\\s/]+(\\?[^?#\\s/]*)?(#[A-Za-z][\\w.:-]*)?)?";
return protocolRE + dojo.regexp.host(flags) + pathRE;
}
dojo.regexp.emailAddress = function(/*Object?*/flags){
// summary: Builds a regular expression that matches an email address
//
//flags: An object
// flags.allowCruft Allow address like <mailto:foo@yahoo.com>. Default is false.
// flags in regexp.host can be applied.
// flags in regexp.ipAddress can be applied.
// flags in regexp.tld can be applied.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if (typeof flags.allowCruft != "boolean") { flags.allowCruft = false; }
flags.allowPort = false; // invalid in email addresses
// user name RE - apostrophes are valid if there's not 2 in a row
var usernameRE = "([\\da-z]+[-._+&'])*[\\da-z]+";
// build emailAddress RE
var emailAddressRE = usernameRE + "@" + dojo.regexp.host(flags);
// Allow email addresses with cruft
if ( flags.allowCruft ) {
emailAddressRE = "<?(mailto\\:)?" + emailAddressRE + ">?";
}
return emailAddressRE; // String
}
dojo.regexp.emailAddressList = function(/*Object?*/flags){
// summary: Builds a regular expression that matches a list of email addresses.
//
// flags: An object.
// flags.listSeparator The character used to separate email addresses. Default is ";", ",", "\n" or " ".
// flags in regexp.emailAddress can be applied.
// flags in regexp.host can be applied.
// flags in regexp.ipAddress can be applied.
// flags in regexp.tld can be applied.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.listSeparator != "string"){ flags.listSeparator = "\\s;,"; }
// build a RE for an Email Address List
var emailAddressRE = dojo.regexp.emailAddress(flags);
var emailAddressListRE = "(" + emailAddressRE + "\\s*[" + flags.listSeparator + "]\\s*)*" +
emailAddressRE + "\\s*[" + flags.listSeparator + "]?\\s*";
return emailAddressListRE; // String
}
dojo.regexp.integer = function(/*Object?*/flags){
// summary: Builds a regular expression that matches an integer
//
// flags: An object
// flags.signed The leading plus-or-minus sign. Can be true, false, or [true, false].
// Default is [true, false], (i.e. will match if it is signed or unsigned).
// flags.separator The character used as the thousands separator. Default is no separator.
// For more than one symbol use an array, e.g. [",", ""], makes ',' optional.
// flags.groupSize group size between separators
// flags.groupSize2 second grouping (for India)
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.signed == "undefined"){ flags.signed = [true, false]; }
if(typeof flags.separator == "undefined"){
flags.separator = "";
} else if(typeof flags.groupSize == "undefined"){
flags.groupSize = 3;
}
// build sign RE
var signRE = dojo.regexp.buildGroupRE(flags.signed,
function(q) { return q ? "[-+]" : ""; }
);
// number RE
var numberRE = dojo.regexp.buildGroupRE(flags.separator,
function(sep){
if(sep == ""){
return "(0|[1-9]\\d*)";
}
var grp = flags.groupSize, grp2 = flags.groupSize2;
if(typeof grp2 != "undefined"){
var grp2RE = "(0|[1-9]\\d{0," + (grp2-1) + "}([" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
return ((grp-grp2) > 0) ? "(" + grp2RE + "|(0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
}
return "(0|[1-9]\\d{0," + (grp-1) + "}([" + sep + "]\\d{" + grp + "})*)";
}
);
// integer RE
return signRE + numberRE; // String
}
dojo.regexp.realNumber = function(/*Object?*/flags){
// summary: Builds a regular expression to match a real number in exponential notation
//
// flags:An object
// flags.places The integer number of decimal places.
// If not given, the decimal part is optional and the number of places is unlimited.
// flags.decimal A string for the character used as the decimal point. Default is ".".
// flags.fractional Whether decimal places are allowed.
// Can be true, false, or [true, false]. Default is [true, false]
// flags.exponent Express in exponential notation. Can be true, false, or [true, false].
// Default is [true, false], (i.e. will match if the exponential part is present are not).
// flags.eSigned The leading plus-or-minus sign on the exponent. Can be true, false,
// or [true, false]. Default is [true, false], (i.e. will match if it is signed or unsigned).
// flags in regexp.integer can be applied.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.places != "number"){ flags.places = Infinity; }
if(typeof flags.decimal != "string"){ flags.decimal = "."; }
if(typeof flags.fractional == "undefined"){ flags.fractional = [true, false]; }
if(typeof flags.exponent == "undefined"){ flags.exponent = [true, false]; }
if(typeof flags.eSigned == "undefined"){ flags.eSigned = [true, false]; }
// integer RE
var integerRE = dojo.regexp.integer(flags);
// decimal RE
var decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
function(q){
var re = "";
if(q && (flags.places > 0)){
re = "\\" + flags.decimal;
if(flags.places == Infinity){
re = "(" + re + "\\d+)?";
}else{
re = re + "\\d{" + flags.places + "}";
}
}
return re;
}
);
// exponent RE
var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
function(q){
if(q){ return "([eE]" + dojo.regexp.integer({ signed: flags.eSigned}) + ")"; }
return "";
}
);
// real number RE
return integerRE + decimalRE + exponentRE; // String
}
dojo.regexp.currency = function(/*Object?*/flags){
// summary: Builds a regular expression to match a monetary value
//
// flags: An object
// flags.symbol A currency symbol such as Yen "�", Pound "�", or the Euro sign "�".
// Default is "$". For more than one symbol use an array, e.g. ["$", ""], makes $ optional.
// flags.placement The symbol can come "before" the number or "after" the number. Default is "before".
// flags.signPlacement The sign can come "before" the number or "after" the sign,
// "around" to put parentheses around negative values, or "end" for the final char. Default is "before".
// flags.cents deprecated, in favor of flags.fractional
// flags in regexp.realNumber can be applied except exponent, eSigned.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.signed == "undefined"){ flags.signed = [true, false]; }
if(typeof flags.symbol == "undefined"){ flags.symbol = "$"; }
if(typeof flags.placement != "string"){ flags.placement = "before"; }
if(typeof flags.signPlacement != "string"){ flags.signPlacement = "before"; }
if(typeof flags.separator == "undefined"){ flags.separator = ","; }
if(typeof flags.fractional == "undefined" && typeof flags.cents != "undefined"){
dojo.deprecated("dojo.regexp.currency: flags.cents", "use flags.fractional instead", "0.5");
flags.fractional = flags.cents;
}
if(typeof flags.decimal != "string"){ flags.decimal = "."; }
// build sign RE
var signRE = dojo.regexp.buildGroupRE(flags.signed,
function(q){ if (q){ return "[-+]"; } return ""; }
);
// build symbol RE
var symbolRE = dojo.regexp.buildGroupRE(flags.symbol,
function(symbol){
// escape all special characters
return "\\s?" + symbol.replace( /([.$?*!=:|\\\/^])/g, "\\$1") + "\\s?";
}
);
switch (flags.signPlacement){
case "before":
symbolRE = signRE + symbolRE;
break;
case "after":
symbolRE = symbolRE + signRE;
break;
}
// number RE
var flagsCopy = flags; //TODO: copy by value?
flagsCopy.signed = false; flagsCopy.exponent = false;
var numberRE = dojo.regexp.realNumber(flagsCopy);
// build currency RE
var currencyRE;
switch (flags.placement){
case "before":
currencyRE = symbolRE + numberRE;
break;
case "after":
currencyRE = numberRE + symbolRE;
break;
}
switch (flags.signPlacement){
case "around":
currencyRE = "(" + currencyRE + "|" + "\\(" + currencyRE + "\\)" + ")";
break;
case "begin":
currencyRE = signRE + currencyRE;
break;
case "end":
currencyRE = currencyRE + signRE;
break;
}
return currencyRE; // String
}
dojo.regexp.us.state = function(/*Object?*/flags){
// summary: A regular expression to match US state and territory abbreviations
//
// flags An object.
// flags.allowTerritories Allow Guam, Puerto Rico, etc. Default is true.
// flags.allowMilitary Allow military 'states', e.g. Armed Forces Europe (AE). Default is true.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.allowTerritories != "boolean"){ flags.allowTerritories = true; }
if(typeof flags.allowMilitary != "boolean"){ flags.allowMilitary = true; }
// state RE
var statesRE =
"AL|AK|AZ|AR|CA|CO|CT|DE|DC|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|" +
"NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY";
// territories RE
var territoriesRE = "AS|FM|GU|MH|MP|PW|PR|VI";
// military states RE
var militaryRE = "AA|AE|AP";
// Build states and territories RE
if(flags.allowTerritories){ statesRE += "|" + territoriesRE; }
if(flags.allowMilitary){ statesRE += "|" + militaryRE; }
return "(" + statesRE + ")"; // String
}
dojo.regexp.time = function(/*Object?*/flags){
// summary: Builds a regular expression to match any International format for time
// description: The RE can match one format or one of multiple formats.
//
// Format
// h 12 hour, no zero padding.
// hh 12 hour, has leading zero.
// H 24 hour, no zero padding.
// HH 24 hour, has leading zero.
// m minutes, no zero padding.
// mm minutes, has leading zero.
// s seconds, no zero padding.
// ss seconds, has leading zero.
// t am or pm, case insensitive.
// All other characters must appear literally in the expression.
//
// Example
// "h:m:s t" -> 2:5:33 PM
// "HH:mm:ss" -> 14:05:33
//
// flags: An object
// flags.format A string or an array of strings. Default is "h:mm:ss t".
// flags.amSymbol The symbol used for AM. Default is "AM".
// flags.pmSymbol The symbol used for PM. Default is "PM".
dojo.deprecated("dojo.regexp.time", "Use dojo.date.parse instead", "0.5");
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.format == "undefined"){ flags.format = "h:mm:ss t"; }
if(typeof flags.amSymbol != "string"){ flags.amSymbol = "AM"; }
if(typeof flags.pmSymbol != "string"){ flags.pmSymbol = "PM"; }
// Converts a time format to a RE
var timeRE = function(format){
// escape all special characters
format = format.replace( /([.$?*!=:|{}\(\)\[\]\\\/^])/g, "\\$1");
var amRE = flags.amSymbol.replace( /([.$?*!=:|{}\(\)\[\]\\\/^])/g, "\\$1");
var pmRE = flags.pmSymbol.replace( /([.$?*!=:|{}\(\)\[\]\\\/^])/g, "\\$1");
// replace tokens with Regular Expressions
format = format.replace("hh", "(0[1-9]|1[0-2])");
format = format.replace("h", "([1-9]|1[0-2])");
format = format.replace("HH", "([01][0-9]|2[0-3])");
format = format.replace("H", "([0-9]|1[0-9]|2[0-3])");
format = format.replace("mm", "([0-5][0-9])");
format = format.replace("m", "([1-5][0-9]|[0-9])");
format = format.replace("ss", "([0-5][0-9])");
format = format.replace("s", "([1-5][0-9]|[0-9])");
format = format.replace("t", "\\s?(" + amRE + "|" + pmRE + ")\\s?" );
return format; // String
};
// build RE for multiple time formats
return dojo.regexp.buildGroupRE(flags.format, timeRE); // String
}
dojo.regexp.numberFormat = function(/*Object?*/flags){
// summary: Builds a regular expression to match any sort of number based format
// description:
// Use this method for phone numbers, social security numbers, zip-codes, etc.
// The RE can match one format or one of multiple formats.
//
// Format
// # Stands for a digit, 0-9.
// ? Stands for an optional digit, 0-9 or nothing.
// All other characters must appear literally in the expression.
//
// Example
// "(###) ###-####" -> (510) 542-9742
// "(###) ###-#### x#???" -> (510) 542-9742 x153
// "###-##-####" -> 506-82-1089 i.e. social security number
// "#####-####" -> 98225-1649 i.e. zip code
//
// flags: An object
// flags.format A string or an Array of strings for multiple formats.
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
if(typeof flags.format == "undefined"){ flags.format = "###-###-####"; }
// Converts a number format to RE.
var digitRE = function(format){
// escape all special characters, except '?'
format = format.replace( /([.$*!=:|{}\(\)\[\]\\\/^])/g, "\\$1");
// Now replace '?' with Regular Expression
format = format.replace(/\?/g, "\\d?");
// replace # with Regular Expression
format = format.replace(/#/g, "\\d");
return format; // String
};
// build RE for multiple number formats
return dojo.regexp.buildGroupRE(flags.format, digitRE); //String
}
dojo.regexp.buildGroupRE = function(/*value or Array of values*/a, /*Function(x) returns a regular expression as a String*/re){
// summary: Builds a regular expression that groups subexpressions
// description: A utility function used by some of the RE generators.
// The subexpressions are constructed by the function, re, in the second parameter.
// re builds one subexpression for each elem in the array a, in the first parameter.
// Returns a string for a regular expression that groups all the subexpressions.
//
// a: A single value or an array of values.
// re: A function. Takes one parameter and converts it to a regular expression.
// case 1: a is a single value.
if(!(a instanceof Array)){
return re(a); // String
}
// case 2: a is an array
var b = [];
for (var i = 0; i < a.length; i++){
// convert each elem to a RE
b.push(re(a[i]));
}
// join the REs as alternatives in a RE group.
return "(" + b.join("|") + ")"; // String
}
dojo.provide("dojo.i18n.number");
dojo.require("dojo.i18n.common");
dojo.require("dojo.lang.common");
/**
* Method to Format and validate a given number
*
* @param Number value
* The number to be formatted and validated.
* @param Object flags
* flags.places number of decimal places to show, default is 0 (cannot be Infinity)
* flags.round true to round the number, false to truncate
* @param String locale
* The locale used to determine the number format.
* @return String
* the formatted number of type String if successful
* or null if an unsupported locale value was provided
**/
dojo.i18n.number.format = function(value, flags /*optional*/, locale /*optional*/){
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
if (typeof flags.decimal == "undefined") {flags.decimal = formatData[2];}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
if (typeof flags.round == "undefined") {flags.round = true;}
if (typeof flags.signed == "undefined") {flags.signed = true;}
var output = (flags.signed && (value < 0)) ? "-" : "";
value = Math.abs(value);
var whole = String((((flags.places > 0) || !flags.round) ? Math.floor : Math.round)(value));
// Splits str into substrings of size count, starting from right to left. Is there a more clever way to do this in JS?
function splitSubstrings(str, count){
for(var subs = []; str.length >= count; str = str.substr(0, str.length - count)){
subs.push(str.substr(-count));
}
if (str.length > 0){subs.push(str);}
return subs.reverse();
}
if (flags.groupSize2 && (whole.length > flags.groupSize)){
var groups = splitSubstrings(whole.substr(0, whole.length - flags.groupSize), flags.groupSize2);
groups.push(whole.substr(-flags.groupSize));
output = output + groups.join(flags.separator);
}else if (flags.groupSize){
output = output + splitSubstrings(whole, flags.groupSize).join(flags.separator);
}else{
output = output + whole;
}
//TODO: what if flags.places is Infinity?
if (flags.places > 0){
//Q: Is it safe to convert to a string and split on ".", or might that be locale dependent? Use Math for now.
var fract = value - Math.floor(value);
fract = (flags.round ? Math.round : Math.floor)(fract * Math.pow(10, flags.places));
output = output + flags.decimal + fract;
}
//TODO: exp
return output;
};
/**
* Method to convert a properly formatted int string to a primative numeric value.
*
* @param String value
* The int string to be convertted
* @param string locale
* The locale used to convert the number string
* @param Object flags
* flags.validate true to check the string for strict adherence to the locale settings for separator, sign, etc.
* Default is true
* @return Number
* Returns a value of type Number, Number.NaN if not a number, or null if locale is not supported.
**/
dojo.i18n.number.parse = function(value, locale /*optional*/, flags /*optional*/){
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
if (typeof flags.decimal == "undefined") {flags.decimal = formatData[2];}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
if (typeof flags.validate == "undefined") {flags.validate = true;}
if (flags.validate && !dojo.i18n.number.isReal(value, locale, flags)) {
return Number.NaN;
}
var numbers = value.split(flags.decimal);
if (numbers.length > 2){return Number.NaN; }
var whole;
if (flags.separator != ""){
whole = Number(numbers[0].replace(new RegExp("\\" + flags.separator, "g"), ""));
} else {
whole = Number(numbers[0]);
}
var fract = (numbers.length == 1) ? 0 : Number(numbers[1]) / Math.pow(10, String(numbers[1]).length);
//TODO: exp
return whole + fract;
};
/**
Validates whether a string is in an integer format.
@param value A string.
@param locale the locale to determine formatting used. By default, the locale defined by the
host environment: dojo.locale
@param flags An object.
flags.signed The leading plus-or-minus sign. Can be true, false, or [true, false].
Default is [true, false], (i.e. sign is optional).
flags.separator The character used as the thousands separator. Default is specified by the locale.
For more than one symbol use an array, e.g. [",", ""], makes ',' optional.
The empty array [] makes the default separator optional.
@return true or false.
*/
dojo.i18n.number.isInteger = function(value, locale /*optional*/, flags /*optional*/) {
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
else if (dojo.lang.isArray(flags.separator) && flags.separator.length ===0){
flags.separator = [formatData[1],""];
}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
var re = new RegExp("^" + dojo.regexp.integer(flags) + "$");
return re.test(value);
};
/**
Validates whether a string is a real valued number.
Format is the usual exponential notation.
@param value A string.
@param locale the locale to determine formatting used. By default, the locale defined by the
host environment: dojo.locale
@param flags An object.
flags.places The integer number of decimal places.
If not given, the decimal part is optional and the number of places is unlimited.
flags.decimal The character used for the decimal point. The default is specified by the locale.
flags.exponent Express in exponential notation. Can be true, false, or [true, false].
Default is [true, false], (i.e. the exponential part is optional).
flags.eSigned The leading plus-or-minus sign on the exponent. Can be true, false,
or [true, false]. Default is [true, false], (i.e. sign is optional).
flags in regexp.integer can be applied.
@return true or false.
*/
dojo.i18n.number.isReal = function(value, locale /*optional*/, flags /*optional*/) {
flags = (typeof flags == "object") ? flags : {};
var formatData = dojo.i18n.number._mapToLocalizedFormatData(dojo.i18n.number.FORMAT_TABLE, locale);
if (typeof flags.separator == "undefined") {flags.separator = formatData[1];}
else if (dojo.lang.isArray(flags.separator) && flags.separator.length ===0){
flags.separator = [formatData[1],""];
}
if (typeof flags.decimal == "undefined") {flags.decimal = formatData[2];}
if (typeof flags.groupSize == "undefined") {flags.groupSize = formatData[3];}
if (typeof flags.groupSize2 == "undefined") {flags.groupSize2 = formatData[4];}
var re = new RegExp("^" + dojo.regexp.realNumber(flags) + "$");
return re.test(value);
};
//TODO: hide in a closure?
//TODO: change to use hashes and mixins, rather than arrays
//Q: fallback algorithm/how to structure table:
// does it make sense to look by country code most of the time (wildcard match on
// language, except where it's relevant) and provide default country when only
// a language is given?
(function() {
dojo.i18n.number.FORMAT_TABLE = {
//0: thousand seperator for monetary, 1: thousand seperator for number, 2: decimal seperator, 3: group size, 4: second group size because of india
'ar-ae': ["","", ",", 1],
'ar-bh': ["","",",", 1],
'ar-dz': ["","",",", 1],
'ar-eg': ["","", ",", 1],
'ar-jo': ["","",",", 1],
'ar-kw': ["","", ",", 1],
'ar-lb': ["","", ",", 1],
'ar-ma': ["","", ",", 1],
'ar-om': ["","", ",", 1],
'ar-qa': ["","", ",", 1],
'ar-sa': ["","", ",", 1],
'ar-sy': ["","", ",", 1],
'ar-tn': ["","", ",", 1],
'ar-ye': ["","", ",", 1],
'cs-cz': [".",".", ",", 3],
'da-dk': [".",".", ",", 3],
'de-at': [".",".", ",", 3],
'de-de': [".",".", ",", 3],
'de-lu': [".",".", ",", 3],
//IBM JSL defect 51278. right now we have problem with single quote. //IBM: explain?
'de-ch': ["'","'", ".", 3], //Q: comma as decimal separator for currency??
//'de-ch': [".",".", ",", 3],
'el-gr': [".",".", ",", 3],
'en-au': [",",",", ".", 3],
'en-ca': [",",",", ".", 3],
'en-gb': [",",",", ".", 3],
'en-hk': [",",",", ".", 3],
'en-ie': [",",",", ".", 3],
'en-in': [",",",", ".", 3,2],//india-english, 1,23,456.78
'en-nz': [",",",", ".", 3],
'en-us': [",",",", ".", 3],
'en-za': [",",",", ".", 3],
'es-ar': [".",".", ",", 3],
'es-bo': [".",".", ",", 3],
'es-cl': [".",".", ",", 3],
'es-co': [".",".", ",", 3],
'es-cr': [".",".", ",", 3],
'es-do': [".",".", ",", 3],
'es-ec': [".",".", ",", 3],
'es-es': [".",".", ",", 3],
'es-gt': [",",",", ".", 3],
'es-hn': [",",",", ".", 3],
'es-mx': [",",",", ".", 3],
'es-ni': [",",",", ".", 3],
'es-pa': [",",",", ".", 3],
'es-pe': [",",",", ".", 3],
'es-pr': [",",",", ".", 3],
'es-py': [".",".",",", 3],
'es-sv': [",", ",",".", 3],
'es-uy': [".",".",",", 3],
'es-ve': [".",".", ",", 3],
'fi-fi': [" "," ", ",", 3],
'fr-be': [".",".",",", 3],
'fr-ca': [" ", " ", ",", 3],
'fr-ch': [" ", " ",".", 3],
'fr-fr': [" "," ", ",", 3],
'fr-lu': [".",".", ",", 3],
'he-il': [",",",", ".", 3],
'hu-hu': [" ", " ",",", 3],
'it-ch': [" "," ", ".", 3],
'it-it': [".",".", ",", 3],
'ja-jp': [",",",", ".", 3],
'ko-kr': [",", ",",".", 3],
'no-no': [".",".", ",", 3],
'nl-be': [" "," ", ",", 3],
'nl-nl': [".",".", ",", 3],
'pl-pl': [".", ".",",", 3],
'pt-br': [".",".", ",", 3],
'pt-pt': [".",".", "$", 3],
'ru-ru': [" ", " ",",", 3],
'sv-se': ["."," ", ",", 3],
'tr-tr': [".",".", ",", 3],
'zh-cn': [",",",", ".", 3],
'zh-hk': [",",",",".", 3],
'zh-tw': [",", ",",".", 3],
'*': [",",",", ".", 3]
};
})();
dojo.i18n.number._mapToLocalizedFormatData = function(table, locale){
locale = dojo.hostenv.normalizeLocale(locale);
//TODO: most- to least-specific search? search by country code?
//TODO: implement aliases to simplify and shorten tables
var data = table[locale];
if (typeof data == 'undefined'){data = table['*'];}
return data;
}
dojo.provide("dojo.validate.common");
dojo.validate.isText = function(/*String*/value, /*Object?*/flags){
// summary:
// Checks if a string has non whitespace characters.
// Parameters allow you to constrain the length.
//
// value: A string
// flags: {length: Number, minlength: Number, maxlength: Number}
// flags.length If set, checks if there are exactly flags.length number of characters.
// flags.minlength If set, checks if there are at least flags.minlength number of characters.
// flags.maxlength If set, checks if there are at most flags.maxlength number of characters.
flags = (typeof flags == "object") ? flags : {};
// test for text
if(/^\s*$/.test(value)){ return false; } // Boolean
// length tests
if(typeof flags.length == "number" && flags.length != value.length){ return false; } // Boolean
if(typeof flags.minlength == "number" && flags.minlength > value.length){ return false; } // Boolean
if(typeof flags.maxlength == "number" && flags.maxlength < value.length){ return false; } // Boolean
return true; // Boolean
}
dojo.validate.isInteger = function(/*String*/value, /*Object?*/flags){
// summary:
// Validates whether a string is in an integer format
//
// value A string
// flags {signed: Boolean|[true,false], separator: String}
// flags.signed The leading plus-or-minus sign. Can be true, false, or [true, false].
// Default is [true, false], (i.e. sign is optional).
// flags.separator The character used as the thousands separator. Default is no separator.
// For more than one symbol use an array, e.g. [",", ""], makes ',' optional.
var re = new RegExp("^" + dojo.regexp.integer(flags) + "$");
return re.test(value); // Boolean
}
dojo.validate.isRealNumber = function(/*String*/value, /*Object?*/flags){
// summary:
// Validates whether a string is a real valued number.
// Format is the usual exponential notation.
//
// value: A string
// flags: {places: Number, decimal: String, exponent: Boolean|[true,false], eSigned: Boolean|[true,false], ...}
// flags.places The integer number of decimal places.
// If not given, the decimal part is optional and the number of places is unlimited.
// flags.decimal The character used for the decimal point. Default is ".".
// flags.exponent Express in exponential notation. Can be true, false, or [true, false].
// Default is [true, false], (i.e. the exponential part is optional).
// flags.eSigned The leading plus-or-minus sign on the exponent. Can be true, false,
// or [true, false]. Default is [true, false], (i.e. sign is optional).
// flags in regexp.integer can be applied.
var re = new RegExp("^" + dojo.regexp.realNumber(flags) + "$");
return re.test(value); // Boolean
}
dojo.validate.isCurrency = function(/*String*/value, /*Object?*/flags){
// summary:
// Validates whether a string denotes a monetary value.
// value: A string
// flags: {signed:Boolean|[true,false], symbol:String, placement:String, separator:String,
// fractional:Boolean|[true,false], decimal:String}
// flags.signed The leading plus-or-minus sign. Can be true, false, or [true, false].
// Default is [true, false], (i.e. sign is optional).
// flags.symbol A currency symbol such as Yen "�", Pound "�", or the Euro sign "�".
// Default is "$". For more than one symbol use an array, e.g. ["$", ""], makes $ optional.
// flags.placement The symbol can come "before" the number or "after". Default is "before".
// flags.separator The character used as the thousands separator. The default is ",".
// flags.fractional The appropriate number of decimal places for fractional currency (e.g. cents)
// Can be true, false, or [true, false]. Default is [true, false], (i.e. cents are optional).
// flags.decimal The character used for the decimal point. Default is ".".
var re = new RegExp("^" + dojo.regexp.currency(flags) + "$");
return re.test(value); // Boolean
}
dojo.validate._isInRangeCache = {};
dojo.validate.isInRange = function(/*String*/value, /*Object?*/flags){
//summary:
// Validates whether a string denoting an integer,
// real number, or monetary value is between a max and min.
//
// value: A string
// flags: {max:Number, min:Number, decimal:String}
// flags.max A number, which the value must be less than or equal to for the validation to be true.
// flags.min A number, which the value must be greater than or equal to for the validation to be true.
// flags.decimal The character used for the decimal point. Default is ".".
//stripping the separator allows NaN to perform as expected, if no separator, we assume ','
//once i18n support is ready for this, instead of assuming, we default to i18n's recommended value
value = value.replace(dojo.lang.has(flags,'separator')?flags.separator:',', '', 'g').
replace(dojo.lang.has(flags,'symbol')?flags.symbol:'$', '');
if(isNaN(value)){
return false; // Boolean
}
// assign default values to missing paramters
flags = (typeof flags == "object") ? flags : {};
var max = (typeof flags.max == "number") ? flags.max : Infinity;
var min = (typeof flags.min == "number") ? flags.min : -Infinity;
var dec = (typeof flags.decimal == "string") ? flags.decimal : ".";
var cache = dojo.validate._isInRangeCache;
var cacheIdx = value+"max"+max+"min"+min+"dec"+dec;
if(typeof cache[cacheIdx] != "undefined"){
return cache[cacheIdx];
}
// splice out anything not part of a number
var pattern = "[^" + dec + "\\deE+-]";
value = value.replace(RegExp(pattern, "g"), "");
// trim ends of things like e, E, or the decimal character
value = value.replace(/^([+-]?)(\D*)/, "$1");
value = value.replace(/(\D*)$/, "");
// replace decimal with ".". The minus sign '-' could be the decimal!
pattern = "(\\d)[" + dec + "](\\d)";
value = value.replace(RegExp(pattern, "g"), "$1.$2");
value = Number(value);
if ( value < min || value > max ) { cache[cacheIdx] = false; return false; } // Boolean
cache[cacheIdx] = true; return true; // Boolean
}
dojo.validate.isNumberFormat = function(/*String*/value, /*Object?*/flags){
// summary:
// Validates any sort of number based format
//
// description:
// Use it for phone numbers, social security numbers, zip-codes, etc.
// The value can be validated against one format or one of multiple formats.
//
// Format
// # Stands for a digit, 0-9.
// ? Stands for an optional digit, 0-9 or nothing.
// All other characters must appear literally in the expression.
//
// Example
// "(###) ###-####" -> (510) 542-9742
// "(###) ###-#### x#???" -> (510) 542-9742 x153
// "###-##-####" -> 506-82-1089 i.e. social security number
// "#####-####" -> 98225-1649 i.e. zip code
//
// value: A string
// flags: {format:String}
// flags.format A string or an Array of strings for multiple formats.
var re = new RegExp("^" + dojo.regexp.numberFormat(flags) + "$", "i");
return re.test(value); // Boolean
}
dojo.validate.isValidLuhn = function(/*String*/value){
//summary: Compares value against the Luhn algorithm to verify its integrity
var sum, parity, curDigit;
if(typeof value!='string'){
value = String(value);
}
value = value.replace(/[- ]/g,''); //ignore dashes and whitespaces
parity = value.length%2;
sum=0;
for(var i=0;i<value.length;i++){
curDigit = parseInt(value.charAt(i));
if(i%2==parity){
curDigit*=2;
}
if(curDigit>9){
curDigit-=9;
}
sum+=curDigit;
}
return !(sum%10); //Boolean
}
/**
Procedural API Description
The main aim is to make input validation expressible in a simple format.
You define profiles which declare the required and optional fields and any constraints they might have.
The results are provided as an object that makes it easy to handle missing and invalid input.
Usage
var results = dojo.validate.check(form, profile);
Profile Object
var profile = {
// filters change the field value and are applied before validation.
trim: ["tx1", "tx2"],
uppercase: ["tx9"],
lowercase: ["tx5", "tx6", "tx7"],
ucfirst: ["tx10"],
digit: ["tx11"],
// required input fields that are blank will be reported missing.
// required radio button groups and drop-down lists with no selection will be reported missing.
// checkbox groups and selectboxes can be required to have more than one value selected.
// List required fields by name and use this notation to require more than one value: {checkboxgroup: 2}, {selectboxname: 3}.
required: ["tx7", "tx8", "pw1", "ta1", "rb1", "rb2", "cb3", "s1", {"doubledip":2}, {"tripledip":3}],
// dependant/conditional fields are required if the target field is present and not blank.
// At present only textbox, password, and textarea fields are supported.
dependencies: {
cc_exp: "cc_no",
cc_type: "cc_no",
},
// Fields can be validated using any boolean valued function.
// Use arrays to specify parameters in addition to the field value.
constraints: {
field_name1: myValidationFunction,
field_name2: dojo.validate.isInteger,
field_name3: [myValidationFunction, additional parameters],
field_name4: [dojo.validate.isValidDate, "YYYY.MM.DD"],
field_name5: [dojo.validate.isEmailAddress, false, true],
},
// Confirm is a sort of conditional validation.
// It associates each field in its property list with another field whose value should be equal.
// If the values are not equal, the field in the property list is reported as Invalid. Unless the target field is blank.
confirm: {
email_confirm: "email",
pw2: "pw1",
}
};
Results Object
isSuccessful(): Returns true if there were no invalid or missing fields, else it returns false.
hasMissing(): Returns true if the results contain any missing fields.
getMissing(): Returns a list of required fields that have values missing.
isMissing(field): Returns true if the field is required and the value is missing.
hasInvalid(): Returns true if the results contain fields with invalid data.
getInvalid(): Returns a list of fields that have invalid values.
isInvalid(field): Returns true if the field has an invalid value.
*/
dojo.provide("dojo.validate.check");
dojo.require("dojo.lang.common");
dojo.validate.check = function(/*HTMLFormElement*/form, /*Object*/profile){
// summary: validates user input of an HTML form based on input profile
//
// description:
// returns an object that contains several methods summarizing the results of the validation
//
// form: form to be validated
// profile: specifies how the form fields are to be validated
// {trim:Array, uppercase:Array, lowercase:Array, ucfirst:Array, digit:Array,
// required:Array, dependencies:Object, constraints:Object, confirm:Object}
// Essentially private properties of results object
var missing = [];
var invalid = [];
// results object summarizes the validation
var results = {
isSuccessful: function() {return ( !this.hasInvalid() && !this.hasMissing() );},
hasMissing: function() {return ( missing.length > 0 );},
getMissing: function() {return missing;},
isMissing: function(elemname) {
for(var i = 0; i < missing.length; i++){
if(elemname == missing[i]){ return true; }
}
return false;
},
hasInvalid: function() {return ( invalid.length > 0 );},
getInvalid: function() {return invalid;},
isInvalid: function(elemname){
for(var i = 0; i < invalid.length; i++){
if(elemname == invalid[i]){ return true; }
}
return false;
}
};
// Filters are applied before fields are validated.
// Trim removes white space at the front and end of the fields.
if(profile.trim instanceof Array){
for(var i = 0; i < profile.trim.length; i++){
var elem = form[profile.trim[i]];
if(dj_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
elem.value = elem.value.replace(/(^\s*|\s*$)/g, "");
}
}
// Convert to uppercase
if(profile.uppercase instanceof Array){
for(var i = 0; i < profile.uppercase.length; i++){
var elem = form[profile.uppercase[i]];
if(dj_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
elem.value = elem.value.toUpperCase();
}
}
// Convert to lowercase
if(profile.lowercase instanceof Array){
for (var i = 0; i < profile.lowercase.length; i++){
var elem = form[profile.lowercase[i]];
if(dj_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
elem.value = elem.value.toLowerCase();
}
}
// Uppercase first letter
if(profile.ucfirst instanceof Array){
for(var i = 0; i < profile.ucfirst.length; i++){
var elem = form[profile.ucfirst[i]];
if(dj_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
elem.value = elem.value.replace(/\b\w+\b/g, function(word) { return word.substring(0,1).toUpperCase() + word.substring(1).toLowerCase(); });
}
}
// Remove non digits characters from the input.
if(profile.digit instanceof Array){
for(var i = 0; i < profile.digit.length; i++){
var elem = form[profile.digit[i]];
if(dj_undef("type", elem) || elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; }
elem.value = elem.value.replace(/\D/g, "");
}
}
// See if required input fields have values missing.
if(profile.required instanceof Array){
for(var i = 0; i < profile.required.length; i++){
if(!dojo.lang.isString(profile.required[i])){ continue; }
var elem = form[profile.required[i]];
// Are textbox, textarea, or password fields blank.
if(!dj_undef("type", elem) && (elem.type == "text" || elem.type == "textarea" || elem.type == "password" || elem.type == "file") && /^\s*$/.test(elem.value)){
missing[missing.length] = elem.name;
}
// Does drop-down box have option selected.
else if(!dj_undef("type", elem) && (elem.type == "select-one" || elem.type == "select-multiple")
&& (elem.selectedIndex == -1
|| /^\s*$/.test(elem.options[elem.selectedIndex].value))){
missing[missing.length] = elem.name;
}
// Does radio button group (or check box group) have option checked.
else if(elem instanceof Array){
var checked = false;
for(var j = 0; j < elem.length; j++){
if (elem[j].checked) { checked = true; }
}
if(!checked){
missing[missing.length] = elem[0].name;
}
}
}
}
// See if checkbox groups and select boxes have x number of required values.
if(profile.required instanceof Array){
for (var i = 0; i < profile.required.length; i++){
if(!dojo.lang.isObject(profile.required[i])){ continue; }
var elem, numRequired;
for(var name in profile.required[i]){
elem = form[name];
numRequired = profile.required[i][name];
}
// case 1: elem is a check box group
if(elem instanceof Array){
var checked = 0;
for(var j = 0; j < elem.length; j++){
if(elem[j].checked){ checked++; }
}
if(checked < numRequired){
missing[missing.length] = elem[0].name;
}
}
// case 2: elem is a select box
else if(!dj_undef("type", elem) && elem.type == "select-multiple" ){
var selected = 0;
for(var j = 0; j < elem.options.length; j++){
if (elem.options[j].selected && !/^\s*$/.test(elem.options[j].value)) { selected++; }
}
if(selected < numRequired){
missing[missing.length] = elem.name;
}
}
}
}
// Dependent fields are required when the target field is present (not blank).
// Todo: Support dependent and target fields that are radio button groups, or select drop-down lists.
// Todo: Make the dependency based on a specific value of the target field.
// Todo: allow dependent fields to have several required values, like {checkboxgroup: 3}.
if(dojo.lang.isObject(profile.dependencies) || dojo.lang.isObject(profile.dependancies)){
if(profile["dependancies"]){
dojo.deprecated("dojo.validate.check", "profile 'dependancies' is deprecated, please use "
+ "'dependencies'", "0.5");
profile.dependencies=profile.dependancies;
}
// properties of dependencies object are the names of dependent fields to be checked
for(name in profile.dependencies){
var elem = form[name]; // the dependent element
if(dj_undef("type", elem)){continue;}
if(elem.type != "text" && elem.type != "textarea" && elem.type != "password"){ continue; } // limited support
if(/\S+/.test(elem.value)){ continue; } // has a value already
if(results.isMissing(elem.name)){ continue; } // already listed as missing
var target = form[profile.dependencies[name]];
if(target.type != "text" && target.type != "textarea" && target.type != "password"){ continue; } // limited support
if(/^\s*$/.test(target.value)){ continue; } // skip if blank
missing[missing.length] = elem.name; // ok the dependent field is missing
}
}
// Find invalid input fields.
if(dojo.lang.isObject(profile.constraints)){
// constraint properties are the names of fields to bevalidated
for(name in profile.constraints){
var elem = form[name];
if(!elem) {continue;}
// skip if blank - its optional unless required, in which case it
// is already listed as missing.
if(!dj_undef("tagName",elem)
&& (elem.tagName.toLowerCase().indexOf("input") >= 0
|| elem.tagName.toLowerCase().indexOf("textarea") >= 0)
&& /^\s*$/.test(elem.value)){
continue;
}
var isValid = true;
// case 1: constraint value is validation function
if(dojo.lang.isFunction(profile.constraints[name])){
isValid = profile.constraints[name](elem.value);
}else if(dojo.lang.isArray(profile.constraints[name])){
// handle nested arrays for multiple constraints
if(dojo.lang.isArray(profile.constraints[name][0])){
for (var i=0; i<profile.constraints[name].length; i++){
isValid = dojo.validate.evaluateConstraint(profile, profile.constraints[name][i], name, elem);
if(!isValid){ break; }
}
}else{
// case 2: constraint value is array, first elem is function,
// tail is parameters
isValid = dojo.validate.evaluateConstraint(profile, profile.constraints[name], name, elem);
}
}
if(!isValid){
invalid[invalid.length] = elem.name;
}
}
}
// Find unequal confirm fields and report them as Invalid.
if(dojo.lang.isObject(profile.confirm)){
for(name in profile.confirm){
var elem = form[name]; // the confirm element
var target = form[profile.confirm[name]];
if (dj_undef("type", elem) || dj_undef("type", target) || (elem.type != "text" && elem.type != "textarea" && elem.type != "password")
||(target.type != elem.type)
||(target.value == elem.value) // it's valid
||(results.isInvalid(elem.name))// already listed as invalid
||(/^\s*$/.test(target.value))) // skip if blank - only confirm if target has a value
{
continue;
}
invalid[invalid.length] = elem.name;
}
}
return results; // Object
};
//TODO: evaluateConstraint doesn't use profile or fieldName args?
dojo.validate.evaluateConstraint=function(profile, /*Array*/constraint, fieldName, elem){
// summary:
// Evaluates dojo.validate.check() constraints that are specified as array
// arguments
//
// description: The arrays are expected to be in the format of:
// constraints:{
// fieldName: [functionToCall, param1, param2, etc.],
// fieldName: [[functionToCallFirst, param1],[functionToCallSecond,param2]]
// }
//
// This function evaluates a single array function in the format of:
// [functionName, argument1, argument2, etc]
//
// The function will be parsed out and evaluated against the incoming parameters.
//
// profile: The dojo.validate.check() profile that this evaluation is against.
// constraint: The single [] array of function and arguments for the function.
// fieldName: The form dom name of the field being validated.
// elem: The form element field.
var isValidSomething = constraint[0];
var params = constraint.slice(1);
params.unshift(elem.value);
if(typeof isValidSomething != "undefined"){
return isValidSomething.apply(null, params);
}
return false; // Boolean
}
dojo.provide("dojo.date.common");
/* Supplementary Date Functions
*******************************/
dojo.date.setDayOfYear = function(/*Date*/dateObject, /*Number*/dayOfYear){
// summary: sets dateObject according to day of the year (1..366)
dateObject.setMonth(0);
dateObject.setDate(dayOfYear);
return dateObject; // Date
}
dojo.date.getDayOfYear = function(/*Date*/dateObject){
// summary: gets the day of the year as represented by dateObject
var fullYear = dateObject.getFullYear();
var lastDayOfPrevYear = new Date(fullYear-1, 11, 31);
return Math.floor((dateObject.getTime() -
lastDayOfPrevYear.getTime()) / 86400000); // Number
}
dojo.date.setWeekOfYear = function(/*Date*/dateObject, /*Number*/week, /*Number*/firstDay){
if(arguments.length == 1){ firstDay = 0; } // Sunday
dojo.unimplemented("dojo.date.setWeekOfYear");
}
dojo.date.getWeekOfYear = function(/*Date*/dateObject, /*Number*/firstDay){
if(arguments.length == 1){ firstDay = 0; } // Sunday
// work out the first day of the year corresponding to the week
var firstDayOfYear = new Date(dateObject.getFullYear(), 0, 1);
var day = firstDayOfYear.getDay();
firstDayOfYear.setDate(firstDayOfYear.getDate() -
day + firstDay - (day > firstDay ? 7 : 0));
return Math.floor((dateObject.getTime() -
firstDayOfYear.getTime()) / 604800000);
}
dojo.date.setIsoWeekOfYear = function(/*Date*/dateObject, /*Number*/week, /*Number*/firstDay){
// summary: unimplemented
if (arguments.length == 1) { firstDay = 1; } // Monday
dojo.unimplemented("dojo.date.setIsoWeekOfYear");
}
dojo.date.getIsoWeekOfYear = function(/*Date*/dateObject, /*Number*/firstDay) {
// summary: unimplemented
if (arguments.length == 1) { firstDay = 1; } // Monday
dojo.unimplemented("dojo.date.getIsoWeekOfYear");
}
/* Informational Functions
**************************/
//DEPRECATED: These timezone arrays will be deprecated in 0.5
dojo.date.shortTimezones = ["IDLW", "BET", "HST", "MART", "AKST", "PST", "MST",
"CST", "EST", "AST", "NFT", "BST", "FST", "AT", "GMT", "CET", "EET", "MSK",
"IRT", "GST", "AFT", "AGTT", "IST", "NPT", "ALMT", "MMT", "JT", "AWST",
"JST", "ACST", "AEST", "LHST", "VUT", "NFT", "NZT", "CHAST", "PHOT",
"LINT"];
dojo.date.timezoneOffsets = [-720, -660, -600, -570, -540, -480, -420, -360,
-300, -240, -210, -180, -120, -60, 0, 60, 120, 180, 210, 240, 270, 300,
330, 345, 360, 390, 420, 480, 540, 570, 600, 630, 660, 690, 720, 765, 780,
840];
/*
dojo.date.timezones = ["International Date Line West", "Bering Standard Time",
"Hawaiian Standard Time", "Marquesas Time", "Alaska Standard Time",
"Pacific Standard Time (USA)", "Mountain Standard Time",
"Central Standard Time (USA)", "Eastern Standard Time (USA)",
"Atlantic Standard Time", "Newfoundland Time", "Brazil Standard Time",
"Fernando de Noronha Standard Time (Brazil)", "Azores Time",
"Greenwich Mean Time", "Central Europe Time", "Eastern Europe Time",
"Moscow Time", "Iran Standard Time", "Gulf Standard Time",
"Afghanistan Time", "Aqtobe Time", "Indian Standard Time", "Nepal Time",
"Almaty Time", "Myanmar Time", "Java Time",
"Australian Western Standard Time", "Japan Standard Time",
"Australian Central Standard Time", "Lord Hove Standard Time (Australia)",
"Vanuata Time", "Norfolk Time (Australia)", "New Zealand Standard Time",
"Chatham Standard Time (New Zealand)", "Phoenix Islands Time (Kribati)",
"Line Islands Time (Kribati)"];
*/
dojo.date.getDaysInMonth = function(/*Date*/dateObject){
// summary: returns the number of days in the month used by dateObject
var month = dateObject.getMonth();
var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (month == 1 && dojo.date.isLeapYear(dateObject)) { return 29; } // Number
else { return days[month]; } // Number
}
dojo.date.isLeapYear = function(/*Date*/dateObject){
// summary:
// Determines if the year of the dateObject is a leap year
//
// description:
// Leap years are years with an additional day YYYY-02-29, where the year
// number is a multiple of four with the following exception: If a year
// is a multiple of 100, then it is only a leap year if it is also a
// multiple of 400. For example, 1900 was not a leap year, but 2000 is one.
var year = dateObject.getFullYear();
return (year%400 == 0) ? true : (year%100 == 0) ? false : (year%4 == 0) ? true : false; // Boolean
}
// FIXME: This is not localized
dojo.date.getTimezoneName = function(/*Date*/dateObject){
// summary:
// Get the user's time zone as provided by the browser
//
// dateObject: needed because the timezone may vary with time (daylight savings)
//
// description:
// Try to get time zone info from toString or toLocaleString
// method of the Date object -- UTC offset is not a time zone.
// See http://www.twinsun.com/tz/tz-link.htm
// Note: results may be inconsistent across browsers.
var str = dateObject.toString(); // Start looking in toString
var tz = ''; // The result -- return empty string if nothing found
var match;
// First look for something in parentheses -- fast lookup, no regex
var pos = str.indexOf('(');
if (pos > -1) {
pos++;
tz = str.substring(pos, str.indexOf(')'));
}
// If at first you don't succeed ...
else{
// If IE knows about the TZ, it appears before the year
// Capital letters or slash before a 4-digit year
// at the end of string
var pat = /([A-Z\/]+) \d{4}$/;
if((match = str.match(pat))) {
tz = match[1];
}
// Some browsers (e.g. Safari) glue the TZ on the end
// of toLocaleString instead of putting it in toString
else{
str = dateObject.toLocaleString();
// Capital letters or slash -- end of string,
// after space
pat = / ([A-Z\/]+)$/;
if((match = str.match(pat))) {
tz = match[1];
}
}
}
// Make sure it doesn't somehow end up return AM or PM
return tz == 'AM' || tz == 'PM' ? '' : tz; //String
}
//FIXME: not localized
dojo.date.getOrdinal = function(dateObject){
// summary: returns the appropriate suffix (English only) for the day of the month, e.g. 'st' for 1, 'nd' for 2, etc.)
var date = dateObject.getDate();
if(date%100 != 11 && date%10 == 1){ return "st"; } // String
else if(date%100 != 12 && date%10 == 2){ return "nd"; } // String
else if(date%100 != 13 && date%10 == 3){ return "rd"; } // String
else{ return "th"; } // String
}
/* compare and add
******************/
dojo.date.compareTypes={
// summary
// bitmask for comparison operations.
DATE:1, TIME:2
};
dojo.date.compare=function(/* Date */ dateA, /* Date */ dateB, /* dojo.date.compareTypes */ options){
// summary
// Compare two date objects by date, time, or both. Returns 0 if equal, positive if a > b, else negative.
var dA=dateA;
var dB=dateB||new Date();
var now=new Date();
//FIXME: shorten this code
with(dojo.date.compareTypes){
var opt=options||(DATE|TIME);
var d1=new Date(
(opt&DATE)?dA.getFullYear():now.getFullYear(),
(opt&DATE)?dA.getMonth():now.getMonth(),
(opt&DATE)?dA.getDate():now.getDate(),
(opt&TIME)?dA.getHours():0,
(opt&TIME)?dA.getMinutes():0,
(opt&TIME)?dA.getSeconds():0
);
var d2=new Date(
(opt&DATE)?dB.getFullYear():now.getFullYear(),
(opt&DATE)?dB.getMonth():now.getMonth(),
(opt&DATE)?dB.getDate():now.getDate(),
(opt&TIME)?dB.getHours():0,
(opt&TIME)?dB.getMinutes():0,
(opt&TIME)?dB.getSeconds():0
);
}
if(d1.valueOf()>d2.valueOf()){
return 1; // int
}
if(d1.valueOf()<d2.valueOf()){
return -1; // int
}
return 0; // int
}
dojo.date.dateParts={
// summary
// constants for use in dojo.date.add
YEAR:0, MONTH:1, DAY:2, HOUR:3, MINUTE:4, SECOND:5, MILLISECOND:6, QUARTER:7, WEEK:8, WEEKDAY:9
};
dojo.date.add = function(/* Date */ dt, /* dojo.date.dateParts */ interv, /* int */ incr){
// summary:
// Add to a Date in intervals of different size, from milliseconds to years
//
// dt:
// A Javascript Date object to start with
//
// interv:
// A constant representing the interval, e.g. YEAR, MONTH, DAY. See dojo.date.dateParts.
//
// incr:
// How much to add to the date
if(typeof dt == 'number'){dt = new Date(dt);} // Allow timestamps
//FIXME: what's the reason behind this? incr = incr || 1;
function fixOvershoot(){
if (sum.getDate() < dt.getDate()){
sum.setDate(0);
}
}
var sum = new Date(dt);
with(dojo.date.dateParts){
switch(interv){
case YEAR:
sum.setFullYear(dt.getFullYear()+incr);
// Keep increment/decrement from 2/29 out of March
fixOvershoot();
break;
case QUARTER:
// Naive quarter is just three months
incr*=3;
// fallthrough...
case MONTH:
sum.setMonth(dt.getMonth()+incr);
// Reset to last day of month if you overshoot
fixOvershoot();
break;
case WEEK:
incr*=7;
// fallthrough...
case DAY:
sum.setDate(dt.getDate() + incr);
break;
case WEEKDAY:
//FIXME: assumes Saturday/Sunday weekend, but even this is not fixed. There are CLDR entries to localize this.
var dat = dt.getDate();
var weeks = 0;
var days = 0;
var strt = 0;
var trgt = 0;
var adj = 0;
// Divide the increment time span into weekspans plus leftover days
// e.g., 8 days is one 5-day weekspan / and two leftover days
// Can't have zero leftover days, so numbers divisible by 5 get
// a days value of 5, and the remaining days make up the number of weeks
var mod = incr % 5;
if (mod == 0) {
days = (incr > 0) ? 5 : -5;
weeks = (incr > 0) ? ((incr-5)/5) : ((incr+5)/5);
}
else {
days = mod;
weeks = parseInt(incr/5);
}
// Get weekday value for orig date param
strt = dt.getDay();
// Orig date is Sat / positive incrementer
// Jump over Sun
if (strt == 6 && incr > 0) {
adj = 1;
}
// Orig date is Sun / negative incrementer
// Jump back over Sat
else if (strt == 0 && incr < 0) {
adj = -1;
}
// Get weekday val for the new date
trgt = (strt + days);
// New date is on Sat or Sun
if (trgt == 0 || trgt == 6) {
adj = (incr > 0) ? 2 : -2;
}
// Increment by number of weeks plus leftover days plus
// weekend adjustments
sum.setDate(dat + (7*weeks) + days + adj);
break;
case HOUR:
sum.setHours(sum.getHours()+incr);
break;
case MINUTE:
sum.setMinutes(sum.getMinutes()+incr);
break;
case SECOND:
sum.setSeconds(sum.getSeconds()+incr);
break;
case MILLISECOND:
sum.setMilliseconds(sum.getMilliseconds()+incr);
break;
default:
// Do nothing
break;
}
}
return sum; // Date
};
dojo.date.diff = function(/* Date */ dtA, /* Date */ dtB, /* dojo.date.dateParts */ interv){
// summary:
// Get the difference in a specific unit of time (e.g., number of months, weeks,
// days, etc.) between two dates.
//
// dtA:
// A Javascript Date object
//
// dtB:
// A Javascript Date object
//
// interv:
// A constant representing the interval, e.g. YEAR, MONTH, DAY. See dojo.date.dateParts.
// Accept timestamp input
if(typeof dtA == 'number'){dtA = new Date(dtA);}
if(typeof dtB == 'number'){dtB = new Date(dtB);}
var yeaDiff = dtB.getFullYear() - dtA.getFullYear();
var monDiff = (dtB.getMonth() - dtA.getMonth()) + (yeaDiff * 12);
var msDiff = dtB.getTime() - dtA.getTime(); // Millisecs
var secDiff = msDiff/1000;
var minDiff = secDiff/60;
var houDiff = minDiff/60;
var dayDiff = houDiff/24;
var weeDiff = dayDiff/7;
var delta = 0; // Integer return value
with(dojo.date.dateParts){
switch(interv){
case YEAR:
delta = yeaDiff;
break;
case QUARTER:
var mA = dtA.getMonth();
var mB = dtB.getMonth();
// Figure out which quarter the months are in
var qA = Math.floor(mA/3) + 1;
var qB = Math.floor(mB/3) + 1;
// Add quarters for any year difference between the dates
qB += (yeaDiff * 4);
delta = qB - qA;
break;
case MONTH:
delta = monDiff;
break;
case WEEK:
// Truncate instead of rounding
// Don't use Math.floor -- value may be negative
delta = parseInt(weeDiff);
break;
case DAY:
delta = dayDiff;
break;
case WEEKDAY:
var days = Math.round(dayDiff);
var weeks = parseInt(days/7);
var mod = days % 7;
// Even number of weeks
if (mod == 0) {
days = weeks*5;
}
// Weeks plus spare change (< 7 days)
else {
var adj = 0;
var aDay = dtA.getDay();
var bDay = dtB.getDay();
weeks = parseInt(days/7);
mod = days % 7;
// Mark the date advanced by the number of
// round weeks (may be zero)
var dtMark = new Date(dtA);
dtMark.setDate(dtMark.getDate()+(weeks*7));
var dayMark = dtMark.getDay();
// Spare change days -- 6 or less
// ----------
// Positive diff
if (dayDiff > 0) {
switch (true) {
// Range starts on Sat
case aDay == 6:
adj = -1;
break;
// Range starts on Sun
case aDay == 0:
adj = 0;
break;
// Range ends on Sat
case bDay == 6:
adj = -1;
break;
// Range ends on Sun
case bDay == 0:
adj = -2;
break;
// Range contains weekend
case (dayMark + mod) > 5:
adj = -2;
break;
default:
// Do nothing
break;
}
}
// Negative diff
else if (dayDiff < 0) {
switch (true) {
// Range starts on Sat
case aDay == 6:
adj = 0;
break;
// Range starts on Sun
case aDay == 0:
adj = 1;
break;
// Range ends on Sat
case bDay == 6:
adj = 2;
break;
// Range ends on Sun
case bDay == 0:
adj = 1;
break;
// Range contains weekend
case (dayMark + mod) < 0:
adj = 2;
break;
default:
// Do nothing
break;
}
}
days += adj;
days -= (weeks*2);
}
delta = days;
break;
case HOUR:
delta = houDiff;
break;
case MINUTE:
delta = minDiff;
break;
case SECOND:
delta = secDiff;
break;
case MILLISECOND:
delta = msDiff;
break;
default:
// Do nothing
break;
}
}
// Round for fractional values and DST leaps
return Math.round(delta); // Number (integer)
};
dojo.provide("dojo.date.supplemental");
dojo.date.getFirstDayOfWeek = function(/*String?*/locale){
// summary: Returns a zero-based index for first day of the week
// description:
// Returns a zero-based index for first day of the week, as used by the local (Gregorian) calendar.
// e.g. Sunday (returns 0), or Monday (returns 1)
// from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/weekData/firstDay
var firstDay = {/*default is 1=Monday*/
mv:5,
ae:6,af:6,bh:6,dj:6,dz:6,eg:6,er:6,et:6,iq:6,ir:6,jo:6,ke:6,kw:6,lb:6,ly:6,ma:6,om:6,qa:6,sa:6,
sd:6,so:6,tn:6,ye:6,
as:0,au:0,az:0,bw:0,ca:0,cn:0,fo:0,ge:0,gl:0,gu:0,hk:0,ie:0,il:0,is:0,jm:0,jp:0,kg:0,kr:0,la:0,
mh:0,mo:0,mp:0,mt:0,nz:0,ph:0,pk:0,sg:0,th:0,tt:0,tw:0,um:0,us:0,uz:0,vi:0,za:0,zw:0,
et:0,mw:0,ng:0,tj:0,
gb:0,
sy:4
};
locale = dojo.hostenv.normalizeLocale(locale);
var country = locale.split("-")[1];
var dow = firstDay[country];
return (typeof dow == 'undefined') ? 1 : dow; /*Number*/
};
dojo.date.getWeekend = function(/*String?*/locale){
// summary: Returns a hash containing the start and end days of the weekend
// description:
// Returns a hash containing the start and end days of the weekend according to local custom using locale,
// or by default in the user's locale.
// e.g. {start:6, end:0}
// from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/weekData/weekend{Start,End}
var weekendStart = {/*default is 6=Saturday*/
eg:5,il:5,sy:5,
'in':0,
ae:4,bh:4,dz:4,iq:4,jo:4,kw:4,lb:4,ly:4,ma:4,om:4,qa:4,sa:4,sd:4,tn:4,ye:4
};
var weekendEnd = {/*default is 0=Sunday*/
ae:5,bh:5,dz:5,iq:5,jo:5,kw:5,lb:5,ly:5,ma:5,om:5,qa:5,sa:5,sd:5,tn:5,ye:5,af:5,ir:5,
eg:6,il:6,sy:6
};
locale = dojo.hostenv.normalizeLocale(locale);
var country = locale.split("-")[1];
var start = weekendStart[country];
var end = weekendEnd[country];
if(typeof start == 'undefined'){start=6;}
if(typeof end == 'undefined'){end=0;}
return {start:start, end:end}; /*Object {start,end}*/
};
dojo.date.isWeekend = function(/*Date?*/dateObj, /*String?*/locale){
// summary:
// Determines if the date falls on a weekend, according to local custom.
var weekend = dojo.date.getWeekend(locale);
var day = (dateObj || new Date()).getDay();
if(weekend.end<weekend.start){
weekend.end+=7;
if(day<weekend.start){ day+=7; }
}
return day >= weekend.start && day <= weekend.end; // Boolean
};
dojo.provide("dojo.date.format");
dojo.require("dojo.lang.array");
dojo.require("dojo.lang.common");
dojo.require("dojo.lang.func");
dojo.require("dojo.string.common");
dojo.require("dojo.i18n.common");
// Load the bundles containing localization information for
// names and formats
dojo.requireLocalization("dojo.i18n.calendar", "gregorian");
dojo.requireLocalization("dojo.i18n.calendar", "gregorianExtras");
//NOTE: Everything in this module assumes Gregorian calendars.
// Other calendars will be implemented in separate modules.
(function(){
dojo.date.format = function(/*Date*/dateObject, /*Object?*/options){
//
// summary:
// Format a Date object as a String, using locale-specific settings.
//
// description:
// Create a string from a Date object using a known localized pattern.
// By default, this method formats both date and time from dateObject.
// Formatting patterns are chosen appropriate to the locale. Different
// formatting lengths may be chosen, with "full" used by default.
// Custom patterns may be used or registered with translations using
// the addCustomBundle method.
// Formatting patterns are implemented using the syntax described at
// http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns
//
// dateObject:
// the date and/or time to be formatted. If a time only is formatted,
// the values in the year, month, and day fields are irrelevant. The
// opposite is true when formatting only dates.
//
// options: object {selector: string, formatLength: string, datePattern: string, timePattern: string, locale: string}
// selector- choice of timeOnly,dateOnly (default: date and time)
// formatLength- choice of long, short, medium or full (plus any custom additions). Defaults to 'full'
// datePattern,timePattern- override pattern with this string
// am,pm- override strings for am/pm in times
// locale- override the locale used to determine formatting rules
//
if(typeof options == "string"){
dojo.deprecated("dojo.date.format", "To format dates with POSIX-style strings, please use dojo.date.strftime instead", "0.5");
return dojo.date.strftime(dateObject, options);
}
// Format a pattern without literals
function formatPattern(dateObject, pattern){
return pattern.replace(/([a-z])\1*/ig, function(match){
var s;
var c = match.charAt(0);
var l = match.length;
var pad;
var widthList = ["abbr", "wide", "narrow"];
switch(c){
case 'G':
if(l>3){dojo.unimplemented("Era format not implemented");}
s = bundle.eras[dateObject.getFullYear() < 0 ? 1 : 0];
break;
case 'y':
s = dateObject.getFullYear();
switch(l){
case 1:
break;
case 2:
s = String(s); s = s.substr(s.length - 2);
break;
default:
pad = true;
}
break;
case 'Q':
case 'q':
s = Math.ceil((dateObject.getMonth()+1)/3);
switch(l){
case 1: case 2:
pad = true;
break;
case 3:
case 4:
dojo.unimplemented("Quarter format not implemented");
}
break;
case 'M':
case 'L':
var m = dateObject.getMonth();
var width;
switch(l){
case 1: case 2:
s = m+1; pad = true;
break;
case 3: case 4: case 5:
width = widthList[l-3];
break;
}
if(width){
var type = (c == "L") ? "standalone" : "format";
var prop = ["months",type,width].join("-");
s = bundle[prop][m];
}
break;
case 'w':
var firstDay = 0;
s = dojo.date.getWeekOfYear(dateObject, firstDay); pad = true;
break;
case 'd':
s = dateObject.getDate(); pad = true;
break;
case 'D':
s = dojo.date.getDayOfYear(dateObject); pad = true;
break;
case 'E':
case 'e':
case 'c': // REVIEW: don't see this in the spec?
var d = dateObject.getDay();
var width;
switch(l){
case 1: case 2:
if(c == 'e'){
var first = dojo.date.getFirstDayOfWeek(options.locale);
d = (d-first+7)%7;
}
if(c != 'c'){
s = d+1; pad = true;
break;
}
// else fallthrough...
case 3: case 4: case 5:
width = widthList[l-3];
break;
}
if(width){
var type = (c == "c") ? "standalone" : "format";
var prop = ["days",type,width].join("-");
s = bundle[prop][d];
}
break;
case 'a':
var timePeriod = (dateObject.getHours() < 12) ? 'am' : 'pm';
s = bundle[timePeriod];
break;
case 'h':
case 'H':
case 'K':
case 'k':
var h = dateObject.getHours();
// strange choices in the date format make it impossible to write this succinctly
switch (c) {
case 'h': // 1-12
s = (h % 12) || 12;
break;
case 'H': // 0-23
s = h;
break;
case 'K': // 0-11
s = (h % 12);
break;
case 'k': // 1-24
s = h || 24;
break;
}
pad = true;
break;
case 'm':
s = dateObject.getMinutes(); pad = true;
break;
case 's':
s = dateObject.getSeconds(); pad = true;
break;
case 'S':
s = Math.round(dateObject.getMilliseconds() * Math.pow(10, l-3));
break;
case 'v': // FIXME: don't know what this is. seems to be same as z?
case 'z':
// We only have one timezone to offer; the one from the browser
s = dojo.date.getTimezoneName(dateObject);
if(s){break;}
l=4;
// fallthrough... use GMT if tz not available
case 'Z':
var offset = dateObject.getTimezoneOffset();
var tz = [
(offset<=0 ? "+" : "-"),
dojo.string.pad(Math.floor(Math.abs(offset)/60), 2),
dojo.string.pad(Math.abs(offset)% 60, 2)
];
if(l==4){
tz.splice(0, 0, "GMT");
tz.splice(3, 0, ":");
}
s = tz.join("");
break;
case 'Y':
case 'u':
case 'W':
case 'F':
case 'g':
case 'A':
// dojo.debug(match+" modifier not yet implemented");
s = "?";
break;
default:
dojo.raise("dojo.date.format: invalid pattern char: "+pattern);
}
if(pad){ s = dojo.string.pad(s, l); }
return s;
});
}
options = options || {};
var locale = dojo.hostenv.normalizeLocale(options.locale);
var formatLength = options.formatLength || 'short';
var bundle = dojo.date._getGregorianBundle(locale);
var str = [];
var sauce = dojo.lang.curry(this, formatPattern, dateObject);
if(options.selector == "yearOnly"){
// Special case as this is not yet driven by CLDR data
var year = dateObject.getFullYear();
if(locale.match(/^zh|^ja/)){
year += "\u5E74";
}
return year;
}
if(options.selector != "timeOnly"){
var datePattern = options.datePattern || bundle["dateFormat-"+formatLength];
if(datePattern){str.push(_processPattern(datePattern, sauce));}
}
if(options.selector != "dateOnly"){
var timePattern = options.timePattern || bundle["timeFormat-"+formatLength];
if(timePattern){str.push(_processPattern(timePattern, sauce));}
}
var result = str.join(" "); //TODO: use locale-specific pattern to assemble date + time
return result; /*String*/
};
dojo.date.parse = function(/*String*/value, /*Object?*/options){
//
// summary:
// Convert a properly formatted string to a primitive Date object,
// using locale-specific settings.
//
// description:
// Create a Date object from a string using a known localized pattern.
// By default, this method parses looking for both date and time in the string.
// Formatting patterns are chosen appropriate to the locale. Different
// formatting lengths may be chosen, with "full" used by default.
// Custom patterns may be used or registered with translations using
// the addCustomBundle method.
// Formatting patterns are implemented using the syntax described at
// http://www.unicode.org/reports/tr35/#Date_Format_Patterns
//
// value:
// A string representation of a date
//
// options: object {selector: string, formatLength: string, datePattern: string, timePattern: string, locale: string, strict: boolean}
// selector- choice of timeOnly, dateOnly, dateTime (default: dateOnly)
// formatLength- choice of long, short, medium or full (plus any custom additions). Defaults to 'full'
// datePattern,timePattern- override pattern with this string
// am,pm- override strings for am/pm in times
// locale- override the locale used to determine formatting rules
// strict- strict parsing, off by default
//
options = options || {};
var locale = dojo.hostenv.normalizeLocale(options.locale);
var info = dojo.date._getGregorianBundle(locale);
var formatLength = options.formatLength || 'full';
if(!options.selector){ options.selector = 'dateOnly'; }
var datePattern = options.datePattern || info["dateFormat-" + formatLength];
var timePattern = options.timePattern || info["timeFormat-" + formatLength];
var pattern;
if(options.selector == 'dateOnly'){
pattern = datePattern;
}
else if(options.selector == 'timeOnly'){
pattern = timePattern;
}else if(options.selector == 'dateTime'){
pattern = datePattern + ' ' + timePattern; //TODO: use locale-specific pattern to assemble date + time
}else{
var msg = "dojo.date.parse: Unknown selector param passed: '" + options.selector + "'.";
msg += " Defaulting to date pattern.";
dojo.debug(msg);
pattern = datePattern;
}
var groups = [];
var dateREString = _processPattern(pattern, dojo.lang.curry(this, _buildDateTimeRE, groups, info, options));
var dateRE = new RegExp("^" + dateREString + "$");
var match = dateRE.exec(value);
if(!match){
return null;
}
var widthList = ['abbr', 'wide', 'narrow'];
//1972 is a leap year. We want to avoid Feb 29 rolling over into Mar 1,
//in the cases where the year is parsed after the month and day.
var result = new Date(1972, 0);
var expected = {};
for(var i=1; i<match.length; i++){
var grp=groups[i-1];
var l=grp.length;
var v=match[i];
switch(grp.charAt(0)){
case 'y':
if(l != 2){
//interpret year literally, so '5' would be 5 A.D.
result.setFullYear(v);
expected.year = v;
}else{
if(v<100){
v = Number(v);
//choose century to apply, according to a sliding window
//of 80 years before and 20 years after present year
var year = '' + new Date().getFullYear();
var century = year.substring(0, 2) * 100;
var yearPart = Number(year.substring(2, 4));
var cutoff = Math.min(yearPart + 20, 99);
var num = (v < cutoff) ? century + v : century - 100 + v;
result.setFullYear(num);
expected.year = num;
}else{
//we expected 2 digits and got more...
if(options.strict){
return null;
}
//interpret literally, so '150' would be 150 A.D.
//also tolerate '1950', if 'yyyy' input passed to 'yy' format
result.setFullYear(v);
expected.year = v;
}
}
break;
case 'M':
if (l>2) {
if(!options.strict){
//Tolerate abbreviating period in month part
v = v.replace(/\./g,'');
//Case-insensitive
v = v.toLowerCase();
}
var months = info['months-format-' + widthList[l-3]].concat();
for (var j=0; j<months.length; j++){
if(!options.strict){
//Case-insensitive
months[j] = months[j].toLowerCase();
}
if(v == months[j]){
result.setMonth(j);
expected.month = j;
break;
}
}
if(j==months.length){
dojo.debug("dojo.date.parse: Could not parse month name: '" + v + "'.");
return null;
}
}else{
result.setMonth(v-1);
expected.month = v-1;
}
break;
case 'E':
case 'e':
if(!options.strict){
//Case-insensitive
v = v.toLowerCase();
}
var days = info['days-format-' + widthList[l-3]].concat();
for (var j=0; j<days.length; j++){
if(!options.strict){
//Case-insensitive
days[j] = days[j].toLowerCase();
}
if(v == days[j]){
//TODO: not sure what to actually do with this input,
//in terms of setting something on the Date obj...?
//without more context, can't affect the actual date
break;
}
}
if(j==days.length){
dojo.debug("dojo.date.parse: Could not parse weekday name: '" + v + "'.");
return null;
}
break;
case 'd':
result.setDate(v);
expected.date = v;
break;
case 'a': //am/pm
var am = options.am || info.am;
var pm = options.pm || info.pm;
if(!options.strict){
v = v.replace(/\./g,'').toLowerCase();
am = am.replace(/\./g,'').toLowerCase();
pm = pm.replace(/\./g,'').toLowerCase();
}
if(options.strict && v != am && v != pm){
dojo.debug("dojo.date.parse: Could not parse am/pm part.");
return null;
}
var hours = result.getHours();
if(v == pm && hours < 12){
result.setHours(hours + 12); //e.g., 3pm -> 15
} else if(v == am && hours == 12){
result.setHours(0); //12am -> 0
}
break;
case 'K': //hour (1-24)
if(v==24){v=0;}
// fallthrough...
case 'h': //hour (1-12)
case 'H': //hour (0-23)
case 'k': //hour (0-11)
//TODO: strict bounds checking, padding
if(v>23){
dojo.debug("dojo.date.parse: Illegal hours value");
return null;
}
//in the 12-hour case, adjusting for am/pm requires the 'a' part
//which for now we will assume always comes after the 'h' part
result.setHours(v);
break;
case 'm': //minutes
result.setMinutes(v);
break;
case 's': //seconds
result.setSeconds(v);
break;
case 'S': //milliseconds
result.setMilliseconds(v);
break;
default:
dojo.unimplemented("dojo.date.parse: unsupported pattern char=" + grp.charAt(0));
}
}
//validate parse date fields versus input date fields
if(expected.year && result.getFullYear() != expected.year){
dojo.debug("Parsed year: '" + result.getFullYear() + "' did not match input year: '" + expected.year + "'.");
return null;
}
if(expected.month && result.getMonth() != expected.month){
dojo.debug("Parsed month: '" + result.getMonth() + "' did not match input month: '" + expected.month + "'.");
return null;
}
if(expected.date && result.getDate() != expected.date){
dojo.debug("Parsed day of month: '" + result.getDate() + "' did not match input day of month: '" + expected.date + "'.");
return null;
}
//TODO: implement a getWeekday() method in order to test
//validity of input strings containing 'EEE' or 'EEEE'...
return result; /*Date*/
};
function _processPattern(pattern, applyPattern, applyLiteral, applyAll){
// Process a pattern with literals in it
// Break up on single quotes, treat every other one as a literal, except '' which becomes '
var identity = function(x){return x;};
applyPattern = applyPattern || identity;
applyLiteral = applyLiteral || identity;
applyAll = applyAll || identity;
//split on single quotes (which escape literals in date format strings)
//but preserve escaped single quotes (e.g., o''clock)
var chunks = pattern.match(/(''|[^'])+/g);
var literal = false;
for(var i=0; i<chunks.length; i++){
if(!chunks[i]){
chunks[i]='';
} else {
chunks[i]=(literal ? applyLiteral : applyPattern)(chunks[i]);
literal = !literal;
}
}
return applyAll(chunks.join(''));
}
function _buildDateTimeRE(groups, info, options, pattern){
return pattern.replace(/([a-z])\1*/ig, function(match){
// Build a simple regexp without parenthesis, which would ruin the match list
var s = '';
var c = match.charAt(0);
var l = match.length;
switch(c){
case 'y':
s = '\\d' + ((l==2) ? '{2,4}' : '+');
break;
case 'M':
s = (l>2) ? '\\S+' : '\\d{1,2}';
break;
case 'd':
s = '\\d{1,2}';
break;
case 'E':
s = '\\S+';
break;
case 'h':
case 'H':
case 'K':
case 'k':
s = '\\d{1,2}';
break;
case 'm':
case 's':
s = '[0-5]\\d';
break;
case 'S':
s = '\\d{1,3}';
break;
case 'a':
var am = options.am || info.am || 'AM';
var pm = options.pm || info.pm || 'PM';
if(options.strict){
s = am + '|' + pm;
}else{
for (var i=0; i < am.length; i++){
s += '[' + am.charAt(i).toLowerCase() + '|' + am.charAt(i).toUpperCase() + ']';
}
s += '|';
for (var i=0; i < pm.length; i++){
s += '[' + pm.charAt(i).toLowerCase() + '|' + pm.charAt(i).toUpperCase() + ']';
}
}
break;
default:
dojo.unimplemented("parse of date format, pattern=" + pattern);
}
if(groups){ groups.push(match); }
//FIXME: replace whitespace within final regexp with more flexible whitespace match instead?
//tolerate whitespace
return '\\s*(' + s + ')\\s*';
});
}
})();
//TODO: try to common strftime and format code somehow?
dojo.date.strftime = function(/*Date*/dateObject, /*String*/format, /*String?*/locale){
//
// summary:
// Formats the date object using the specifications of the POSIX strftime function
//
// description:
// see <http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html>
// zero pad
var padChar = null;
function _(s, n){
return dojo.string.pad(s, n || 2, padChar || "0");
}
var info = dojo.date._getGregorianBundle(locale);
function $(property){
switch (property){
case "a": // abbreviated weekday name according to the current locale
return dojo.date.getDayShortName(dateObject, locale);
case "A": // full weekday name according to the current locale
return dojo.date.getDayName(dateObject, locale);
case "b":
case "h": // abbreviated month name according to the current locale
return dojo.date.getMonthShortName(dateObject, locale);
case "B": // full month name according to the current locale
return dojo.date.getMonthName(dateObject, locale);
case "c": // preferred date and time representation for the current
// locale
return dojo.date.format(dateObject, {locale: locale});
case "C": // century number (the year divided by 100 and truncated
// to an integer, range 00 to 99)
return _(Math.floor(dateObject.getFullYear()/100));
case "d": // day of the month as a decimal number (range 01 to 31)
return _(dateObject.getDate());
case "D": // same as %m/%d/%y
return $("m") + "/" + $("d") + "/" + $("y");
case "e": // day of the month as a decimal number, a single digit is
// preceded by a space (range ' 1' to '31')
if(padChar == null){ padChar = " "; }
return _(dateObject.getDate());
case "f": // month as a decimal number, a single digit is
// preceded by a space (range ' 1' to '12')
if(padChar == null){ padChar = " "; }
return _(dateObject.getMonth()+1);
case "g": // like %G, but without the century.
break;
case "G": // The 4-digit year corresponding to the ISO week number
// (see %V). This has the same format and value as %Y,
// except that if the ISO week number belongs to the
// previous or next year, that year is used instead.
dojo.unimplemented("unimplemented modifier 'G'");
break;
case "F": // same as %Y-%m-%d
return $("Y") + "-" + $("m") + "-" + $("d");
case "H": // hour as a decimal number using a 24-hour clock (range
// 00 to 23)
return _(dateObject.getHours());
case "I": // hour as a decimal number using a 12-hour clock (range
// 01 to 12)
return _(dateObject.getHours() % 12 || 12);
case "j": // day of the year as a decimal number (range 001 to 366)
return _(dojo.date.getDayOfYear(dateObject), 3);
case "k": // Hour as a decimal number using a 24-hour clock (range
// 0 to 23 (space-padded))
if (padChar == null) { padChar = " "; }
return _(dateObject.getHours());
case "l": // Hour as a decimal number using a 12-hour clock (range
// 1 to 12 (space-padded))
if (padChar == null) { padChar = " "; }
return _(dateObject.getHours() % 12 || 12);
case "m": // month as a decimal number (range 01 to 12)
return _(dateObject.getMonth() + 1);
case "M": // minute as a decimal number
return _(dateObject.getMinutes());
case "n":
return "\n";
case "p": // either `am' or `pm' according to the given time value,
// or the corresponding strings for the current locale
return info[dateObject.getHours() < 12 ? "am" : "pm"];
case "r": // time in a.m. and p.m. notation
return $("I") + ":" + $("M") + ":" + $("S") + " " + $("p");
case "R": // time in 24 hour notation
return $("H") + ":" + $("M");
case "S": // second as a decimal number
return _(dateObject.getSeconds());
case "t":
return "\t";
case "T": // current time, equal to %H:%M:%S
return $("H") + ":" + $("M") + ":" + $("S");
case "u": // weekday as a decimal number [1,7], with 1 representing
// Monday
return String(dateObject.getDay() || 7);
case "U": // week number of the current year as a decimal number,
// starting with the first Sunday as the first day of the
// first week
return _(dojo.date.getWeekOfYear(dateObject));
case "V": // week number of the year (Monday as the first day of the
// week) as a decimal number [01,53]. If the week containing
// 1 January has four or more days in the new year, then it
// is considered week 1. Otherwise, it is the last week of
// the previous year, and the next week is week 1.
return _(dojo.date.getIsoWeekOfYear(dateObject));
case "W": // week number of the current year as a decimal number,
// starting with the first Monday as the first day of the
// first week
return _(dojo.date.getWeekOfYear(dateObject, 1));
case "w": // day of the week as a decimal, Sunday being 0
return String(dateObject.getDay());
case "x": // preferred date representation for the current locale
// without the time
return dojo.date.format(dateObject, {selector:'dateOnly', locale:locale});
case "X": // preferred time representation for the current locale
// without the date
return dojo.date.format(dateObject, {selector:'timeOnly', locale:locale});
case "y": // year as a decimal number without a century (range 00 to
// 99)
return _(dateObject.getFullYear()%100);
case "Y": // year as a decimal number including the century
return String(dateObject.getFullYear());
case "z": // time zone or name or abbreviation
var timezoneOffset = dateObject.getTimezoneOffset();
return (timezoneOffset > 0 ? "-" : "+") +
_(Math.floor(Math.abs(timezoneOffset)/60)) + ":" +
_(Math.abs(timezoneOffset)%60);
case "Z": // time zone or name or abbreviation
return dojo.date.getTimezoneName(dateObject);
case "%":
return "%";
}
}
// parse the formatting string and construct the resulting string
var string = "";
var i = 0;
var index = 0;
var switchCase = null;
while ((index = format.indexOf("%", i)) != -1){
string += format.substring(i, index++);
// inspect modifier flag
switch (format.charAt(index++)) {
case "_": // Pad a numeric result string with spaces.
padChar = " "; break;
case "-": // Do not pad a numeric result string.
padChar = ""; break;
case "0": // Pad a numeric result string with zeros.
padChar = "0"; break;
case "^": // Convert characters in result string to uppercase.
switchCase = "upper"; break;
case "*": // Convert characters in result string to lowercase
switchCase = "lower"; break;
case "#": // Swap the case of the result string.
switchCase = "swap"; break;
default: // no modifier flag so decrement the index
padChar = null; index--; break;
}
// toggle case if a flag is set
var property = $(format.charAt(index++));
switch (switchCase){
case "upper":
property = property.toUpperCase();
break;
case "lower":
property = property.toLowerCase();
break;
case "swap": // Upper to lower, and versey-vicea
var compareString = property.toLowerCase();
var swapString = '';
var j = 0;
var ch = '';
while (j < property.length){
ch = property.charAt(j);
swapString += (ch == compareString.charAt(j)) ?
ch.toUpperCase() : ch.toLowerCase();
j++;
}
property = swapString;
break;
default:
break;
}
switchCase = null;
string += property;
i = index;
}
string += format.substring(i);
return string; // String
};
(function(){
var _customFormats = [];
dojo.date.addCustomFormats = function(/*String*/packageName, /*String*/bundleName){
//
// summary:
// Add a reference to a bundle containing localized custom formats to be
// used by date/time formatting and parsing routines.
//
// description:
// The user may add custom localized formats where the bundle has properties following the
// same naming convention used by dojo for the CLDR data: dateFormat-xxxx / timeFormat-xxxx
// The pattern string should match the format used by the CLDR.
// See dojo.date.format for details.
// The resources must be loaded by dojo.requireLocalization() prior to use
_customFormats.push({pkg:packageName,name:bundleName});
};
dojo.date._getGregorianBundle = function(/*String*/locale){
var gregorian = {};
dojo.lang.forEach(_customFormats, function(desc){
var bundle = dojo.i18n.getLocalization(desc.pkg, desc.name, locale);
gregorian = dojo.lang.mixin(gregorian, bundle);
}, this);
return gregorian; /*Object*/
};
})();
dojo.date.addCustomFormats("dojo.i18n.calendar","gregorian");
dojo.date.addCustomFormats("dojo.i18n.calendar","gregorianExtras");
dojo.date.getNames = function(/*String*/item, /*String*/type, /*String?*/use, /*String?*/locale){
//
// summary:
// Used to get localized strings for day or month names.
//
// item: 'months' || 'days'
// type: 'wide' || 'narrow' || 'abbr' (e.g. "Monday", "Mon", or "M" respectively, in English)
// use: 'standAlone' || 'format' (default)
// locale: override locale used to find the names
var label;
var lookup = dojo.date._getGregorianBundle(locale);
var props = [item, use, type];
if(use == 'standAlone'){
label = lookup[props.join('-')];
}
props[1] = 'format';
// return by copy so changes won't be made accidentally to the in-memory model
return (label || lookup[props.join('-')]).concat(); /*Array*/
};
// Convenience methods
dojo.date.getDayName = function(/*Date*/dateObject, /*String?*/locale){
// summary: gets the full localized day of the week corresponding to the date object
return dojo.date.getNames('days', 'wide', 'format', locale)[dateObject.getDay()]; /*String*/
};
dojo.date.getDayShortName = function(/*Date*/dateObject, /*String?*/locale){
// summary: gets the abbreviated localized day of the week corresponding to the date object
return dojo.date.getNames('days', 'abbr', 'format', locale)[dateObject.getDay()]; /*String*/
};
dojo.date.getMonthName = function(/*Date*/dateObject, /*String?*/locale){
// summary: gets the full localized month name corresponding to the date object
return dojo.date.getNames('months', 'wide', 'format', locale)[dateObject.getMonth()]; /*String*/
};
dojo.date.getMonthShortName = function(/*Date*/dateObject, /*String?*/locale){
// summary: gets the abbreviated localized month name corresponding to the date object
return dojo.date.getNames('months', 'abbr', 'format', locale)[dateObject.getMonth()]; /*String*/
};
//FIXME: not localized
dojo.date.toRelativeString = function(/*Date*/dateObject){
// summary:
// Returns an description in English of the date relative to the current date. Note: this is not localized yet. English only.
//
// description: Example returns:
// - "1 minute ago"
// - "4 minutes ago"
// - "Yesterday"
// - "2 days ago"
var now = new Date();
var diff = (now - dateObject) / 1000;
var end = " ago";
var future = false;
if(diff < 0){
future = true;
end = " from now";
diff = -diff;
}
if(diff < 60){
diff = Math.round(diff);
return diff + " second" + (diff == 1 ? "" : "s") + end;
}
if(diff < 60*60){
diff = Math.round(diff/60);
return diff + " minute" + (diff == 1 ? "" : "s") + end;
}
if(diff < 60*60*24){
diff = Math.round(diff/3600);
return diff + " hour" + (diff == 1 ? "" : "s") + end;
}
if(diff < 60*60*24*7){
diff = Math.round(diff/(3600*24));
if(diff == 1){
return future ? "Tomorrow" : "Yesterday";
}else{
return diff + " days" + end;
}
}
return dojo.date.format(dateObject); // String
};
//FIXME: SQL methods can probably be moved to a different module without i18n deps
dojo.date.toSql = function(/*Date*/dateObject, /*Boolean?*/noTime){
// summary:
// Convert a Date to a SQL string
// noTime: whether to ignore the time portion of the Date. Defaults to false.
return dojo.date.strftime(dateObject, "%F" + !noTime ? " %T" : ""); // String
};
dojo.date.fromSql = function(/*String*/sqlDate){
// summary:
// Convert a SQL date string to a JavaScript Date object
var parts = sqlDate.split(/[\- :]/g);
while(parts.length < 6){
parts.push(0);
}
return new Date(parts[0], (parseInt(parts[1],10)-1), parts[2], parts[3], parts[4], parts[5]); // Date
};
dojo.provide("dojo.date.serialize");
dojo.require("dojo.string.common");
/* ISO 8601 Functions
*********************/
dojo.date.setIso8601 = function(/*Date*/dateObject, /*String*/formattedString){
// summary: sets a Date object based on an ISO 8601 formatted string (uses date and time)
var comps = (formattedString.indexOf("T") == -1) ? formattedString.split(" ") : formattedString.split("T");
dateObject = dojo.date.setIso8601Date(dateObject, comps[0]);
if(comps.length == 2){ dateObject = dojo.date.setIso8601Time(dateObject, comps[1]); }
return dateObject; /* Date or null */
};
dojo.date.fromIso8601 = function(/*String*/formattedString){
// summary: returns a Date object based on an ISO 8601 formatted string (uses date and time)
return dojo.date.setIso8601(new Date(0, 0), formattedString);
};
dojo.date.setIso8601Date = function(/*String*/dateObject, /*String*/formattedString){
// summary: sets a Date object based on an ISO 8601 formatted string (date only)
var regexp = "^([0-9]{4})((-?([0-9]{2})(-?([0-9]{2}))?)|" +
"(-?([0-9]{3}))|(-?W([0-9]{2})(-?([1-7]))?))?$";
var d = formattedString.match(new RegExp(regexp));
if(!d){
dojo.debug("invalid date string: " + formattedString);
return null; // null
}
var year = d[1];
var month = d[4];
var date = d[6];
var dayofyear = d[8];
var week = d[10];
var dayofweek = d[12] ? d[12] : 1;
dateObject.setFullYear(year);
if(dayofyear){
dateObject.setMonth(0);
dateObject.setDate(Number(dayofyear));
}
else if(week){
dateObject.setMonth(0);
dateObject.setDate(1);
var gd = dateObject.getDay();
var day = gd ? gd : 7;
var offset = Number(dayofweek) + (7 * Number(week));
if(day <= 4){ dateObject.setDate(offset + 1 - day); }
else{ dateObject.setDate(offset + 8 - day); }
} else{
if(month){
dateObject.setDate(1);
dateObject.setMonth(month - 1);
}
if(date){ dateObject.setDate(date); }
}
return dateObject; // Date
};
dojo.date.fromIso8601Date = function(/*String*/formattedString){
// summary: returns a Date object based on an ISO 8601 formatted string (date only)
return dojo.date.setIso8601Date(new Date(0, 0), formattedString);
};
dojo.date.setIso8601Time = function(/*Date*/dateObject, /*String*/formattedString){
// summary: sets a Date object based on an ISO 8601 formatted string (time only)
// first strip timezone info from the end
var timezone = "Z|(([-+])([0-9]{2})(:?([0-9]{2}))?)$";
var d = formattedString.match(new RegExp(timezone));
var offset = 0; // local time if no tz info
if(d){
if(d[0] != 'Z'){
offset = (Number(d[3]) * 60) + Number(d[5]);
offset *= ((d[2] == '-') ? 1 : -1);
}
offset -= dateObject.getTimezoneOffset();
formattedString = formattedString.substr(0, formattedString.length - d[0].length);
}
// then work out the time
var regexp = "^([0-9]{2})(:?([0-9]{2})(:?([0-9]{2})(\.([0-9]+))?)?)?$";
d = formattedString.match(new RegExp(regexp));
if(!d){
dojo.debug("invalid time string: " + formattedString);
return null; // null
}
var hours = d[1];
var mins = Number((d[3]) ? d[3] : 0);
var secs = (d[5]) ? d[5] : 0;
var ms = d[7] ? (Number("0." + d[7]) * 1000) : 0;
dateObject.setHours(hours);
dateObject.setMinutes(mins);
dateObject.setSeconds(secs);
dateObject.setMilliseconds(ms);
if(offset !== 0){
dateObject.setTime(dateObject.getTime() + offset * 60000);
}
return dateObject; // Date
};
dojo.date.fromIso8601Time = function(/*String*/formattedString){
// summary: returns a Date object based on an ISO 8601 formatted string (date only)
return dojo.date.setIso8601Time(new Date(0, 0), formattedString);
};
/* RFC-3339 Date Functions
*************************/
dojo.date.toRfc3339 = function(/*Date?*/dateObject, /*String?*/selector){
// summary:
// Format a JavaScript Date object as a string according to RFC 3339
//
// dateObject:
// A JavaScript date, or the current date and time, by default
//
// selector:
// "dateOnly" or "timeOnly" to format selected portions of the Date object.
// Date and time will be formatted by default.
//FIXME: tolerate Number, string input?
if(!dateObject){
dateObject = new Date();
}
var _ = dojo.string.pad;
var formattedDate = [];
if(selector != "timeOnly"){
var date = [_(dateObject.getFullYear(),4), _(dateObject.getMonth()+1,2), _(dateObject.getDate(),2)].join('-');
formattedDate.push(date);
}
if(selector != "dateOnly"){
var time = [_(dateObject.getHours(),2), _(dateObject.getMinutes(),2), _(dateObject.getSeconds(),2)].join(':');
var timezoneOffset = dateObject.getTimezoneOffset();
time += (timezoneOffset > 0 ? "-" : "+") +
_(Math.floor(Math.abs(timezoneOffset)/60),2) + ":" +
_(Math.abs(timezoneOffset)%60,2);
formattedDate.push(time);
}
return formattedDate.join('T'); // String
};
dojo.date.fromRfc3339 = function(/*String*/rfcDate){
// summary:
// Create a JavaScript Date object from a string formatted according to RFC 3339
//
// rfcDate:
// A string such as 2005-06-30T08:05:00-07:00
// "any" is also supported in place of a time.
// backwards compatible support for use of "any" instead of just not
// including the time
if(rfcDate.indexOf("Tany")!=-1){
rfcDate = rfcDate.replace("Tany","");
}
var dateObject = new Date();
return dojo.date.setIso8601(dateObject, rfcDate); // Date or null
};
dojo.provide("dojo.validate.datetime");
/**
Validates a time value in any International format.
The value can be validated against one format or one of multiple formats.
Format
h 12 hour, no zero padding.
hh 12 hour, has leading zero.
H 24 hour, no zero padding.
HH 24 hour, has leading zero.
m minutes, no zero padding.
mm minutes, has leading zero.
s seconds, no zero padding.
ss seconds, has leading zero.
All other characters must appear literally in the expression.
Example
"h:m:s t" -> 2:5:33 PM
"HH:mm:ss" -> 14:05:33
@param value A string.
@param flags An object.
flags.format A string or an array of strings. Default is "h:mm:ss t".
flags.amSymbol The symbol used for AM. Default is "AM".
flags.pmSymbol The symbol used for PM. Default is "PM".
@return true or false
*/
dojo.validate.isValidTime = function(value, flags) {
dojo.deprecated("dojo.validate.datetime", "use dojo.date.parse instead", "0.5");
var re = new RegExp("^" + dojo.regexp.time(flags) + "$", "i");
return re.test(value);
}
/**
Validates 12-hour time format.
Zero-padding is not allowed for hours, required for minutes and seconds.
Seconds are optional.
@param value A string.
@return true or false
*/
dojo.validate.is12HourTime = function(value) {
dojo.deprecated("dojo.validate.datetime", "use dojo.date.parse instead", "0.5");
return dojo.validate.isValidTime(value, {format: ["h:mm:ss t", "h:mm t"]});
}
/**
Validates 24-hour military time format.
Zero-padding is required for hours, minutes, and seconds.
Seconds are optional.
@param value A string.
@return true or false
*/
dojo.validate.is24HourTime = function(value) {
dojo.deprecated("dojo.validate.datetime", "use dojo.date.parse instead", "0.5");
return dojo.validate.isValidTime(value, {format: ["HH:mm:ss", "HH:mm"]} );
}
/**
Returns true if the date conforms to the format given and is a valid date. Otherwise returns false.
@param dateValue A string for the date.
@param format A string, default is "MM/DD/YYYY".
@return true or false
Accepts any type of format, including ISO8601.
All characters in the format string are treated literally except the following tokens:
YYYY - matches a 4 digit year
M - matches a non zero-padded month
MM - matches a zero-padded month
D - matches a non zero-padded date
DD - matches a zero-padded date
DDD - matches an ordinal date, 001-365, and 366 on leapyear
ww - matches week of year, 01-53
d - matches day of week, 1-7
Examples: These are all today's date.
Date Format
2005-W42-3 YYYY-Www-d
2005-292 YYYY-DDD
20051019 YYYYMMDD
10/19/2005 M/D/YYYY
19.10.2005 D.M.YYYY
*/
dojo.validate.isValidDate = function(dateValue, format) {
dojo.deprecated("dojo.validate.datetime", "use dojo.date.parse instead", "0.5");
// Default is the American format
if (typeof format == "object" && typeof format.format == "string"){ format = format.format; }
if (typeof format != "string") { format = "MM/DD/YYYY"; }
// Create a literal regular expression based on format
var reLiteral = format.replace(/([$^.*+?=!:|\/\\\(\)\[\]\{\}])/g, "\\$1");
// Convert all the tokens to RE elements
reLiteral = reLiteral.replace( "YYYY", "([0-9]{4})" );
reLiteral = reLiteral.replace( "MM", "(0[1-9]|10|11|12)" );
reLiteral = reLiteral.replace( "M", "([1-9]|10|11|12)" );
reLiteral = reLiteral.replace( "DDD", "(00[1-9]|0[1-9][0-9]|[12][0-9][0-9]|3[0-5][0-9]|36[0-6])" );
reLiteral = reLiteral.replace( "DD", "(0[1-9]|[12][0-9]|30|31)" );
reLiteral = reLiteral.replace( "D", "([1-9]|[12][0-9]|30|31)" );
reLiteral = reLiteral.replace( "ww", "(0[1-9]|[1-4][0-9]|5[0-3])" );
reLiteral = reLiteral.replace( "d", "([1-7])" );
// Anchor pattern to begining and end of string
reLiteral = "^" + reLiteral + "$";
// Dynamic RE that parses the original format given
var re = new RegExp(reLiteral);
// Test if date is in a valid format
if (!re.test(dateValue)) return false;
// Parse date to get elements and check if date is valid
// Assume valid values for date elements not given.
var year = 0, month = 1, date = 1, dayofyear = 1, week = 1, day = 1;
// Capture tokens
var tokens = format.match( /(YYYY|MM|M|DDD|DD|D|ww|d)/g );
// Capture date values
var values = re.exec(dateValue);
// Match up tokens with date values
for (var i = 0; i < tokens.length; i++) {
switch (tokens[i]) {
case "YYYY":
year = Number(values[i+1]); break;
case "M":
case "MM":
month = Number(values[i+1]); break;
case "D":
case "DD":
date = Number(values[i+1]); break;
case "DDD":
dayofyear = Number(values[i+1]); break;
case "ww":
week = Number(values[i+1]); break;
case "d":
day = Number(values[i+1]); break;
}
}
// Leap years are divisible by 4, but not by 100, unless by 400
var leapyear = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
// 31st of a month with 30 days
if (date == 31 && (month == 4 || month == 6 || month == 9 || month == 11)) return false;
// February 30th or 31st
if (date >= 30 && month == 2) return false;
// February 29th outside a leap year
if (date == 29 && month == 2 && !leapyear) return false;
if (dayofyear == 366 && !leapyear) return false;
return true;
}
dojo.provide("dojo.validate.web");
dojo.validate.isIpAddress = function(/*String*/value, /*Object?*/flags) {
// summary: Validates an IP address
//
// description:
// Supports 5 formats for IPv4: dotted decimal, dotted hex, dotted octal, decimal and hexadecimal.
// Supports 2 formats for Ipv6.
//
// value A string.
// flags An object. All flags are boolean with default = true.
// flags.allowDottedDecimal Example, 207.142.131.235. No zero padding.
// flags.allowDottedHex Example, 0x18.0x11.0x9b.0x28. Case insensitive. Zero padding allowed.
// flags.allowDottedOctal Example, 0030.0021.0233.0050. Zero padding allowed.
// flags.allowDecimal Example, 3482223595. A decimal number between 0-4294967295.
// flags.allowHex Example, 0xCF8E83EB. Hexadecimal number between 0x0-0xFFFFFFFF.
// Case insensitive. Zero padding allowed.
// flags.allowIPv6 IPv6 address written as eight groups of four hexadecimal digits.
// flags.allowHybrid IPv6 address written as six groups of four hexadecimal digits
// followed by the usual 4 dotted decimal digit notation of IPv4. x:x:x:x:x:x:d.d.d.d
var re = new RegExp("^" + dojo.regexp.ipAddress(flags) + "$", "i");
return re.test(value); // Boolean
}
dojo.validate.isUrl = function(/*String*/value, /*Object?*/flags) {
// summary: Checks if a string could be a valid URL
// value: A string
// flags: An object
// flags.scheme Can be true, false, or [true, false].
// This means: required, not allowed, or either.
// flags in regexp.host can be applied.
// flags in regexp.ipAddress can be applied.
// flags in regexp.tld can be applied.
var re = new RegExp("^" + dojo.regexp.url(flags) + "$", "i");
return re.test(value); // Boolean
}
dojo.validate.isEmailAddress = function(/*String*/value, /*Object?*/flags) {
// summary: Checks if a string could be a valid email address
//
// value: A string
// flags: An object
// flags.allowCruft Allow address like <mailto:foo@yahoo.com>. Default is false.
// flags in regexp.host can be applied.
// flags in regexp.ipAddress can be applied.
// flags in regexp.tld can be applied.
var re = new RegExp("^" + dojo.regexp.emailAddress(flags) + "$", "i");
return re.test(value); // Boolean
}
dojo.validate.isEmailAddressList = function(/*String*/value, /*Object?*/flags) {
// summary: Checks if a string could be a valid email address list.
//
// value A string.
// flags An object.
// flags.listSeparator The character used to separate email addresses. Default is ";", ",", "\n" or " ".
// flags in regexp.emailAddress can be applied.
// flags in regexp.host can be applied.
// flags in regexp.ipAddress can be applied.
// flags in regexp.tld can be applied.
var re = new RegExp("^" + dojo.regexp.emailAddressList(flags) + "$", "i");
return re.test(value); // Boolean
}
dojo.validate.getEmailAddressList = function(/*String*/value, /*Object?*/flags) {
// summary: Check if value is an email address list. If an empty list
// is returned, the value didn't pass the test or it was empty.
//
// value: A string
// flags: An object (same as dojo.validate.isEmailAddressList)
if(!flags) { flags = {}; }
if(!flags.listSeparator) { flags.listSeparator = "\\s;,"; }
if ( dojo.validate.isEmailAddressList(value, flags) ) {
return value.split(new RegExp("\\s*[" + flags.listSeparator + "]\\s*")); // Array
}
return []; // Array
}
dojo.provide('dojo.validate.creditCard');
dojo.require("dojo.lang.common");
/*
Validates Credit Cards using account number rules in conjunction with the Luhn algorigthm
*/
dojo.validate.isValidCreditCard = function(/*String|Int*/value, /*String*/ccType){
//Summary:
// checks if type matches the # scheme, and if Luhn checksum is accurate (unless its an Enroute card, the checkSum is skipped)
//Value: Boolean
if(value&&ccType&&((ccType.toLowerCase()=='er'||dojo.validate.isValidLuhn(value))&&(dojo.validate.isValidCreditCardNumber(value,ccType.toLowerCase())))){
return true; //Boolean
}
return false; //Boolean
}
dojo.validate.isValidCreditCardNumber = function(/*String|Int*/value,/*String?*/ccType) {
//Summary:
// checks if the # matches the pattern for that card or any card types if none is specified
// value == CC #, white spaces and dashes are ignored
// ccType is of the values in cardinfo -- if Omitted it it returns a | delimited string of matching card types, or false if no matches found
//Value: Boolean
if(typeof value!='string'){
value = String(value);
}
value = value.replace(/[- ]/g,''); //ignore dashes and whitespaces
/* FIXME: not sure on all the abbreviations for credit cards,below is what each stands for atleast to my knowledge
mc: Mastercard
ec: Eurocard
vi: Visa
ax: American Express
dc: Diners Club
bl: Carte Blanch
di: Discover
jcb: JCB
er: Enroute
*/
var results=[];
var cardinfo = {
'mc':'5[1-5][0-9]{14}','ec':'5[1-5][0-9]{14}','vi':'4([0-9]{12}|[0-9]{15})',
'ax':'3[47][0-9]{13}', 'dc':'3(0[0-5][0-9]{11}|[68][0-9]{12})',
'bl':'3(0[0-5][0-9]{11}|[68][0-9]{12})','di':'6011[0-9]{12}',
'jcb':'(3[0-9]{15}|(2131|1800)[0-9]{11})','er':'2(014|149)[0-9]{11}'
};
if(ccType&&dojo.lang.has(cardinfo,ccType.toLowerCase())){
return Boolean(value.match(cardinfo[ccType.toLowerCase()])); // boolean
}else{
for(var p in cardinfo){
if(value.match('^'+cardinfo[p]+'$')!=null){
results.push(p);
}
}
return (results.length)?results.join('|'):false; // string | boolean
}
}
dojo.validate.isValidCvv = function(/*String|Int*/value, /*String*/ccType) {
//Summary:
// returns true if the security code (CCV) matches the correct format for supplied ccType
//Value: Boolean
if(typeof value!='string'){
value=String(value);
}
var format;
switch (ccType.toLowerCase()){
case 'mc':
case 'ec':
case 'vi':
case 'di':
format = '###';
break;
case 'ax':
format = '####';
break;
default:
return false; //Boolean
}
var flags = {format:format};
//FIXME? Why does isNumberFormat take an object for flags when its only parameter is either a string or an array inside the object?
if ((value.length == format.length)&&(dojo.validate.isNumberFormat(value, flags))){
return true; //Boolean
}
return false; //Boolean
}
dojo.provide("dojo.validate.us");
dojo.validate.us.isCurrency = function(/*String*/value, /*Object?*/flags){
// summary: Validates U.S. currency
// value: the representation to check
// flags: flags in validate.isCurrency can be applied.
return dojo.validate.isCurrency(value, flags); // Boolean
}
dojo.validate.us.isState = function(/*String*/value, /*Object?*/flags){
// summary: Validates US state and territory abbreviations.
//
// value: A two character string
// flags: An object
// flags.allowTerritories Allow Guam, Puerto Rico, etc. Default is true.
// flags.allowMilitary Allow military 'states', e.g. Armed Forces Europe (AE). Default is true.
var re = new RegExp("^" + dojo.regexp.us.state(flags) + "$", "i");
return re.test(value); // Boolean
}
dojo.validate.us.isPhoneNumber = function(/*String*/value){
// summary: Validates 10 US digit phone number for several common formats
// value: The telephone number string
var flags = {
format: [
"###-###-####",
"(###) ###-####",
"(###) ### ####",
"###.###.####",
"###/###-####",
"### ### ####",
"###-###-#### x#???",
"(###) ###-#### x#???",
"(###) ### #### x#???",
"###.###.#### x#???",
"###/###-#### x#???",
"### ### #### x#???",
"##########"
]
};
return dojo.validate.isNumberFormat(value, flags); // Boolean
}
dojo.validate.us.isSocialSecurityNumber = function(/*String*/value){
// summary: Validates social security number
var flags = {
format: [
"###-##-####",
"### ## ####",
"#########"
]
};
return dojo.validate.isNumberFormat(value, flags); // Boolean
}
dojo.validate.us.isZipCode = function(/*String*/value){
// summary: Validates U.S. zip-code
var flags = {
format: [
"#####-####",
"##### ####",
"#########",
"#####"
]
};
return dojo.validate.isNumberFormat(value, flags); // Boolean
}