Skip to content
Browse files

First Commit for Mongo DB Driver

  • Loading branch information...
0 parents commit f304d1756a41bdd88c7b924f2b5a9cc321dde60a Christian Kvalheim committed
Sorry, we could not display the entire diff because it was too big.
1 .gitignore
@@ -0,0 +1 @@
+.DS_Store
10 Makefile
@@ -0,0 +1,10 @@
+
+NODE = node
+
+test:
+ @$(NODE) spec/spec.node.js
+
+integrate_test:
+ @$(NODE) integration/integration_tests.js
+
+.PHONY: test
132 dummy.js
@@ -0,0 +1,132 @@
+require.paths.unshift("lib");
+require("mongodb/connection");
+require("mongodb/db");
+
+sys = require("sys");
+
+require("mongodb/bson/bson")
+require("mongodb/bson/collections")
+require("mongodb/bson/binary_parser")
+require("goog/math/integer")
+require("goog/math/long")
+
+sys = require("sys");
+
+// var a = {b:23, a:12, d:14, c:45};
+// var b = {2:32, 1:12};
+// var c = [{2:32}, {1:12}];
+//
+// for(var i in a) {
+// sys.puts("=== " + i);
+// }
+//
+// for(var i in b) {
+// sys.puts("=== " + i);
+// }
+//
+// for(var i in c) {
+// sys.puts("===" + sys.inspect(c[i]));
+// }
+//
+// var a = new BSON().serialize(new OrderedHash().add('a', 1));
+// new BinaryParser().pprint(a);
+// var b = new BSON().deserialize(b);
+
+// List off all the nodes
+// var nodes = [{host: "127.0.0.1", port: 27017}, {host: "127.0.0.1", port: 27017}];
+var nodes = [{host: "127.0.0.1", port: 27017}];
+// Create a db object
+var db = new Db('test', nodes, {});
+db.addListener("connect", function() {
+ // We can now use the db to access stuff
+ // this.collections_info(null, function(reply) {
+ // sys.puts("db.collections_info callback");
+ // for(var index in reply[0].documents) {
+ // var document = reply[0].documents[index];
+ // sys.puts("name: " + document.name);
+ // }
+ // });
+ //
+ // this.collection_names(null, function(reply) {
+ // sys.puts("db.collection_names callback");
+ // for(var index in reply[0].documents) {
+ // var document = reply[0].documents[index];
+ // sys.puts("name: " + document.name);
+ // }
+ // });
+
+ // setInterval(function() {
+ // db.collection_names(null, function(reply) {
+ // sys.puts("db.collection_names callback 2");
+ // for(var index in reply[0].documents) {
+ // var document = reply[0].documents[index];
+ // // sys.puts("name: " + document.name);
+ // }
+ // });
+ // }, 1);
+
+ // for(var i = 0; i < 10000; i++) {
+ // this.collection_names(null, function(reply) {
+ // sys.puts("db.collection_names callback 2");
+ // for(var index in reply[0].documents) {
+ // var document = reply[0].documents[index];
+ // sys.puts("name: " + document.name);
+ // }
+ // });
+ // }
+
+ // db.collection_names(null, function(reply) {
+ // sys.puts("db.collection_names callback 2");
+ // for(var index in reply[0].documents) {
+ // var document = reply[0].documents[index];
+ // sys.puts("name: " + document.name);
+ // }
+ // });
+
+
+ // this.authenticate("admin", "admin", function(reply){
+ // sys.puts("authentication request");
+ // sys.puts(sys.inspect(reply[0].is_error()));
+ // sys.puts(sys.inspect(reply[0].error_message()));
+ // // Logout
+ // // db.logout(function(reply) {
+ // // sys.puts("executed logout command");
+ // // // sys.puts(sys.inspect(reply));
+ // // });
+ // db.lastError(function(reply) {
+ // sys.puts("got last error");
+ // sys.puts(sys.inspect(reply));
+ // });
+ // });
+
+ // db.createCollection("crazy", function(reply) {
+ // sys.puts("================== collection create function executed");
+ // // db.dropCollection("crazy", function(reply) {
+ // // sys.puts("================== collection drop function executed");
+ // // });
+ // });
+
+ // db.renameCollection("crazy", "crazy2", function(reply) {
+ // sys.puts("================== collection rename function executed");
+ // });
+
+ // db.dropCollection("crazy2", function(reply) {
+ // sys.puts("================== collection drop function executed");
+ // });
+});
+// Open the app
+db.open();
+// Let's call a list of all collection names in the specified db
+// db.collections_info(null, function(reply) {
+// sys.puts("db.collections_info callback");
+// });
+
+
+// Close the database
+// db.close();
+
+// var connection = new Connection('127.0.0.1', 27017);
+// sys.puts("Connection created");
+// connection.addListener("connect", function() {
+// sys.puts("Tcp Connection connected");
+// });
199 integration/integration_tests.js
@@ -0,0 +1,199 @@
+require.paths.unshift("./lib");
+
+GLOBAL.DEBUG = true;
+
+sys = require("sys");
+test = require("mjsunit");
+require("mongodb/db");
+
+/*******************************************************************************************************
+ Integration Tests
+*******************************************************************************************************/
+
+// Test the creation of a collection on the mongo db
+function test_collection_methods() {
+ client.createCollection('integration_test_collection', function(replies) {
+ // Verify that all the result are correct coming back (should contain the value ok)
+ test.assertEquals(1, replies[0].documents[0].ok);
+ // Let's check that the collection was created correctly
+ client.collection_names(null, function(replies) {
+ test.assertEquals("integration_tests_.integration_test_collection", replies[0].documents[0].name);
+ // Rename the collection and check that it's gone
+ client.renameCollection("integration_test_collection", "integration_test_collection2", function(replies) {
+ test.assertEquals(1, replies[0].documents[0].ok);
+ // Drop the collection and check that it's gone
+ client.dropCollection("integration_test_collection2", function(replies) {
+ test.assertEquals(1, replies[0].documents[0].ok);
+ finished_tests.push({test_collection_methods:'ok'});
+ })
+ });
+ });
+ })
+}
+
+// Test the authentication method for the user
+function test_authentication() {
+ var user_name = 'spongebob';
+ var password = 'password';
+ var user_password = MD5.hex_md5(user_name + ":mongo:" + password);
+
+ client.authenticate('admin', 'admin', function(replies) {
+ test.assertEquals(0, replies[0].documents[0].ok);
+ test.assertEquals("auth fails", replies[0].documents[0].errmsg);
+ // Fetch a user collection
+ var user_collection = client.collection('system.users');
+ // Insert a user document
+ var user_doc = new OrderedHash().add('user', user_name).add('pwd', user_password);
+ // Insert the user into the system users collections
+ user_collection.insert(user_doc, function(replies) {
+ test.assertTrue(replies[0].documents[0]['_id'].toHexString().length == 24);
+ // Ensure authentication works correctly
+ client.authenticate(user_name, password, function(replies) {
+ test.assertEquals(1, replies[0].documents[0].ok);
+ finished_tests.push({test_authentication:'ok'});
+ });
+ });
+ });
+}
+
+// Test the access to collections
+function test_collections() {
+ // Create two collections
+ var spiderman_collection = client.collection('test.spiderman');
+ var mario_collection = client.collection('test.mario');
+ // Insert test documents (creates collections)
+ spiderman_collection.insert(new OrderedHash().add("foo", 5));
+ mario_collection.insert(new OrderedHash().add("bar", 0));
+ // Assert collections
+ client.collections(function(collections) {
+ test.assertTrue(collections.length >= 2);
+ test.assertTrue(locate_collection_by_name("test.spiderman", collections) != null);
+ test.assertTrue(locate_collection_by_name("test.mario", collections) != null);
+ test.assertTrue(locate_collection_by_name("does_not_exist", collections) == null);
+ finished_tests.push({test_collections:'ok'});
+ });
+}
+
+// Test the generation of the object ids
+function test_object_id_generation() {
+ var collection = client.collection('test_object_id_generation.data');
+ var number_of_tests_done = 0;
+
+ // Insert test documents (creates collections and test fetch by query)
+ collection.insert(new OrderedHash().add("name", "Fred").add("age", 42), function(ids) {
+ test.assertEquals(1, ids.length);
+ test.assertEquals(1, ids[0].documents.length);
+ test.assertTrue(ids[0].documents[0]['_id'].toHexString().length == 24);
+ // Locate the first document inserted
+ collection.findOne(new OrderedHash().add("name", "Fred"), function(records) {
+ test.assertEquals(1, records.length);
+ test.assertEquals(1, records[0].documents.length);
+ test.assertEquals(ids[0].documents[0]['_id'].toHexString(), records[0].documents[0]['_id'].toHexString());
+ number_of_tests_done++;
+ });
+ });
+
+ // Insert another test document and collect using ObjectId
+ collection.insert(new OrderedHash().add("name", "Pat").add("age", 21), function(ids) {
+ test.assertEquals(1, ids.length);
+ test.assertEquals(1, ids[0].documents.length);
+ test.assertTrue(ids[0].documents[0]['_id'].toHexString().length == 24);
+ // Locate the first document inserted
+ collection.findOne(ids[0].documents[0]['_id'], function(records) {
+ test.assertEquals(1, records.length);
+ test.assertEquals(1, records[0].documents.length);
+ test.assertEquals(ids[0].documents[0]['_id'].toHexString(), records[0].documents[0]['_id'].toHexString());
+ number_of_tests_done++;
+ });
+ });
+
+ // Manually created id
+ var objectId = new ObjectID(null);
+
+ // Insert a manually created document with generated oid
+ collection.insert(new OrderedHash().add("_id", objectId.id).add("name", "Donald").add("age", 95), function(ids) {
+ test.assertEquals(1, ids.length);
+ test.assertEquals(1, ids[0].documents.length);
+ test.assertTrue(ids[0].documents[0]['_id'].toHexString().length == 24);
+ test.assertEquals(objectId.toHexString(), ids[0].documents[0]['_id'].toHexString());
+ // Locate the first document inserted
+ collection.findOne(ids[0].documents[0]['_id'], function(records) {
+ test.assertEquals(1, records.length);
+ test.assertEquals(1, records[0].documents.length);
+ test.assertEquals(ids[0].documents[0]['_id'].toHexString(), records[0].documents[0]['_id'].toHexString());
+ test.assertEquals(objectId.toHexString(), records[0].documents[0]['_id'].toHexString());
+ number_of_tests_done++;
+ });
+ });
+
+ var intervalId = setInterval(function() {
+ if(number_of_tests_done == 3) {
+ clearInterval(intervalId);
+ finished_tests.push({test_object_id_generation:'ok'});
+ }
+ }, 100);
+}
+
+/*******************************************************************************************************
+ Setup For Running Tests
+*******************************************************************************************************/
+// Set up the client connection
+var client = new Db('integration_tests_', [{host: "127.0.0.1", port: 27017}], {});
+client.addListener("connect", function() {
+ // Do cleanup of the db
+ client.dropDatabase(function() {
+ // Run all the tests
+ run_all_tests();
+ // Start the timer that checks that all the tests have finished or failed
+ ensure_tests_finished();
+ });
+});
+client.open();
+
+function ensure_tests_finished() {
+ var intervalId = setInterval(function() {
+ if(finished_tests.length >= client_tests.length) {
+ // Print out the result
+ sys.puts("= Results =========================================================");
+ // Stop interval timer and close db connection
+ clearInterval(intervalId);
+ client.close();
+ // Print all the statuses
+ finished_tests.forEach(function(t) {
+ for(var i in t) {
+ sys.puts(i + " = " + sys.inspect(t[i]));
+ }
+ });
+ }
+ }, 100);
+};
+
+// All the client tests
+var client_tests = [test_collection_methods, test_authentication, test_collections, test_object_id_generation];
+// var client_tests = [test_object_id_generation];
+var finished_tests = [];
+// Run all the tests
+function run_all_tests() {
+ sys.puts("= Executing tests =====================================================");
+ // Run all the tests
+ client_tests.forEach(function (t) {
+ var function_name = t.name;
+ sys.puts("executing test: [" + function_name + "]");
+ try {
+ t();
+ } catch(error) {
+ sys.puts(sys.inspect(error));
+ finished_tests.push({function_name:error});
+ }
+ });
+}
+
+/**
+ Helper Utilities for the testing
+**/
+function locate_collection_by_name(collectionName, collections) {
+ for(var index in collections) {
+ var collection = collections[index];
+ if(collection.collectionName == collectionName) return collection;
+ }
+}
425 lib/active_support/active_support.js
@@ -0,0 +1,425 @@
+/**
+ * ActiveSupport for JavaScript library, version '0.1'
+ * (c) 2007 Nicolas Sanguinetti
+ *
+ * ActiveSupport for JavaScript is freely distributable under the terms of an
+ * MIT-style license. For details, see our web site:
+ * http://code.google.com/p/active-support-for-javascript/
+ *
+ */
+
+var ActiveSupport = {
+ Version: '0.1',
+
+ pluralizeMethods: function(module) {
+ $H(module).each(function(pair) {
+ module[pair.first().pluralize()] = module[pair.first()];
+ });
+ },
+ pluralize: function(count, singular) {
+ return count.abs() == 1 ? count + " " + singular : count + " " + singular.pluralize();
+ }
+}
+
+window.pluralize = ActiveSupport.pluralize;
+
+
+// String Interpolation
+
+var InterpolatableString = Class.create();
+InterpolatableString.prototype = {
+ initialize: function(string, binding) {
+ this.string = string;
+ this.tokens = (string.match(/#{([^}]+)}/ig) || []).map(function(token) {
+ return new InterpolatableString.Token(token, binding);
+ });
+ },
+ toString: function() {
+ return this.tokens.inject(this.string.toString(), function(result, token) {
+ return result.gsub(token.toRegExp(), token.evaluate()).toString();
+ }.bind(this));
+ }
+};
+
+InterpolatableString.Token = Class.create();
+InterpolatableString.Token.prototype = {
+ initialize: function(token, binding) {
+ this.token = token.replace(/^#{(.*)}$/, "$1");
+ this.binding = binding;
+ },
+ toRegExp: function() {
+ var token = ("#{" + this.token + "}").replace(/\(/, "\\(").replace(/\)/, "\\)").replace(/\[/, "\\[").replace(/\]/, "\\]").replace(/\./, "\\.").replace(/\-/, "\\-");
+ return new RegExp(token, "i");
+ },
+ evaluate: function() {
+ var token = this.token;
+ return (function() { return eval(token); }.bind(this.binding))();
+ }
+};
+
+var $Q = function(string, binding) {
+ return string.interpolate(binding || window);
+};
+
+// Inflector
+
+var Inflector = {
+ pluralize: function(word) {
+ if (Inflections.uncountables.include(word.toLowerCase()))
+ return word;
+ return Inflections.plurals.map(function(pair) {
+ return word.replace(pair.first(), pair.last());
+ }).detect(function(plural) { return word != plural; }) || word;
+ },
+ singularize: function(word) {
+ if (Inflections.uncountables.include(word.toLowerCase()))
+ return word;
+ return Inflections.singulars.map(function(pair) {
+ return word.replace(pair.first(), pair.last());
+ }).detect(function(singular) { return word != singular; }) || word;
+ },
+ ordinalize: function(number) {
+ if ($R(11, 13).include(number % 100)) {
+ return number + "th";
+ }
+ switch (number % 10) {
+ case 1: return number + "st";
+ case 2: return number + "nd";
+ case 3: return number + "rd";
+ default: return number + "th";
+ }
+ }
+};
+
+var Inflections = {
+ plurals: [],
+ singulars: [],
+ uncountables: [],
+
+ plural: function(rule, replacement) {
+ this.plurals.unshift([rule, replacement]);
+ },
+ singular: function(rule, replacement) {
+ this.singulars.unshift([rule, replacement]);
+ },
+ irregular: function(singular, plural) {
+ this.plural(new RegExp(singular.charAt(0) + singular.substring(1) + "$", "i"), "$1" + plural.substring(1));
+ this.singular(new RegExp(plural.charAt(0) + plural.substring(1) + "$", "i"), "$1" + singular.substring(1));
+ },
+ uncountable: function(uncountable) {
+ this.uncountables = this.uncountables.concat($A(arguments));
+ }
+};
+
+with (Inflections) {
+ plural(/$/, "s");
+ plural(/s$/i, "s");
+ plural(/(ax|test)is$/i, "$1es");
+ plural(/(octop|vir)us$/i, "$1i");
+ plural(/(alias|status)$/i, "$1es");
+ plural(/(bu)s$/i, "$1ses");
+ plural(/(buffal|tomat)o$/i, "$1oes");
+ plural(/([ti])um$/i, "$1a");
+ plural(/sis$/i, "ses");
+ plural(/(?:([^f])fe|([lr])f)$/i, "$1$2ves");
+ plural(/(hive)$/i, "$1s");
+ plural(/([^aeiouy]|qu)y$/i, "$1ies");
+ plural(/([^aeiouy]|qu)ies$/i, "$1y");
+ plural(/(x|ch|ss|sh)$/i, "$1es");
+ plural(/(matr|vert|ind)ix|ex$/i, "$1ices");
+ plural(/([m|l])ouse$/i, "$1ice");
+ plural(/^(ox)$/i, "$1en");
+ plural(/(quiz)$/i, "$1zes");
+
+ singular(/s$/i, '');
+ singular(/(n)ews$/i, '$1ews');
+ singular(/([ti])a$/i, '$1um');
+ singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '$1$2sis');
+ singular(/(^analy)ses$/i, '$1sis');
+ singular(/([^f])ves$/i, '$1fe');
+ singular(/(hive)s$/i, '$1');
+ singular(/(tive)s$/i, '$1');
+ singular(/([lr])ves$/i, '$1f');
+ singular(/([^aeiouy]|qu)ies$/i, '$1y');
+ singular(/(s)eries$/i, '$1eries');
+ singular(/(m)ovies$/i, '$1ovie');
+ singular(/(x|ch|ss|sh)es$/i, '$1');
+ singular(/([m|l])ice$/i, '$1ouse');
+ singular(/(bus)es$/i, '$1');
+ singular(/(o)es$/i, '$1');
+ singular(/(shoe)s$/i, '$1');
+ singular(/(cris|ax|test)es$/i, '$1is');
+ singular(/([octop|vir])i$/i, '$1us');
+ singular(/(alias|status)es$/i, '$1');
+ singular(/^(ox)en/i, '$1');
+ singular(/(vert|ind)ices$/i, '$1ex');
+ singular(/(matr)ices$/i, '$1ix');
+ singular(/(quiz)zes$/i, '$1');
+
+ irregular("person", "people");
+ irregular("man", "men");
+ irregular("child", "children");
+ irregular("sex", "sexes");
+ irregular("move", "moves");
+
+ uncountable("equipment", "information", "rice", "money", "species", "series", "fish", "sheep");
+};
+
+// String Extensions
+
+Object.extend(String.prototype, {
+ interpolate: function(binding) {
+ return new InterpolatableString(this, binding || window).toString();
+ },
+ pluralize: function() {
+ return Inflector.pluralize(this.toString());
+ },
+ singularize: function() {
+ return Inflector.singularize(this.toString());
+ }
+});
+
+// Array Extensions
+
+Array.prototype.toSentence = function() {
+ var options = Object.extend({
+ "connector": "and",
+ "skip_last_comma": false
+ }, arguments[0] || {});
+
+ switch (this.size()) {
+ case 0: return "";
+ case 1: return this.reduce();
+ case 2: return $Q("#{this.first()} #{options['connector']} #{this.last()}", this);
+ default: return $Q("#{this.slice(0, -1).join(', ')}#{options['skip_last_comma'] ? '' : ','} #{options['connector']} #{this.last()}", this);
+ }
+};
+
+// Number Extensions
+
+$w("abs acos asin atan ceil cos exp floor log pow round sin sqrt tan").each(function(method) {
+ Number.prototype[method] = Math[method].methodize();
+});
+
+Object.extend(Number.prototype, {
+ ordinalize: function() {
+ return Inflector.ordinalize(this);
+ }
+});
+
+Number.ByteExtensions = {
+ byte: function() { return this; },
+ kilobyte: function() { return this * 1024; },
+ megabyte: function() { return this * (1024).kilobytes(); },
+ gigabyte: function() { return this * (1024).megabytes(); },
+ terabyte: function() { return this * (1024).gigabytes(); },
+ petabyte: function() { return this * (1024).terabytes(); },
+ exabyte: function() { return this * (1024).petabytes(); }
+};
+ActiveSupport.pluralizeMethods(Number.ByteExtensions);
+Object.extend(Number.prototype, Number.ByteExtensions);
+
+Number.IntervalExtensions = {
+ second: function() { return this * 1000; },
+ minute: function() { return this.seconds() * 60; },
+ hour: function() { return this.minutes() * 60; },
+ day: function() { return this.hours() * 24; },
+ week: function() { return this.days() * 7; },
+ fortnight: function() { return this.weeks() * 2; },
+ month: function() { return this.days() * 30; },
+ year: function() { return this.months() * 12 }
+};
+ActiveSupport.pluralizeMethods(Number.IntervalExtensions);
+Object.extend(Number.prototype, Number.IntervalExtensions);
+
+Number.TimeExtensions = {
+ since: function(reference) { return new Date((reference || new Date()).getTime() + this); },
+ until: function(reference) { return new Date((reference || new Date()).getTime() - this); }
+};
+Number.TimeExtensions.toDate = (0).ago;
+Number.TimeExtensions.fromNow = Number.TimeExtensions.since.curry(null);
+Number.TimeExtensions.ago = Number.TimeExtensions.until.curry(null);
+Object.extend(Number.prototype, Number.TimeExtensions);
+
+// Date Extensions
+
+Object.extend(Date, {
+ MONTHS: $w("January February March April May June July August September October November December"),
+ ABBR_MONTHS: $w("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"),
+ WEEKDAYS: $w("Sunday Monday Tuesday Wednesday Thursday Friday Saturday"),
+ ABBR_WEEKDAYS: $w("Sun Mon Tue Wed Thu Fri Sat"),
+ RELATIVE_DATE_OUTPUT: {
+ today: "today",
+ yesterday: "yesterday",
+ tomorrow: "tomorrow",
+ hour_format: "%H:%M, ",
+ date_format: "%b %o",
+ year_format: ", %Y"
+ },
+ RELATIVE_TIME_RANGES: {
+ 0: "less than a minute",
+ 15: "#{pluralize(this, 'minute')}",
+ 25: "less than half an hour",
+ 35: "about half an hour",
+ 55: "less than an hour",
+ 65: "about an hour",
+ 85: "less than an hour and a half",
+ 95: "about an hour and a half",
+ 115: "less than 2 hours",
+ 125: "about 2 hours",
+ 145: "less than 2 hours and a half",
+ 155: "about 2 hours and a half",
+ 175: "less than 3 hours",
+ 185: "around 3 hours"
+ },
+ STRING_FORMATS: {
+ "%a": function() { return Date.ABBR_WEEKDAYS[this.getDay()]; },
+ "%A": function() { return Date.WEEKDAYS[this.getDay()]; },
+ "%b": function() { return Date.ABBR_MONTHS[this.getMonth()]; },
+ "%B": function() { return Date.MONTHS[this.getMonth()]; },
+ "%c": function() { return this.toLocaleString(); },
+ "%d": function() { return this.getDate().toPaddedString(2); },
+ "%H": function() { return this.getHours().toPaddedString(2); },
+ "%I": function() { return (this.getHours() % 12).toPaddedString(2); },
+ "%j": function() { throw Error("not implemented"); },
+ "%m": function() { return (this.getMonth() + 1).toPaddedString(2); },
+ "%M": function() { return this.getMinutes().toPaddedString(2); },
+ "%o": function() { return this.getDate().ordinalize(); },
+ "%p": function() { return Math.floor(this.getHour() / 12) == 0 ? "AM" : "PM"; },
+ "%S": function() { return this.getSeconds().toPaddedString(2); },
+ "%U": function() { throw Error("not implemented"); },
+ "%W": function() { throw Error("not implemented"); },
+ "%w": function() { return this.getDay(); },
+ "%x": function() { throw Error("not implemented"); },
+ "%X": function() { throw Error("not implemented"); },
+ "%y": function() { return this.getYear().toPaddedString(2); },
+ "%Y": function() { return this.getFullYear().toPaddedString(4); },
+ "%Z": function() { throw Error("not implemented"); }
+ },
+ now: function() {
+ return new Date();
+ },
+ today: function() {
+ return new Date().atBeginningOfDay();
+ }
+});
+
+Object.extend(Date.prototype, {
+ equals: function(otherDate) {
+ return this.getFullYear() == otherDate.getFullYear() && this.getMonth() == otherDate.getMonth() && this.getDate() == otherDate.getDate();
+ },
+ isLeapYear: function() {
+ var year = this.getFullYear();
+ return (year % 4 == 0 && year % 100 != 0) || year % 400;
+ },
+ getMonthName: function() {
+ return Date.MONTHS[this.getMonth()];
+ },
+ getDaysInMonth: function() {
+ switch (this.getMonth() + 1) {
+ case 2:
+ return this.isLeapYear() ? 29 : 28;
+ case 4:
+ case 6:
+ case 9:
+ case 11:
+ return 30;
+ default:
+ return 31;
+ }
+ },
+ isToday: function() {
+ return this.midnight().equals(new Date().midnight());
+ },
+ succ: function() {
+ return (1).second().fromNow();
+ },
+ toFormattedString: function(format) {
+ return format.gsub(/%[a-zA-Z]/, function(pattern) {
+ return Date.STRING_FORMATS[pattern].bind(this)().toString();
+ }.bind(this)).replace(/%%/, "%");
+ },
+ relativeDate: function() {
+ var targetTime = this.atBeginningOfDay();
+ var today = Date.today();
+
+ if (targetTime.equals(today)) {
+ return Date.RELATIVE_DATE_OUTPUT["today"];
+ } else if (targetTime.equals(today.yesterday())) {
+ return Date.RELATIVE_DATE_OUTPUT["yesterday"];
+ } else if (targetTime.equals(today.tomorrow())) {
+ return Date.RELATIVE_DATE_OUTPUT["tomorrow"];
+ } else {
+ var format = Date.RELATIVE_DATE_OUTPUT["date_format"];
+ format += targetTime.getFullYear() == today.getFullYear() ? "" : Date.RELATIVE_DATE_OUTPUT["year_format"];
+ return this.strftime(format);
+ }
+ },
+ relativeTime: function() {
+ var options = Object.extend({ prefix: "", suffix: "" }, arguments[0] || {});
+ var distanceInMinutes = ((Date.now().getTime() - this.getTime()).abs() / 60000).round();
+ return $H(Date.RELATIVE_TIME_RANGES).map(function(pair) {
+ return (distanceInMinutes <= pair.first()) ?
+ (options["prefix"] + " " + $Q(pair.last(), distanceInMinutes) + " " + options["suffix"]).strip() : false;
+ }).find(Prototype.K) || $Q("#{this.relativeDate()} at #{this.strftime('%H:%M')}", this);
+ },
+ since: function(seconds) {
+ return seconds.since(this);
+ },
+ ago: function(seconds) {
+ return this.since(-seconds);
+ },
+ beginningOfDay: function() {
+ return new Date(this).setHours(0).setMinutes(0).setSeconds(0);
+ },
+ beginningOfWeek: function() {
+ var daysToSunday = this.getDay() == 0 ? 6 : this.getDay() - 1;
+ return daysToSunday.days().until(this.beginningOfDay());
+ },
+ beginningOfMonth: function() {
+ return this.beginningOfDay().setDate(1);
+ },
+ beginningOfQuarter: function() {
+ return this.beginningOfMonth().setMonth([9, 6, 3, 0].detect(function(m) { return m <= this.getMonth(); }.bind(this)));
+ },
+ beginningOfYear: function() {
+ return this.beginningOfMonth().setMonth(0);
+ },
+ endOfDay: function() {
+ return new Date(this).setHours(23).setMinutes(59).setSeconds(59);
+ },
+ endOfMonth: function() {
+ return this.beginningOfDay().setDate(this.getDaysInMonth());
+ },
+ endOfQuarter: function() {
+ return this.setMonth([2, 5, 8, 11].detect(function(m) { return m >= this.getMonth(); }.bind(this))).endOfMonth();
+ },
+ yesterday: function() {
+ return this.setDate(this.getDate() - 1);
+ },
+ tomorrow: function() {
+ return this.setDate(this.getDate() + 1);
+ }
+});
+
+$w("setDate setMonth setFullYear setYear setHours setMinutes setSeconds setMilliseconds setTime").each(function(method) {
+ Date.prototype[method + "WithoutChaining"] = Date.prototype[method];
+ Date.prototype[method] = function() {
+ this[method + "WithoutChaining"].call(this, $A(arguments));
+ return this;
+ }
+});
+
+$w("beginningOfDay beginningOfWeek beginningOfMonth beginningOfQuarter beginningOfYear endOfDay endOfMonth endOfQuarter").each(function(method) {
+ Date.prototype["at" + method.charAt(0).toUpperCase() + method.substring(1)] = Date.prototype[method];
+});
+
+Date.prototype.strftime = Date.prototype.toFormattedString;
+Date.prototype.midnight = Date.prototype.beginningOfDay;
+Date.prototype.monday = Date.prototype.beginningOfWeek;
+
+Date.WEEKDAYS.each(function(dayName, dayIndex) {
+ Date.prototype["is" + dayName] = function() {
+ return this.getDay() % 7 == dayIndex;
+ }
+});
1,598 lib/goog/date/date.js
1,598 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
385 lib/goog/date/daterange.js
@@ -0,0 +1,385 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Copyright 2008 Google Inc. All Rights Reserved.
+
+/**
+ * @fileoverview Date range data structure. Based loosely on
+ * com.google.common.util.DateRange.
+ *
+ */
+
+goog.provide('goog.date.DateRange');
+goog.provide('goog.date.DateRange.Iterator');
+goog.provide('goog.date.DateRange.StandardDateRangeKeys');
+
+goog.require('goog.date.Date');
+goog.require('goog.date.Interval');
+goog.require('goog.iter.Iterator');
+goog.require('goog.iter.StopIteration');
+
+/**
+ * Constructs a date range.
+ * @constructor
+ * @param {Date} startDate The start date of the range.
+ * @param {Date} endDate The end date of the range.
+ */
+goog.date.DateRange = function(startDate, endDate) {
+ /**
+ * The start date.
+ * @type {goog.date.Date}
+ * @private
+ */
+ this.startDate_ = new goog.date.Date(startDate);
+
+ /**
+ * The end date.
+ * @type {goog.date.Date}
+ * @private
+ */
+ this.endDate_ = new goog.date.Date(endDate);
+};
+
+
+/**
+ * The first possible day, as far as this class is concerned.
+ * @type {goog.date.Date}
+ */
+goog.date.DateRange.MINIMUM_DATE = new goog.date.Date(0000, 0, 1);
+
+
+/**
+ * The last possible day, as far as this class is concerned.
+ * @type {goog.date.Date}
+ */
+goog.date.DateRange.MAXIMUM_DATE = new goog.date.Date(9999, 11, 31);
+
+
+/**
+ * @return {goog.date.Date} The start date.
+ */
+goog.date.DateRange.prototype.getStartDate = function() {
+ return this.startDate_;
+};
+
+
+/**
+ * @return {goog.date.Date} The end date.
+ */
+goog.date.DateRange.prototype.getEndDate = function() {
+ return this.endDate_;
+};
+
+
+/**
+ * @return {goog.iter.Iterator} An iterator over the date range.
+ */
+goog.date.DateRange.prototype.iterator = function() {
+ return new goog.date.DateRange.Iterator(this);
+};
+
+
+/**
+ * Tests two {@link goog.date.DateRange} objects for equality.
+ * @param {goog.date.DateRange} a A date range.
+ * @param {goog.date.DateRange} b A date range.
+ * @return {boolean} Whether |a| is the same range as |b|.
+ */
+goog.date.DateRange.equals = function(a, b) {
+ // Test for same object reference; type conversion is irrelevant.
+ if (a === b) {
+ return true;
+ }
+
+ if (a == null || b == null) {
+ return false;
+ }
+
+ return a.startDate_.equals(b.startDate_) && a.endDate_.equals(b.endDate_);
+};
+
+
+/**
+ * Calculates a date that is a number of days after a date. Does not modify its
+ * input.
+ * @param {Date} date The input date.
+ * @param {number} offset Number of days.
+ * @return {goog.date.Date} The date that is |offset| days after |date|.
+ * @private
+ */
+goog.date.DateRange.offsetInDays_ = function(date, offset) {
+ var newDate = new goog.date.Date(date);
+ newDate.add(new goog.date.Interval(goog.date.Interval.DAYS, offset));
+ return newDate;
+};
+
+
+/**
+ * Calculates the Monday before a date. If the input is a Monday, returns the
+ * input. Does not modify its input.
+ * @param {Date} date The input date.
+ * @return {goog.date.Date} If |date| is a Monday, return |date|; otherwise
+ * return the Monday before |date|.
+ * @private
+ */
+goog.date.DateRange.currentOrLastMonday_ = function(date) {
+ var newDate = new goog.date.Date(date);
+ newDate.add(new goog.date.Interval(goog.date.Interval.DAYS,
+ -newDate.getIsoWeekday()));
+ return newDate;
+};
+
+
+/**
+ * Calculates a date that is a number of months after the first day in the
+ * month that contains its input. Does not modify its input.
+ * @param {Date} date The input date.
+ * @param {number} offset Number of months.
+ * @return {goog.date.Date} The date that is |offset| months after the first
+ * day in the month that contains |date|.
+ * @private
+ */
+goog.date.DateRange.offsetInMonths_ = function(date, offset) {
+ var newDate = new goog.date.Date(date);
+ newDate.setDate(1);
+ newDate.add(new goog.date.Interval(goog.date.Interval.MONTHS, offset));
+ return newDate;
+};
+
+
+/**
+ * Returns the range from yesterday to yesterday.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that includes only yesterday.
+ */
+goog.date.DateRange.yesterday = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ var yesterday = goog.date.DateRange.offsetInDays_(today, -1);
+ return new goog.date.DateRange(yesterday, yesterday);
+};
+
+
+/**
+ * Returns the range from today to today.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that includes only today.
+ */
+goog.date.DateRange.today = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ return new goog.date.DateRange(today, today);
+};
+
+
+/**
+ * Returns the range that includes the seven days that end yesterday.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that includes the seven days that
+ * end yesterday.
+ */
+goog.date.DateRange.last7Days = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ var yesterday = goog.date.DateRange.offsetInDays_(today, -1);
+ return new goog.date.DateRange(goog.date.DateRange.offsetInDays_(today, -7),
+ yesterday);
+};
+
+
+/**
+ * Returns the range that starts the first of this month and ends the last day
+ * of this month.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that starts the first of this month
+ * and ends the last day of this month.
+ */
+goog.date.DateRange.thisMonth = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ return new goog.date.DateRange(
+ goog.date.DateRange.offsetInMonths_(today, 0),
+ goog.date.DateRange.offsetInDays_(
+ goog.date.DateRange.offsetInMonths_(today, 1),
+ -1));
+};
+
+
+/**
+ * Returns the range that starts the first of last month and ends the last day
+ * of last month.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that starts the first of last month
+ * and ends the last day of last month.
+ */
+goog.date.DateRange.lastMonth = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ return new goog.date.DateRange(
+ goog.date.DateRange.offsetInMonths_(today, -1),
+ goog.date.DateRange.offsetInDays_(
+ goog.date.DateRange.offsetInMonths_(today, 0),
+ -1));
+};
+
+
+/**
+ * Returns the range that starts the Monday on or before today and ends the
+ * Sunday on or after today.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that starts the Monday on or before
+ * today and ends the Sunday on or after today.
+ */
+goog.date.DateRange.thisWeek = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ var start = goog.date.DateRange.offsetInDays_(today, -today.getIsoWeekday());
+ var end = goog.date.DateRange.offsetInDays_(start, 6);
+ return new goog.date.DateRange(start, end);
+};
+
+
+/**
+ * Returns the range that starts seven days before the Monday on or before
+ * today and ends the Sunday on or before yesterday.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that starts seven days before the
+ * Monday on or before today and ends the Sunday on or before yesterday.
+ */
+goog.date.DateRange.lastWeek = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ var start = goog.date.DateRange.offsetInDays_(today,
+ - 7 - today.getIsoWeekday());
+ var end = goog.date.DateRange.offsetInDays_(start, 6);
+ return new goog.date.DateRange(start, end);
+};
+
+
+/**
+ * Returns the range that starts seven days before the Monday on or before
+ * today and ends the Friday before today.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that starts seven days before the
+ * Monday on or before today and ends the Friday before today.
+ */
+goog.date.DateRange.lastBusinessWeek = function(opt_today) {
+ var today = new goog.date.Date(opt_today);
+ var start = goog.date.DateRange.offsetInDays_(today,
+ - 7 - today.getIsoWeekday());
+ var end = goog.date.DateRange.offsetInDays_(start, 4);
+ return new goog.date.DateRange(start, end);
+};
+
+
+/**
+ * Returns the range that includes all days between January 1, 1900 and
+ * December 31, 9999.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The range that includes all days between
+ * January 1, 1900 and December 31, 9999.
+ */
+goog.date.DateRange.allTime = function(opt_today) {
+ return new goog.date.DateRange(
+ goog.date.DateRange.MINIMUM_DATE,
+ goog.date.DateRange.MAXIMUM_DATE);
+};
+
+
+/**
+ * Standard date range keys. Equivalent to the enum IDs in
+ * DateRange.java http://go/datarange.java
+ *
+ * @enum {string}
+ */
+goog.date.DateRange.StandardDateRangeKeys = {
+ YESTERDAY: 'yesterday',
+ TODAY: 'today',
+ LAST_7_DAYS: 'last7days',
+ THIS_MONTH: 'thismonth',
+ LAST_MONTH: 'lastmonth',
+ THIS_WEEK: 'thisweek',
+ LAST_WEEK: 'lastweek',
+ LAST_BUSINESS_WEEK: 'lastbusinessweek',
+ ALL_TIME: 'alltime'
+};
+
+
+/**
+ * @param {string} dateRangeKey A standard date range key.
+ * @param {Date} opt_today The date to consider today. Defaults to today.
+ * @return {goog.date.DateRange} The date range that corresponds to that key.
+ * @throws {Error} If no standard date range with that key exists.
+ */
+goog.date.DateRange.standardDateRange = function(dateRangeKey, opt_today) {
+ switch (dateRangeKey) {
+ case goog.date.DateRange.StandardDateRangeKeys.YESTERDAY:
+ return goog.date.DateRange.yesterday(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.TODAY:
+ return goog.date.DateRange.today(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.LAST_7_DAYS:
+ return goog.date.DateRange.last7Days(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.THIS_MONTH:
+ return goog.date.DateRange.thisMonth(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.LAST_MONTH:
+ return goog.date.DateRange.lastMonth(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.THIS_WEEK:
+ return goog.date.DateRange.thisWeek(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.LAST_WEEK:
+ return goog.date.DateRange.lastWeek(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.LAST_BUSINESS_WEEK:
+ return goog.date.DateRange.lastBusinessWeek(opt_today);
+
+ case goog.date.DateRange.StandardDateRangeKeys.ALL_TIME:
+ return goog.date.DateRange.allTime(opt_today);
+
+ default:
+ throw Error('no such date range key: ' + dateRangeKey);
+ }
+};
+
+
+
+/**
+ * Creates an iterator over the dates in a {@link goog.date.DateRange}.
+ * @constructor
+ * @extends {goog.iter.Iterator}
+ * @param {goog.date.DateRange} dateRange The date range to iterate.
+ */
+goog.date.DateRange.Iterator = function(dateRange) {
+ /**
+ * The next date.
+ * @type {goog.date.Date}
+ * @private
+ */
+ this.nextDate_ = dateRange.getStartDate().clone();
+
+ /**
+ * The end date, expressed as an integer: YYYYMMDD.
+ * @type {number}
+ * @private
+ */
+ this.endDate_ = Number(dateRange.getEndDate().toIsoString());
+};
+goog.inherits(goog.date.DateRange.Iterator, goog.iter.Iterator);
+
+
+/** @inheritDoc */
+goog.date.DateRange.Iterator.prototype.next = function() {
+ if (Number(this.nextDate_.toIsoString()) > this.endDate_) {
+ throw goog.iter.StopIteration;
+ }
+
+ var rv = this.nextDate_.clone();
+ this.nextDate_.add(new goog.date.Interval(goog.date.Interval.DAYS, 1));
+ return rv;
+};
425 lib/goog/date/relative.js
@@ -0,0 +1,425 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+/**
+ * @fileoverview Functions for formatting relative dates. Such as "3 days ago"
+ * "3 hours ago", "14 minutes ago", "12 days ago", "Today", "Yesterday".
+ *
+ */
+
+goog.provide('goog.date.relative');
+
+goog.require('goog.i18n.DateTimeFormat');
+
+
+/**
+ * Number of milliseconds in a minute.
+ * @type {number}
+ * @private
+ */
+goog.date.relative.MINUTE_MS_ = 60000;
+
+
+/**
+ * Number of milliseconds in a day.
+ * @type {number}
+ * @private
+ */
+goog.date.relative.DAY_MS_ = 86400000;
+
+
+/**
+ * Enumeration used to identify time units internally.
+ * @enum {number}
+ * @private
+ */
+goog.date.relative.Unit_ = {
+ MINUTES: 0,
+ HOURS: 1,
+ DAYS: 2
+};
+
+
+/**
+ * Full date formatter.
+ * @type {goog.i18n.DateTimeFormat}
+ * @private
+ */
+goog.date.relative.fullDateFormatter_;
+
+
+/**
+ * Short time formatter.
+ * @type {goog.i18n.DateTimeFormat}
+ * @private
+ */
+goog.date.relative.shortTimeFormatter_;
+
+
+/**
+ * Month-date formatter.
+ * @type {goog.i18n.DateTimeFormat}
+ * @private
+ */
+goog.date.relative.monthDateFormatter_;
+
+
+/**
+ * Returns a date in month format, e.g. Mar 15.
+ * @param {Date} date The date object.
+ * @return {string} The formatted string.
+ * @private
+ */
+goog.date.relative.formatMonth_ = function(date) {
+ if (!goog.date.relative.monthDateFormatter_) {
+ goog.date.relative.monthDateFormatter_ =
+ new goog.i18n.DateTimeFormat('MMM dd');
+ }
+ return goog.date.relative.monthDateFormatter_.format(date);
+};
+
+
+/**
+ * Returns a date in short-time format, e.g. 2:50 PM.
+ * @param {Date} date The date object.
+ * @return {string} The formatted string.
+ * @private
+ */
+goog.date.relative.formatShortTime_ = function(date) {
+ if (!goog.date.relative.shortTimeFormatter_) {
+ goog.date.relative.shortTimeFormatter_ = new goog.i18n.DateTimeFormat(
+ goog.i18n.DateTimeFormat.Format.SHORT_TIME);
+ }
+ return goog.date.relative.shortTimeFormatter_.format(date);
+};
+
+
+/**
+ * Returns a date in full date format, e.g. Tuesday, March 24, 2009.
+ * @param {Date} date The date object.
+ * @return {string} The formatted string.
+ * @private
+ */
+goog.date.relative.formatFullDate_ = function(date) {
+ if (!goog.date.relative.fullDateFormatter_) {
+ goog.date.relative.fullDateFormatter_ = new goog.i18n.DateTimeFormat(
+ goog.i18n.DateTimeFormat.Format.FULL_DATE);
+ }
+ return goog.date.relative.fullDateFormatter_.format(date);
+};
+
+
+/**
+ * Accepts a timestamp in milliseconds and outputs a relative time in the form
+ * of "1 hour ago", "1 day ago", "in 1 hour", "in 2 days" etc. If the date
+ * delta is over 2 weeks, then the output string will be empty.
+ * @param {number} dateMs Date in milliseconds.
+ * @return {string} The formatted date.
+ */
+goog.date.relative.format = function(dateMs) {
+
+ var now = goog.now();
+ var delta = Math.floor((now - dateMs) / goog.date.relative.MINUTE_MS_);
+
+ var future = false;
+
+ if (delta < 0) {
+ future = true;
+ delta *= -1;
+ }
+
+ if (delta < 60) { // Minutes.
+ return goog.date.relative.getMessage_(
+ delta, future, goog.date.relative.Unit_.MINUTES);
+
+ } else {
+ delta = Math.floor(delta / 60);
+ if (delta < 24) { // Hours.
+ return goog.date.relative.getMessage_(
+ delta, future, goog.date.relative.Unit_.HOURS);
+
+ } else {
+ // Timezone offset is in minutes. We pass goog.now so that we can easily
+ // unit test this, the JSCompiler will optimize it away for us.
+ var offset = new Date(goog.now()).getTimezoneOffset() *
+ goog.date.relative.MINUTE_MS_;
+
+ // Convert to days ago.
+ delta = Math.floor((now + offset) / goog.date.relative.DAY_MS_) -
+ Math.floor((dateMs + offset) / goog.date.relative.DAY_MS_);
+
+ if (future) {
+ delta *= -1;
+ }
+
+ // Uses days for less than 2-weeks.
+ if (delta < 14) {
+ return goog.date.relative.getMessage_(
+ delta, future, goog.date.relative.Unit_.DAYS);
+
+ } else {
+ // For messages older than 2 weeks do not show anything. The client
+ // should decide the date format to show.
+ return '';
+ }
+ }
+ }
+};
+
+
+/**
+ * Accepts a timestamp in milliseconds and outputs a relative time in the form
+ * of "1 hour ago", "1 day ago". All future times will be returned as 0 minutes
+ * ago.
+ *
+ * This is provided for compatibility with users of the previous incarnation of
+ * the above {@see #format} method who relied on it protecting against
+ * future dates.
+ *
+ * @param {number} dateMs Date in milliseconds.
+ * @return {string} The formatted date.
+ */
+goog.date.relative.formatPast = function(dateMs) {
+ var now = goog.now();
+ if (now < dateMs) {
+ dateMs = now;
+ }
+ return goog.date.relative.format(dateMs);
+};
+
+
+/**
+ * Accepts a timestamp in milliseconds and outputs a relative day. i.e. "Today",
+ * "Yesterday" or "Sept 15".
+ *
+ * @param {number} dateMs Date in milliseconds.
+ * @return {string} The formatted date.
+ */
+goog.date.relative.formatDay = function(dateMs) {
+ var message;
+ var today = new Date(goog.now());
+
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(0);
+ today.setMilliseconds(0);
+
+ var yesterday = new Date(today.getTime() - goog.date.relative.DAY_MS_);
+ if (today.getTime() < dateMs) {
+ /** @desc Today. */
+ var MSG_TODAY = goog.getMsg('Today');
+ message = MSG_TODAY;
+ } else if (yesterday.getTime() < dateMs) {
+ /** @desc Yesterday. */
+ var MSG_YESTERDAY = goog.getMsg('Yesterday');
+ message = MSG_YESTERDAY;
+ } else {
+ message = goog.date.relative.formatMonth_(new Date(dateMs));
+ }
+ return message;
+};
+
+
+/**
+ * Formats a date, adding the relative date in parenthesis. If the date is less
+ * than 24 hours then the time will be printed, otherwise the full-date will be
+ * used. Examples:
+ * 2:20 PM (1 minute ago)
+ * Monday, February 27, 2009 (4 days ago)
+ * Tuesday, March 20, 2005 // Too long ago for a relative date.
+ *
+ * @param {Date} date A date object.
+ * @param {string} opt_shortTimeMsg An optional short time message can be
+ * provided if available, so that it's not recalculated in this function.
+ * @param {string} opt_fullDateMsg An optional date message can be
+ * provided if available, so that it's not recalculated in this function.
+ * @return {string} The date string in the above form.
+ */
+goog.date.relative.getDateString = function(
+ date, opt_shortTimeMsg, opt_fullDateMsg) {
+ return goog.date.relative.getDateString_(
+ date, goog.date.relative.format, opt_shortTimeMsg, opt_fullDateMsg)
+};
+
+
+/**
+ * Formats a date, adding the relative date in parenthesis. Functions the same
+ * as #getDateString but ensures that the date is always seen to be in the past.
+ * If the date is in the future, it will be shown as 0 minutes ago.
+ *
+ * This is provided for compatibility with users of the previous incarnation of
+ * the above {@see #getDateString} method who relied on it protecting against
+ * future dates.
+ *
+ * @param {Date} date A timestamp or date object.
+ * @param {string} opt_shortTimeMsg An optional short time message can be
+ * provided if available, so that it's not recalculated in this function.
+ * @param {string} opt_fullDateMsg An optional date message can be
+ * provided if available, so that it's not recalculated in this function.
+ * @return {string} The date string in the above form.
+ */
+goog.date.relative.getPastDateString = function(
+ date, opt_shortTimeMsg, opt_fullDateMsg) {
+ return goog.date.relative.getDateString_(
+ date, goog.date.relative.formatPast, opt_shortTimeMsg, opt_fullDateMsg)
+};
+
+
+/**
+ * Formats a date, adding the relative date in parenthesis. If the date is less
+ * than 24 hours then the time will be printed, otherwise the full-date will be
+ * used. Examples:
+ * 2:20 PM (1 minute ago)
+ * Monday, February 27, 2009 (4 days ago)
+ * Tuesday, March 20, 2005 // Too long ago for a relative date.
+ *
+ * @param {Date} date A timestamp or date object.
+ * @param {function(number) : string} relativeFormatter Function to use when
+ * formatting the relative date.
+ * @param {string} opt_shortTimeMsg An optional short time message can be
+ * provided if available, so that it's not recalculated in this function.
+ * @param {string} opt_fullDateMsg An optional date message can be
+ * provided if available, so that it's not recalculated in this function.
+ * @return {string} The date string in the above form.
+ * @private
+ */
+goog.date.relative.getDateString_ = function(
+ date, relativeFormatter, opt_shortTimeMsg, opt_fullDateMsg) {
+ var dateMs = date.getTime();
+
+ var relativeDate = relativeFormatter(dateMs);
+
+ if (relativeDate) {
+ relativeDate = ' (' + relativeDate + ')';
+ }
+
+ var delta = Math.floor((goog.now() - dateMs) / goog.date.relative.MINUTE_MS_);
+ if (delta < 60 * 24) {
+ return (opt_shortTimeMsg || goog.date.relative.formatShortTime_(date)) +
+ relativeDate;
+ } else {
+ return (opt_fullDateMsg || goog.date.relative.formatFullDate_(date)) +
+ relativeDate;
+ }
+};
+
+
+/**
+ * Gets a localized relative date string for a given delta and unit.
+ * @param {number} delta Number of minutes/hours/days.
+ * @param {boolean} future Whether the delta is in the future.
+ * @param {goog.date.relative.Unit_} unit The units the delta is in.
+ * @return {string} The message.
+ * @private
+ */
+goog.date.relative.getMessage_ = function(delta, future, unit) {
+ if (!future && unit == goog.date.relative.Unit_.MINUTES) {
+ /**
+ * @desc Relative date indicating how many minutes ago something happened
+ * (singular).
+ */
+ var MSG_MINUTES_AGO_SINGULAR =
+ goog.getMsg('{$num} minute ago', {'num' : delta});
+
+ /**
+ * @desc Relative date indicating how many minutes ago something happened
+ * (plural).
+ */
+ var MSG_MINUTES_AGO_PLURAL =
+ goog.getMsg('{$num} minutes ago', {'num' : delta});
+
+ return delta == 1 ? MSG_MINUTES_AGO_SINGULAR : MSG_MINUTES_AGO_PLURAL;
+
+ } else if (future && unit == goog.date.relative.Unit_.MINUTES) {
+ /**
+ * @desc Relative date indicating in how many minutes something happens
+ * (singular).
+ */
+ var MSG_IN_MINUTES_SINGULAR =
+ goog.getMsg('in {$num} minute', {'num' : delta});
+
+ /**
+ * @desc Relative date indicating in how many minutes something happens
+ * (plural).
+ */
+ var MSG_IN_MINUTES_PLURAL =
+ goog.getMsg('in {$num} minutes', {'num' : delta});
+
+ return delta == 1 ? MSG_IN_MINUTES_SINGULAR : MSG_IN_MINUTES_PLURAL;
+
+ } else if (!future && unit == goog.date.relative.Unit_.HOURS) {
+ /**
+ * @desc Relative date indicating how many hours ago something happened
+ * (singular).
+ */
+ var MSG_HOURS_AGO_SINGULAR =
+ goog.getMsg('{$num} hour ago', {'num' : delta});
+
+ /**
+ * @desc Relative date indicating how many hours ago something happened
+ * (plural).
+ */
+ var MSG_HOURS_AGO_PLURAL = goog.getMsg('{$num} hours ago', {'num' : delta});
+
+ return delta == 1 ? MSG_HOURS_AGO_SINGULAR : MSG_HOURS_AGO_PLURAL;
+
+ } else if (future && unit == goog.date.relative.Unit_.HOURS) {
+ /**
+ * @desc Relative date indicating in how many hours something happens
+ * (singular).
+ */
+ var MSG_IN_HOURS_SINGULAR = goog.getMsg('in {$num} hour', {'num' : delta});
+
+ /**
+ * @desc Relative date indicating in how many hours something happens
+ * (plural).
+ */
+ var MSG_IN_HOURS_PLURAL = goog.getMsg('in {$num} hours', {'num' : delta});
+
+ return delta == 1 ? MSG_IN_HOURS_SINGULAR : MSG_IN_HOURS_PLURAL;
+
+ } else if (!future && unit == goog.date.relative.Unit_.DAYS) {
+ /**
+ * @desc Relative date indicating how many days ago something happened
+ * (singular).
+ */
+ var MSG_DAYS_AGO_SINGULAR = goog.getMsg('{$num} day ago', {'num' : delta});
+
+ /**
+ * @desc Relative date indicating how many days ago something happened
+ * (plural).
+ */
+ var MSG_DAYS_AGO_PLURAL = goog.getMsg('{$num} days ago', {'num' : delta});
+
+ return delta == 1 ? MSG_DAYS_AGO_SINGULAR : MSG_DAYS_AGO_PLURAL;
+
+ } else if (future && unit == goog.date.relative.Unit_.DAYS) {
+ /**
+ * @desc Relative date indicating in how many days something happens
+ * (singular).
+ */
+ var MSG_IN_DAYS_SINGULAR = goog.getMsg('in {$num} day', {'num' : delta});
+
+ /**
+ * @desc Relative date indicating in how many days something happens
+ * (plural).
+ */
+ var MSG_IN_DAYS_PLURAL = goog.getMsg('in {$num} days', {'num' : delta});
+
+ return delta == 1 ? MSG_IN_DAYS_SINGULAR : MSG_IN_DAYS_PLURAL;
+
+ } else {
+ return '';
+ }
+};
176 lib/goog/date/utcdatetime.js
@@ -0,0 +1,176 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+/**
+ * @fileoverview Locale independent date/time class.
+ *
+ */
+
+goog.provide('goog.date.UtcDateTime');
+
+goog.require('goog.date');
+goog.require('goog.date.Date');
+goog.require('goog.date.DateTime');
+goog.require('goog.date.Interval');
+
+
+
+/**
+ * Class representing a date/time in GMT+0 time zone, without daylight saving.
+ * Defaults to current date and time if none is specified. The get... and the
+ * getUTC... methods are equivalent.
+ *
+ * @param {number|Object} opt_year Four digit UTC year or a date-like object.
+ * If not set, the created object will contain the date determined by
+ * goog.now().
+ * @param {number} opt_month UTC month, 0 = Jan, 11 = Dec.
+ * @param {number} opt_date UTC date of month, 1 - 31.
+ * @param {number} opt_hours UTC hours, 0 - 23.
+ * @param {number} opt_minutes UTC minutes, 0 - 59.
+ * @param {number} opt_seconds UTC seconds, 0 - 59.
+ * @param {number} opt_milliseconds UTC milliseconds, 0 - 999.
+ * @constructor
+ * @extends {goog.date.DateTime}
+ */
+goog.date.UtcDateTime = function(opt_year, opt_month, opt_date, opt_hours,
+ opt_minutes, opt_seconds, opt_milliseconds) {
+ var timestamp;
+ if (goog.isNumber(opt_year)) {
+ timestamp = Date.UTC(opt_year, opt_month || 0, opt_date || 1,
+ opt_hours || 0, opt_minutes || 0, opt_seconds || 0,
+ opt_milliseconds || 0);
+ } else {
+ timestamp = opt_year ? opt_year.getTime() : goog.now();
+ }
+ this.date_ = new Date(timestamp);
+};
+goog.inherits(goog.date.UtcDateTime, goog.date.DateTime);
+
+
+/**
+ * Creates a DateTime from a UTC datetime string expressed in ISO 8601 format.
+ *
+ * @param {string} formatted A date or datetime expressed in ISO 8601 format.
+ * @return {goog.date.UtcDateTime} Parsed date or null if parse fails.
+ */
+goog.date.UtcDateTime.fromIsoString = function(formatted) {
+ var ret = new goog.date.UtcDateTime(2000);
+ return goog.date.setIso8601DateTime(ret, formatted) ? ret : null;
+};
+
+
+/**
+ * Clones the UtcDateTime object.
+ *
+ * @return {goog.date.UtcDateTime} A clone of the datetime object.
+ */
+goog.date.UtcDateTime.prototype.clone = function() {
+ var date = new goog.date.UtcDateTime(this.date_);
+ date.firstDayOfWeek_ = this.firstDayOfWeek_;
+ date.firstWeekCutOffDay_ = this.firstWeekCutOffDay_;
+ return date;
+};
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.add = function(interval) {
+ if (interval.years || interval.months) {
+ var yearsMonths = new goog.date.Interval(interval.years, interval.months);
+ goog.date.Date.prototype.add.call(this, yearsMonths);
+ }
+ var daysAndTimeMillis = 1000 * (interval.seconds + 60 * (interval.minutes +
+ 60 * (interval.hours + 24 * interval.days)));
+ this.date_ = new Date(this.date_.getTime() + daysAndTimeMillis);
+};
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getTimezoneOffset = function() {
+ return 0;
+};
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getFullYear =
+ goog.date.DateTime.prototype.getUTCFullYear;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getMonth =
+ goog.date.DateTime.prototype.getUTCMonth;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getDate =
+ goog.date.DateTime.prototype.getUTCDate;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getHours =
+ goog.date.DateTime.prototype.getUTCHours;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getMinutes =
+ goog.date.DateTime.prototype.getUTCMinutes;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getSeconds =
+ goog.date.DateTime.prototype.getUTCSeconds;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getMilliseconds =
+ goog.date.DateTime.prototype.getUTCMilliseconds;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.getDay =
+ goog.date.DateTime.prototype.getUTCDay;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.setFullYear =
+ goog.date.DateTime.prototype.setUTCFullYear;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.setMonth =
+ goog.date.DateTime.prototype.setUTCMonth;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.setDate =
+ goog.date.DateTime.prototype.setUTCDate;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.setHours =
+ goog.date.DateTime.prototype.setUTCHours;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.setMinutes =
+ goog.date.DateTime.prototype.setUTCMinutes;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.setSeconds =
+ goog.date.DateTime.prototype.setUTCSeconds;
+
+
+/** @inheritDoc */
+goog.date.UtcDateTime.prototype.setMilliseconds =
+ goog.date.DateTime.prototype.setUTCMilliseconds;
241 lib/goog/math/bezier.js
@@ -0,0 +1,241 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Copyright 2007 Google Inc. All Rights Reserved.
+
+
+/**
+ * @fileoverview Represents a cubic Bezier curve.
+ *
+ * Uses the deCasteljau algorithm to compute points on the curve.
+ * http://en.wikipedia.org/wiki/De_Casteljau's_algorithm
+ *
+ * Currently it uses an unrolled version of the algorithm for speed. Eventually
+ * it may be useful to use the loop form of the algorithm in order to support
+ * curves of arbitrary degree.
+ *
+ */
+
+goog.provide('goog.math.Bezier');
+
+goog.require('goog.math');
+goog.require('goog.math.Coordinate');
+
+
+/**
+ * Object representing a cubic bezier curve.
+ * @param {number} x0 X coordinate of the start point.
+ * @param {number} y0 Y coordinate of the start point.
+ * @param {number} x1 X coordinate of the first control point.
+ * @param {number} y1 Y coordinate of the first control point.
+ * @param {number} x2 X coordinate of the second control point.
+ * @param {number} y2 Y coordinate of the second control point.
+ * @param {number} x3 X coordinate of the end point.
+ * @param {number} y3 Y coordinate of the end point.
+ * @constructor
+ */
+goog.math.Bezier = function(x0, y0, x1, y1, x2, y2, x3, y3) {
+ /**
+ * X coordinate of the first point.
+ * @type {number}
+ */
+ this.x0 = x0;
+
+ /**
+ * Y coordinate of the first point.
+ * @type {number}
+ */
+ this.y0 = y0;
+
+ /**
+ * X coordinate of the first control point.
+ * @type {number}
+ */
+ this.x1 = x1;
+
+ /**
+ * Y coordinate of the first control point.
+ * @type {number}
+ */
+ this.y1 = y1;
+
+ /**
+ * X coordinate of the second control point.
+ * @type {number}
+ */
+ this.x2 = x2;
+
+ /**
+ * Y coordinate of the second control point.
+ * @type {number}
+ */
+ this.y2 = y2;
+
+ /**
+ * X coordinate of the end point.
+ * @type {number}
+ */
+ this.x3 = x3;
+
+ /**
+ * Y coordinate of the end point.
+ * @type {number}
+ */
+ this.y3 = y3;
+};
+
+
+/**
+ * Constant used to approximate ellipses.
+ * See: http://canvaspaint.org/blog/2006/12/ellipse/
+ * @type {number}
+ */
+goog.math.Bezier.KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
+
+
+/**
+ * @return {goog.math.Bezier} A copy of this curve.
+ */
+goog.math.Bezier.prototype.clone = function() {
+ return new goog.math.Bezier(this.x0, this.y0, this.x1, this.y1, this.x2,
+ this.y2, this.x3, this.y3);
+};
+
+
+/**
+ * Test if the given curve is exactly the same as this one.
+ * @param {goog.math.Bezier} other The other curve.
+ * @return {boolean} Whether the given curve is the same as this one.
+ */
+goog.math.Bezier.prototype.equals = function(other) {
+ return this.x0 == other.x0 && this.y0 == other.y0 && this.x1 == other.x1 &&
+ this.y1 == other.y1 && this.x2 == other.x2 && this.y2 == other.y2 &&
+ this.x3 == other.x3 && this.y3 == other.y3;
+};
+
+
+/**
+ * Modifies the curve in place to progress in the opposite direction.
+ */
+goog.math.Bezier.prototype.flip = function() {
+ var temp = this.x0;
+ this.x0 = this.x3;
+ this.x3 = temp;
+ temp = this.y0;
+ this.y0 = this.y3;
+ this.y3 = temp;
+
+ temp = this.x1;
+ this.x1 = this.x2;
+ this.x2 = temp;
+ temp = this.y1;
+ this.y1 = this.y2;
+ this.y2 = temp;
+};
+
+
+/**
+ * Computes the curve at a point between 0 and 1.
+ * @param {number} t The point on the curve to find.
+ * @return {goog.math.Coordinate} The computed coordinate.
+ */
+goog.math.Bezier.prototype.getPoint = function(t) {
+ // Special case start and end
+ if (t == 0) {
+ return new goog.math.Coordinate(this.x0, this.y0);
+ } else if (t == 1) {
+ return new goog.math.Coordinate(this.x3, this.y3);
+ }
+
+ // Step one - from 4 points to 3
+ var ix0 = goog.math.lerp(this.x0, this.x1, t);
+ var iy0 = goog.math.lerp(this.y0, this.y1, t);
+
+ var ix1 = goog.math.lerp(this.x1, this.x2, t);
+ var iy1 = goog.math.lerp(this.y1, this.y2, t);
+
+ var ix2 = goog.math.lerp(this.x2, this.x3, t);
+ var iy2 = goog.math.lerp(this.y2, this.y3, t);
+
+ // Step two - from 3 points to 2
+ ix0 = goog.math.lerp(ix0, ix1, t);
+ iy0 = goog.math.lerp(iy0, iy1, t);
+
+ ix1 = goog.math.lerp(ix1, ix2, t);
+ iy1 = goog.math.lerp(iy1, iy2, t);
+
+ // Final step - last point
+ return new goog.math.Coordinate(goog.math.lerp(ix0, ix1, t),
+ goog.math.lerp(iy0, iy1, t));
+};
+
+
+/**
+ * Changes this curve in place to be the portion of itself from [t, 1].
+ * @param {number} t The start of the desired portion of the curve.
+ */
+goog.math.Bezier.prototype.subdivideLeft = function(t) {
+ if (t == 1) {
+ return;
+ }
+
+ // Step one - from 4 points to 3
+ var ix0 = goog.math.lerp(this.x0, this.x1, t);
+ var iy0 = goog.math.lerp(this.y0, this.y1, t);
+
+ var ix1 = goog.math.lerp(this.x1, this.x2, t);
+ var iy1 = goog.math.lerp(this.y1, this.y2, t);
+
+ var ix2 = goog.math.lerp(this.x2, this.x3, t);
+ var iy2 = goog.math.lerp(this.y2, this.y3, t);
+
+ // Collect our new x1 and y1
+ this.x1 = ix0;
+ this.y1 = iy0;
+
+ // Step two - from 3 points to 2
+ ix0 = goog.math.lerp(ix0, ix1, t);
+ iy0 = goog.math.lerp(iy0, iy1, t);
+
+ ix1 = goog.math.lerp(ix1, ix2, t);
+ iy1 = goog.math.lerp(iy1, iy2, t);
+
+ // Collect our new x2 and y2
+ this.x2 = ix0;
+ this.y2 = iy0;
+
+ // Final step - last point
+ this.x3 = goog.math.lerp(ix0, ix1, t);
+ this.y3 = goog.math.lerp(iy0, iy1, t);
+};
+
+
+/**
+ * Changes this curve in place to be the portion of itself from [0, t].
+ * @param {number} t The end of the desired portion of the curve.
+ */
+goog.math.Bezier.prototype.subdivideRight = function(t) {
+ this.flip();
+ this.subdivideLeft(1 - t);
+ this.flip();
+};
+
+
+/**
+ * Changes this curve in place to be the portion of itself from [s, t].
+ * @param {number} s The start of the desired portion of the curve.
+ * @param {number} t The end of the desired portion of the curve.
+ */
+goog.math.Bezier.prototype.subdivide = function(s, t) {
+ this.subdivideRight(s);
+ this.subdivideLeft((t - s) / (1 - s));
+};
208 lib/goog/math/box.js
@@ -0,0 +1,208 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+
+/**
+ * @fileoverview A utility class for representing a numeric box.
+ */
+
+
+goog.provide('goog.math.Box');
+
+goog.require('goog.math.Coordinate');
+
+
+
+/**
+ * Class for representing a box. A box is specified as a top, right, bottom,
+ * and left. A box is useful for representing margins and padding.
+ *
+ * @param {number} top Top.
+ * @param {number} right Right.
+ * @param {number} bottom Bottom.
+ * @param {number} left Left.
+ * @constructor
+ */
+goog.math.Box = function(top, right, bottom, left) {
+ /**
+ * Top
+ * @type {number}
+ */
+ this.top = top;
+
+ /**
+ * Right
+ * @type {number}
+ */
+ this.right = right;
+
+ /**
+ * Bottom
+ * @type {number}
+ */
+ this.bottom = bottom;
+
+ /**
+ * Left
+ * @type {number}
+ */
+ this.left = left;
+};
+
+
+/**
+ * Creates a Box by bounding a collection of goog.math.Coordinate objects
+ * @param {goog.math.Coordinate} var_args Coordinates to be included inside the
+ * box.
+ * @return {goog.math.Box} A Box containing all the specified Coordinates.
+ */
+goog.math.Box.boundingBox = function(var_args) {
+ var box = new goog.math.Box(arguments[0].y, arguments[0].x,
+ arguments[0].y, arguments[0].x);
+ for (var i = 1; i < arguments.length; i++) {
+ var coord = arguments[i];
+ box.top = Math.min(box.top, coord.y);
+ box.right = Math.max(box.right, coord.x);
+ box.bottom = Math.max(box.bottom, coord.y);
+ box.left = Math.min(box.left, coord.x);
+ }
+ return box;
+};
+
+
+/**
+ * Creates a copy of the box with the same dimensions.
+ * @return {goog.math.Box} A clone of this Box.
+ */
+goog.math.Box.prototype.clone = function() {
+ return new goog.math.Box(this.top, this.right, this.bottom, this.left);
+};
+
+
+if (goog.DEBUG) {
+ /**
+ * Returns a nice string representing the box.
+ * @return {string} In the form (50t, 73r, 24b, 13l).
+ */
+ goog.math.Box.prototype.toString = function() {
+ return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' +
+ this.left + 'l)';
+ };
+}
+
+
+/**
+ * Returns whether the box contains a coordinate or another box.
+ *
+ * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
+ * @return {boolean} Whether the box contains the coordinate or other box.
+ */
+goog.math.Box.prototype.contains = function(other) {
+ return goog.math.Box.contains(this, other);
+};
+
+
+/**
+ * Expands box with the given margins.
+ *
+ * @param {number|goog.math.Box} top Top margin or box with all margins.
+ * @param {number} opt_right Right margin.
+ * @param {number} opt_bottom Bottom margin.
+ * @param {number} opt_left Left margin.
+ * @return {goog.math.Box} A reference to this Box.
+ */
+goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom,
+ opt_left) {
+ if (goog.isObject(top)) {
+ this.top -= top.top;
+ this.right += top.right;
+ this.bottom += top.bottom;
+ this.left -= top.left;
+ } else {
+ this.top -= top;
+ this.right += opt_right;
+ this.bottom += opt_bottom;
+ this.left -= opt_left;
+ }
+
+ return this;
+};
+
+
+/**
+ * Compares boxes for equality.
+ * @param {goog.math.Box} a A Box.
+ * @param {goog.math.Box} b A Box.
+ * @return {boolean} True iff the boxes are equal, or if both are null.
+ */
+goog.math.Box.equals = function(a, b) {
+ if (a == b) {
+ return true;
+ }
+ if (!a || !b) {
+ return false;
+ }
+ return a.top == b.top && a.right == b.right &&
+ a.bottom == b.bottom && a.left == b.left;
+};
+
+
+/**
+ * Returns whether a box contains a coordinate.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
+ * @return {boolean} Whether the box contains the coordinate or other box.
+ */
+goog.math.Box.contains = function(box, other) {
+ if (!box || !other) {
+ return false;
+ }
+
+ if (other instanceof goog.math.Box) {
+ return other.left >= box.left && other.right <= box.right &&
+ other.top >= box.top && other.bottom <= box.bottom;
+ }
+
+ // other is a Coordinate.
+ return other.x >= box.left && other.x <= box.right &&
+ other.y >= box.top && other.y <= box.bottom;
+};
+
+
+/**
+ * Returns the distance between a coordinate and the nearest corner/side of a
+ * box. Returns zero if the coordinate is inside the box.
+ *
+ * @param {goog.math.Box} box A Box.
+ * @param {goog.math.Coordinate} coord A Coordinate.
+ * @return {number} The distance between {@code coord} and the nearest
+ * corner/side of {@code box}, or zero if {@code coord} is inside
+ * {@code box}.
+ */
+goog.math.Box.distance = function(box, coord) {
+ if (coord.x >= box.left && coord.x <= box.right) {
+ if (coord.y >= box.top && coord.y <= box.bottom) {
+ return 0;
+ }
+ return coord.y < box.top ? box.top - coord.y : coord.y - box.bottom;
+ }
+
+ if (coord.y >= box.top && coord.y <= box.bottom) {
+ return coord.x < box.left ? box.left - coord.x : coord.x - box.right;
+ }
+
+ return goog.math.Coordinate.distance(coord,
+ new goog.math.Coordinate(coord.x < box.left ? box.left : box.right,
+ coord.y < box.top ? box.top : box.bottom));
+};
135 lib/goog/math/coordinate.js
@@ -0,0 +1,135 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Copyright 2006 Google Inc. All Rights Reserved.
+
+/**
+ * @fileoverview A utility class for representing two-dimensional positions.
+ */
+
+
+goog.provide('goog.math.Coordinate');
+
+
+/**
+ * Class for representing coordinates and positions.
+ * @param {number} opt_x Left, defaults to 0.
+ * @param {number} opt_y Top, defaults to 0.
+ * @constructor
+ */
+goog.math.Coordinate = function(opt_x, opt_y) {
+ /**
+ * X-value
+ * @type {number}
+ */
+ this.x = goog.isDef(opt_x) ? opt_x : 0;
+
+ /**