From 66483d114baff67364ad45dba53d62de9f26cae8 Mon Sep 17 00:00:00 2001
From: Romain Courteaud <romain@nexedi.com>
Date: Fri, 24 Jun 2016 07:28:58 +0000
Subject: [PATCH] Update jio.js to 3.11.0

---
 .../web_page_module/rjs_jio_js.js             |  5514 ++++--
 .../web_page_module/rjs_jio_js.xml            |     4 +-
 .../portal_skins/erp5_core/jio.js.js          | 15776 +++++++++-------
 3 files changed, 12593 insertions(+), 8701 deletions(-)

diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js
index 1d8f142f5b..de2f614c12 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js
@@ -1527,422 +1527,243 @@ if (typeof define === 'function' && define.amd) {
   module.exports = LZString
 }
 ;//! moment.js
-//! version : 2.5.0
+//! version : 2.13.0
 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
 //! license : MIT
 //! momentjs.com
 
-(function (undefined) {
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+    typeof define === 'function' && define.amd ? define(factory) :
+    global.moment = factory()
+}(this, function () { 'use strict';
 
-    /************************************
-        Constants
-    ************************************/
+    var hookCallback;
 
-    var moment,
-        VERSION = "2.5.0",
-        global = this,
-        round = Math.round,
-        i,
-
-        YEAR = 0,
-        MONTH = 1,
-        DATE = 2,
-        HOUR = 3,
-        MINUTE = 4,
-        SECOND = 5,
-        MILLISECOND = 6,
-
-        // internal storage for language config files
-        languages = {},
-
-        // check for nodeJS
-        hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'),
-
-        // ASP.NET json date format regex
-        aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
-        aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
-
-        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
-        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
-        isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
-
-        // format tokens
-        formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,
-        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
-
-        // parsing token regexes
-        parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
-        parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
-        parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
-        parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
-        parseTokenDigits = /\d+/, // nonzero number of digits
-        parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
-        parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
-        parseTokenT = /T/i, // T (ISO separator)
-        parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
-
-        //strict parsing regexes
-        parseTokenOneDigit = /\d/, // 0 - 9
-        parseTokenTwoDigits = /\d\d/, // 00 - 99
-        parseTokenThreeDigits = /\d{3}/, // 000 - 999
-        parseTokenFourDigits = /\d{4}/, // 0000 - 9999
-        parseTokenSixDigits = /[+\-]?\d{6}/, // -999,999 - 999,999
-
-        // iso 8601 regex
-        // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
-        isoRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
-
-        isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
-
-        isoDates = [
-            'YYYY-MM-DD',
-            'GGGG-[W]WW',
-            'GGGG-[W]WW-E',
-            'YYYY-DDD'
-        ],
-
-        // iso time formats and regexes
-        isoTimes = [
-            ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
-            ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
-            ['HH:mm', /(T| )\d\d:\d\d/],
-            ['HH', /(T| )\d\d/]
-        ],
-
-        // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
-        parseTimezoneChunker = /([\+\-]|\d\d)/gi,
-
-        // getter and setter names
-        proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
-        unitMillisecondFactors = {
-            'Milliseconds' : 1,
-            'Seconds' : 1e3,
-            'Minutes' : 6e4,
-            'Hours' : 36e5,
-            'Days' : 864e5,
-            'Months' : 2592e6,
-            'Years' : 31536e6
-        },
-
-        unitAliases = {
-            ms : 'millisecond',
-            s : 'second',
-            m : 'minute',
-            h : 'hour',
-            d : 'day',
-            D : 'date',
-            w : 'week',
-            W : 'isoWeek',
-            M : 'month',
-            y : 'year',
-            DDD : 'dayOfYear',
-            e : 'weekday',
-            E : 'isoWeekday',
-            gg: 'weekYear',
-            GG: 'isoWeekYear'
-        },
-
-        camelFunctions = {
-            dayofyear : 'dayOfYear',
-            isoweekday : 'isoWeekday',
-            isoweek : 'isoWeek',
-            weekyear : 'weekYear',
-            isoweekyear : 'isoWeekYear'
-        },
-
-        // format function strings
-        formatFunctions = {},
-
-        // tokens to ordinalize and pad
-        ordinalizeTokens = 'DDD w W M D d'.split(' '),
-        paddedTokens = 'M D H h m s w W'.split(' '),
-
-        formatTokenFunctions = {
-            M    : function () {
-                return this.month() + 1;
-            },
-            MMM  : function (format) {
-                return this.lang().monthsShort(this, format);
-            },
-            MMMM : function (format) {
-                return this.lang().months(this, format);
-            },
-            D    : function () {
-                return this.date();
-            },
-            DDD  : function () {
-                return this.dayOfYear();
-            },
-            d    : function () {
-                return this.day();
-            },
-            dd   : function (format) {
-                return this.lang().weekdaysMin(this, format);
-            },
-            ddd  : function (format) {
-                return this.lang().weekdaysShort(this, format);
-            },
-            dddd : function (format) {
-                return this.lang().weekdays(this, format);
-            },
-            w    : function () {
-                return this.week();
-            },
-            W    : function () {
-                return this.isoWeek();
-            },
-            YY   : function () {
-                return leftZeroFill(this.year() % 100, 2);
-            },
-            YYYY : function () {
-                return leftZeroFill(this.year(), 4);
-            },
-            YYYYY : function () {
-                return leftZeroFill(this.year(), 5);
-            },
-            YYYYYY : function () {
-                var y = this.year(), sign = y >= 0 ? '+' : '-';
-                return sign + leftZeroFill(Math.abs(y), 6);
-            },
-            gg   : function () {
-                return leftZeroFill(this.weekYear() % 100, 2);
-            },
-            gggg : function () {
-                return this.weekYear();
-            },
-            ggggg : function () {
-                return leftZeroFill(this.weekYear(), 5);
-            },
-            GG   : function () {
-                return leftZeroFill(this.isoWeekYear() % 100, 2);
-            },
-            GGGG : function () {
-                return this.isoWeekYear();
-            },
-            GGGGG : function () {
-                return leftZeroFill(this.isoWeekYear(), 5);
-            },
-            e : function () {
-                return this.weekday();
-            },
-            E : function () {
-                return this.isoWeekday();
-            },
-            a    : function () {
-                return this.lang().meridiem(this.hours(), this.minutes(), true);
-            },
-            A    : function () {
-                return this.lang().meridiem(this.hours(), this.minutes(), false);
-            },
-            H    : function () {
-                return this.hours();
-            },
-            h    : function () {
-                return this.hours() % 12 || 12;
-            },
-            m    : function () {
-                return this.minutes();
-            },
-            s    : function () {
-                return this.seconds();
-            },
-            S    : function () {
-                return toInt(this.milliseconds() / 100);
-            },
-            SS   : function () {
-                return leftZeroFill(toInt(this.milliseconds() / 10), 2);
-            },
-            SSS  : function () {
-                return leftZeroFill(this.milliseconds(), 3);
-            },
-            SSSS : function () {
-                return leftZeroFill(this.milliseconds(), 3);
-            },
-            Z    : function () {
-                var a = -this.zone(),
-                    b = "+";
-                if (a < 0) {
-                    a = -a;
-                    b = "-";
-                }
-                return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2);
-            },
-            ZZ   : function () {
-                var a = -this.zone(),
-                    b = "+";
-                if (a < 0) {
-                    a = -a;
-                    b = "-";
-                }
-                return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
-            },
-            z : function () {
-                return this.zoneAbbr();
-            },
-            zz : function () {
-                return this.zoneName();
-            },
-            X    : function () {
-                return this.unix();
-            },
-            Q : function () {
-                return this.quarter();
-            }
-        },
-
-        lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
-
-    function padToken(func, count) {
-        return function (a) {
-            return leftZeroFill(func.call(this, a), count);
-        };
-    }
-    function ordinalizeToken(func, period) {
-        return function (a) {
-            return this.lang().ordinal(func.call(this, a), period);
-        };
+    function utils_hooks__hooks () {
+        return hookCallback.apply(null, arguments);
     }
 
-    while (ordinalizeTokens.length) {
-        i = ordinalizeTokens.pop();
-        formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
-    }
-    while (paddedTokens.length) {
-        i = paddedTokens.pop();
-        formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
+    // This is done to register the method called with moment()
+    // without creating circular dependencies.
+    function setHookCallback (callback) {
+        hookCallback = callback;
     }
-    formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
-
-
-    /************************************
-        Constructors
-    ************************************/
-
-    function Language() {
 
+    function isArray(input) {
+        return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
     }
 
-    // Moment prototype object
-    function Moment(config) {
-        checkOverflow(config);
-        extend(this, config);
+    function isDate(input) {
+        return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
     }
 
-    // Duration Constructor
-    function Duration(duration) {
-        var normalizedInput = normalizeObjectUnits(duration),
-            years = normalizedInput.year || 0,
-            months = normalizedInput.month || 0,
-            weeks = normalizedInput.week || 0,
-            days = normalizedInput.day || 0,
-            hours = normalizedInput.hour || 0,
-            minutes = normalizedInput.minute || 0,
-            seconds = normalizedInput.second || 0,
-            milliseconds = normalizedInput.millisecond || 0;
-
-        // representation for dateAddRemove
-        this._milliseconds = +milliseconds +
-            seconds * 1e3 + // 1000
-            minutes * 6e4 + // 1000 * 60
-            hours * 36e5; // 1000 * 60 * 60
-        // Because of dateAddRemove treats 24 hours as different from a
-        // day when working around DST, we need to store them separately
-        this._days = +days +
-            weeks * 7;
-        // It is impossible translate months into days without knowing
-        // which months you are are talking about, so we have to store
-        // it separately.
-        this._months = +months +
-            years * 12;
-
-        this._data = {};
-
-        this._bubble();
+    function map(arr, fn) {
+        var res = [], i;
+        for (i = 0; i < arr.length; ++i) {
+            res.push(fn(arr[i], i));
+        }
+        return res;
     }
 
-    /************************************
-        Helpers
-    ************************************/
-
+    function hasOwnProp(a, b) {
+        return Object.prototype.hasOwnProperty.call(a, b);
+    }
 
     function extend(a, b) {
         for (var i in b) {
-            if (b.hasOwnProperty(i)) {
+            if (hasOwnProp(b, i)) {
                 a[i] = b[i];
             }
         }
 
-        if (b.hasOwnProperty("toString")) {
+        if (hasOwnProp(b, 'toString')) {
             a.toString = b.toString;
         }
 
-        if (b.hasOwnProperty("valueOf")) {
+        if (hasOwnProp(b, 'valueOf')) {
             a.valueOf = b.valueOf;
         }
 
         return a;
     }
 
-    function absRound(number) {
-        if (number < 0) {
-            return Math.ceil(number);
-        } else {
-            return Math.floor(number);
+    function create_utc__createUTC (input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, true).utc();
+    }
+
+    function defaultParsingFlags() {
+        // We need to deep clone this object.
+        return {
+            empty           : false,
+            unusedTokens    : [],
+            unusedInput     : [],
+            overflow        : -2,
+            charsLeftOver   : 0,
+            nullInput       : false,
+            invalidMonth    : null,
+            invalidFormat   : false,
+            userInvalidated : false,
+            iso             : false,
+            parsedDateParts : [],
+            meridiem        : null
+        };
+    }
+
+    function getParsingFlags(m) {
+        if (m._pf == null) {
+            m._pf = defaultParsingFlags();
         }
+        return m._pf;
     }
 
-    // left zero fill a number
-    // see http://jsperf.com/left-zero-filling for performance comparison
-    function leftZeroFill(number, targetLength, forceSign) {
-        var output = Math.abs(number) + '',
-            sign = number >= 0;
+    var some;
+    if (Array.prototype.some) {
+        some = Array.prototype.some;
+    } else {
+        some = function (fun) {
+            var t = Object(this);
+            var len = t.length >>> 0;
+
+            for (var i = 0; i < len; i++) {
+                if (i in t && fun.call(this, t[i], i, t)) {
+                    return true;
+                }
+            }
+
+            return false;
+        };
+    }
+
+    function valid__isValid(m) {
+        if (m._isValid == null) {
+            var flags = getParsingFlags(m);
+            var parsedParts = some.call(flags.parsedDateParts, function (i) {
+                return i != null;
+            });
+            m._isValid = !isNaN(m._d.getTime()) &&
+                flags.overflow < 0 &&
+                !flags.empty &&
+                !flags.invalidMonth &&
+                !flags.invalidWeekday &&
+                !flags.nullInput &&
+                !flags.invalidFormat &&
+                !flags.userInvalidated &&
+                (!flags.meridiem || (flags.meridiem && parsedParts));
 
-        while (output.length < targetLength) {
-            output = '0' + output;
+            if (m._strict) {
+                m._isValid = m._isValid &&
+                    flags.charsLeftOver === 0 &&
+                    flags.unusedTokens.length === 0 &&
+                    flags.bigHour === undefined;
+            }
         }
-        return (sign ? (forceSign ? '+' : '') : '-') + output;
+        return m._isValid;
     }
 
-    // helper function for _.addTime and _.subtractTime
-    function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) {
-        var milliseconds = duration._milliseconds,
-            days = duration._days,
-            months = duration._months,
-            minutes,
-            hours;
+    function valid__createInvalid (flags) {
+        var m = create_utc__createUTC(NaN);
+        if (flags != null) {
+            extend(getParsingFlags(m), flags);
+        }
+        else {
+            getParsingFlags(m).userInvalidated = true;
+        }
 
-        if (milliseconds) {
-            mom._d.setTime(+mom._d + milliseconds * isAdding);
+        return m;
+    }
+
+    function isUndefined(input) {
+        return input === void 0;
+    }
+
+    // Plugins that add properties should also add the key here (null value),
+    // so we can properly clone ourselves.
+    var momentProperties = utils_hooks__hooks.momentProperties = [];
+
+    function copyConfig(to, from) {
+        var i, prop, val;
+
+        if (!isUndefined(from._isAMomentObject)) {
+            to._isAMomentObject = from._isAMomentObject;
         }
-        // store the minutes and hours so we can restore them
-        if (days || months) {
-            minutes = mom.minute();
-            hours = mom.hour();
+        if (!isUndefined(from._i)) {
+            to._i = from._i;
         }
-        if (days) {
-            mom.date(mom.date() + days * isAdding);
+        if (!isUndefined(from._f)) {
+            to._f = from._f;
         }
-        if (months) {
-            mom.month(mom.month() + months * isAdding);
+        if (!isUndefined(from._l)) {
+            to._l = from._l;
+        }
+        if (!isUndefined(from._strict)) {
+            to._strict = from._strict;
+        }
+        if (!isUndefined(from._tzm)) {
+            to._tzm = from._tzm;
+        }
+        if (!isUndefined(from._isUTC)) {
+            to._isUTC = from._isUTC;
+        }
+        if (!isUndefined(from._offset)) {
+            to._offset = from._offset;
+        }
+        if (!isUndefined(from._pf)) {
+            to._pf = getParsingFlags(from);
         }
-        if (milliseconds && !ignoreUpdateOffset) {
-            moment.updateOffset(mom);
+        if (!isUndefined(from._locale)) {
+            to._locale = from._locale;
         }
-        // restore the minutes and hours after possibly changing dst
-        if (days || months) {
-            mom.minute(minutes);
-            mom.hour(hours);
+
+        if (momentProperties.length > 0) {
+            for (i in momentProperties) {
+                prop = momentProperties[i];
+                val = from[prop];
+                if (!isUndefined(val)) {
+                    to[prop] = val;
+                }
+            }
         }
+
+        return to;
     }
 
-    // check if is an array
-    function isArray(input) {
-        return Object.prototype.toString.call(input) === '[object Array]';
+    var updateInProgress = false;
+
+    // Moment prototype object
+    function Moment(config) {
+        copyConfig(this, config);
+        this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+        // Prevent infinite loop in case updateOffset creates new moment
+        // objects.
+        if (updateInProgress === false) {
+            updateInProgress = true;
+            utils_hooks__hooks.updateOffset(this);
+            updateInProgress = false;
+        }
     }
 
-    function isDate(input) {
-        return  Object.prototype.toString.call(input) === '[object Date]' ||
-                input instanceof Date;
+    function isMoment (obj) {
+        return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
+    }
+
+    function absFloor (number) {
+        if (number < 0) {
+            return Math.ceil(number);
+        } else {
+            return Math.floor(number);
+        }
+    }
+
+    function toInt(argumentForCoercion) {
+        var coercedNumber = +argumentForCoercion,
+            value = 0;
+
+        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+            value = absFloor(coercedNumber);
+        }
+
+        return value;
     }
 
     // compare two arrays, return the number of differences
@@ -1960,432 +1781,379 @@ if (typeof define === 'function' && define.amd) {
         return diffs + lengthDiff;
     }
 
-    function normalizeUnits(units) {
-        if (units) {
-            var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
-            units = unitAliases[units] || camelFunctions[lowered] || lowered;
+    function warn(msg) {
+        if (utils_hooks__hooks.suppressDeprecationWarnings === false &&
+                (typeof console !==  'undefined') && console.warn) {
+            console.warn('Deprecation warning: ' + msg);
         }
-        return units;
     }
 
-    function normalizeObjectUnits(inputObject) {
-        var normalizedInput = {},
-            normalizedProp,
-            prop;
+    function deprecate(msg, fn) {
+        var firstTime = true;
 
-        for (prop in inputObject) {
-            if (inputObject.hasOwnProperty(prop)) {
-                normalizedProp = normalizeUnits(prop);
-                if (normalizedProp) {
-                    normalizedInput[normalizedProp] = inputObject[prop];
-                }
+        return extend(function () {
+            if (utils_hooks__hooks.deprecationHandler != null) {
+                utils_hooks__hooks.deprecationHandler(null, msg);
             }
-        }
-
-        return normalizedInput;
+            if (firstTime) {
+                warn(msg + '\nArguments: ' + Array.prototype.slice.call(arguments).join(', ') + '\n' + (new Error()).stack);
+                firstTime = false;
+            }
+            return fn.apply(this, arguments);
+        }, fn);
     }
 
-    function makeList(field) {
-        var count, setter;
+    var deprecations = {};
 
-        if (field.indexOf('week') === 0) {
-            count = 7;
-            setter = 'day';
-        }
-        else if (field.indexOf('month') === 0) {
-            count = 12;
-            setter = 'month';
+    function deprecateSimple(name, msg) {
+        if (utils_hooks__hooks.deprecationHandler != null) {
+            utils_hooks__hooks.deprecationHandler(name, msg);
         }
-        else {
-            return;
+        if (!deprecations[name]) {
+            warn(msg);
+            deprecations[name] = true;
         }
+    }
 
-        moment[field] = function (format, index) {
-            var i, getter,
-                method = moment.fn._lang[field],
-                results = [];
-
-            if (typeof format === 'number') {
-                index = format;
-                format = undefined;
-            }
-
-            getter = function (i) {
-                var m = moment().utc().set(setter, i);
-                return method.call(moment.fn._lang, m, format || '');
-            };
+    utils_hooks__hooks.suppressDeprecationWarnings = false;
+    utils_hooks__hooks.deprecationHandler = null;
 
-            if (index != null) {
-                return getter(index);
-            }
-            else {
-                for (i = 0; i < count; i++) {
-                    results.push(getter(i));
-                }
-                return results;
-            }
-        };
+    function isFunction(input) {
+        return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
     }
 
-    function toInt(argumentForCoercion) {
-        var coercedNumber = +argumentForCoercion,
-            value = 0;
+    function isObject(input) {
+        return Object.prototype.toString.call(input) === '[object Object]';
+    }
 
-        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
-            if (coercedNumber >= 0) {
-                value = Math.floor(coercedNumber);
+    function locale_set__set (config) {
+        var prop, i;
+        for (i in config) {
+            prop = config[i];
+            if (isFunction(prop)) {
+                this[i] = prop;
             } else {
-                value = Math.ceil(coercedNumber);
+                this['_' + i] = prop;
             }
         }
-
-        return value;
+        this._config = config;
+        // Lenient ordinal parsing accepts just a number in addition to
+        // number + (possibly) stuff coming from _ordinalParseLenient.
+        this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source);
     }
 
-    function daysInMonth(year, month) {
-        return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
-    }
-
-    function daysInYear(year) {
-        return isLeapYear(year) ? 366 : 365;
+    function mergeConfigs(parentConfig, childConfig) {
+        var res = extend({}, parentConfig), prop;
+        for (prop in childConfig) {
+            if (hasOwnProp(childConfig, prop)) {
+                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                    res[prop] = {};
+                    extend(res[prop], parentConfig[prop]);
+                    extend(res[prop], childConfig[prop]);
+                } else if (childConfig[prop] != null) {
+                    res[prop] = childConfig[prop];
+                } else {
+                    delete res[prop];
+                }
+            }
+        }
+        return res;
     }
 
-    function isLeapYear(year) {
-        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    function Locale(config) {
+        if (config != null) {
+            this.set(config);
+        }
     }
 
-    function checkOverflow(m) {
-        var overflow;
-        if (m._a && m._pf.overflow === -2) {
-            overflow =
-                m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
-                m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
-                m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
-                m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
-                m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
-                m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
-                -1;
+    var keys;
 
-            if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
-                overflow = DATE;
+    if (Object.keys) {
+        keys = Object.keys;
+    } else {
+        keys = function (obj) {
+            var i, res = [];
+            for (i in obj) {
+                if (hasOwnProp(obj, i)) {
+                    res.push(i);
+                }
             }
-
-            m._pf.overflow = overflow;
-        }
+            return res;
+        };
     }
 
-    function initializeParsingFlags(config) {
-        config._pf = {
-            empty : false,
-            unusedTokens : [],
-            unusedInput : [],
-            overflow : -2,
-            charsLeftOver : 0,
-            nullInput : false,
-            invalidMonth : null,
-            invalidFormat : false,
-            userInvalidated : false,
-            iso: false
-        };
+    // internal storage for locale config files
+    var locales = {};
+    var globalLocale;
+
+    function normalizeLocale(key) {
+        return key ? key.toLowerCase().replace('_', '-') : key;
     }
 
-    function isValid(m) {
-        if (m._isValid == null) {
-            m._isValid = !isNaN(m._d.getTime()) &&
-                m._pf.overflow < 0 &&
-                !m._pf.empty &&
-                !m._pf.invalidMonth &&
-                !m._pf.nullInput &&
-                !m._pf.invalidFormat &&
-                !m._pf.userInvalidated;
+    // pick the locale from the array
+    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+    function chooseLocale(names) {
+        var i = 0, j, next, locale, split;
 
-            if (m._strict) {
-                m._isValid = m._isValid &&
-                    m._pf.charsLeftOver === 0 &&
-                    m._pf.unusedTokens.length === 0;
+        while (i < names.length) {
+            split = normalizeLocale(names[i]).split('-');
+            j = split.length;
+            next = normalizeLocale(names[i + 1]);
+            next = next ? next.split('-') : null;
+            while (j > 0) {
+                locale = loadLocale(split.slice(0, j).join('-'));
+                if (locale) {
+                    return locale;
+                }
+                if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+                    //the next array item is better than a shallower substring of this one
+                    break;
+                }
+                j--;
             }
+            i++;
         }
-        return m._isValid;
-    }
-
-    function normalizeLanguage(key) {
-        return key ? key.toLowerCase().replace('_', '-') : key;
+        return null;
     }
 
-    // Return a moment from input, that is local/utc/zone equivalent to model.
-    function makeAs(input, model) {
-        return model._isUTC ? moment(input).zone(model._offset || 0) :
-            moment(input).local();
+    function loadLocale(name) {
+        var oldLocale = null;
+        // TODO: Find a better way to register and load all the locales in Node
+        if (!locales[name] && (typeof module !== 'undefined') &&
+                module && module.exports) {
+            try {
+                oldLocale = globalLocale._abbr;
+                require('./locale/' + name);
+                // because defineLocale currently also sets the global locale, we
+                // want to undo that for lazy loaded locales
+                locale_locales__getSetGlobalLocale(oldLocale);
+            } catch (e) { }
+        }
+        return locales[name];
     }
 
-    /************************************
-        Languages
-    ************************************/
+    // This function will load locale and then set the global locale.  If
+    // no arguments are passed in, it will simply return the current global
+    // locale key.
+    function locale_locales__getSetGlobalLocale (key, values) {
+        var data;
+        if (key) {
+            if (isUndefined(values)) {
+                data = locale_locales__getLocale(key);
+            }
+            else {
+                data = defineLocale(key, values);
+            }
 
+            if (data) {
+                // moment.duration._locale = moment._locale = data;
+                globalLocale = data;
+            }
+        }
 
-    extend(Language.prototype, {
+        return globalLocale._abbr;
+    }
 
-        set : function (config) {
-            var prop, i;
-            for (i in config) {
-                prop = config[i];
-                if (typeof prop === 'function') {
-                    this[i] = prop;
+    function defineLocale (name, config) {
+        if (config !== null) {
+            config.abbr = name;
+            if (locales[name] != null) {
+                deprecateSimple('defineLocaleOverride',
+                        'use moment.updateLocale(localeName, config) to change ' +
+                        'an existing locale. moment.defineLocale(localeName, ' +
+                        'config) should only be used for creating a new locale');
+                config = mergeConfigs(locales[name]._config, config);
+            } else if (config.parentLocale != null) {
+                if (locales[config.parentLocale] != null) {
+                    config = mergeConfigs(locales[config.parentLocale]._config, config);
                 } else {
-                    this['_' + i] = prop;
+                    // treat as if there is no base config
+                    deprecateSimple('parentLocaleUndefined',
+                            'specified parentLocale is not defined yet');
                 }
             }
-        },
-
-        _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
-        months : function (m) {
-            return this._months[m.month()];
-        },
+            locales[name] = new Locale(config);
 
-        _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
-        monthsShort : function (m) {
-            return this._monthsShort[m.month()];
-        },
+            // backwards compat for now: also set the locale
+            locale_locales__getSetGlobalLocale(name);
 
-        monthsParse : function (monthName) {
-            var i, mom, regex;
+            return locales[name];
+        } else {
+            // useful for testing
+            delete locales[name];
+            return null;
+        }
+    }
 
-            if (!this._monthsParse) {
-                this._monthsParse = [];
+    function updateLocale(name, config) {
+        if (config != null) {
+            var locale;
+            if (locales[name] != null) {
+                config = mergeConfigs(locales[name]._config, config);
             }
+            locale = new Locale(config);
+            locale.parentLocale = locales[name];
+            locales[name] = locale;
 
-            for (i = 0; i < 12; i++) {
-                // make the regex if we don't have it already
-                if (!this._monthsParse[i]) {
-                    mom = moment.utc([2000, i]);
-                    regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
-                    this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
-                }
-                // test the regex
-                if (this._monthsParse[i].test(monthName)) {
-                    return i;
+            // backwards compat for now: also set the locale
+            locale_locales__getSetGlobalLocale(name);
+        } else {
+            // pass null for config to unupdate, useful for tests
+            if (locales[name] != null) {
+                if (locales[name].parentLocale != null) {
+                    locales[name] = locales[name].parentLocale;
+                } else if (locales[name] != null) {
+                    delete locales[name];
                 }
             }
-        },
-
-        _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
-        weekdays : function (m) {
-            return this._weekdays[m.day()];
-        },
+        }
+        return locales[name];
+    }
 
-        _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
-        weekdaysShort : function (m) {
-            return this._weekdaysShort[m.day()];
-        },
+    // returns locale data
+    function locale_locales__getLocale (key) {
+        var locale;
 
-        _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
-        weekdaysMin : function (m) {
-            return this._weekdaysMin[m.day()];
-        },
+        if (key && key._locale && key._locale._abbr) {
+            key = key._locale._abbr;
+        }
 
-        weekdaysParse : function (weekdayName) {
-            var i, mom, regex;
+        if (!key) {
+            return globalLocale;
+        }
 
-            if (!this._weekdaysParse) {
-                this._weekdaysParse = [];
+        if (!isArray(key)) {
+            //short-circuit everything else
+            locale = loadLocale(key);
+            if (locale) {
+                return locale;
             }
+            key = [key];
+        }
 
-            for (i = 0; i < 7; i++) {
-                // make the regex if we don't have it already
-                if (!this._weekdaysParse[i]) {
-                    mom = moment([2000, 1]).day(i);
-                    regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
-                    this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
-                }
-                // test the regex
-                if (this._weekdaysParse[i].test(weekdayName)) {
-                    return i;
-                }
-            }
-        },
+        return chooseLocale(key);
+    }
 
-        _longDateFormat : {
-            LT : "h:mm A",
-            L : "MM/DD/YYYY",
-            LL : "MMMM D YYYY",
-            LLL : "MMMM D YYYY LT",
-            LLLL : "dddd, MMMM D YYYY LT"
-        },
-        longDateFormat : function (key) {
-            var output = this._longDateFormat[key];
-            if (!output && this._longDateFormat[key.toUpperCase()]) {
-                output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
-                    return val.slice(1);
-                });
-                this._longDateFormat[key] = output;
-            }
-            return output;
-        },
-
-        isPM : function (input) {
-            // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
-            // Using charAt should be more compatible.
-            return ((input + '').toLowerCase().charAt(0) === 'p');
-        },
+    function locale_locales__listLocales() {
+        return keys(locales);
+    }
 
-        _meridiemParse : /[ap]\.?m?\.?/i,
-        meridiem : function (hours, minutes, isLower) {
-            if (hours > 11) {
-                return isLower ? 'pm' : 'PM';
-            } else {
-                return isLower ? 'am' : 'AM';
-            }
-        },
+    var aliases = {};
 
-        _calendar : {
-            sameDay : '[Today at] LT',
-            nextDay : '[Tomorrow at] LT',
-            nextWeek : 'dddd [at] LT',
-            lastDay : '[Yesterday at] LT',
-            lastWeek : '[Last] dddd [at] LT',
-            sameElse : 'L'
-        },
-        calendar : function (key, mom) {
-            var output = this._calendar[key];
-            return typeof output === 'function' ? output.apply(mom) : output;
-        },
+    function addUnitAlias (unit, shorthand) {
+        var lowerCase = unit.toLowerCase();
+        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+    }
 
-        _relativeTime : {
-            future : "in %s",
-            past : "%s ago",
-            s : "a few seconds",
-            m : "a minute",
-            mm : "%d minutes",
-            h : "an hour",
-            hh : "%d hours",
-            d : "a day",
-            dd : "%d days",
-            M : "a month",
-            MM : "%d months",
-            y : "a year",
-            yy : "%d years"
-        },
-        relativeTime : function (number, withoutSuffix, string, isFuture) {
-            var output = this._relativeTime[string];
-            return (typeof output === 'function') ?
-                output(number, withoutSuffix, string, isFuture) :
-                output.replace(/%d/i, number);
-        },
-        pastFuture : function (diff, output) {
-            var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
-            return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
-        },
+    function normalizeUnits(units) {
+        return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+    }
 
-        ordinal : function (number) {
-            return this._ordinal.replace("%d", number);
-        },
-        _ordinal : "%d",
+    function normalizeObjectUnits(inputObject) {
+        var normalizedInput = {},
+            normalizedProp,
+            prop;
 
-        preparse : function (string) {
-            return string;
-        },
+        for (prop in inputObject) {
+            if (hasOwnProp(inputObject, prop)) {
+                normalizedProp = normalizeUnits(prop);
+                if (normalizedProp) {
+                    normalizedInput[normalizedProp] = inputObject[prop];
+                }
+            }
+        }
 
-        postformat : function (string) {
-            return string;
-        },
+        return normalizedInput;
+    }
 
-        week : function (mom) {
-            return weekOfYear(mom, this._week.dow, this._week.doy).week;
-        },
+    function makeGetSet (unit, keepTime) {
+        return function (value) {
+            if (value != null) {
+                get_set__set(this, unit, value);
+                utils_hooks__hooks.updateOffset(this, keepTime);
+                return this;
+            } else {
+                return get_set__get(this, unit);
+            }
+        };
+    }
 
-        _week : {
-            dow : 0, // Sunday is the first day of the week.
-            doy : 6  // The week that contains Jan 1st is the first week of the year.
-        },
+    function get_set__get (mom, unit) {
+        return mom.isValid() ?
+            mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
+    }
 
-        _invalidDate: 'Invalid date',
-        invalidDate: function () {
-            return this._invalidDate;
+    function get_set__set (mom, unit, value) {
+        if (mom.isValid()) {
+            mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
         }
-    });
+    }
 
-    // Loads a language definition into the `languages` cache.  The function
-    // takes a key and optionally values.  If not in the browser and no values
-    // are provided, it will load the language file module.  As a convenience,
-    // this function also returns the language values.
-    function loadLang(key, values) {
-        values.abbr = key;
-        if (!languages[key]) {
-            languages[key] = new Language();
+    // MOMENTS
+
+    function getSet (units, value) {
+        var unit;
+        if (typeof units === 'object') {
+            for (unit in units) {
+                this.set(unit, units[unit]);
+            }
+        } else {
+            units = normalizeUnits(units);
+            if (isFunction(this[units])) {
+                return this[units](value);
+            }
         }
-        languages[key].set(values);
-        return languages[key];
+        return this;
     }
 
-    // Remove a language from the `languages` cache. Mostly useful in tests.
-    function unloadLang(key) {
-        delete languages[key];
+    function zeroFill(number, targetLength, forceSign) {
+        var absNumber = '' + Math.abs(number),
+            zerosToFill = targetLength - absNumber.length,
+            sign = number >= 0;
+        return (sign ? (forceSign ? '+' : '') : '-') +
+            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
     }
 
-    // Determines which language definition to use and returns it.
-    //
-    // With no parameters, it will return the global language.  If you
-    // pass in a language key, such as 'en', it will return the
-    // definition for 'en', so long as 'en' has already been loaded using
-    // moment.lang.
-    function getLangDefinition(key) {
-        var i = 0, j, lang, next, split,
-            get = function (k) {
-                if (!languages[k] && hasModule) {
-                    try {
-                        require('./lang/' + k);
-                    } catch (e) { }
-                }
-                return languages[k];
-            };
+    var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
 
-        if (!key) {
-            return moment.fn._lang;
-        }
+    var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
 
-        if (!isArray(key)) {
-            //short-circuit everything else
-            lang = get(key);
-            if (lang) {
-                return lang;
-            }
-            key = [key];
-        }
+    var formatFunctions = {};
 
-        //pick the language from the array
-        //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
-        //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
-        while (i < key.length) {
-            split = normalizeLanguage(key[i]).split('-');
-            j = split.length;
-            next = normalizeLanguage(key[i + 1]);
-            next = next ? next.split('-') : null;
-            while (j > 0) {
-                lang = get(split.slice(0, j).join('-'));
-                if (lang) {
-                    return lang;
-                }
-                if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
-                    //the next array item is better than a shallower substring of this one
-                    break;
-                }
-                j--;
-            }
-            i++;
+    var formatTokenFunctions = {};
+
+    // token:    'M'
+    // padded:   ['MM', 2]
+    // ordinal:  'Mo'
+    // callback: function () { this.month() + 1 }
+    function addFormatToken (token, padded, ordinal, callback) {
+        var func = callback;
+        if (typeof callback === 'string') {
+            func = function () {
+                return this[callback]();
+            };
+        }
+        if (token) {
+            formatTokenFunctions[token] = func;
+        }
+        if (padded) {
+            formatTokenFunctions[padded[0]] = function () {
+                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+            };
+        }
+        if (ordinal) {
+            formatTokenFunctions[ordinal] = function () {
+                return this.localeData().ordinal(func.apply(this, arguments), token);
+            };
         }
-        return moment.fn._lang;
     }
 
-    /************************************
-        Formatting
-    ************************************/
-
-
     function removeFormattingTokens(input) {
         if (input.match(/\[[\s\S]/)) {
-            return input.replace(/^\[|\]$/g, "");
+            return input.replace(/^\[|\]$/g, '');
         }
-        return input.replace(/\\/g, "");
+        return input.replace(/\\/g, '');
     }
 
     function makeFormatFunction(format) {
@@ -2400,7 +2168,7 @@ if (typeof define === 'function' && define.amd) {
         }
 
         return function (mom) {
-            var output = "";
+            var output = '', i;
             for (i = 0; i < length; i++) {
                 output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
             }
@@ -2410,25 +2178,21 @@ if (typeof define === 'function' && define.amd) {
 
     // format date using native date object
     function formatMoment(m, format) {
-
         if (!m.isValid()) {
-            return m.lang().invalidDate();
+            return m.localeData().invalidDate();
         }
 
-        format = expandFormat(format, m.lang());
-
-        if (!formatFunctions[format]) {
-            formatFunctions[format] = makeFormatFunction(format);
-        }
+        format = expandFormat(format, m.localeData());
+        formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
 
         return formatFunctions[format](m);
     }
 
-    function expandFormat(format, lang) {
+    function expandFormat(format, locale) {
         var i = 5;
 
         function replaceLongDateFormatTokens(input) {
-            return lang.longDateFormat(input) || input;
+            return locale.longDateFormat(input) || input;
         }
 
         localFormattingTokens.lastIndex = 0;
@@ -2441,1455 +2205,3367 @@ if (typeof define === 'function' && define.amd) {
         return format;
     }
 
+    var match1         = /\d/;            //       0 - 9
+    var match2         = /\d\d/;          //      00 - 99
+    var match3         = /\d{3}/;         //     000 - 999
+    var match4         = /\d{4}/;         //    0000 - 9999
+    var match6         = /[+-]?\d{6}/;    // -999999 - 999999
+    var match1to2      = /\d\d?/;         //       0 - 99
+    var match3to4      = /\d\d\d\d?/;     //     999 - 9999
+    var match5to6      = /\d\d\d\d\d\d?/; //   99999 - 999999
+    var match1to3      = /\d{1,3}/;       //       0 - 999
+    var match1to4      = /\d{1,4}/;       //       0 - 9999
+    var match1to6      = /[+-]?\d{1,6}/;  // -999999 - 999999
 
-    /************************************
-        Parsing
-    ************************************/
-
-
-    // get the regex to find the next token
-    function getParseRegexForToken(token, config) {
-        var a, strict = config._strict;
-        switch (token) {
-        case 'DDDD':
-            return parseTokenThreeDigits;
-        case 'YYYY':
-        case 'GGGG':
-        case 'gggg':
-            return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
-        case 'YYYYYY':
-        case 'YYYYY':
-        case 'GGGGG':
-        case 'ggggg':
-            return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
-        case 'S':
-            if (strict) { return parseTokenOneDigit; }
-            /* falls through */
-        case 'SS':
-            if (strict) { return parseTokenTwoDigits; }
-            /* falls through */
-        case 'SSS':
-        case 'DDD':
-            return strict ? parseTokenThreeDigits : parseTokenOneToThreeDigits;
-        case 'MMM':
-        case 'MMMM':
-        case 'dd':
-        case 'ddd':
-        case 'dddd':
-            return parseTokenWord;
-        case 'a':
-        case 'A':
-            return getLangDefinition(config._l)._meridiemParse;
-        case 'X':
-            return parseTokenTimestampMs;
-        case 'Z':
-        case 'ZZ':
-            return parseTokenTimezone;
-        case 'T':
-            return parseTokenT;
-        case 'SSSS':
-            return parseTokenDigits;
-        case 'MM':
-        case 'DD':
-        case 'YY':
-        case 'GG':
-        case 'gg':
-        case 'HH':
-        case 'hh':
-        case 'mm':
-        case 'ss':
-        case 'ww':
-        case 'WW':
-            return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
-        case 'M':
-        case 'D':
-        case 'd':
-        case 'H':
-        case 'h':
-        case 'm':
-        case 's':
-        case 'w':
-        case 'W':
-        case 'e':
-        case 'E':
-            return strict ? parseTokenOneDigit : parseTokenOneOrTwoDigits;
-        default :
-            a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
-            return a;
-        }
+    var matchUnsigned  = /\d+/;           //       0 - inf
+    var matchSigned    = /[+-]?\d+/;      //    -inf - inf
+
+    var matchOffset    = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
+    var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+
+    var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
+
+    // any word (or two) characters or numbers including two/three word month in arabic.
+    // includes scottish gaelic two word and hyphenated months
+    var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
+
+
+    var regexes = {};
+
+    function addRegexToken (token, regex, strictRegex) {
+        regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
+            return (isStrict && strictRegex) ? strictRegex : regex;
+        };
     }
 
-    function timezoneMinutesFromString(string) {
-        string = string || "";
-        var possibleTzMatches = (string.match(parseTokenTimezone) || []),
-            tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
-            parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
-            minutes = +(parts[1] * 60) + toInt(parts[2]);
+    function getParseRegexForToken (token, config) {
+        if (!hasOwnProp(regexes, token)) {
+            return new RegExp(unescapeFormat(token));
+        }
+
+        return regexes[token](config._strict, config._locale);
+    }
 
-        return parts[0] === '+' ? -minutes : minutes;
+    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+    function unescapeFormat(s) {
+        return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+            return p1 || p2 || p3 || p4;
+        }));
     }
 
-    // function to convert string input to date
-    function addTimeToArrayFromToken(token, input, config) {
-        var a, datePartArray = config._a;
+    function regexEscape(s) {
+        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    }
 
-        switch (token) {
-        // MONTH
-        case 'M' : // fall through to MM
-        case 'MM' :
-            if (input != null) {
-                datePartArray[MONTH] = toInt(input) - 1;
-            }
-            break;
-        case 'MMM' : // fall through to MMMM
-        case 'MMMM' :
-            a = getLangDefinition(config._l).monthsParse(input);
-            // if we didn't find a month name, mark the date as invalid.
-            if (a != null) {
-                datePartArray[MONTH] = a;
-            } else {
-                config._pf.invalidMonth = input;
-            }
-            break;
-        // DAY OF MONTH
-        case 'D' : // fall through to DD
-        case 'DD' :
-            if (input != null) {
-                datePartArray[DATE] = toInt(input);
-            }
-            break;
-        // DAY OF YEAR
-        case 'DDD' : // fall through to DDDD
-        case 'DDDD' :
-            if (input != null) {
-                config._dayOfYear = toInt(input);
-            }
+    var tokens = {};
 
-            break;
-        // YEAR
-        case 'YY' :
-            datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
-            break;
-        case 'YYYY' :
-        case 'YYYYY' :
-        case 'YYYYYY' :
-            datePartArray[YEAR] = toInt(input);
-            break;
-        // AM / PM
-        case 'a' : // fall through to A
-        case 'A' :
-            config._isPm = getLangDefinition(config._l).isPM(input);
-            break;
-        // 24 HOUR
-        case 'H' : // fall through to hh
-        case 'HH' : // fall through to hh
-        case 'h' : // fall through to hh
-        case 'hh' :
-            datePartArray[HOUR] = toInt(input);
-            break;
-        // MINUTE
-        case 'm' : // fall through to mm
-        case 'mm' :
-            datePartArray[MINUTE] = toInt(input);
-            break;
-        // SECOND
-        case 's' : // fall through to ss
-        case 'ss' :
-            datePartArray[SECOND] = toInt(input);
-            break;
-        // MILLISECOND
-        case 'S' :
-        case 'SS' :
-        case 'SSS' :
-        case 'SSSS' :
-            datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
-            break;
-        // UNIX TIMESTAMP WITH MS
-        case 'X':
-            config._d = new Date(parseFloat(input) * 1000);
-            break;
-        // TIMEZONE
-        case 'Z' : // fall through to ZZ
-        case 'ZZ' :
-            config._useUTC = true;
-            config._tzm = timezoneMinutesFromString(input);
-            break;
-        case 'w':
-        case 'ww':
-        case 'W':
-        case 'WW':
-        case 'd':
-        case 'dd':
-        case 'ddd':
-        case 'dddd':
-        case 'e':
-        case 'E':
-            token = token.substr(0, 1);
-            /* falls through */
-        case 'gg':
-        case 'gggg':
-        case 'GG':
-        case 'GGGG':
-        case 'GGGGG':
-            token = token.substr(0, 2);
-            if (input) {
-                config._w = config._w || {};
-                config._w[token] = input;
-            }
-            break;
+    function addParseToken (token, callback) {
+        var i, func = callback;
+        if (typeof token === 'string') {
+            token = [token];
+        }
+        if (typeof callback === 'number') {
+            func = function (input, array) {
+                array[callback] = toInt(input);
+            };
+        }
+        for (i = 0; i < token.length; i++) {
+            tokens[token[i]] = func;
         }
     }
 
-    // convert an array to a date.
-    // the array should mirror the parameters below
-    // note: all values past the year are optional and will default to the lowest possible value.
-    // [year, month, day , hour, minute, second, millisecond]
-    function dateFromConfig(config) {
-        var i, date, input = [], currentDate,
-            yearToUse, fixYear, w, temp, lang, weekday, week;
+    function addWeekParseToken (token, callback) {
+        addParseToken(token, function (input, array, config, token) {
+            config._w = config._w || {};
+            callback(input, config._w, config, token);
+        });
+    }
 
-        if (config._d) {
-            return;
+    function addTimeToArrayFromToken(token, input, config) {
+        if (input != null && hasOwnProp(tokens, token)) {
+            tokens[token](input, config._a, config, token);
         }
+    }
 
-        currentDate = currentDateArray(config);
+    var YEAR = 0;
+    var MONTH = 1;
+    var DATE = 2;
+    var HOUR = 3;
+    var MINUTE = 4;
+    var SECOND = 5;
+    var MILLISECOND = 6;
+    var WEEK = 7;
+    var WEEKDAY = 8;
 
-        //compute day of the year from weeks and weekdays
-        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
-            fixYear = function (val) {
-                var int_val = parseInt(val, 10);
-                return val ?
-                  (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) :
-                  (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]);
-            };
+    var indexOf;
 
-            w = config._w;
-            if (w.GG != null || w.W != null || w.E != null) {
-                temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1);
+    if (Array.prototype.indexOf) {
+        indexOf = Array.prototype.indexOf;
+    } else {
+        indexOf = function (o) {
+            // I know
+            var i;
+            for (i = 0; i < this.length; ++i) {
+                if (this[i] === o) {
+                    return i;
+                }
             }
-            else {
-                lang = getLangDefinition(config._l);
-                weekday = w.d != null ?  parseWeekday(w.d, lang) :
-                  (w.e != null ?  parseInt(w.e, 10) + lang._week.dow : 0);
+            return -1;
+        };
+    }
 
-                week = parseInt(w.w, 10) || 1;
+    function daysInMonth(year, month) {
+        return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+    }
 
-                //if we're parsing 'd', then the low day numbers may be next week
-                if (w.d != null && weekday < lang._week.dow) {
-                    week++;
-                }
+    // FORMATTING
 
-                temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow);
-            }
+    addFormatToken('M', ['MM', 2], 'Mo', function () {
+        return this.month() + 1;
+    });
 
-            config._a[YEAR] = temp.year;
-            config._dayOfYear = temp.dayOfYear;
-        }
+    addFormatToken('MMM', 0, 0, function (format) {
+        return this.localeData().monthsShort(this, format);
+    });
 
-        //if the day of the year is set, figure out what it is
-        if (config._dayOfYear) {
-            yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR];
+    addFormatToken('MMMM', 0, 0, function (format) {
+        return this.localeData().months(this, format);
+    });
 
-            if (config._dayOfYear > daysInYear(yearToUse)) {
-                config._pf._overflowDayOfYear = true;
-            }
+    // ALIASES
 
-            date = makeUTCDate(yearToUse, 0, config._dayOfYear);
-            config._a[MONTH] = date.getUTCMonth();
-            config._a[DATE] = date.getUTCDate();
-        }
+    addUnitAlias('month', 'M');
 
-        // Default to current date.
-        // * if no year, month, day of month are given, default to today
-        // * if day of month is given, default month and year
-        // * if month is given, default only year
-        // * if year is given, don't default anything
-        for (i = 0; i < 3 && config._a[i] == null; ++i) {
-            config._a[i] = input[i] = currentDate[i];
-        }
+    // PARSING
 
-        // Zero out whatever was not defaulted, including time
-        for (; i < 7; i++) {
-            config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+    addRegexToken('M',    match1to2);
+    addRegexToken('MM',   match1to2, match2);
+    addRegexToken('MMM',  function (isStrict, locale) {
+        return locale.monthsShortRegex(isStrict);
+    });
+    addRegexToken('MMMM', function (isStrict, locale) {
+        return locale.monthsRegex(isStrict);
+    });
+
+    addParseToken(['M', 'MM'], function (input, array) {
+        array[MONTH] = toInt(input) - 1;
+    });
+
+    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+        var month = config._locale.monthsParse(input, token, config._strict);
+        // if we didn't find a month name, mark the date as invalid.
+        if (month != null) {
+            array[MONTH] = month;
+        } else {
+            getParsingFlags(config).invalidMonth = input;
         }
+    });
 
-        // add the offsets to the time to be parsed so that we can have a clean array for checking isValid
-        input[HOUR] += toInt((config._tzm || 0) / 60);
-        input[MINUTE] += toInt((config._tzm || 0) % 60);
+    // LOCALES
 
-        config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
+    var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/;
+    var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+    function localeMonths (m, format) {
+        return isArray(this._months) ? this._months[m.month()] :
+            this._months[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
     }
 
-    function dateFromObject(config) {
-        var normalizedInput;
+    var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+    function localeMonthsShort (m, format) {
+        return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
+            this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
+    }
 
-        if (config._d) {
-            return;
+    function units_month__handleStrictParse(monthName, format, strict) {
+        var i, ii, mom, llc = monthName.toLocaleLowerCase();
+        if (!this._monthsParse) {
+            // this is not used
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+            for (i = 0; i < 12; ++i) {
+                mom = create_utc__createUTC([2000, i]);
+                this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
+                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+            }
         }
 
-        normalizedInput = normalizeObjectUnits(config._i);
-        config._a = [
-            normalizedInput.year,
-            normalizedInput.month,
-            normalizedInput.day,
-            normalizedInput.hour,
-            normalizedInput.minute,
-            normalizedInput.second,
-            normalizedInput.millisecond
-        ];
-
-        dateFromConfig(config);
-    }
-
-    function currentDateArray(config) {
-        var now = new Date();
-        if (config._useUTC) {
-            return [
-                now.getUTCFullYear(),
-                now.getUTCMonth(),
-                now.getUTCDate()
-            ];
+        if (strict) {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
         } else {
-            return [now.getFullYear(), now.getMonth(), now.getDate()];
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
         }
     }
 
-    // date from string and format string
-    function makeDateFromStringAndFormat(config) {
-
-        config._a = [];
-        config._pf.empty = true;
+    function localeMonthsParse (monthName, format, strict) {
+        var i, mom, regex;
 
-        // This array is used to make a Date, either with `new Date` or `Date.UTC`
-        var lang = getLangDefinition(config._l),
-            string = '' + config._i,
-            i, parsedInput, tokens, token, skipped,
-            stringLength = string.length,
-            totalParsedInputLength = 0;
+        if (this._monthsParseExact) {
+            return units_month__handleStrictParse.call(this, monthName, format, strict);
+        }
 
-        tokens = expandFormat(config._f, lang).match(formattingTokens) || [];
+        if (!this._monthsParse) {
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+        }
 
-        for (i = 0; i < tokens.length; i++) {
-            token = tokens[i];
-            parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
-            if (parsedInput) {
-                skipped = string.substr(0, string.indexOf(parsedInput));
-                if (skipped.length > 0) {
-                    config._pf.unusedInput.push(skipped);
-                }
-                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
-                totalParsedInputLength += parsedInput.length;
+        // TODO: add sorting
+        // Sorting makes sure if one month (or abbr) is a prefix of another
+        // see sorting in computeMonthsParse
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = create_utc__createUTC([2000, i]);
+            if (strict && !this._longMonthsParse[i]) {
+                this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
+                this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
             }
-            // don't parse if it's not a known token
-            if (formatTokenFunctions[token]) {
-                if (parsedInput) {
-                    config._pf.empty = false;
-                }
-                else {
-                    config._pf.unusedTokens.push(token);
-                }
-                addTimeToArrayFromToken(token, parsedInput, config);
+            if (!strict && !this._monthsParse[i]) {
+                regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
             }
-            else if (config._strict && !parsedInput) {
-                config._pf.unusedTokens.push(token);
+            // test the regex
+            if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+                return i;
+            } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+                return i;
+            } else if (!strict && this._monthsParse[i].test(monthName)) {
+                return i;
             }
         }
+    }
 
-        // add remaining unparsed input length to the string
-        config._pf.charsLeftOver = stringLength - totalParsedInputLength;
-        if (string.length > 0) {
-            config._pf.unusedInput.push(string);
-        }
+    // MOMENTS
 
-        // handle am pm
-        if (config._isPm && config._a[HOUR] < 12) {
-            config._a[HOUR] += 12;
+    function setMonth (mom, value) {
+        var dayOfMonth;
+
+        if (!mom.isValid()) {
+            // No op
+            return mom;
         }
-        // if is 12 am, change hours to 0
-        if (config._isPm === false && config._a[HOUR] === 12) {
-            config._a[HOUR] = 0;
+
+        if (typeof value === 'string') {
+            if (/^\d+$/.test(value)) {
+                value = toInt(value);
+            } else {
+                value = mom.localeData().monthsParse(value);
+                // TODO: Another silent failure?
+                if (typeof value !== 'number') {
+                    return mom;
+                }
+            }
         }
 
-        dateFromConfig(config);
-        checkOverflow(config);
+        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+        return mom;
     }
 
-    function unescapeFormat(s) {
-        return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
-            return p1 || p2 || p3 || p4;
-        });
+    function getSetMonth (value) {
+        if (value != null) {
+            setMonth(this, value);
+            utils_hooks__hooks.updateOffset(this, true);
+            return this;
+        } else {
+            return get_set__get(this, 'Month');
+        }
     }
 
-    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
-    function regexpEscape(s) {
-        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    function getDaysInMonth () {
+        return daysInMonth(this.year(), this.month());
     }
 
-    // date from string and array of format strings
-    function makeDateFromStringAndArray(config) {
-        var tempConfig,
-            bestMoment,
+    var defaultMonthsShortRegex = matchWord;
+    function monthsShortRegex (isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsShortStrictRegex;
+            } else {
+                return this._monthsShortRegex;
+            }
+        } else {
+            return this._monthsShortStrictRegex && isStrict ?
+                this._monthsShortStrictRegex : this._monthsShortRegex;
+        }
+    }
 
-            scoreToBeat,
-            i,
-            currentScore;
-
-        if (config._f.length === 0) {
-            config._pf.invalidFormat = true;
-            config._d = new Date(NaN);
-            return;
+    var defaultMonthsRegex = matchWord;
+    function monthsRegex (isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsStrictRegex;
+            } else {
+                return this._monthsRegex;
+            }
+        } else {
+            return this._monthsStrictRegex && isStrict ?
+                this._monthsStrictRegex : this._monthsRegex;
         }
+    }
 
-        for (i = 0; i < config._f.length; i++) {
-            currentScore = 0;
-            tempConfig = extend({}, config);
-            initializeParsingFlags(tempConfig);
-            tempConfig._f = config._f[i];
-            makeDateFromStringAndFormat(tempConfig);
+    function computeMonthsParse () {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
 
-            if (!isValid(tempConfig)) {
-                continue;
-            }
+        var shortPieces = [], longPieces = [], mixedPieces = [],
+            i, mom;
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = create_utc__createUTC([2000, i]);
+            shortPieces.push(this.monthsShort(mom, ''));
+            longPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.monthsShort(mom, ''));
+        }
+        // Sorting makes sure if one month (or abbr) is a prefix of another it
+        // will match the longer piece.
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+        for (i = 0; i < 12; i++) {
+            shortPieces[i] = regexEscape(shortPieces[i]);
+            longPieces[i] = regexEscape(longPieces[i]);
+            mixedPieces[i] = regexEscape(mixedPieces[i]);
+        }
 
-            // if there is any input that was not parsed add a penalty for that format
-            currentScore += tempConfig._pf.charsLeftOver;
+        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._monthsShortRegex = this._monthsRegex;
+        this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+        this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+    }
 
-            //or tokens
-            currentScore += tempConfig._pf.unusedTokens.length * 10;
+    function checkOverflow (m) {
+        var overflow;
+        var a = m._a;
 
-            tempConfig._pf.score = currentScore;
+        if (a && getParsingFlags(m).overflow === -2) {
+            overflow =
+                a[MONTH]       < 0 || a[MONTH]       > 11  ? MONTH :
+                a[DATE]        < 1 || a[DATE]        > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
+                a[HOUR]        < 0 || a[HOUR]        > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
+                a[MINUTE]      < 0 || a[MINUTE]      > 59  ? MINUTE :
+                a[SECOND]      < 0 || a[SECOND]      > 59  ? SECOND :
+                a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
+                -1;
 
-            if (scoreToBeat == null || currentScore < scoreToBeat) {
-                scoreToBeat = currentScore;
-                bestMoment = tempConfig;
+            if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+                overflow = DATE;
             }
+            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+                overflow = WEEK;
+            }
+            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+                overflow = WEEKDAY;
+            }
+
+            getParsingFlags(m).overflow = overflow;
         }
 
-        extend(config, bestMoment || tempConfig);
+        return m;
     }
 
+    // iso 8601 regex
+    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+    var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/;
+    var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/;
+
+    var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
+
+    var isoDates = [
+        ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+        ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+        ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+        ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+        ['YYYY-DDD', /\d{4}-\d{3}/],
+        ['YYYY-MM', /\d{4}-\d\d/, false],
+        ['YYYYYYMMDD', /[+-]\d{10}/],
+        ['YYYYMMDD', /\d{8}/],
+        // YYYYMM is NOT allowed by the standard
+        ['GGGG[W]WWE', /\d{4}W\d{3}/],
+        ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+        ['YYYYDDD', /\d{7}/]
+    ];
+
+    // iso time formats and regexes
+    var isoTimes = [
+        ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+        ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+        ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+        ['HH:mm', /\d\d:\d\d/],
+        ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+        ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+        ['HHmmss', /\d\d\d\d\d\d/],
+        ['HHmm', /\d\d\d\d/],
+        ['HH', /\d\d/]
+    ];
+
+    var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+
     // date from iso format
-    function makeDateFromString(config) {
-        var i,
+    function configFromISO(config) {
+        var i, l,
             string = config._i,
-            match = isoRegex.exec(string);
+            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+            allowTime, dateFormat, timeFormat, tzFormat;
 
         if (match) {
-            config._pf.iso = true;
-            for (i = 4; i > 0; i--) {
-                if (match[i]) {
-                    // match[5] should be "T" or undefined
-                    config._f = isoDates[i - 1] + (match[6] || " ");
+            getParsingFlags(config).iso = true;
+
+            for (i = 0, l = isoDates.length; i < l; i++) {
+                if (isoDates[i][1].exec(match[1])) {
+                    dateFormat = isoDates[i][0];
+                    allowTime = isoDates[i][2] !== false;
                     break;
                 }
             }
-            for (i = 0; i < 4; i++) {
-                if (isoTimes[i][1].exec(string)) {
-                    config._f += isoTimes[i][0];
-                    break;
+            if (dateFormat == null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[3]) {
+                for (i = 0, l = isoTimes.length; i < l; i++) {
+                    if (isoTimes[i][1].exec(match[3])) {
+                        // match[2] should be 'T' or space
+                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                        break;
+                    }
+                }
+                if (timeFormat == null) {
+                    config._isValid = false;
+                    return;
                 }
             }
-            if (string.match(parseTokenTimezone)) {
-                config._f += "Z";
+            if (!allowTime && timeFormat != null) {
+                config._isValid = false;
+                return;
             }
-            makeDateFromStringAndFormat(config);
-        }
-        else {
-            config._d = new Date(string);
+            if (match[4]) {
+                if (tzRegex.exec(match[4])) {
+                    tzFormat = 'Z';
+                } else {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+            configFromStringAndFormat(config);
+        } else {
+            config._isValid = false;
         }
     }
 
-    function makeDateFromInput(config) {
-        var input = config._i,
-            matched = aspNetJsonRegex.exec(input);
+    // date from iso format or fallback
+    function configFromString(config) {
+        var matched = aspNetJsonRegex.exec(config._i);
 
-        if (input === undefined) {
-            config._d = new Date();
-        } else if (matched) {
+        if (matched !== null) {
             config._d = new Date(+matched[1]);
-        } else if (typeof input === 'string') {
-            makeDateFromString(config);
-        } else if (isArray(input)) {
-            config._a = input.slice(0);
-            dateFromConfig(config);
-        } else if (isDate(input)) {
-            config._d = new Date(+input);
-        } else if (typeof(input) === 'object') {
-            dateFromObject(config);
-        } else {
-            config._d = new Date(input);
+            return;
+        }
+
+        configFromISO(config);
+        if (config._isValid === false) {
+            delete config._isValid;
+            utils_hooks__hooks.createFromInputFallback(config);
         }
     }
 
-    function makeDate(y, m, d, h, M, s, ms) {
+    utils_hooks__hooks.createFromInputFallback = deprecate(
+        'moment construction falls back to js Date. This is ' +
+        'discouraged and will be removed in upcoming major ' +
+        'release. Please refer to ' +
+        'https://github.com/moment/moment/issues/1407 for more info.',
+        function (config) {
+            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+        }
+    );
+
+    function createDate (y, m, d, h, M, s, ms) {
         //can't just apply() to create a date:
         //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
         var date = new Date(y, m, d, h, M, s, ms);
 
-        //the date constructor doesn't accept years < 1970
-        if (y < 1970) {
+        //the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
             date.setFullYear(y);
         }
         return date;
     }
 
-    function makeUTCDate(y) {
+    function createUTCDate (y) {
         var date = new Date(Date.UTC.apply(null, arguments));
-        if (y < 1970) {
+
+        //the Date.UTC function remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
             date.setUTCFullYear(y);
         }
         return date;
     }
 
-    function parseWeekday(input, language) {
-        if (typeof input === 'string') {
-            if (!isNaN(input)) {
-                input = parseInt(input, 10);
-            }
-            else {
-                input = language.weekdaysParse(input);
-                if (typeof input !== 'number') {
-                    return null;
-                }
-            }
-        }
-        return input;
-    }
+    // FORMATTING
+
+    addFormatToken('Y', 0, 0, function () {
+        var y = this.year();
+        return y <= 9999 ? '' + y : '+' + y;
+    });
 
-    /************************************
-        Relative Time
-    ************************************/
+    addFormatToken(0, ['YY', 2], 0, function () {
+        return this.year() % 100;
+    });
 
+    addFormatToken(0, ['YYYY',   4],       0, 'year');
+    addFormatToken(0, ['YYYYY',  5],       0, 'year');
+    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
 
-    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
-    function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
-        return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+    // ALIASES
+
+    addUnitAlias('year', 'y');
+
+    // PARSING
+
+    addRegexToken('Y',      matchSigned);
+    addRegexToken('YY',     match1to2, match2);
+    addRegexToken('YYYY',   match1to4, match4);
+    addRegexToken('YYYYY',  match1to6, match6);
+    addRegexToken('YYYYYY', match1to6, match6);
+
+    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+    addParseToken('YYYY', function (input, array) {
+        array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input);
+    });
+    addParseToken('YY', function (input, array) {
+        array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input);
+    });
+    addParseToken('Y', function (input, array) {
+        array[YEAR] = parseInt(input, 10);
+    });
+
+    // HELPERS
+
+    function daysInYear(year) {
+        return isLeapYear(year) ? 366 : 365;
     }
 
-    function relativeTime(milliseconds, withoutSuffix, lang) {
-        var seconds = round(Math.abs(milliseconds) / 1000),
-            minutes = round(seconds / 60),
-            hours = round(minutes / 60),
-            days = round(hours / 24),
-            years = round(days / 365),
-            args = seconds < 45 && ['s', seconds] ||
-                minutes === 1 && ['m'] ||
-                minutes < 45 && ['mm', minutes] ||
-                hours === 1 && ['h'] ||
-                hours < 22 && ['hh', hours] ||
-                days === 1 && ['d'] ||
-                days <= 25 && ['dd', days] ||
-                days <= 45 && ['M'] ||
-                days < 345 && ['MM', round(days / 30)] ||
-                years === 1 && ['y'] || ['yy', years];
-        args[2] = withoutSuffix;
-        args[3] = milliseconds > 0;
-        args[4] = lang;
-        return substituteTimeAgo.apply({}, args);
+    function isLeapYear(year) {
+        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
     }
 
+    // HOOKS
+
+    utils_hooks__hooks.parseTwoDigitYear = function (input) {
+        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+    };
 
-    /************************************
-        Week of Year
-    ************************************/
+    // MOMENTS
 
+    var getSetYear = makeGetSet('FullYear', true);
 
-    // firstDayOfWeek       0 = sun, 6 = sat
-    //                      the day of the week that starts the week
-    //                      (usually sunday or monday)
-    // firstDayOfWeekOfYear 0 = sun, 6 = sat
-    //                      the first week is the week that contains the first
-    //                      of this day of the week
-    //                      (eg. ISO weeks use thursday (4))
-    function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
-        var end = firstDayOfWeekOfYear - firstDayOfWeek,
-            daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
-            adjustedMoment;
+    function getIsLeapYear () {
+        return isLeapYear(this.year());
+    }
 
+    // start-of-first-week - start-of-year
+    function firstWeekOffset(year, dow, doy) {
+        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+            fwd = 7 + dow - doy,
+            // first-week day local weekday -- which local weekday is fwd
+            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
 
-        if (daysToDayOfWeek > end) {
-            daysToDayOfWeek -= 7;
-        }
+        return -fwdlw + fwd - 1;
+    }
 
-        if (daysToDayOfWeek < end - 7) {
-            daysToDayOfWeek += 7;
+    //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+        var localWeekday = (7 + weekday - dow) % 7,
+            weekOffset = firstWeekOffset(year, dow, doy),
+            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+            resYear, resDayOfYear;
+
+        if (dayOfYear <= 0) {
+            resYear = year - 1;
+            resDayOfYear = daysInYear(resYear) + dayOfYear;
+        } else if (dayOfYear > daysInYear(year)) {
+            resYear = year + 1;
+            resDayOfYear = dayOfYear - daysInYear(year);
+        } else {
+            resYear = year;
+            resDayOfYear = dayOfYear;
         }
 
-        adjustedMoment = moment(mom).add('d', daysToDayOfWeek);
         return {
-            week: Math.ceil(adjustedMoment.dayOfYear() / 7),
-            year: adjustedMoment.year()
+            year: resYear,
+            dayOfYear: resDayOfYear
         };
     }
 
-    //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
-    function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
-        // The only solid way to create an iso date from year is to use
-        // a string format (Date.UTC handles only years > 1900). Don't ask why
-        // it doesn't need Z at the end.
-        var d = new Date(leftZeroFill(year, 6, true) + '-01-01').getUTCDay(),
-            daysToAdd, dayOfYear;
+    function weekOfYear(mom, dow, doy) {
+        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+            resWeek, resYear;
 
-        weekday = weekday != null ? weekday : firstDayOfWeek;
-        daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0);
-        dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
+        if (week < 1) {
+            resYear = mom.year() - 1;
+            resWeek = week + weeksInYear(resYear, dow, doy);
+        } else if (week > weeksInYear(mom.year(), dow, doy)) {
+            resWeek = week - weeksInYear(mom.year(), dow, doy);
+            resYear = mom.year() + 1;
+        } else {
+            resYear = mom.year();
+            resWeek = week;
+        }
 
         return {
-            year: dayOfYear > 0 ? year : year - 1,
-            dayOfYear: dayOfYear > 0 ?  dayOfYear : daysInYear(year - 1) + dayOfYear
+            week: resWeek,
+            year: resYear
         };
     }
 
-    /************************************
-        Top Level Functions
-    ************************************/
+    function weeksInYear(year, dow, doy) {
+        var weekOffset = firstWeekOffset(year, dow, doy),
+            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+    }
 
-    function makeMoment(config) {
-        var input = config._i,
-            format = config._f;
+    // Pick the first defined of two or three arguments.
+    function defaults(a, b, c) {
+        if (a != null) {
+            return a;
+        }
+        if (b != null) {
+            return b;
+        }
+        return c;
+    }
 
-        if (typeof config._pf === 'undefined') {
-            initializeParsingFlags(config);
+    function currentDateArray(config) {
+        // hooks is actually the exported moment object
+        var nowValue = new Date(utils_hooks__hooks.now());
+        if (config._useUTC) {
+            return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
         }
+        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+    }
+
+    // convert an array to a date.
+    // the array should mirror the parameters below
+    // note: all values past the year are optional and will default to the lowest possible value.
+    // [year, month, day , hour, minute, second, millisecond]
+    function configFromArray (config) {
+        var i, date, input = [], currentDate, yearToUse;
 
-        if (input === null) {
-            return moment.invalid({nullInput: true});
+        if (config._d) {
+            return;
         }
 
-        if (typeof input === 'string') {
-            config._i = input = getLangDefinition().preparse(input);
+        currentDate = currentDateArray(config);
+
+        //compute day of the year from weeks and weekdays
+        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+            dayOfYearFromWeekInfo(config);
         }
 
-        if (moment.isMoment(input)) {
-            config = extend({}, input);
+        //if the day of the year is set, figure out what it is
+        if (config._dayOfYear) {
+            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
 
-            config._d = new Date(+input._d);
-        } else if (format) {
-            if (isArray(format)) {
-                makeDateFromStringAndArray(config);
-            } else {
-                makeDateFromStringAndFormat(config);
+            if (config._dayOfYear > daysInYear(yearToUse)) {
+                getParsingFlags(config)._overflowDayOfYear = true;
             }
-        } else {
-            makeDateFromInput(config);
+
+            date = createUTCDate(yearToUse, 0, config._dayOfYear);
+            config._a[MONTH] = date.getUTCMonth();
+            config._a[DATE] = date.getUTCDate();
         }
 
-        return new Moment(config);
-    }
+        // Default to current date.
+        // * if no year, month, day of month are given, default to today
+        // * if day of month is given, default month and year
+        // * if month is given, default only year
+        // * if year is given, don't default anything
+        for (i = 0; i < 3 && config._a[i] == null; ++i) {
+            config._a[i] = input[i] = currentDate[i];
+        }
 
-    moment = function (input, format, lang, strict) {
-        if (typeof(lang) === "boolean") {
-            strict = lang;
-            lang = undefined;
+        // Zero out whatever was not defaulted, including time
+        for (; i < 7; i++) {
+            config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
         }
-        return makeMoment({
-            _i : input,
-            _f : format,
-            _l : lang,
-            _strict : strict,
-            _isUTC : false
-        });
-    };
 
-    // creating with utc
-    moment.utc = function (input, format, lang, strict) {
-        var m;
+        // Check for 24:00:00.000
+        if (config._a[HOUR] === 24 &&
+                config._a[MINUTE] === 0 &&
+                config._a[SECOND] === 0 &&
+                config._a[MILLISECOND] === 0) {
+            config._nextDay = true;
+            config._a[HOUR] = 0;
+        }
 
-        if (typeof(lang) === "boolean") {
-            strict = lang;
-            lang = undefined;
+        config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+        // Apply timezone offset from input. The actual utcOffset can be changed
+        // with parseZone.
+        if (config._tzm != null) {
+            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
         }
-        m = makeMoment({
-            _useUTC : true,
-            _isUTC : true,
-            _l : lang,
-            _i : input,
-            _f : format,
-            _strict : strict
-        }).utc();
 
-        return m;
-    };
+        if (config._nextDay) {
+            config._a[HOUR] = 24;
+        }
+    }
 
-    // creating with unix timestamp (in seconds)
-    moment.unix = function (input) {
-        return moment(input * 1000);
-    };
+    function dayOfYearFromWeekInfo(config) {
+        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
 
-    // duration
-    moment.duration = function (input, key) {
-        var duration = input,
-            // matching against regexp is expensive, do it on demand
-            match = null,
-            sign,
-            ret,
-            parseIso;
+        w = config._w;
+        if (w.GG != null || w.W != null || w.E != null) {
+            dow = 1;
+            doy = 4;
 
-        if (moment.isDuration(input)) {
-            duration = {
-                ms: input._milliseconds,
-                d: input._days,
-                M: input._months
-            };
-        } else if (typeof input === 'number') {
-            duration = {};
-            if (key) {
-                duration[key] = input;
+            // TODO: We need to take the current isoWeekYear, but that depends on
+            // how we interpret now (local, utc, fixed offset). So create
+            // a now version of current config (take local/utc/offset flags, and
+            // create now).
+            weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year);
+            week = defaults(w.W, 1);
+            weekday = defaults(w.E, 1);
+            if (weekday < 1 || weekday > 7) {
+                weekdayOverflow = true;
+            }
+        } else {
+            dow = config._locale._week.dow;
+            doy = config._locale._week.doy;
+
+            weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year);
+            week = defaults(w.w, 1);
+
+            if (w.d != null) {
+                // weekday -- low day numbers are considered next week
+                weekday = w.d;
+                if (weekday < 0 || weekday > 6) {
+                    weekdayOverflow = true;
+                }
+            } else if (w.e != null) {
+                // local weekday -- counting starts from begining of week
+                weekday = w.e + dow;
+                if (w.e < 0 || w.e > 6) {
+                    weekdayOverflow = true;
+                }
             } else {
-                duration.milliseconds = input;
+                // default to begining of week
+                weekday = dow;
             }
-        } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
-            sign = (match[1] === "-") ? -1 : 1;
-            duration = {
-                y: 0,
-                d: toInt(match[DATE]) * sign,
-                h: toInt(match[HOUR]) * sign,
-                m: toInt(match[MINUTE]) * sign,
-                s: toInt(match[SECOND]) * sign,
-                ms: toInt(match[MILLISECOND]) * sign
-            };
-        } else if (!!(match = isoDurationRegex.exec(input))) {
-            sign = (match[1] === "-") ? -1 : 1;
-            parseIso = function (inp) {
-                // We'd normally use ~~inp for this, but unfortunately it also
-                // converts floats to ints.
-                // inp may be undefined, so careful calling replace on it.
-                var res = inp && parseFloat(inp.replace(',', '.'));
-                // apply sign while we're at it
-                return (isNaN(res) ? 0 : res) * sign;
-            };
-            duration = {
-                y: parseIso(match[2]),
-                M: parseIso(match[3]),
-                d: parseIso(match[4]),
-                h: parseIso(match[5]),
-                m: parseIso(match[6]),
-                s: parseIso(match[7]),
-                w: parseIso(match[8])
-            };
         }
+        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+            getParsingFlags(config)._overflowWeeks = true;
+        } else if (weekdayOverflow != null) {
+            getParsingFlags(config)._overflowWeekday = true;
+        } else {
+            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+            config._a[YEAR] = temp.year;
+            config._dayOfYear = temp.dayOfYear;
+        }
+    }
 
-        ret = new Duration(duration);
+    // constant that refers to the ISO standard
+    utils_hooks__hooks.ISO_8601 = function () {};
 
-        if (moment.isDuration(input) && input.hasOwnProperty('_lang')) {
-            ret._lang = input._lang;
+    // date from string and format string
+    function configFromStringAndFormat(config) {
+        // TODO: Move this to another part of the creation flow to prevent circular deps
+        if (config._f === utils_hooks__hooks.ISO_8601) {
+            configFromISO(config);
+            return;
         }
 
-        return ret;
-    };
+        config._a = [];
+        getParsingFlags(config).empty = true;
 
-    // version number
-    moment.version = VERSION;
+        // This array is used to make a Date, either with `new Date` or `Date.UTC`
+        var string = '' + config._i,
+            i, parsedInput, tokens, token, skipped,
+            stringLength = string.length,
+            totalParsedInputLength = 0;
 
-    // default format
-    moment.defaultFormat = isoFormat;
+        tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
 
-    // This function will be called whenever a moment is mutated.
-    // It is intended to keep the offset in sync with the timezone.
-    moment.updateOffset = function () {};
+        for (i = 0; i < tokens.length; i++) {
+            token = tokens[i];
+            parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+            // console.log('token', token, 'parsedInput', parsedInput,
+            //         'regex', getParseRegexForToken(token, config));
+            if (parsedInput) {
+                skipped = string.substr(0, string.indexOf(parsedInput));
+                if (skipped.length > 0) {
+                    getParsingFlags(config).unusedInput.push(skipped);
+                }
+                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+                totalParsedInputLength += parsedInput.length;
+            }
+            // don't parse if it's not a known token
+            if (formatTokenFunctions[token]) {
+                if (parsedInput) {
+                    getParsingFlags(config).empty = false;
+                }
+                else {
+                    getParsingFlags(config).unusedTokens.push(token);
+                }
+                addTimeToArrayFromToken(token, parsedInput, config);
+            }
+            else if (config._strict && !parsedInput) {
+                getParsingFlags(config).unusedTokens.push(token);
+            }
+        }
 
-    // This function will load languages and then set the global language.  If
-    // no arguments are passed in, it will simply return the current global
-    // language key.
-    moment.lang = function (key, values) {
-        var r;
-        if (!key) {
-            return moment.fn._lang._abbr;
-        }
-        if (values) {
-            loadLang(normalizeLanguage(key), values);
-        } else if (values === null) {
-            unloadLang(key);
-            key = 'en';
-        } else if (!languages[key]) {
-            getLangDefinition(key);
-        }
-        r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
-        return r._abbr;
-    };
+        // add remaining unparsed input length to the string
+        getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+        if (string.length > 0) {
+            getParsingFlags(config).unusedInput.push(string);
+        }
 
-    // returns language data
-    moment.langData = function (key) {
-        if (key && key._lang && key._lang._abbr) {
-            key = key._lang._abbr;
+        // clear _12h flag if hour is <= 12
+        if (getParsingFlags(config).bigHour === true &&
+                config._a[HOUR] <= 12 &&
+                config._a[HOUR] > 0) {
+            getParsingFlags(config).bigHour = undefined;
         }
-        return getLangDefinition(key);
-    };
 
-    // compare moment object
-    moment.isMoment = function (obj) {
-        return obj instanceof Moment;
-    };
+        getParsingFlags(config).parsedDateParts = config._a.slice(0);
+        getParsingFlags(config).meridiem = config._meridiem;
+        // handle meridiem
+        config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
 
-    // for typechecking Duration objects
-    moment.isDuration = function (obj) {
-        return obj instanceof Duration;
-    };
+        configFromArray(config);
+        checkOverflow(config);
+    }
+
+
+    function meridiemFixWrap (locale, hour, meridiem) {
+        var isPm;
+
+        if (meridiem == null) {
+            // nothing to do
+            return hour;
+        }
+        if (locale.meridiemHour != null) {
+            return locale.meridiemHour(hour, meridiem);
+        } else if (locale.isPM != null) {
+            // Fallback
+            isPm = locale.isPM(meridiem);
+            if (isPm && hour < 12) {
+                hour += 12;
+            }
+            if (!isPm && hour === 12) {
+                hour = 0;
+            }
+            return hour;
+        } else {
+            // this is not supposed to happen
+            return hour;
+        }
+    }
+
+    // date from string and array of format strings
+    function configFromStringAndArray(config) {
+        var tempConfig,
+            bestMoment,
+
+            scoreToBeat,
+            i,
+            currentScore;
+
+        if (config._f.length === 0) {
+            getParsingFlags(config).invalidFormat = true;
+            config._d = new Date(NaN);
+            return;
+        }
+
+        for (i = 0; i < config._f.length; i++) {
+            currentScore = 0;
+            tempConfig = copyConfig({}, config);
+            if (config._useUTC != null) {
+                tempConfig._useUTC = config._useUTC;
+            }
+            tempConfig._f = config._f[i];
+            configFromStringAndFormat(tempConfig);
+
+            if (!valid__isValid(tempConfig)) {
+                continue;
+            }
+
+            // if there is any input that was not parsed add a penalty for that format
+            currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+            //or tokens
+            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+            getParsingFlags(tempConfig).score = currentScore;
+
+            if (scoreToBeat == null || currentScore < scoreToBeat) {
+                scoreToBeat = currentScore;
+                bestMoment = tempConfig;
+            }
+        }
+
+        extend(config, bestMoment || tempConfig);
+    }
+
+    function configFromObject(config) {
+        if (config._d) {
+            return;
+        }
+
+        var i = normalizeObjectUnits(config._i);
+        config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
+            return obj && parseInt(obj, 10);
+        });
+
+        configFromArray(config);
+    }
+
+    function createFromConfig (config) {
+        var res = new Moment(checkOverflow(prepareConfig(config)));
+        if (res._nextDay) {
+            // Adding is smart enough around DST
+            res.add(1, 'd');
+            res._nextDay = undefined;
+        }
+
+        return res;
+    }
+
+    function prepareConfig (config) {
+        var input = config._i,
+            format = config._f;
+
+        config._locale = config._locale || locale_locales__getLocale(config._l);
+
+        if (input === null || (format === undefined && input === '')) {
+            return valid__createInvalid({nullInput: true});
+        }
+
+        if (typeof input === 'string') {
+            config._i = input = config._locale.preparse(input);
+        }
+
+        if (isMoment(input)) {
+            return new Moment(checkOverflow(input));
+        } else if (isArray(format)) {
+            configFromStringAndArray(config);
+        } else if (format) {
+            configFromStringAndFormat(config);
+        } else if (isDate(input)) {
+            config._d = input;
+        } else {
+            configFromInput(config);
+        }
+
+        if (!valid__isValid(config)) {
+            config._d = null;
+        }
+
+        return config;
+    }
+
+    function configFromInput(config) {
+        var input = config._i;
+        if (input === undefined) {
+            config._d = new Date(utils_hooks__hooks.now());
+        } else if (isDate(input)) {
+            config._d = new Date(input.valueOf());
+        } else if (typeof input === 'string') {
+            configFromString(config);
+        } else if (isArray(input)) {
+            config._a = map(input.slice(0), function (obj) {
+                return parseInt(obj, 10);
+            });
+            configFromArray(config);
+        } else if (typeof(input) === 'object') {
+            configFromObject(config);
+        } else if (typeof(input) === 'number') {
+            // from milliseconds
+            config._d = new Date(input);
+        } else {
+            utils_hooks__hooks.createFromInputFallback(config);
+        }
+    }
+
+    function createLocalOrUTC (input, format, locale, strict, isUTC) {
+        var c = {};
+
+        if (typeof(locale) === 'boolean') {
+            strict = locale;
+            locale = undefined;
+        }
+        // object construction must be done this way.
+        // https://github.com/moment/moment/issues/1423
+        c._isAMomentObject = true;
+        c._useUTC = c._isUTC = isUTC;
+        c._l = locale;
+        c._i = input;
+        c._f = format;
+        c._strict = strict;
+
+        return createFromConfig(c);
+    }
+
+    function local__createLocal (input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, false);
+    }
+
+    var prototypeMin = deprecate(
+         'moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
+         function () {
+             var other = local__createLocal.apply(null, arguments);
+             if (this.isValid() && other.isValid()) {
+                 return other < this ? this : other;
+             } else {
+                 return valid__createInvalid();
+             }
+         }
+     );
+
+    var prototypeMax = deprecate(
+        'moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
+        function () {
+            var other = local__createLocal.apply(null, arguments);
+            if (this.isValid() && other.isValid()) {
+                return other > this ? this : other;
+            } else {
+                return valid__createInvalid();
+            }
+        }
+    );
+
+    // Pick a moment m from moments so that m[fn](other) is true for all
+    // other. This relies on the function fn to be transitive.
+    //
+    // moments should either be an array of moment objects or an array, whose
+    // first element is an array of moment objects.
+    function pickBy(fn, moments) {
+        var res, i;
+        if (moments.length === 1 && isArray(moments[0])) {
+            moments = moments[0];
+        }
+        if (!moments.length) {
+            return local__createLocal();
+        }
+        res = moments[0];
+        for (i = 1; i < moments.length; ++i) {
+            if (!moments[i].isValid() || moments[i][fn](res)) {
+                res = moments[i];
+            }
+        }
+        return res;
+    }
+
+    // TODO: Use [].sort instead?
+    function min () {
+        var args = [].slice.call(arguments, 0);
+
+        return pickBy('isBefore', args);
+    }
+
+    function max () {
+        var args = [].slice.call(arguments, 0);
+
+        return pickBy('isAfter', args);
+    }
+
+    var now = function () {
+        return Date.now ? Date.now() : +(new Date());
+    };
+
+    function Duration (duration) {
+        var normalizedInput = normalizeObjectUnits(duration),
+            years = normalizedInput.year || 0,
+            quarters = normalizedInput.quarter || 0,
+            months = normalizedInput.month || 0,
+            weeks = normalizedInput.week || 0,
+            days = normalizedInput.day || 0,
+            hours = normalizedInput.hour || 0,
+            minutes = normalizedInput.minute || 0,
+            seconds = normalizedInput.second || 0,
+            milliseconds = normalizedInput.millisecond || 0;
+
+        // representation for dateAddRemove
+        this._milliseconds = +milliseconds +
+            seconds * 1e3 + // 1000
+            minutes * 6e4 + // 1000 * 60
+            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+        // Because of dateAddRemove treats 24 hours as different from a
+        // day when working around DST, we need to store them separately
+        this._days = +days +
+            weeks * 7;
+        // It is impossible translate months into days without knowing
+        // which months you are are talking about, so we have to store
+        // it separately.
+        this._months = +months +
+            quarters * 3 +
+            years * 12;
+
+        this._data = {};
+
+        this._locale = locale_locales__getLocale();
+
+        this._bubble();
+    }
+
+    function isDuration (obj) {
+        return obj instanceof Duration;
+    }
+
+    // FORMATTING
+
+    function offset (token, separator) {
+        addFormatToken(token, 0, 0, function () {
+            var offset = this.utcOffset();
+            var sign = '+';
+            if (offset < 0) {
+                offset = -offset;
+                sign = '-';
+            }
+            return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+        });
+    }
+
+    offset('Z', ':');
+    offset('ZZ', '');
+
+    // PARSING
+
+    addRegexToken('Z',  matchShortOffset);
+    addRegexToken('ZZ', matchShortOffset);
+    addParseToken(['Z', 'ZZ'], function (input, array, config) {
+        config._useUTC = true;
+        config._tzm = offsetFromString(matchShortOffset, input);
+    });
+
+    // HELPERS
+
+    // timezone chunker
+    // '+10:00' > ['10',  '00']
+    // '-1530'  > ['-15', '30']
+    var chunkOffset = /([\+\-]|\d\d)/gi;
+
+    function offsetFromString(matcher, string) {
+        var matches = ((string || '').match(matcher) || []);
+        var chunk   = matches[matches.length - 1] || [];
+        var parts   = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+        var minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+        return parts[0] === '+' ? minutes : -minutes;
+    }
+
+    // Return a moment from input, that is local/utc/zone equivalent to model.
+    function cloneWithOffset(input, model) {
+        var res, diff;
+        if (model._isUTC) {
+            res = model.clone();
+            diff = (isMoment(input) || isDate(input) ? input.valueOf() : local__createLocal(input).valueOf()) - res.valueOf();
+            // Use low-level api, because this fn is low-level api.
+            res._d.setTime(res._d.valueOf() + diff);
+            utils_hooks__hooks.updateOffset(res, false);
+            return res;
+        } else {
+            return local__createLocal(input).local();
+        }
+    }
+
+    function getDateOffset (m) {
+        // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+        // https://github.com/moment/moment/pull/1871
+        return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+    }
+
+    // HOOKS
+
+    // This function will be called whenever a moment is mutated.
+    // It is intended to keep the offset in sync with the timezone.
+    utils_hooks__hooks.updateOffset = function () {};
+
+    // MOMENTS
+
+    // keepLocalTime = true means only change the timezone, without
+    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+    // +0200, so we adjust the time as needed, to be valid.
+    //
+    // Keeping the time actually adds/subtracts (one hour)
+    // from the actual represented time. That is why we call updateOffset
+    // a second time. In case it wants us to change the offset again
+    // _changeInProgress == true case, then we have to adjust, because
+    // there is no such time in the given timezone.
+    function getSetOffset (input, keepLocalTime) {
+        var offset = this._offset || 0,
+            localAdjust;
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        if (input != null) {
+            if (typeof input === 'string') {
+                input = offsetFromString(matchShortOffset, input);
+            } else if (Math.abs(input) < 16) {
+                input = input * 60;
+            }
+            if (!this._isUTC && keepLocalTime) {
+                localAdjust = getDateOffset(this);
+            }
+            this._offset = input;
+            this._isUTC = true;
+            if (localAdjust != null) {
+                this.add(localAdjust, 'm');
+            }
+            if (offset !== input) {
+                if (!keepLocalTime || this._changeInProgress) {
+                    add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false);
+                } else if (!this._changeInProgress) {
+                    this._changeInProgress = true;
+                    utils_hooks__hooks.updateOffset(this, true);
+                    this._changeInProgress = null;
+                }
+            }
+            return this;
+        } else {
+            return this._isUTC ? offset : getDateOffset(this);
+        }
+    }
+
+    function getSetZone (input, keepLocalTime) {
+        if (input != null) {
+            if (typeof input !== 'string') {
+                input = -input;
+            }
+
+            this.utcOffset(input, keepLocalTime);
+
+            return this;
+        } else {
+            return -this.utcOffset();
+        }
+    }
+
+    function setOffsetToUTC (keepLocalTime) {
+        return this.utcOffset(0, keepLocalTime);
+    }
+
+    function setOffsetToLocal (keepLocalTime) {
+        if (this._isUTC) {
+            this.utcOffset(0, keepLocalTime);
+            this._isUTC = false;
+
+            if (keepLocalTime) {
+                this.subtract(getDateOffset(this), 'm');
+            }
+        }
+        return this;
+    }
+
+    function setOffsetToParsedOffset () {
+        if (this._tzm) {
+            this.utcOffset(this._tzm);
+        } else if (typeof this._i === 'string') {
+            this.utcOffset(offsetFromString(matchOffset, this._i));
+        }
+        return this;
+    }
+
+    function hasAlignedHourOffset (input) {
+        if (!this.isValid()) {
+            return false;
+        }
+        input = input ? local__createLocal(input).utcOffset() : 0;
+
+        return (this.utcOffset() - input) % 60 === 0;
+    }
+
+    function isDaylightSavingTime () {
+        return (
+            this.utcOffset() > this.clone().month(0).utcOffset() ||
+            this.utcOffset() > this.clone().month(5).utcOffset()
+        );
+    }
+
+    function isDaylightSavingTimeShifted () {
+        if (!isUndefined(this._isDSTShifted)) {
+            return this._isDSTShifted;
+        }
+
+        var c = {};
+
+        copyConfig(c, this);
+        c = prepareConfig(c);
+
+        if (c._a) {
+            var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a);
+            this._isDSTShifted = this.isValid() &&
+                compareArrays(c._a, other.toArray()) > 0;
+        } else {
+            this._isDSTShifted = false;
+        }
+
+        return this._isDSTShifted;
+    }
+
+    function isLocal () {
+        return this.isValid() ? !this._isUTC : false;
+    }
+
+    function isUtcOffset () {
+        return this.isValid() ? this._isUTC : false;
+    }
+
+    function isUtc () {
+        return this.isValid() ? this._isUTC && this._offset === 0 : false;
+    }
+
+    // ASP.NET json date format regex
+    var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/;
+
+    // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+    // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+    // and further modified to allow for strings containing both week and day
+    var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;
+
+    function create__createDuration (input, key) {
+        var duration = input,
+            // matching against regexp is expensive, do it on demand
+            match = null,
+            sign,
+            ret,
+            diffRes;
+
+        if (isDuration(input)) {
+            duration = {
+                ms : input._milliseconds,
+                d  : input._days,
+                M  : input._months
+            };
+        } else if (typeof input === 'number') {
+            duration = {};
+            if (key) {
+                duration[key] = input;
+            } else {
+                duration.milliseconds = input;
+            }
+        } else if (!!(match = aspNetRegex.exec(input))) {
+            sign = (match[1] === '-') ? -1 : 1;
+            duration = {
+                y  : 0,
+                d  : toInt(match[DATE])        * sign,
+                h  : toInt(match[HOUR])        * sign,
+                m  : toInt(match[MINUTE])      * sign,
+                s  : toInt(match[SECOND])      * sign,
+                ms : toInt(match[MILLISECOND]) * sign
+            };
+        } else if (!!(match = isoRegex.exec(input))) {
+            sign = (match[1] === '-') ? -1 : 1;
+            duration = {
+                y : parseIso(match[2], sign),
+                M : parseIso(match[3], sign),
+                w : parseIso(match[4], sign),
+                d : parseIso(match[5], sign),
+                h : parseIso(match[6], sign),
+                m : parseIso(match[7], sign),
+                s : parseIso(match[8], sign)
+            };
+        } else if (duration == null) {// checks for null or undefined
+            duration = {};
+        } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
+            diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to));
+
+            duration = {};
+            duration.ms = diffRes.milliseconds;
+            duration.M = diffRes.months;
+        }
+
+        ret = new Duration(duration);
+
+        if (isDuration(input) && hasOwnProp(input, '_locale')) {
+            ret._locale = input._locale;
+        }
+
+        return ret;
+    }
+
+    create__createDuration.fn = Duration.prototype;
+
+    function parseIso (inp, sign) {
+        // We'd normally use ~~inp for this, but unfortunately it also
+        // converts floats to ints.
+        // inp may be undefined, so careful calling replace on it.
+        var res = inp && parseFloat(inp.replace(',', '.'));
+        // apply sign while we're at it
+        return (isNaN(res) ? 0 : res) * sign;
+    }
+
+    function positiveMomentsDifference(base, other) {
+        var res = {milliseconds: 0, months: 0};
+
+        res.months = other.month() - base.month() +
+            (other.year() - base.year()) * 12;
+        if (base.clone().add(res.months, 'M').isAfter(other)) {
+            --res.months;
+        }
+
+        res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
+
+        return res;
+    }
+
+    function momentsDifference(base, other) {
+        var res;
+        if (!(base.isValid() && other.isValid())) {
+            return {milliseconds: 0, months: 0};
+        }
+
+        other = cloneWithOffset(other, base);
+        if (base.isBefore(other)) {
+            res = positiveMomentsDifference(base, other);
+        } else {
+            res = positiveMomentsDifference(other, base);
+            res.milliseconds = -res.milliseconds;
+            res.months = -res.months;
+        }
+
+        return res;
+    }
+
+    function absRound (number) {
+        if (number < 0) {
+            return Math.round(-1 * number) * -1;
+        } else {
+            return Math.round(number);
+        }
+    }
+
+    // TODO: remove 'name' arg after deprecation is removed
+    function createAdder(direction, name) {
+        return function (val, period) {
+            var dur, tmp;
+            //invert the arguments, but complain about it
+            if (period !== null && !isNaN(+period)) {
+                deprecateSimple(name, 'moment().' + name  + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
+                tmp = val; val = period; period = tmp;
+            }
+
+            val = typeof val === 'string' ? +val : val;
+            dur = create__createDuration(val, period);
+            add_subtract__addSubtract(this, dur, direction);
+            return this;
+        };
+    }
+
+    function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) {
+        var milliseconds = duration._milliseconds,
+            days = absRound(duration._days),
+            months = absRound(duration._months);
+
+        if (!mom.isValid()) {
+            // No op
+            return;
+        }
+
+        updateOffset = updateOffset == null ? true : updateOffset;
+
+        if (milliseconds) {
+            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
+        }
+        if (days) {
+            get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding);
+        }
+        if (months) {
+            setMonth(mom, get_set__get(mom, 'Month') + months * isAdding);
+        }
+        if (updateOffset) {
+            utils_hooks__hooks.updateOffset(mom, days || months);
+        }
+    }
+
+    var add_subtract__add      = createAdder(1, 'add');
+    var add_subtract__subtract = createAdder(-1, 'subtract');
+
+    function moment_calendar__calendar (time, formats) {
+        // We want to compare the start of today, vs this.
+        // Getting start-of-today depends on whether we're local/utc/offset or not.
+        var now = time || local__createLocal(),
+            sod = cloneWithOffset(now, this).startOf('day'),
+            diff = this.diff(sod, 'days', true),
+            format = diff < -6 ? 'sameElse' :
+                diff < -1 ? 'lastWeek' :
+                diff < 0 ? 'lastDay' :
+                diff < 1 ? 'sameDay' :
+                diff < 2 ? 'nextDay' :
+                diff < 7 ? 'nextWeek' : 'sameElse';
+
+        var output = formats && (isFunction(formats[format]) ? formats[format]() : formats[format]);
+
+        return this.format(output || this.localeData().calendar(format, this, local__createLocal(now)));
+    }
+
+    function clone () {
+        return new Moment(this);
+    }
+
+    function isAfter (input, units) {
+        var localInput = isMoment(input) ? input : local__createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+        if (units === 'millisecond') {
+            return this.valueOf() > localInput.valueOf();
+        } else {
+            return localInput.valueOf() < this.clone().startOf(units).valueOf();
+        }
+    }
+
+    function isBefore (input, units) {
+        var localInput = isMoment(input) ? input : local__createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+        if (units === 'millisecond') {
+            return this.valueOf() < localInput.valueOf();
+        } else {
+            return this.clone().endOf(units).valueOf() < localInput.valueOf();
+        }
+    }
+
+    function isBetween (from, to, units, inclusivity) {
+        inclusivity = inclusivity || '()';
+        return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
+            (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
+    }
+
+    function isSame (input, units) {
+        var localInput = isMoment(input) ? input : local__createLocal(input),
+            inputMs;
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units || 'millisecond');
+        if (units === 'millisecond') {
+            return this.valueOf() === localInput.valueOf();
+        } else {
+            inputMs = localInput.valueOf();
+            return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
+        }
+    }
+
+    function isSameOrAfter (input, units) {
+        return this.isSame(input, units) || this.isAfter(input,units);
+    }
+
+    function isSameOrBefore (input, units) {
+        return this.isSame(input, units) || this.isBefore(input,units);
+    }
+
+    function diff (input, units, asFloat) {
+        var that,
+            zoneDelta,
+            delta, output;
+
+        if (!this.isValid()) {
+            return NaN;
+        }
+
+        that = cloneWithOffset(input, this);
+
+        if (!that.isValid()) {
+            return NaN;
+        }
+
+        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
+
+        units = normalizeUnits(units);
+
+        if (units === 'year' || units === 'month' || units === 'quarter') {
+            output = monthDiff(this, that);
+            if (units === 'quarter') {
+                output = output / 3;
+            } else if (units === 'year') {
+                output = output / 12;
+            }
+        } else {
+            delta = this - that;
+            output = units === 'second' ? delta / 1e3 : // 1000
+                units === 'minute' ? delta / 6e4 : // 1000 * 60
+                units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60
+                units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
+                units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
+                delta;
+        }
+        return asFloat ? output : absFloor(output);
+    }
+
+    function monthDiff (a, b) {
+        // difference in months
+        var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+            // b is in (anchor - 1 month, anchor + 1 month)
+            anchor = a.clone().add(wholeMonthDiff, 'months'),
+            anchor2, adjust;
+
+        if (b - anchor < 0) {
+            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor - anchor2);
+        } else {
+            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor2 - anchor);
+        }
+
+        //check for negative zero, return zero if negative zero
+        return -(wholeMonthDiff + adjust) || 0;
+    }
+
+    utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+    utils_hooks__hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
+
+    function toString () {
+        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+    }
+
+    function moment_format__toISOString () {
+        var m = this.clone().utc();
+        if (0 < m.year() && m.year() <= 9999) {
+            if (isFunction(Date.prototype.toISOString)) {
+                // native implementation is ~50x faster, use it when we can
+                return this.toDate().toISOString();
+            } else {
+                return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+            }
+        } else {
+            return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+        }
+    }
+
+    function format (inputString) {
+        if (!inputString) {
+            inputString = this.isUtc() ? utils_hooks__hooks.defaultFormatUtc : utils_hooks__hooks.defaultFormat;
+        }
+        var output = formatMoment(this, inputString);
+        return this.localeData().postformat(output);
+    }
+
+    function from (time, withoutSuffix) {
+        if (this.isValid() &&
+                ((isMoment(time) && time.isValid()) ||
+                 local__createLocal(time).isValid())) {
+            return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
+    }
+
+    function fromNow (withoutSuffix) {
+        return this.from(local__createLocal(), withoutSuffix);
+    }
+
+    function to (time, withoutSuffix) {
+        if (this.isValid() &&
+                ((isMoment(time) && time.isValid()) ||
+                 local__createLocal(time).isValid())) {
+            return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
+    }
+
+    function toNow (withoutSuffix) {
+        return this.to(local__createLocal(), withoutSuffix);
+    }
+
+    // If passed a locale key, it will set the locale for this
+    // instance.  Otherwise, it will return the locale configuration
+    // variables for this instance.
+    function locale (key) {
+        var newLocaleData;
+
+        if (key === undefined) {
+            return this._locale._abbr;
+        } else {
+            newLocaleData = locale_locales__getLocale(key);
+            if (newLocaleData != null) {
+                this._locale = newLocaleData;
+            }
+            return this;
+        }
+    }
+
+    var lang = deprecate(
+        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+        function (key) {
+            if (key === undefined) {
+                return this.localeData();
+            } else {
+                return this.locale(key);
+            }
+        }
+    );
+
+    function localeData () {
+        return this._locale;
+    }
+
+    function startOf (units) {
+        units = normalizeUnits(units);
+        // the following switch intentionally omits break keywords
+        // to utilize falling through the cases.
+        switch (units) {
+        case 'year':
+            this.month(0);
+            /* falls through */
+        case 'quarter':
+        case 'month':
+            this.date(1);
+            /* falls through */
+        case 'week':
+        case 'isoWeek':
+        case 'day':
+        case 'date':
+            this.hours(0);
+            /* falls through */
+        case 'hour':
+            this.minutes(0);
+            /* falls through */
+        case 'minute':
+            this.seconds(0);
+            /* falls through */
+        case 'second':
+            this.milliseconds(0);
+        }
+
+        // weeks are a special case
+        if (units === 'week') {
+            this.weekday(0);
+        }
+        if (units === 'isoWeek') {
+            this.isoWeekday(1);
+        }
+
+        // quarters are also special
+        if (units === 'quarter') {
+            this.month(Math.floor(this.month() / 3) * 3);
+        }
+
+        return this;
+    }
+
+    function endOf (units) {
+        units = normalizeUnits(units);
+        if (units === undefined || units === 'millisecond') {
+            return this;
+        }
+
+        // 'date' is an alias for 'day', so it should be considered as such.
+        if (units === 'date') {
+            units = 'day';
+        }
+
+        return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
+    }
+
+    function to_type__valueOf () {
+        return this._d.valueOf() - ((this._offset || 0) * 60000);
+    }
+
+    function unix () {
+        return Math.floor(this.valueOf() / 1000);
+    }
+
+    function toDate () {
+        return this._offset ? new Date(this.valueOf()) : this._d;
+    }
+
+    function toArray () {
+        var m = this;
+        return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
+    }
+
+    function toObject () {
+        var m = this;
+        return {
+            years: m.year(),
+            months: m.month(),
+            date: m.date(),
+            hours: m.hours(),
+            minutes: m.minutes(),
+            seconds: m.seconds(),
+            milliseconds: m.milliseconds()
+        };
+    }
+
+    function toJSON () {
+        // new Date(NaN).toJSON() === null
+        return this.isValid() ? this.toISOString() : null;
+    }
+
+    function moment_valid__isValid () {
+        return valid__isValid(this);
+    }
+
+    function parsingFlags () {
+        return extend({}, getParsingFlags(this));
+    }
+
+    function invalidAt () {
+        return getParsingFlags(this).overflow;
+    }
+
+    function creationData() {
+        return {
+            input: this._i,
+            format: this._f,
+            locale: this._locale,
+            isUTC: this._isUTC,
+            strict: this._strict
+        };
+    }
+
+    // FORMATTING
+
+    addFormatToken(0, ['gg', 2], 0, function () {
+        return this.weekYear() % 100;
+    });
+
+    addFormatToken(0, ['GG', 2], 0, function () {
+        return this.isoWeekYear() % 100;
+    });
+
+    function addWeekYearFormatToken (token, getter) {
+        addFormatToken(0, [token, token.length], 0, getter);
+    }
+
+    addWeekYearFormatToken('gggg',     'weekYear');
+    addWeekYearFormatToken('ggggg',    'weekYear');
+    addWeekYearFormatToken('GGGG',  'isoWeekYear');
+    addWeekYearFormatToken('GGGGG', 'isoWeekYear');
+
+    // ALIASES
+
+    addUnitAlias('weekYear', 'gg');
+    addUnitAlias('isoWeekYear', 'GG');
+
+    // PARSING
+
+    addRegexToken('G',      matchSigned);
+    addRegexToken('g',      matchSigned);
+    addRegexToken('GG',     match1to2, match2);
+    addRegexToken('gg',     match1to2, match2);
+    addRegexToken('GGGG',   match1to4, match4);
+    addRegexToken('gggg',   match1to4, match4);
+    addRegexToken('GGGGG',  match1to6, match6);
+    addRegexToken('ggggg',  match1to6, match6);
+
+    addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
+        week[token.substr(0, 2)] = toInt(input);
+    });
+
+    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+        week[token] = utils_hooks__hooks.parseTwoDigitYear(input);
+    });
+
+    // MOMENTS
+
+    function getSetWeekYear (input) {
+        return getSetWeekYearHelper.call(this,
+                input,
+                this.week(),
+                this.weekday(),
+                this.localeData()._week.dow,
+                this.localeData()._week.doy);
+    }
+
+    function getSetISOWeekYear (input) {
+        return getSetWeekYearHelper.call(this,
+                input, this.isoWeek(), this.isoWeekday(), 1, 4);
+    }
+
+    function getISOWeeksInYear () {
+        return weeksInYear(this.year(), 1, 4);
+    }
+
+    function getWeeksInYear () {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+    }
+
+    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+        var weeksTarget;
+        if (input == null) {
+            return weekOfYear(this, dow, doy).year;
+        } else {
+            weeksTarget = weeksInYear(input, dow, doy);
+            if (week > weeksTarget) {
+                week = weeksTarget;
+            }
+            return setWeekAll.call(this, input, week, weekday, dow, doy);
+        }
+    }
+
+    function setWeekAll(weekYear, week, weekday, dow, doy) {
+        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
+
+        this.year(date.getUTCFullYear());
+        this.month(date.getUTCMonth());
+        this.date(date.getUTCDate());
+        return this;
+    }
+
+    // FORMATTING
+
+    addFormatToken('Q', 0, 'Qo', 'quarter');
+
+    // ALIASES
+
+    addUnitAlias('quarter', 'Q');
+
+    // PARSING
+
+    addRegexToken('Q', match1);
+    addParseToken('Q', function (input, array) {
+        array[MONTH] = (toInt(input) - 1) * 3;
+    });
+
+    // MOMENTS
+
+    function getSetQuarter (input) {
+        return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+    }
+
+    // FORMATTING
+
+    addFormatToken('w', ['ww', 2], 'wo', 'week');
+    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+    // ALIASES
+
+    addUnitAlias('week', 'w');
+    addUnitAlias('isoWeek', 'W');
+
+    // PARSING
+
+    addRegexToken('w',  match1to2);
+    addRegexToken('ww', match1to2, match2);
+    addRegexToken('W',  match1to2);
+    addRegexToken('WW', match1to2, match2);
+
+    addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
+        week[token.substr(0, 1)] = toInt(input);
+    });
+
+    // HELPERS
+
+    // LOCALES
+
+    function localeWeek (mom) {
+        return weekOfYear(mom, this._week.dow, this._week.doy).week;
+    }
+
+    var defaultLocaleWeek = {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    };
+
+    function localeFirstDayOfWeek () {
+        return this._week.dow;
+    }
+
+    function localeFirstDayOfYear () {
+        return this._week.doy;
+    }
+
+    // MOMENTS
+
+    function getSetWeek (input) {
+        var week = this.localeData().week(this);
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
+
+    function getSetISOWeek (input) {
+        var week = weekOfYear(this, 1, 4).week;
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
+
+    // FORMATTING
+
+    addFormatToken('D', ['DD', 2], 'Do', 'date');
+
+    // ALIASES
+
+    addUnitAlias('date', 'D');
+
+    // PARSING
+
+    addRegexToken('D',  match1to2);
+    addRegexToken('DD', match1to2, match2);
+    addRegexToken('Do', function (isStrict, locale) {
+        return isStrict ? locale._ordinalParse : locale._ordinalParseLenient;
+    });
+
+    addParseToken(['D', 'DD'], DATE);
+    addParseToken('Do', function (input, array) {
+        array[DATE] = toInt(input.match(match1to2)[0], 10);
+    });
+
+    // MOMENTS
+
+    var getSetDayOfMonth = makeGetSet('Date', true);
+
+    // FORMATTING
+
+    addFormatToken('d', 0, 'do', 'day');
+
+    addFormatToken('dd', 0, 0, function (format) {
+        return this.localeData().weekdaysMin(this, format);
+    });
+
+    addFormatToken('ddd', 0, 0, function (format) {
+        return this.localeData().weekdaysShort(this, format);
+    });
+
+    addFormatToken('dddd', 0, 0, function (format) {
+        return this.localeData().weekdays(this, format);
+    });
+
+    addFormatToken('e', 0, 0, 'weekday');
+    addFormatToken('E', 0, 0, 'isoWeekday');
+
+    // ALIASES
+
+    addUnitAlias('day', 'd');
+    addUnitAlias('weekday', 'e');
+    addUnitAlias('isoWeekday', 'E');
+
+    // PARSING
+
+    addRegexToken('d',    match1to2);
+    addRegexToken('e',    match1to2);
+    addRegexToken('E',    match1to2);
+    addRegexToken('dd',   function (isStrict, locale) {
+        return locale.weekdaysMinRegex(isStrict);
+    });
+    addRegexToken('ddd',   function (isStrict, locale) {
+        return locale.weekdaysShortRegex(isStrict);
+    });
+    addRegexToken('dddd',   function (isStrict, locale) {
+        return locale.weekdaysRegex(isStrict);
+    });
+
+    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+        var weekday = config._locale.weekdaysParse(input, token, config._strict);
+        // if we didn't get a weekday name, mark the date as invalid
+        if (weekday != null) {
+            week.d = weekday;
+        } else {
+            getParsingFlags(config).invalidWeekday = input;
+        }
+    });
+
+    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+        week[token] = toInt(input);
+    });
+
+    // HELPERS
+
+    function parseWeekday(input, locale) {
+        if (typeof input !== 'string') {
+            return input;
+        }
+
+        if (!isNaN(input)) {
+            return parseInt(input, 10);
+        }
+
+        input = locale.weekdaysParse(input);
+        if (typeof input === 'number') {
+            return input;
+        }
+
+        return null;
+    }
+
+    // LOCALES
+
+    var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+    function localeWeekdays (m, format) {
+        return isArray(this._weekdays) ? this._weekdays[m.day()] :
+            this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
+    }
+
+    var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+    function localeWeekdaysShort (m) {
+        return this._weekdaysShort[m.day()];
+    }
+
+    var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+    function localeWeekdaysMin (m) {
+        return this._weekdaysMin[m.day()];
+    }
+
+    function day_of_week__handleStrictParse(weekdayName, format, strict) {
+        var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._minWeekdaysParse = [];
+
+            for (i = 0; i < 7; ++i) {
+                mom = create_utc__createUTC([2000, 1]).day(i);
+                this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
+                this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
+                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+            }
+        }
+
+        if (strict) {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        }
+    }
+
+    function localeWeekdaysParse (weekdayName, format, strict) {
+        var i, mom, regex;
+
+        if (this._weekdaysParseExact) {
+            return day_of_week__handleStrictParse.call(this, weekdayName, format, strict);
+        }
+
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._minWeekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._fullWeekdaysParse = [];
+        }
+
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+
+            mom = create_utc__createUTC([2000, 1]).day(i);
+            if (strict && !this._fullWeekdaysParse[i]) {
+                this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
+                this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
+                this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
+            }
+            if (!this._weekdaysParse[i]) {
+                regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+            }
+            // test the regex
+            if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
+                return i;
+            } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
+                return i;
+            } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
+                return i;
+            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+                return i;
+            }
+        }
+    }
+
+    // MOMENTS
+
+    function getSetDayOfWeek (input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+        if (input != null) {
+            input = parseWeekday(input, this.localeData());
+            return this.add(input - day, 'd');
+        } else {
+            return day;
+        }
+    }
+
+    function getSetLocaleDayOfWeek (input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+        return input == null ? weekday : this.add(input - weekday, 'd');
+    }
+
+    function getSetISODayOfWeek (input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        // behaves the same as moment#day except
+        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+        // as a setter, sunday should belong to the previous week.
+        return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
+    }
+
+    var defaultWeekdaysRegex = matchWord;
+    function weekdaysRegex (isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysStrictRegex;
+            } else {
+                return this._weekdaysRegex;
+            }
+        } else {
+            return this._weekdaysStrictRegex && isStrict ?
+                this._weekdaysStrictRegex : this._weekdaysRegex;
+        }
+    }
+
+    var defaultWeekdaysShortRegex = matchWord;
+    function weekdaysShortRegex (isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysShortStrictRegex;
+            } else {
+                return this._weekdaysShortRegex;
+            }
+        } else {
+            return this._weekdaysShortStrictRegex && isStrict ?
+                this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
+        }
+    }
+
+    var defaultWeekdaysMinRegex = matchWord;
+    function weekdaysMinRegex (isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysMinStrictRegex;
+            } else {
+                return this._weekdaysMinRegex;
+            }
+        } else {
+            return this._weekdaysMinStrictRegex && isStrict ?
+                this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
+        }
+    }
+
+
+    function computeWeekdaysParse () {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
+
+        var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
+            i, mom, minp, shortp, longp;
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+            mom = create_utc__createUTC([2000, 1]).day(i);
+            minp = this.weekdaysMin(mom, '');
+            shortp = this.weekdaysShort(mom, '');
+            longp = this.weekdays(mom, '');
+            minPieces.push(minp);
+            shortPieces.push(shortp);
+            longPieces.push(longp);
+            mixedPieces.push(minp);
+            mixedPieces.push(shortp);
+            mixedPieces.push(longp);
+        }
+        // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+        // will match the longer piece.
+        minPieces.sort(cmpLenRev);
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+        for (i = 0; i < 7; i++) {
+            shortPieces[i] = regexEscape(shortPieces[i]);
+            longPieces[i] = regexEscape(longPieces[i]);
+            mixedPieces[i] = regexEscape(mixedPieces[i]);
+        }
+
+        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._weekdaysShortRegex = this._weekdaysRegex;
+        this._weekdaysMinRegex = this._weekdaysRegex;
+
+        this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+        this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+        this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
+    }
+
+    // FORMATTING
+
+    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+
+    // ALIASES
+
+    addUnitAlias('dayOfYear', 'DDD');
+
+    // PARSING
+
+    addRegexToken('DDD',  match1to3);
+    addRegexToken('DDDD', match3);
+    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+        config._dayOfYear = toInt(input);
+    });
+
+    // HELPERS
+
+    // MOMENTS
+
+    function getSetDayOfYear (input) {
+        var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
+        return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
+    }
+
+    // FORMATTING
+
+    function hFormat() {
+        return this.hours() % 12 || 12;
+    }
+
+    function kFormat() {
+        return this.hours() || 24;
+    }
+
+    addFormatToken('H', ['HH', 2], 0, 'hour');
+    addFormatToken('h', ['hh', 2], 0, hFormat);
+    addFormatToken('k', ['kk', 2], 0, kFormat);
+
+    addFormatToken('hmm', 0, 0, function () {
+        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('hmmss', 0, 0, function () {
+        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2);
+    });
+
+    addFormatToken('Hmm', 0, 0, function () {
+        return '' + this.hours() + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('Hmmss', 0, 0, function () {
+        return '' + this.hours() + zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2);
+    });
 
-    for (i = lists.length - 1; i >= 0; --i) {
-        makeList(lists[i]);
+    function meridiem (token, lowercase) {
+        addFormatToken(token, 0, 0, function () {
+            return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
+        });
     }
 
-    moment.normalizeUnits = function (units) {
-        return normalizeUnits(units);
-    };
+    meridiem('a', true);
+    meridiem('A', false);
 
-    moment.invalid = function (flags) {
-        var m = moment.utc(NaN);
-        if (flags != null) {
-            extend(m._pf, flags);
-        }
-        else {
-            m._pf.userInvalidated = true;
-        }
+    // ALIASES
 
-        return m;
-    };
+    addUnitAlias('hour', 'h');
 
-    moment.parseZone = function (input) {
-        return moment(input).parseZone();
-    };
+    // PARSING
 
-    /************************************
-        Moment Prototype
-    ************************************/
+    function matchMeridiem (isStrict, locale) {
+        return locale._meridiemParse;
+    }
 
+    addRegexToken('a',  matchMeridiem);
+    addRegexToken('A',  matchMeridiem);
+    addRegexToken('H',  match1to2);
+    addRegexToken('h',  match1to2);
+    addRegexToken('HH', match1to2, match2);
+    addRegexToken('hh', match1to2, match2);
 
-    extend(moment.fn = Moment.prototype, {
+    addRegexToken('hmm', match3to4);
+    addRegexToken('hmmss', match5to6);
+    addRegexToken('Hmm', match3to4);
+    addRegexToken('Hmmss', match5to6);
 
-        clone : function () {
-            return moment(this);
-        },
+    addParseToken(['H', 'HH'], HOUR);
+    addParseToken(['a', 'A'], function (input, array, config) {
+        config._isPm = config._locale.isPM(input);
+        config._meridiem = input;
+    });
+    addParseToken(['h', 'hh'], function (input, array, config) {
+        array[HOUR] = toInt(input);
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmmss', function (input, array, config) {
+        var pos1 = input.length - 4;
+        var pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('Hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+    });
+    addParseToken('Hmmss', function (input, array, config) {
+        var pos1 = input.length - 4;
+        var pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+    });
 
-        valueOf : function () {
-            return +this._d + ((this._offset || 0) * 60000);
-        },
+    // LOCALES
 
-        unix : function () {
-            return Math.floor(+this / 1000);
-        },
+    function localeIsPM (input) {
+        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+        // Using charAt should be more compatible.
+        return ((input + '').toLowerCase().charAt(0) === 'p');
+    }
 
-        toString : function () {
-            return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
-        },
+    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
+    function localeMeridiem (hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'pm' : 'PM';
+        } else {
+            return isLower ? 'am' : 'AM';
+        }
+    }
 
-        toDate : function () {
-            return this._offset ? new Date(+this) : this._d;
-        },
 
-        toISOString : function () {
-            var m = moment(this).utc();
-            if (0 < m.year() && m.year() <= 9999) {
-                return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
-            } else {
-                return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
-            }
-        },
+    // MOMENTS
 
-        toArray : function () {
-            var m = this;
-            return [
-                m.year(),
-                m.month(),
-                m.date(),
-                m.hours(),
-                m.minutes(),
-                m.seconds(),
-                m.milliseconds()
-            ];
-        },
+    // Setting the hour should keep the time, because the user explicitly
+    // specified which hour he wants. So trying to maintain the same hour (in
+    // a new timezone) makes sense. Adding/subtracting hours does not follow
+    // this rule.
+    var getSetHour = makeGetSet('Hours', true);
 
-        isValid : function () {
-            return isValid(this);
-        },
+    // FORMATTING
 
-        isDSTShifted : function () {
+    addFormatToken('m', ['mm', 2], 0, 'minute');
 
-            if (this._a) {
-                return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
-            }
+    // ALIASES
 
-            return false;
-        },
+    addUnitAlias('minute', 'm');
 
-        parsingFlags : function () {
-            return extend({}, this._pf);
-        },
+    // PARSING
 
-        invalidAt: function () {
-            return this._pf.overflow;
-        },
+    addRegexToken('m',  match1to2);
+    addRegexToken('mm', match1to2, match2);
+    addParseToken(['m', 'mm'], MINUTE);
 
-        utc : function () {
-            return this.zone(0);
-        },
+    // MOMENTS
 
-        local : function () {
-            this.zone(0);
-            this._isUTC = false;
-            return this;
-        },
+    var getSetMinute = makeGetSet('Minutes', false);
 
-        format : function (inputString) {
-            var output = formatMoment(this, inputString || moment.defaultFormat);
-            return this.lang().postformat(output);
-        },
+    // FORMATTING
 
-        add : function (input, val) {
-            var dur;
-            // switch args to support add('s', 1) and add(1, 's')
-            if (typeof input === 'string') {
-                dur = moment.duration(+val, input);
-            } else {
-                dur = moment.duration(input, val);
-            }
-            addOrSubtractDurationFromMoment(this, dur, 1);
-            return this;
-        },
+    addFormatToken('s', ['ss', 2], 0, 'second');
 
-        subtract : function (input, val) {
-            var dur;
-            // switch args to support subtract('s', 1) and subtract(1, 's')
-            if (typeof input === 'string') {
-                dur = moment.duration(+val, input);
-            } else {
-                dur = moment.duration(input, val);
-            }
-            addOrSubtractDurationFromMoment(this, dur, -1);
-            return this;
-        },
+    // ALIASES
 
-        diff : function (input, units, asFloat) {
-            var that = makeAs(input, this),
-                zoneDiff = (this.zone() - that.zone()) * 6e4,
-                diff, output;
+    addUnitAlias('second', 's');
 
-            units = normalizeUnits(units);
+    // PARSING
 
-            if (units === 'year' || units === 'month') {
-                // average number of days in the months in the given dates
-                diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
-                // difference in months
-                output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
-                // adjust by taking difference in days, average number of days
-                // and dst in the given months.
-                output += ((this - moment(this).startOf('month')) -
-                        (that - moment(that).startOf('month'))) / diff;
-                // same as above but with zones, to negate all dst
-                output -= ((this.zone() - moment(this).startOf('month').zone()) -
-                        (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff;
-                if (units === 'year') {
-                    output = output / 12;
-                }
-            } else {
-                diff = (this - that);
-                output = units === 'second' ? diff / 1e3 : // 1000
-                    units === 'minute' ? diff / 6e4 : // 1000 * 60
-                    units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
-                    units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
-                    units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
-                    diff;
-            }
-            return asFloat ? output : absRound(output);
-        },
+    addRegexToken('s',  match1to2);
+    addRegexToken('ss', match1to2, match2);
+    addParseToken(['s', 'ss'], SECOND);
 
-        from : function (time, withoutSuffix) {
-            return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
-        },
+    // MOMENTS
 
-        fromNow : function (withoutSuffix) {
-            return this.from(moment(), withoutSuffix);
-        },
+    var getSetSecond = makeGetSet('Seconds', false);
 
-        calendar : function () {
-            // We want to compare the start of today, vs this.
-            // Getting start-of-today depends on whether we're zone'd or not.
-            var sod = makeAs(moment(), this).startOf('day'),
-                diff = this.diff(sod, 'days', true),
-                format = diff < -6 ? 'sameElse' :
-                    diff < -1 ? 'lastWeek' :
-                    diff < 0 ? 'lastDay' :
-                    diff < 1 ? 'sameDay' :
-                    diff < 2 ? 'nextDay' :
-                    diff < 7 ? 'nextWeek' : 'sameElse';
-            return this.format(this.lang().calendar(format, this));
-        },
+    // FORMATTING
 
-        isLeapYear : function () {
-            return isLeapYear(this.year());
-        },
+    addFormatToken('S', 0, 0, function () {
+        return ~~(this.millisecond() / 100);
+    });
 
-        isDST : function () {
-            return (this.zone() < this.clone().month(0).zone() ||
-                this.zone() < this.clone().month(5).zone());
-        },
+    addFormatToken(0, ['SS', 2], 0, function () {
+        return ~~(this.millisecond() / 10);
+    });
 
-        day : function (input) {
-            var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
-            if (input != null) {
-                input = parseWeekday(input, this.lang());
-                return this.add({ d : input - day });
-            } else {
-                return day;
-            }
-        },
+    addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+    addFormatToken(0, ['SSSS', 4], 0, function () {
+        return this.millisecond() * 10;
+    });
+    addFormatToken(0, ['SSSSS', 5], 0, function () {
+        return this.millisecond() * 100;
+    });
+    addFormatToken(0, ['SSSSSS', 6], 0, function () {
+        return this.millisecond() * 1000;
+    });
+    addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+        return this.millisecond() * 10000;
+    });
+    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+        return this.millisecond() * 100000;
+    });
+    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+        return this.millisecond() * 1000000;
+    });
 
-        month : function (input) {
-            var utc = this._isUTC ? 'UTC' : '',
-                dayOfMonth;
 
-            if (input != null) {
-                if (typeof input === 'string') {
-                    input = this.lang().monthsParse(input);
-                    if (typeof input !== 'number') {
-                        return this;
-                    }
-                }
+    // ALIASES
 
-                dayOfMonth = this.date();
-                this.date(1);
-                this._d['set' + utc + 'Month'](input);
-                this.date(Math.min(dayOfMonth, this.daysInMonth()));
+    addUnitAlias('millisecond', 'ms');
 
-                moment.updateOffset(this);
-                return this;
-            } else {
-                return this._d['get' + utc + 'Month']();
-            }
-        },
+    // PARSING
 
-        startOf: function (units) {
-            units = normalizeUnits(units);
-            // the following switch intentionally omits break keywords
-            // to utilize falling through the cases.
-            switch (units) {
-            case 'year':
-                this.month(0);
-                /* falls through */
-            case 'month':
-                this.date(1);
-                /* falls through */
-            case 'week':
-            case 'isoWeek':
-            case 'day':
-                this.hours(0);
-                /* falls through */
-            case 'hour':
-                this.minutes(0);
-                /* falls through */
-            case 'minute':
-                this.seconds(0);
-                /* falls through */
-            case 'second':
-                this.milliseconds(0);
-                /* falls through */
-            }
-
-            // weeks are a special case
-            if (units === 'week') {
-                this.weekday(0);
-            } else if (units === 'isoWeek') {
-                this.isoWeekday(1);
-            }
+    addRegexToken('S',    match1to3, match1);
+    addRegexToken('SS',   match1to3, match2);
+    addRegexToken('SSS',  match1to3, match3);
 
-            return this;
-        },
+    var token;
+    for (token = 'SSSS'; token.length <= 9; token += 'S') {
+        addRegexToken(token, matchUnsigned);
+    }
 
-        endOf: function (units) {
-            units = normalizeUnits(units);
-            return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1);
-        },
+    function parseMs(input, array) {
+        array[MILLISECOND] = toInt(('0.' + input) * 1000);
+    }
 
-        isAfter: function (input, units) {
-            units = typeof units !== 'undefined' ? units : 'millisecond';
-            return +this.clone().startOf(units) > +moment(input).startOf(units);
-        },
+    for (token = 'S'; token.length <= 9; token += 'S') {
+        addParseToken(token, parseMs);
+    }
+    // MOMENTS
 
-        isBefore: function (input, units) {
-            units = typeof units !== 'undefined' ? units : 'millisecond';
-            return +this.clone().startOf(units) < +moment(input).startOf(units);
-        },
+    var getSetMillisecond = makeGetSet('Milliseconds', false);
 
-        isSame: function (input, units) {
-            units = units || 'ms';
-            return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
-        },
+    // FORMATTING
 
-        min: function (other) {
-            other = moment.apply(null, arguments);
-            return other < this ? this : other;
-        },
+    addFormatToken('z',  0, 0, 'zoneAbbr');
+    addFormatToken('zz', 0, 0, 'zoneName');
+
+    // MOMENTS
+
+    function getZoneAbbr () {
+        return this._isUTC ? 'UTC' : '';
+    }
+
+    function getZoneName () {
+        return this._isUTC ? 'Coordinated Universal Time' : '';
+    }
+
+    var momentPrototype__proto = Moment.prototype;
+
+    momentPrototype__proto.add               = add_subtract__add;
+    momentPrototype__proto.calendar          = moment_calendar__calendar;
+    momentPrototype__proto.clone             = clone;
+    momentPrototype__proto.diff              = diff;
+    momentPrototype__proto.endOf             = endOf;
+    momentPrototype__proto.format            = format;
+    momentPrototype__proto.from              = from;
+    momentPrototype__proto.fromNow           = fromNow;
+    momentPrototype__proto.to                = to;
+    momentPrototype__proto.toNow             = toNow;
+    momentPrototype__proto.get               = getSet;
+    momentPrototype__proto.invalidAt         = invalidAt;
+    momentPrototype__proto.isAfter           = isAfter;
+    momentPrototype__proto.isBefore          = isBefore;
+    momentPrototype__proto.isBetween         = isBetween;
+    momentPrototype__proto.isSame            = isSame;
+    momentPrototype__proto.isSameOrAfter     = isSameOrAfter;
+    momentPrototype__proto.isSameOrBefore    = isSameOrBefore;
+    momentPrototype__proto.isValid           = moment_valid__isValid;
+    momentPrototype__proto.lang              = lang;
+    momentPrototype__proto.locale            = locale;
+    momentPrototype__proto.localeData        = localeData;
+    momentPrototype__proto.max               = prototypeMax;
+    momentPrototype__proto.min               = prototypeMin;
+    momentPrototype__proto.parsingFlags      = parsingFlags;
+    momentPrototype__proto.set               = getSet;
+    momentPrototype__proto.startOf           = startOf;
+    momentPrototype__proto.subtract          = add_subtract__subtract;
+    momentPrototype__proto.toArray           = toArray;
+    momentPrototype__proto.toObject          = toObject;
+    momentPrototype__proto.toDate            = toDate;
+    momentPrototype__proto.toISOString       = moment_format__toISOString;
+    momentPrototype__proto.toJSON            = toJSON;
+    momentPrototype__proto.toString          = toString;
+    momentPrototype__proto.unix              = unix;
+    momentPrototype__proto.valueOf           = to_type__valueOf;
+    momentPrototype__proto.creationData      = creationData;
 
-        max: function (other) {
-            other = moment.apply(null, arguments);
-            return other > this ? this : other;
-        },
+    // Year
+    momentPrototype__proto.year       = getSetYear;
+    momentPrototype__proto.isLeapYear = getIsLeapYear;
 
-        zone : function (input) {
-            var offset = this._offset || 0;
-            if (input != null) {
-                if (typeof input === "string") {
-                    input = timezoneMinutesFromString(input);
-                }
-                if (Math.abs(input) < 16) {
-                    input = input * 60;
-                }
-                this._offset = input;
-                this._isUTC = true;
-                if (offset !== input) {
-                    addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true);
-                }
-            } else {
-                return this._isUTC ? offset : this._d.getTimezoneOffset();
-            }
-            return this;
-        },
+    // Week Year
+    momentPrototype__proto.weekYear    = getSetWeekYear;
+    momentPrototype__proto.isoWeekYear = getSetISOWeekYear;
 
-        zoneAbbr : function () {
-            return this._isUTC ? "UTC" : "";
-        },
+    // Quarter
+    momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter;
 
-        zoneName : function () {
-            return this._isUTC ? "Coordinated Universal Time" : "";
-        },
+    // Month
+    momentPrototype__proto.month       = getSetMonth;
+    momentPrototype__proto.daysInMonth = getDaysInMonth;
 
-        parseZone : function () {
-            if (this._tzm) {
-                this.zone(this._tzm);
-            } else if (typeof this._i === 'string') {
-                this.zone(this._i);
-            }
-            return this;
-        },
+    // Week
+    momentPrototype__proto.week           = momentPrototype__proto.weeks        = getSetWeek;
+    momentPrototype__proto.isoWeek        = momentPrototype__proto.isoWeeks     = getSetISOWeek;
+    momentPrototype__proto.weeksInYear    = getWeeksInYear;
+    momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear;
+
+    // Day
+    momentPrototype__proto.date       = getSetDayOfMonth;
+    momentPrototype__proto.day        = momentPrototype__proto.days             = getSetDayOfWeek;
+    momentPrototype__proto.weekday    = getSetLocaleDayOfWeek;
+    momentPrototype__proto.isoWeekday = getSetISODayOfWeek;
+    momentPrototype__proto.dayOfYear  = getSetDayOfYear;
+
+    // Hour
+    momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour;
+
+    // Minute
+    momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute;
+
+    // Second
+    momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond;
+
+    // Millisecond
+    momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond;
+
+    // Offset
+    momentPrototype__proto.utcOffset            = getSetOffset;
+    momentPrototype__proto.utc                  = setOffsetToUTC;
+    momentPrototype__proto.local                = setOffsetToLocal;
+    momentPrototype__proto.parseZone            = setOffsetToParsedOffset;
+    momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset;
+    momentPrototype__proto.isDST                = isDaylightSavingTime;
+    momentPrototype__proto.isDSTShifted         = isDaylightSavingTimeShifted;
+    momentPrototype__proto.isLocal              = isLocal;
+    momentPrototype__proto.isUtcOffset          = isUtcOffset;
+    momentPrototype__proto.isUtc                = isUtc;
+    momentPrototype__proto.isUTC                = isUtc;
+
+    // Timezone
+    momentPrototype__proto.zoneAbbr = getZoneAbbr;
+    momentPrototype__proto.zoneName = getZoneName;
+
+    // Deprecations
+    momentPrototype__proto.dates  = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
+    momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
+    momentPrototype__proto.years  = deprecate('years accessor is deprecated. Use year instead', getSetYear);
+    momentPrototype__proto.zone   = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone);
+
+    var momentPrototype = momentPrototype__proto;
+
+    function moment__createUnix (input) {
+        return local__createLocal(input * 1000);
+    }
+
+    function moment__createInZone () {
+        return local__createLocal.apply(null, arguments).parseZone();
+    }
+
+    var defaultCalendar = {
+        sameDay : '[Today at] LT',
+        nextDay : '[Tomorrow at] LT',
+        nextWeek : 'dddd [at] LT',
+        lastDay : '[Yesterday at] LT',
+        lastWeek : '[Last] dddd [at] LT',
+        sameElse : 'L'
+    };
 
-        hasAlignedHourOffset : function (input) {
-            if (!input) {
-                input = 0;
-            }
-            else {
-                input = moment(input).zone();
-            }
+    function locale_calendar__calendar (key, mom, now) {
+        var output = this._calendar[key];
+        return isFunction(output) ? output.call(mom, now) : output;
+    }
 
-            return (this.zone() - input) % 60 === 0;
-        },
+    var defaultLongDateFormat = {
+        LTS  : 'h:mm:ss A',
+        LT   : 'h:mm A',
+        L    : 'MM/DD/YYYY',
+        LL   : 'MMMM D, YYYY',
+        LLL  : 'MMMM D, YYYY h:mm A',
+        LLLL : 'dddd, MMMM D, YYYY h:mm A'
+    };
 
-        daysInMonth : function () {
-            return daysInMonth(this.year(), this.month());
-        },
+    function longDateFormat (key) {
+        var format = this._longDateFormat[key],
+            formatUpper = this._longDateFormat[key.toUpperCase()];
 
-        dayOfYear : function (input) {
-            var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
-            return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
-        },
+        if (format || !formatUpper) {
+            return format;
+        }
 
-        quarter : function () {
-            return Math.ceil((this.month() + 1.0) / 3.0);
-        },
+        this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
+            return val.slice(1);
+        });
 
-        weekYear : function (input) {
-            var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year;
-            return input == null ? year : this.add("y", (input - year));
-        },
+        return this._longDateFormat[key];
+    }
 
-        isoWeekYear : function (input) {
-            var year = weekOfYear(this, 1, 4).year;
-            return input == null ? year : this.add("y", (input - year));
-        },
+    var defaultInvalidDate = 'Invalid date';
 
-        week : function (input) {
-            var week = this.lang().week(this);
-            return input == null ? week : this.add("d", (input - week) * 7);
-        },
+    function invalidDate () {
+        return this._invalidDate;
+    }
 
-        isoWeek : function (input) {
-            var week = weekOfYear(this, 1, 4).week;
-            return input == null ? week : this.add("d", (input - week) * 7);
-        },
+    var defaultOrdinal = '%d';
+    var defaultOrdinalParse = /\d{1,2}/;
 
-        weekday : function (input) {
-            var weekday = (this.day() + 7 - this.lang()._week.dow) % 7;
-            return input == null ? weekday : this.add("d", input - weekday);
-        },
+    function ordinal (number) {
+        return this._ordinal.replace('%d', number);
+    }
 
-        isoWeekday : function (input) {
-            // behaves the same as moment#day except
-            // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
-            // as a setter, sunday should belong to the previous week.
-            return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
-        },
+    function preParsePostFormat (string) {
+        return string;
+    }
 
-        get : function (units) {
-            units = normalizeUnits(units);
-            return this[units]();
-        },
+    var defaultRelativeTime = {
+        future : 'in %s',
+        past   : '%s ago',
+        s  : 'a few seconds',
+        m  : 'a minute',
+        mm : '%d minutes',
+        h  : 'an hour',
+        hh : '%d hours',
+        d  : 'a day',
+        dd : '%d days',
+        M  : 'a month',
+        MM : '%d months',
+        y  : 'a year',
+        yy : '%d years'
+    };
 
-        set : function (units, value) {
-            units = normalizeUnits(units);
-            if (typeof this[units] === 'function') {
-                this[units](value);
+    function relative__relativeTime (number, withoutSuffix, string, isFuture) {
+        var output = this._relativeTime[string];
+        return (isFunction(output)) ?
+            output(number, withoutSuffix, string, isFuture) :
+            output.replace(/%d/i, number);
+    }
+
+    function pastFuture (diff, output) {
+        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+    }
+
+    var prototype__proto = Locale.prototype;
+
+    prototype__proto._calendar       = defaultCalendar;
+    prototype__proto.calendar        = locale_calendar__calendar;
+    prototype__proto._longDateFormat = defaultLongDateFormat;
+    prototype__proto.longDateFormat  = longDateFormat;
+    prototype__proto._invalidDate    = defaultInvalidDate;
+    prototype__proto.invalidDate     = invalidDate;
+    prototype__proto._ordinal        = defaultOrdinal;
+    prototype__proto.ordinal         = ordinal;
+    prototype__proto._ordinalParse   = defaultOrdinalParse;
+    prototype__proto.preparse        = preParsePostFormat;
+    prototype__proto.postformat      = preParsePostFormat;
+    prototype__proto._relativeTime   = defaultRelativeTime;
+    prototype__proto.relativeTime    = relative__relativeTime;
+    prototype__proto.pastFuture      = pastFuture;
+    prototype__proto.set             = locale_set__set;
+
+    // Month
+    prototype__proto.months            =        localeMonths;
+    prototype__proto._months           = defaultLocaleMonths;
+    prototype__proto.monthsShort       =        localeMonthsShort;
+    prototype__proto._monthsShort      = defaultLocaleMonthsShort;
+    prototype__proto.monthsParse       =        localeMonthsParse;
+    prototype__proto._monthsRegex      = defaultMonthsRegex;
+    prototype__proto.monthsRegex       = monthsRegex;
+    prototype__proto._monthsShortRegex = defaultMonthsShortRegex;
+    prototype__proto.monthsShortRegex  = monthsShortRegex;
+
+    // Week
+    prototype__proto.week = localeWeek;
+    prototype__proto._week = defaultLocaleWeek;
+    prototype__proto.firstDayOfYear = localeFirstDayOfYear;
+    prototype__proto.firstDayOfWeek = localeFirstDayOfWeek;
+
+    // Day of Week
+    prototype__proto.weekdays       =        localeWeekdays;
+    prototype__proto._weekdays      = defaultLocaleWeekdays;
+    prototype__proto.weekdaysMin    =        localeWeekdaysMin;
+    prototype__proto._weekdaysMin   = defaultLocaleWeekdaysMin;
+    prototype__proto.weekdaysShort  =        localeWeekdaysShort;
+    prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort;
+    prototype__proto.weekdaysParse  =        localeWeekdaysParse;
+
+    prototype__proto._weekdaysRegex      = defaultWeekdaysRegex;
+    prototype__proto.weekdaysRegex       =        weekdaysRegex;
+    prototype__proto._weekdaysShortRegex = defaultWeekdaysShortRegex;
+    prototype__proto.weekdaysShortRegex  =        weekdaysShortRegex;
+    prototype__proto._weekdaysMinRegex   = defaultWeekdaysMinRegex;
+    prototype__proto.weekdaysMinRegex    =        weekdaysMinRegex;
+
+    // Hours
+    prototype__proto.isPM = localeIsPM;
+    prototype__proto._meridiemParse = defaultLocaleMeridiemParse;
+    prototype__proto.meridiem = localeMeridiem;
+
+    function lists__get (format, index, field, setter) {
+        var locale = locale_locales__getLocale();
+        var utc = create_utc__createUTC().set(setter, index);
+        return locale[field](utc, format);
+    }
+
+    function listMonthsImpl (format, index, field) {
+        if (typeof format === 'number') {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+
+        if (index != null) {
+            return lists__get(format, index, field, 'month');
+        }
+
+        var i;
+        var out = [];
+        for (i = 0; i < 12; i++) {
+            out[i] = lists__get(format, i, field, 'month');
+        }
+        return out;
+    }
+
+    // ()
+    // (5)
+    // (fmt, 5)
+    // (fmt)
+    // (true)
+    // (true, 5)
+    // (true, fmt, 5)
+    // (true, fmt)
+    function listWeekdaysImpl (localeSorted, format, index, field) {
+        if (typeof localeSorted === 'boolean') {
+            if (typeof format === 'number') {
+                index = format;
+                format = undefined;
             }
-            return this;
-        },
 
-        // If passed a language key, it will set the language for this
-        // instance.  Otherwise, it will return the language configuration
-        // variables for this instance.
-        lang : function (key) {
-            if (key === undefined) {
-                return this._lang;
-            } else {
-                this._lang = getLangDefinition(key);
-                return this;
+            format = format || '';
+        } else {
+            format = localeSorted;
+            index = format;
+            localeSorted = false;
+
+            if (typeof format === 'number') {
+                index = format;
+                format = undefined;
             }
+
+            format = format || '';
         }
-    });
 
-    // helper for adding shortcuts
-    function makeGetterAndSetter(name, key) {
-        moment.fn[name] = moment.fn[name + 's'] = function (input) {
-            var utc = this._isUTC ? 'UTC' : '';
-            if (input != null) {
-                this._d['set' + utc + key](input);
-                moment.updateOffset(this);
-                return this;
-            } else {
-                return this._d['get' + utc + key]();
-            }
-        };
-    }
+        var locale = locale_locales__getLocale(),
+            shift = localeSorted ? locale._week.dow : 0;
 
-    // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
-    for (i = 0; i < proxyGettersAndSetters.length; i ++) {
-        makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]);
-    }
+        if (index != null) {
+            return lists__get(format, (index + shift) % 7, field, 'day');
+        }
 
-    // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
-    makeGetterAndSetter('year', 'FullYear');
+        var i;
+        var out = [];
+        for (i = 0; i < 7; i++) {
+            out[i] = lists__get(format, (i + shift) % 7, field, 'day');
+        }
+        return out;
+    }
 
-    // add plural methods
-    moment.fn.days = moment.fn.day;
-    moment.fn.months = moment.fn.month;
-    moment.fn.weeks = moment.fn.week;
-    moment.fn.isoWeeks = moment.fn.isoWeek;
+    function lists__listMonths (format, index) {
+        return listMonthsImpl(format, index, 'months');
+    }
 
-    // add aliased format methods
-    moment.fn.toJSON = moment.fn.toISOString;
+    function lists__listMonthsShort (format, index) {
+        return listMonthsImpl(format, index, 'monthsShort');
+    }
 
-    /************************************
-        Duration Prototype
-    ************************************/
+    function lists__listWeekdays (localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
+    }
 
+    function lists__listWeekdaysShort (localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
+    }
 
-    extend(moment.duration.fn = Duration.prototype, {
+    function lists__listWeekdaysMin (localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
+    }
 
-        _bubble : function () {
-            var milliseconds = this._milliseconds,
-                days = this._days,
-                months = this._months,
-                data = this._data,
-                seconds, minutes, hours, years;
+    locale_locales__getSetGlobalLocale('en', {
+        ordinalParse: /\d{1,2}(th|st|nd|rd)/,
+        ordinal : function (number) {
+            var b = number % 10,
+                output = (toInt(number % 100 / 10) === 1) ? 'th' :
+                (b === 1) ? 'st' :
+                (b === 2) ? 'nd' :
+                (b === 3) ? 'rd' : 'th';
+            return number + output;
+        }
+    });
 
-            // The following code bubbles up values, see the tests for
-            // examples of what that means.
-            data.milliseconds = milliseconds % 1000;
+    // Side effect imports
+    utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale);
+    utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale);
 
-            seconds = absRound(milliseconds / 1000);
-            data.seconds = seconds % 60;
+    var mathAbs = Math.abs;
 
-            minutes = absRound(seconds / 60);
-            data.minutes = minutes % 60;
+    function duration_abs__abs () {
+        var data           = this._data;
 
-            hours = absRound(minutes / 60);
-            data.hours = hours % 24;
+        this._milliseconds = mathAbs(this._milliseconds);
+        this._days         = mathAbs(this._days);
+        this._months       = mathAbs(this._months);
 
-            days += absRound(hours / 24);
-            data.days = days % 30;
+        data.milliseconds  = mathAbs(data.milliseconds);
+        data.seconds       = mathAbs(data.seconds);
+        data.minutes       = mathAbs(data.minutes);
+        data.hours         = mathAbs(data.hours);
+        data.months        = mathAbs(data.months);
+        data.years         = mathAbs(data.years);
 
-            months += absRound(days / 30);
-            data.months = months % 12;
+        return this;
+    }
 
-            years = absRound(months / 12);
-            data.years = years;
-        },
+    function duration_add_subtract__addSubtract (duration, input, value, direction) {
+        var other = create__createDuration(input, value);
 
-        weeks : function () {
-            return absRound(this.days() / 7);
-        },
+        duration._milliseconds += direction * other._milliseconds;
+        duration._days         += direction * other._days;
+        duration._months       += direction * other._months;
 
-        valueOf : function () {
-            return this._milliseconds +
-              this._days * 864e5 +
-              (this._months % 12) * 2592e6 +
-              toInt(this._months / 12) * 31536e6;
-        },
+        return duration._bubble();
+    }
 
-        humanize : function (withSuffix) {
-            var difference = +this,
-                output = relativeTime(difference, !withSuffix, this.lang());
+    // supports only 2.0-style add(1, 's') or add(duration)
+    function duration_add_subtract__add (input, value) {
+        return duration_add_subtract__addSubtract(this, input, value, 1);
+    }
 
-            if (withSuffix) {
-                output = this.lang().pastFuture(difference, output);
-            }
+    // supports only 2.0-style subtract(1, 's') or subtract(duration)
+    function duration_add_subtract__subtract (input, value) {
+        return duration_add_subtract__addSubtract(this, input, value, -1);
+    }
 
-            return this.lang().postformat(output);
-        },
+    function absCeil (number) {
+        if (number < 0) {
+            return Math.floor(number);
+        } else {
+            return Math.ceil(number);
+        }
+    }
 
-        add : function (input, val) {
-            // supports only 2.0-style add(1, 's') or add(moment)
-            var dur = moment.duration(input, val);
+    function bubble () {
+        var milliseconds = this._milliseconds;
+        var days         = this._days;
+        var months       = this._months;
+        var data         = this._data;
+        var seconds, minutes, hours, years, monthsFromDays;
 
-            this._milliseconds += dur._milliseconds;
-            this._days += dur._days;
-            this._months += dur._months;
+        // if we have a mix of positive and negative values, bubble down first
+        // check: https://github.com/moment/moment/issues/2166
+        if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
+                (milliseconds <= 0 && days <= 0 && months <= 0))) {
+            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+            days = 0;
+            months = 0;
+        }
 
-            this._bubble();
+        // The following code bubbles up values, see the tests for
+        // examples of what that means.
+        data.milliseconds = milliseconds % 1000;
 
-            return this;
-        },
+        seconds           = absFloor(milliseconds / 1000);
+        data.seconds      = seconds % 60;
 
-        subtract : function (input, val) {
-            var dur = moment.duration(input, val);
+        minutes           = absFloor(seconds / 60);
+        data.minutes      = minutes % 60;
 
-            this._milliseconds -= dur._milliseconds;
-            this._days -= dur._days;
-            this._months -= dur._months;
+        hours             = absFloor(minutes / 60);
+        data.hours        = hours % 24;
 
-            this._bubble();
+        days += absFloor(hours / 24);
 
-            return this;
-        },
+        // convert days to months
+        monthsFromDays = absFloor(daysToMonths(days));
+        months += monthsFromDays;
+        days -= absCeil(monthsToDays(monthsFromDays));
 
-        get : function (units) {
-            units = normalizeUnits(units);
-            return this[units.toLowerCase() + 's']();
-        },
+        // 12 months -> 1 year
+        years = absFloor(months / 12);
+        months %= 12;
 
-        as : function (units) {
-            units = normalizeUnits(units);
-            return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's']();
-        },
+        data.days   = days;
+        data.months = months;
+        data.years  = years;
 
-        lang : moment.fn.lang,
-
-        toIsoString : function () {
-            // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
-            var years = Math.abs(this.years()),
-                months = Math.abs(this.months()),
-                days = Math.abs(this.days()),
-                hours = Math.abs(this.hours()),
-                minutes = Math.abs(this.minutes()),
-                seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
-
-            if (!this.asSeconds()) {
-                // this is the same as C#'s (Noda) and python (isodate)...
-                // but not other JS (goog.date)
-                return 'P0D';
-            }
-
-            return (this.asSeconds() < 0 ? '-' : '') +
-                'P' +
-                (years ? years + 'Y' : '') +
-                (months ? months + 'M' : '') +
-                (days ? days + 'D' : '') +
-                ((hours || minutes || seconds) ? 'T' : '') +
-                (hours ? hours + 'H' : '') +
-                (minutes ? minutes + 'M' : '') +
-                (seconds ? seconds + 'S' : '');
-        }
-    });
+        return this;
+    }
 
-    function makeDurationGetter(name) {
-        moment.duration.fn[name] = function () {
-            return this._data[name];
-        };
+    function daysToMonths (days) {
+        // 400 years have 146097 days (taking into account leap year rules)
+        // 400 years have 12 months === 4800
+        return days * 4800 / 146097;
     }
 
-    function makeDurationAsGetter(name, factor) {
-        moment.duration.fn['as' + name] = function () {
-            return +this / factor;
-        };
+    function monthsToDays (months) {
+        // the reverse of daysToMonths
+        return months * 146097 / 4800;
     }
 
-    for (i in unitMillisecondFactors) {
-        if (unitMillisecondFactors.hasOwnProperty(i)) {
-            makeDurationAsGetter(i, unitMillisecondFactors[i]);
-            makeDurationGetter(i.toLowerCase());
+    function as (units) {
+        var days;
+        var months;
+        var milliseconds = this._milliseconds;
+
+        units = normalizeUnits(units);
+
+        if (units === 'month' || units === 'year') {
+            days   = this._days   + milliseconds / 864e5;
+            months = this._months + daysToMonths(days);
+            return units === 'month' ? months : months / 12;
+        } else {
+            // handle milliseconds separately because of floating point math errors (issue #1867)
+            days = this._days + Math.round(monthsToDays(this._months));
+            switch (units) {
+                case 'week'   : return days / 7     + milliseconds / 6048e5;
+                case 'day'    : return days         + milliseconds / 864e5;
+                case 'hour'   : return days * 24    + milliseconds / 36e5;
+                case 'minute' : return days * 1440  + milliseconds / 6e4;
+                case 'second' : return days * 86400 + milliseconds / 1000;
+                // Math.floor prevents floating point math errors here
+                case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
+                default: throw new Error('Unknown unit ' + units);
+            }
         }
     }
 
-    makeDurationAsGetter('Weeks', 6048e5);
-    moment.duration.fn.asMonths = function () {
-        return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12;
-    };
+    // TODO: Use this.as('ms')?
+    function duration_as__valueOf () {
+        return (
+            this._milliseconds +
+            this._days * 864e5 +
+            (this._months % 12) * 2592e6 +
+            toInt(this._months / 12) * 31536e6
+        );
+    }
 
+    function makeAs (alias) {
+        return function () {
+            return this.as(alias);
+        };
+    }
 
-    /************************************
-        Default Lang
-    ************************************/
+    var asMilliseconds = makeAs('ms');
+    var asSeconds      = makeAs('s');
+    var asMinutes      = makeAs('m');
+    var asHours        = makeAs('h');
+    var asDays         = makeAs('d');
+    var asWeeks        = makeAs('w');
+    var asMonths       = makeAs('M');
+    var asYears        = makeAs('y');
 
+    function duration_get__get (units) {
+        units = normalizeUnits(units);
+        return this[units + 's']();
+    }
 
-    // Set default language, other languages will inherit from English.
-    moment.lang('en', {
-        ordinal : function (number) {
-            var b = number % 10,
-                output = (toInt(number % 100 / 10) === 1) ? 'th' :
-                (b === 1) ? 'st' :
-                (b === 2) ? 'nd' :
-                (b === 3) ? 'rd' : 'th';
-            return number + output;
-        }
-    });
+    function makeGetter(name) {
+        return function () {
+            return this._data[name];
+        };
+    }
+
+    var milliseconds = makeGetter('milliseconds');
+    var seconds      = makeGetter('seconds');
+    var minutes      = makeGetter('minutes');
+    var hours        = makeGetter('hours');
+    var days         = makeGetter('days');
+    var months       = makeGetter('months');
+    var years        = makeGetter('years');
 
-    /* EMBED_LANGUAGES */
+    function weeks () {
+        return absFloor(this.days() / 7);
+    }
 
-    /************************************
-        Exposing Moment
-    ************************************/
+    var round = Math.round;
+    var thresholds = {
+        s: 45,  // seconds to minute
+        m: 45,  // minutes to hour
+        h: 22,  // hours to day
+        d: 26,  // days to month
+        M: 11   // months to year
+    };
 
-    function makeGlobal(deprecate) {
-        var warned = false, local_moment = moment;
-        /*global ender:false */
-        if (typeof ender !== 'undefined') {
-            return;
+    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+    }
+
+    function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) {
+        var duration = create__createDuration(posNegDuration).abs();
+        var seconds  = round(duration.as('s'));
+        var minutes  = round(duration.as('m'));
+        var hours    = round(duration.as('h'));
+        var days     = round(duration.as('d'));
+        var months   = round(duration.as('M'));
+        var years    = round(duration.as('y'));
+
+        var a = seconds < thresholds.s && ['s', seconds]  ||
+                minutes <= 1           && ['m']           ||
+                minutes < thresholds.m && ['mm', minutes] ||
+                hours   <= 1           && ['h']           ||
+                hours   < thresholds.h && ['hh', hours]   ||
+                days    <= 1           && ['d']           ||
+                days    < thresholds.d && ['dd', days]    ||
+                months  <= 1           && ['M']           ||
+                months  < thresholds.M && ['MM', months]  ||
+                years   <= 1           && ['y']           || ['yy', years];
+
+        a[2] = withoutSuffix;
+        a[3] = +posNegDuration > 0;
+        a[4] = locale;
+        return substituteTimeAgo.apply(null, a);
+    }
+
+    // This function allows you to set a threshold for relative time strings
+    function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) {
+        if (thresholds[threshold] === undefined) {
+            return false;
         }
-        // here, `this` means `window` in the browser, or `global` on the server
-        // add `moment` as a global object via a string identifier,
-        // for Closure Compiler "advanced" mode
-        if (deprecate) {
-            global.moment = function () {
-                if (!warned && console && console.warn) {
-                    warned = true;
-                    console.warn(
-                            "Accessing Moment through the global scope is " +
-                            "deprecated, and will be removed in an upcoming " +
-                            "release.");
-                }
-                return local_moment.apply(null, arguments);
-            };
-            extend(global.moment, local_moment);
-        } else {
-            global['moment'] = moment;
+        if (limit === undefined) {
+            return thresholds[threshold];
         }
+        thresholds[threshold] = limit;
+        return true;
     }
 
-    // CommonJS module is defined
-    if (hasModule) {
-        module.exports = moment;
-        makeGlobal(true);
-    } else if (typeof define === "function" && define.amd) {
-        define("moment", function (require, exports, module) {
-            if (module.config && module.config() && module.config().noGlobal !== true) {
-                // If user provided noGlobal, he is aware of global
-                makeGlobal(module.config().noGlobal === undefined);
-            }
+    function humanize (withSuffix) {
+        var locale = this.localeData();
+        var output = duration_humanize__relativeTime(this, !withSuffix, locale);
+
+        if (withSuffix) {
+            output = locale.pastFuture(+this, output);
+        }
+
+        return locale.postformat(output);
+    }
+
+    var iso_string__abs = Math.abs;
+
+    function iso_string__toISOString() {
+        // for ISO strings we do not use the normal bubbling rules:
+        //  * milliseconds bubble up until they become hours
+        //  * days do not bubble at all
+        //  * months bubble up until they become years
+        // This is because there is no context-free conversion between hours and days
+        // (think of clock changes)
+        // and also not between days and months (28-31 days per month)
+        var seconds = iso_string__abs(this._milliseconds) / 1000;
+        var days         = iso_string__abs(this._days);
+        var months       = iso_string__abs(this._months);
+        var minutes, hours, years;
+
+        // 3600 seconds -> 60 minutes -> 1 hour
+        minutes           = absFloor(seconds / 60);
+        hours             = absFloor(minutes / 60);
+        seconds %= 60;
+        minutes %= 60;
+
+        // 12 months -> 1 year
+        years  = absFloor(months / 12);
+        months %= 12;
+
+
+        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+        var Y = years;
+        var M = months;
+        var D = days;
+        var h = hours;
+        var m = minutes;
+        var s = seconds;
+        var total = this.asSeconds();
+
+        if (!total) {
+            // this is the same as C#'s (Noda) and python (isodate)...
+            // but not other JS (goog.date)
+            return 'P0D';
+        }
+
+        return (total < 0 ? '-' : '') +
+            'P' +
+            (Y ? Y + 'Y' : '') +
+            (M ? M + 'M' : '') +
+            (D ? D + 'D' : '') +
+            ((h || m || s) ? 'T' : '') +
+            (h ? h + 'H' : '') +
+            (m ? m + 'M' : '') +
+            (s ? s + 'S' : '');
+    }
+
+    var duration_prototype__proto = Duration.prototype;
+
+    duration_prototype__proto.abs            = duration_abs__abs;
+    duration_prototype__proto.add            = duration_add_subtract__add;
+    duration_prototype__proto.subtract       = duration_add_subtract__subtract;
+    duration_prototype__proto.as             = as;
+    duration_prototype__proto.asMilliseconds = asMilliseconds;
+    duration_prototype__proto.asSeconds      = asSeconds;
+    duration_prototype__proto.asMinutes      = asMinutes;
+    duration_prototype__proto.asHours        = asHours;
+    duration_prototype__proto.asDays         = asDays;
+    duration_prototype__proto.asWeeks        = asWeeks;
+    duration_prototype__proto.asMonths       = asMonths;
+    duration_prototype__proto.asYears        = asYears;
+    duration_prototype__proto.valueOf        = duration_as__valueOf;
+    duration_prototype__proto._bubble        = bubble;
+    duration_prototype__proto.get            = duration_get__get;
+    duration_prototype__proto.milliseconds   = milliseconds;
+    duration_prototype__proto.seconds        = seconds;
+    duration_prototype__proto.minutes        = minutes;
+    duration_prototype__proto.hours          = hours;
+    duration_prototype__proto.days           = days;
+    duration_prototype__proto.weeks          = weeks;
+    duration_prototype__proto.months         = months;
+    duration_prototype__proto.years          = years;
+    duration_prototype__proto.humanize       = humanize;
+    duration_prototype__proto.toISOString    = iso_string__toISOString;
+    duration_prototype__proto.toString       = iso_string__toISOString;
+    duration_prototype__proto.toJSON         = iso_string__toISOString;
+    duration_prototype__proto.locale         = locale;
+    duration_prototype__proto.localeData     = localeData;
+
+    // Deprecations
+    duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString);
+    duration_prototype__proto.lang = lang;
+
+    // Side effect imports
+
+    // FORMATTING
+
+    addFormatToken('X', 0, 0, 'unix');
+    addFormatToken('x', 0, 0, 'valueOf');
+
+    // PARSING
+
+    addRegexToken('x', matchSigned);
+    addRegexToken('X', matchTimestamp);
+    addParseToken('X', function (input, array, config) {
+        config._d = new Date(parseFloat(input, 10) * 1000);
+    });
+    addParseToken('x', function (input, array, config) {
+        config._d = new Date(toInt(input));
+    });
 
-            return moment;
-        });
-    } else {
-        makeGlobal();
-    }
-}).call(this);
-;/**
+    // Side effect imports
+
+
+    utils_hooks__hooks.version = '2.13.0';
+
+    setHookCallback(local__createLocal);
+
+    utils_hooks__hooks.fn                    = momentPrototype;
+    utils_hooks__hooks.min                   = min;
+    utils_hooks__hooks.max                   = max;
+    utils_hooks__hooks.now                   = now;
+    utils_hooks__hooks.utc                   = create_utc__createUTC;
+    utils_hooks__hooks.unix                  = moment__createUnix;
+    utils_hooks__hooks.months                = lists__listMonths;
+    utils_hooks__hooks.isDate                = isDate;
+    utils_hooks__hooks.locale                = locale_locales__getSetGlobalLocale;
+    utils_hooks__hooks.invalid               = valid__createInvalid;
+    utils_hooks__hooks.duration              = create__createDuration;
+    utils_hooks__hooks.isMoment              = isMoment;
+    utils_hooks__hooks.weekdays              = lists__listWeekdays;
+    utils_hooks__hooks.parseZone             = moment__createInZone;
+    utils_hooks__hooks.localeData            = locale_locales__getLocale;
+    utils_hooks__hooks.isDuration            = isDuration;
+    utils_hooks__hooks.monthsShort           = lists__listMonthsShort;
+    utils_hooks__hooks.weekdaysMin           = lists__listWeekdaysMin;
+    utils_hooks__hooks.defineLocale          = defineLocale;
+    utils_hooks__hooks.updateLocale          = updateLocale;
+    utils_hooks__hooks.locales               = locale_locales__listLocales;
+    utils_hooks__hooks.weekdaysShort         = lists__listWeekdaysShort;
+    utils_hooks__hooks.normalizeUnits        = normalizeUnits;
+    utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold;
+    utils_hooks__hooks.prototype             = momentPrototype;
+
+    var _moment = utils_hooks__hooks;
+
+    return _moment;
+
+}));;/**
  * Parse a text request to a json query object tree
  *
  * @param  {String} string The string to parse
@@ -5688,7 +7364,22 @@ return new Parser;
       this.mom = moment(str);
       this.setPrecision(MONTH);
     } else if (str.match(/\d\d\d\d/)) {
-      this.mom = moment(str);
+      // Creating a moment with only the year will show this deprecation
+      // warning:
+      //
+      // Deprecation warning: moment construction falls back to js Date. This is
+      // discouraged and will be removed in upcoming major release. Please refer
+      // to https://github.com/moment/moment/issues/1407 for more info.
+      //
+      // TL;DR: parsing year-only strings with momentjs falls back to native
+      // Date and it won't correctly represent the year in local time if UTF
+      // offset is negative.
+      //
+      // The solution is to use the format parameter, so momentjs won't fall
+      // back to the native Date and we will have the correct year in local
+      // time.
+      //
+      this.mom = moment(str, 'YYYY');
       this.setPrecision(YEAR);
     }
 
@@ -6467,7 +8158,7 @@ return new Parser;
         var ceilHeapSize = function (v) {
             // The asm.js spec says:
             // The heap object's byteLength must be either
-            // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
+            // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
             // Also, byteLengths smaller than 2^16 are deprecated.
             var p;
             // If v is smaller than 2^16, the smallest possible solution
@@ -6898,7 +8589,9 @@ return new Parser;
                                          id, options);
           }
           // Already exists on destination
-          throw new jIO.util.jIOError("Conflict on '" + id + "'",
+          throw new jIO.util.jIOError("Conflict on '" + id + "': " +
+                                      JSON.stringify(doc) + " !== " +
+                                      JSON.stringify(remote_doc),
                                       409);
         });
     }
@@ -7003,7 +8696,9 @@ return new Parser;
                     return;
                   }
                   if (conflict_force !== true) {
-                    throw new jIO.util.jIOError("Conflict on '" + id + "'",
+                    throw new jIO.util.jIOError("Conflict on '" + id + "': " +
+                                                JSON.stringify(doc) + " !== " +
+                                                JSON.stringify(remote_doc),
                                                 409);
                   }
                 }
@@ -7361,7 +9056,7 @@ return new Parser;
  */
 
 /*jslint nomen: true*/
-/*global jIO*/
+/*global jIO, RSVP*/
 
 /**
  * JIO Memory Storage. Type = 'memory'.
@@ -7376,7 +9071,7 @@ return new Parser;
  * @class MemoryStorage
  */
 
-(function (jIO) {
+(function (jIO, JSON, RSVP) {
   "use strict";
 
   /**
@@ -7395,13 +9090,13 @@ return new Parser;
         attachments: {}
       };
     }
-    this._database[id].doc = metadata;
+    this._database[id].doc = JSON.stringify(metadata);
     return id;
   };
 
   MemoryStorage.prototype.get = function (id) {
     try {
-      return this._database[id].doc;
+      return JSON.parse(this._database[id].doc);
     } catch (error) {
       if (error instanceof TypeError) {
         throw new jIO.util.jIOError(
@@ -7448,7 +9143,7 @@ return new Parser;
           404
         );
       }
-      return result;
+      return jIO.util.dataURItoBlob(result);
     } catch (error) {
       if (error instanceof TypeError) {
         throw new jIO.util.jIOError(
@@ -7470,7 +9165,13 @@ return new Parser;
       }
       throw error;
     }
-    attachment_dict[name] = blob;
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.readBlobAsDataURL(blob);
+      })
+      .push(function (evt) {
+        attachment_dict[name] = evt.target.result;
+      });
   };
 
   MemoryStorage.prototype.removeAttachment = function (id, name) {
@@ -7501,7 +9202,7 @@ return new Parser;
           rows.push({
             id: i,
             value: {},
-            doc: this._database[i]
+            doc: JSON.parse(this._database[i].doc)
           });
         } else {
           rows.push({
@@ -7517,7 +9218,7 @@ return new Parser;
 
   jIO.addStorage('memory', MemoryStorage);
 
-}(jIO));
+}(jIO, JSON, RSVP));
 ;/*jslint nomen: true*/
 /*global RSVP, Blob, LZString, DOMException*/
 (function (RSVP, Blob, LZString, DOMException) {
@@ -8778,7 +10479,7 @@ return new Parser;
       .push(function () {
         var form = json._embedded._view,
           converted_json = {
-            portal_type: json.portal_type
+            portal_type: json._links.type.name
           },
           form_data_json = {},
           field,
@@ -8786,6 +10487,11 @@ return new Parser;
           prefix_length,
           result;
 
+        if (json._links.hasOwnProperty('parent')) {
+          converted_json.parent_relative_url =
+            new URI(json._links.parent.href).segment(2);
+        }
+
         form_data_json.form_id = {
           "key": [form.form_id.key],
           "default": form.form_id["default"]
@@ -8905,7 +10611,6 @@ return new Parser;
           hateoas = JSON.parse(response.target.responseText);
 
         function pushResult(json) {
-          json.portal_type = json._links.type.name;
           return extractPropertyFromFormJSON(json)
             .push(function (json2) {
               return convertJSONToGet(json2);
@@ -9023,7 +10728,6 @@ return new Parser;
                                    {"_view": this._default_view_reference})
         .push(function (response) {
           var result = JSON.parse(response.target.responseText);
-          result.portal_type = result._links.type.name;
           // Remove all ERP5 hateoas links / convert them into jIO ID
 
           // XXX Change default action to an jio urn with attachment name inside
@@ -9332,10 +11036,18 @@ return new Parser;
   };
 
   QueryStorage.prototype.hasCapacity = function (name) {
+    var this_storage_capacity_list = ["limit",
+                                      "sort",
+                                      "select",
+                                      "query"];
+
+    if (this_storage_capacity_list.indexOf(name) !== -1) {
+      return true;
+    }
     if (name === "list") {
       return this._sub_storage.hasCapacity(name);
     }
-    return true;
+    return false;
   };
   QueryStorage.prototype.buildQuery = function (options) {
     var substorage = this._sub_storage,
diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml
index 1b9f4c21ab..20d549d7ca 100644
--- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml
+++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml
@@ -236,7 +236,7 @@
             </item>
             <item>
                 <key> <string>serial</string> </key>
-                <value> <string>950.24601.18563.49681</string> </value>
+                <value> <string>951.35286.47701.22630</string> </value>
             </item>
             <item>
                 <key> <string>state</string> </key>
@@ -254,7 +254,7 @@
                     </tuple>
                     <state>
                       <tuple>
-                        <float>1460128878.65</float>
+                        <float>1466697567.97</float>
                         <string>UTC</string>
                       </tuple>
                     </state>
diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
index 78d6289ce7..de2f614c12 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js
@@ -169,7 +169,7 @@ var objectHelper = (function () {
             if (object.hasOwnProperty(propertyName)) {
                 property = object[propertyName];
                 // be aware, arrays are 'object', too
-                if (typeof property === "object") {
+                if ((typeof property === "object") && !(property instanceof RegExp)) {
                     deepFreeze(property);
                 }
             }
@@ -1527,422 +1527,243 @@ if (typeof define === 'function' && define.amd) {
   module.exports = LZString
 }
 ;//! moment.js
-//! version : 2.5.0
+//! version : 2.13.0
 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
 //! license : MIT
 //! momentjs.com
 
-(function (undefined) {
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+    typeof define === 'function' && define.amd ? define(factory) :
+    global.moment = factory()
+}(this, function () { 'use strict';
 
-    /************************************
-        Constants
-    ************************************/
+    var hookCallback;
 
-    var moment,
-        VERSION = "2.5.0",
-        global = this,
-        round = Math.round,
-        i,
-
-        YEAR = 0,
-        MONTH = 1,
-        DATE = 2,
-        HOUR = 3,
-        MINUTE = 4,
-        SECOND = 5,
-        MILLISECOND = 6,
-
-        // internal storage for language config files
-        languages = {},
-
-        // check for nodeJS
-        hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'),
-
-        // ASP.NET json date format regex
-        aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
-        aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
-
-        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
-        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
-        isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
-
-        // format tokens
-        formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,
-        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
-
-        // parsing token regexes
-        parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
-        parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
-        parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
-        parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
-        parseTokenDigits = /\d+/, // nonzero number of digits
-        parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
-        parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
-        parseTokenT = /T/i, // T (ISO separator)
-        parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
-
-        //strict parsing regexes
-        parseTokenOneDigit = /\d/, // 0 - 9
-        parseTokenTwoDigits = /\d\d/, // 00 - 99
-        parseTokenThreeDigits = /\d{3}/, // 000 - 999
-        parseTokenFourDigits = /\d{4}/, // 0000 - 9999
-        parseTokenSixDigits = /[+\-]?\d{6}/, // -999,999 - 999,999
-
-        // iso 8601 regex
-        // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
-        isoRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
-
-        isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
-
-        isoDates = [
-            'YYYY-MM-DD',
-            'GGGG-[W]WW',
-            'GGGG-[W]WW-E',
-            'YYYY-DDD'
-        ],
-
-        // iso time formats and regexes
-        isoTimes = [
-            ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
-            ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
-            ['HH:mm', /(T| )\d\d:\d\d/],
-            ['HH', /(T| )\d\d/]
-        ],
-
-        // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
-        parseTimezoneChunker = /([\+\-]|\d\d)/gi,
-
-        // getter and setter names
-        proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
-        unitMillisecondFactors = {
-            'Milliseconds' : 1,
-            'Seconds' : 1e3,
-            'Minutes' : 6e4,
-            'Hours' : 36e5,
-            'Days' : 864e5,
-            'Months' : 2592e6,
-            'Years' : 31536e6
-        },
-
-        unitAliases = {
-            ms : 'millisecond',
-            s : 'second',
-            m : 'minute',
-            h : 'hour',
-            d : 'day',
-            D : 'date',
-            w : 'week',
-            W : 'isoWeek',
-            M : 'month',
-            y : 'year',
-            DDD : 'dayOfYear',
-            e : 'weekday',
-            E : 'isoWeekday',
-            gg: 'weekYear',
-            GG: 'isoWeekYear'
-        },
-
-        camelFunctions = {
-            dayofyear : 'dayOfYear',
-            isoweekday : 'isoWeekday',
-            isoweek : 'isoWeek',
-            weekyear : 'weekYear',
-            isoweekyear : 'isoWeekYear'
-        },
-
-        // format function strings
-        formatFunctions = {},
-
-        // tokens to ordinalize and pad
-        ordinalizeTokens = 'DDD w W M D d'.split(' '),
-        paddedTokens = 'M D H h m s w W'.split(' '),
-
-        formatTokenFunctions = {
-            M    : function () {
-                return this.month() + 1;
-            },
-            MMM  : function (format) {
-                return this.lang().monthsShort(this, format);
-            },
-            MMMM : function (format) {
-                return this.lang().months(this, format);
-            },
-            D    : function () {
-                return this.date();
-            },
-            DDD  : function () {
-                return this.dayOfYear();
-            },
-            d    : function () {
-                return this.day();
-            },
-            dd   : function (format) {
-                return this.lang().weekdaysMin(this, format);
-            },
-            ddd  : function (format) {
-                return this.lang().weekdaysShort(this, format);
-            },
-            dddd : function (format) {
-                return this.lang().weekdays(this, format);
-            },
-            w    : function () {
-                return this.week();
-            },
-            W    : function () {
-                return this.isoWeek();
-            },
-            YY   : function () {
-                return leftZeroFill(this.year() % 100, 2);
-            },
-            YYYY : function () {
-                return leftZeroFill(this.year(), 4);
-            },
-            YYYYY : function () {
-                return leftZeroFill(this.year(), 5);
-            },
-            YYYYYY : function () {
-                var y = this.year(), sign = y >= 0 ? '+' : '-';
-                return sign + leftZeroFill(Math.abs(y), 6);
-            },
-            gg   : function () {
-                return leftZeroFill(this.weekYear() % 100, 2);
-            },
-            gggg : function () {
-                return this.weekYear();
-            },
-            ggggg : function () {
-                return leftZeroFill(this.weekYear(), 5);
-            },
-            GG   : function () {
-                return leftZeroFill(this.isoWeekYear() % 100, 2);
-            },
-            GGGG : function () {
-                return this.isoWeekYear();
-            },
-            GGGGG : function () {
-                return leftZeroFill(this.isoWeekYear(), 5);
-            },
-            e : function () {
-                return this.weekday();
-            },
-            E : function () {
-                return this.isoWeekday();
-            },
-            a    : function () {
-                return this.lang().meridiem(this.hours(), this.minutes(), true);
-            },
-            A    : function () {
-                return this.lang().meridiem(this.hours(), this.minutes(), false);
-            },
-            H    : function () {
-                return this.hours();
-            },
-            h    : function () {
-                return this.hours() % 12 || 12;
-            },
-            m    : function () {
-                return this.minutes();
-            },
-            s    : function () {
-                return this.seconds();
-            },
-            S    : function () {
-                return toInt(this.milliseconds() / 100);
-            },
-            SS   : function () {
-                return leftZeroFill(toInt(this.milliseconds() / 10), 2);
-            },
-            SSS  : function () {
-                return leftZeroFill(this.milliseconds(), 3);
-            },
-            SSSS : function () {
-                return leftZeroFill(this.milliseconds(), 3);
-            },
-            Z    : function () {
-                var a = -this.zone(),
-                    b = "+";
-                if (a < 0) {
-                    a = -a;
-                    b = "-";
-                }
-                return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2);
-            },
-            ZZ   : function () {
-                var a = -this.zone(),
-                    b = "+";
-                if (a < 0) {
-                    a = -a;
-                    b = "-";
-                }
-                return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
-            },
-            z : function () {
-                return this.zoneAbbr();
-            },
-            zz : function () {
-                return this.zoneName();
-            },
-            X    : function () {
-                return this.unix();
-            },
-            Q : function () {
-                return this.quarter();
-            }
-        },
-
-        lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
-
-    function padToken(func, count) {
-        return function (a) {
-            return leftZeroFill(func.call(this, a), count);
-        };
-    }
-    function ordinalizeToken(func, period) {
-        return function (a) {
-            return this.lang().ordinal(func.call(this, a), period);
-        };
+    function utils_hooks__hooks () {
+        return hookCallback.apply(null, arguments);
     }
 
-    while (ordinalizeTokens.length) {
-        i = ordinalizeTokens.pop();
-        formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
-    }
-    while (paddedTokens.length) {
-        i = paddedTokens.pop();
-        formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
+    // This is done to register the method called with moment()
+    // without creating circular dependencies.
+    function setHookCallback (callback) {
+        hookCallback = callback;
     }
-    formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
-
-
-    /************************************
-        Constructors
-    ************************************/
-
-    function Language() {
 
+    function isArray(input) {
+        return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
     }
 
-    // Moment prototype object
-    function Moment(config) {
-        checkOverflow(config);
-        extend(this, config);
+    function isDate(input) {
+        return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
     }
 
-    // Duration Constructor
-    function Duration(duration) {
-        var normalizedInput = normalizeObjectUnits(duration),
-            years = normalizedInput.year || 0,
-            months = normalizedInput.month || 0,
-            weeks = normalizedInput.week || 0,
-            days = normalizedInput.day || 0,
-            hours = normalizedInput.hour || 0,
-            minutes = normalizedInput.minute || 0,
-            seconds = normalizedInput.second || 0,
-            milliseconds = normalizedInput.millisecond || 0;
-
-        // representation for dateAddRemove
-        this._milliseconds = +milliseconds +
-            seconds * 1e3 + // 1000
-            minutes * 6e4 + // 1000 * 60
-            hours * 36e5; // 1000 * 60 * 60
-        // Because of dateAddRemove treats 24 hours as different from a
-        // day when working around DST, we need to store them separately
-        this._days = +days +
-            weeks * 7;
-        // It is impossible translate months into days without knowing
-        // which months you are are talking about, so we have to store
-        // it separately.
-        this._months = +months +
-            years * 12;
-
-        this._data = {};
-
-        this._bubble();
+    function map(arr, fn) {
+        var res = [], i;
+        for (i = 0; i < arr.length; ++i) {
+            res.push(fn(arr[i], i));
+        }
+        return res;
     }
 
-    /************************************
-        Helpers
-    ************************************/
-
+    function hasOwnProp(a, b) {
+        return Object.prototype.hasOwnProperty.call(a, b);
+    }
 
     function extend(a, b) {
         for (var i in b) {
-            if (b.hasOwnProperty(i)) {
+            if (hasOwnProp(b, i)) {
                 a[i] = b[i];
             }
         }
 
-        if (b.hasOwnProperty("toString")) {
+        if (hasOwnProp(b, 'toString')) {
             a.toString = b.toString;
         }
 
-        if (b.hasOwnProperty("valueOf")) {
+        if (hasOwnProp(b, 'valueOf')) {
             a.valueOf = b.valueOf;
         }
 
         return a;
     }
 
-    function absRound(number) {
-        if (number < 0) {
-            return Math.ceil(number);
-        } else {
-            return Math.floor(number);
+    function create_utc__createUTC (input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, true).utc();
+    }
+
+    function defaultParsingFlags() {
+        // We need to deep clone this object.
+        return {
+            empty           : false,
+            unusedTokens    : [],
+            unusedInput     : [],
+            overflow        : -2,
+            charsLeftOver   : 0,
+            nullInput       : false,
+            invalidMonth    : null,
+            invalidFormat   : false,
+            userInvalidated : false,
+            iso             : false,
+            parsedDateParts : [],
+            meridiem        : null
+        };
+    }
+
+    function getParsingFlags(m) {
+        if (m._pf == null) {
+            m._pf = defaultParsingFlags();
         }
+        return m._pf;
     }
 
-    // left zero fill a number
-    // see http://jsperf.com/left-zero-filling for performance comparison
-    function leftZeroFill(number, targetLength, forceSign) {
-        var output = Math.abs(number) + '',
-            sign = number >= 0;
+    var some;
+    if (Array.prototype.some) {
+        some = Array.prototype.some;
+    } else {
+        some = function (fun) {
+            var t = Object(this);
+            var len = t.length >>> 0;
+
+            for (var i = 0; i < len; i++) {
+                if (i in t && fun.call(this, t[i], i, t)) {
+                    return true;
+                }
+            }
+
+            return false;
+        };
+    }
+
+    function valid__isValid(m) {
+        if (m._isValid == null) {
+            var flags = getParsingFlags(m);
+            var parsedParts = some.call(flags.parsedDateParts, function (i) {
+                return i != null;
+            });
+            m._isValid = !isNaN(m._d.getTime()) &&
+                flags.overflow < 0 &&
+                !flags.empty &&
+                !flags.invalidMonth &&
+                !flags.invalidWeekday &&
+                !flags.nullInput &&
+                !flags.invalidFormat &&
+                !flags.userInvalidated &&
+                (!flags.meridiem || (flags.meridiem && parsedParts));
 
-        while (output.length < targetLength) {
-            output = '0' + output;
+            if (m._strict) {
+                m._isValid = m._isValid &&
+                    flags.charsLeftOver === 0 &&
+                    flags.unusedTokens.length === 0 &&
+                    flags.bigHour === undefined;
+            }
         }
-        return (sign ? (forceSign ? '+' : '') : '-') + output;
+        return m._isValid;
     }
 
-    // helper function for _.addTime and _.subtractTime
-    function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) {
-        var milliseconds = duration._milliseconds,
-            days = duration._days,
-            months = duration._months,
-            minutes,
-            hours;
+    function valid__createInvalid (flags) {
+        var m = create_utc__createUTC(NaN);
+        if (flags != null) {
+            extend(getParsingFlags(m), flags);
+        }
+        else {
+            getParsingFlags(m).userInvalidated = true;
+        }
 
-        if (milliseconds) {
-            mom._d.setTime(+mom._d + milliseconds * isAdding);
+        return m;
+    }
+
+    function isUndefined(input) {
+        return input === void 0;
+    }
+
+    // Plugins that add properties should also add the key here (null value),
+    // so we can properly clone ourselves.
+    var momentProperties = utils_hooks__hooks.momentProperties = [];
+
+    function copyConfig(to, from) {
+        var i, prop, val;
+
+        if (!isUndefined(from._isAMomentObject)) {
+            to._isAMomentObject = from._isAMomentObject;
         }
-        // store the minutes and hours so we can restore them
-        if (days || months) {
-            minutes = mom.minute();
-            hours = mom.hour();
+        if (!isUndefined(from._i)) {
+            to._i = from._i;
         }
-        if (days) {
-            mom.date(mom.date() + days * isAdding);
+        if (!isUndefined(from._f)) {
+            to._f = from._f;
         }
-        if (months) {
-            mom.month(mom.month() + months * isAdding);
+        if (!isUndefined(from._l)) {
+            to._l = from._l;
+        }
+        if (!isUndefined(from._strict)) {
+            to._strict = from._strict;
+        }
+        if (!isUndefined(from._tzm)) {
+            to._tzm = from._tzm;
+        }
+        if (!isUndefined(from._isUTC)) {
+            to._isUTC = from._isUTC;
+        }
+        if (!isUndefined(from._offset)) {
+            to._offset = from._offset;
+        }
+        if (!isUndefined(from._pf)) {
+            to._pf = getParsingFlags(from);
         }
-        if (milliseconds && !ignoreUpdateOffset) {
-            moment.updateOffset(mom);
+        if (!isUndefined(from._locale)) {
+            to._locale = from._locale;
         }
-        // restore the minutes and hours after possibly changing dst
-        if (days || months) {
-            mom.minute(minutes);
-            mom.hour(hours);
+
+        if (momentProperties.length > 0) {
+            for (i in momentProperties) {
+                prop = momentProperties[i];
+                val = from[prop];
+                if (!isUndefined(val)) {
+                    to[prop] = val;
+                }
+            }
         }
+
+        return to;
     }
 
-    // check if is an array
-    function isArray(input) {
-        return Object.prototype.toString.call(input) === '[object Array]';
+    var updateInProgress = false;
+
+    // Moment prototype object
+    function Moment(config) {
+        copyConfig(this, config);
+        this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+        // Prevent infinite loop in case updateOffset creates new moment
+        // objects.
+        if (updateInProgress === false) {
+            updateInProgress = true;
+            utils_hooks__hooks.updateOffset(this);
+            updateInProgress = false;
+        }
     }
 
-    function isDate(input) {
-        return  Object.prototype.toString.call(input) === '[object Date]' ||
-                input instanceof Date;
+    function isMoment (obj) {
+        return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
+    }
+
+    function absFloor (number) {
+        if (number < 0) {
+            return Math.ceil(number);
+        } else {
+            return Math.floor(number);
+        }
+    }
+
+    function toInt(argumentForCoercion) {
+        var coercedNumber = +argumentForCoercion,
+            value = 0;
+
+        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+            value = absFloor(coercedNumber);
+        }
+
+        return value;
     }
 
     // compare two arrays, return the number of differences
@@ -1960,432 +1781,379 @@ if (typeof define === 'function' && define.amd) {
         return diffs + lengthDiff;
     }
 
-    function normalizeUnits(units) {
-        if (units) {
-            var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
-            units = unitAliases[units] || camelFunctions[lowered] || lowered;
+    function warn(msg) {
+        if (utils_hooks__hooks.suppressDeprecationWarnings === false &&
+                (typeof console !==  'undefined') && console.warn) {
+            console.warn('Deprecation warning: ' + msg);
         }
-        return units;
     }
 
-    function normalizeObjectUnits(inputObject) {
-        var normalizedInput = {},
-            normalizedProp,
-            prop;
+    function deprecate(msg, fn) {
+        var firstTime = true;
 
-        for (prop in inputObject) {
-            if (inputObject.hasOwnProperty(prop)) {
-                normalizedProp = normalizeUnits(prop);
-                if (normalizedProp) {
-                    normalizedInput[normalizedProp] = inputObject[prop];
-                }
+        return extend(function () {
+            if (utils_hooks__hooks.deprecationHandler != null) {
+                utils_hooks__hooks.deprecationHandler(null, msg);
             }
-        }
-
-        return normalizedInput;
+            if (firstTime) {
+                warn(msg + '\nArguments: ' + Array.prototype.slice.call(arguments).join(', ') + '\n' + (new Error()).stack);
+                firstTime = false;
+            }
+            return fn.apply(this, arguments);
+        }, fn);
     }
 
-    function makeList(field) {
-        var count, setter;
+    var deprecations = {};
 
-        if (field.indexOf('week') === 0) {
-            count = 7;
-            setter = 'day';
-        }
-        else if (field.indexOf('month') === 0) {
-            count = 12;
-            setter = 'month';
+    function deprecateSimple(name, msg) {
+        if (utils_hooks__hooks.deprecationHandler != null) {
+            utils_hooks__hooks.deprecationHandler(name, msg);
         }
-        else {
-            return;
+        if (!deprecations[name]) {
+            warn(msg);
+            deprecations[name] = true;
         }
+    }
 
-        moment[field] = function (format, index) {
-            var i, getter,
-                method = moment.fn._lang[field],
-                results = [];
-
-            if (typeof format === 'number') {
-                index = format;
-                format = undefined;
-            }
-
-            getter = function (i) {
-                var m = moment().utc().set(setter, i);
-                return method.call(moment.fn._lang, m, format || '');
-            };
+    utils_hooks__hooks.suppressDeprecationWarnings = false;
+    utils_hooks__hooks.deprecationHandler = null;
 
-            if (index != null) {
-                return getter(index);
-            }
-            else {
-                for (i = 0; i < count; i++) {
-                    results.push(getter(i));
-                }
-                return results;
-            }
-        };
+    function isFunction(input) {
+        return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
     }
 
-    function toInt(argumentForCoercion) {
-        var coercedNumber = +argumentForCoercion,
-            value = 0;
+    function isObject(input) {
+        return Object.prototype.toString.call(input) === '[object Object]';
+    }
 
-        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
-            if (coercedNumber >= 0) {
-                value = Math.floor(coercedNumber);
+    function locale_set__set (config) {
+        var prop, i;
+        for (i in config) {
+            prop = config[i];
+            if (isFunction(prop)) {
+                this[i] = prop;
             } else {
-                value = Math.ceil(coercedNumber);
+                this['_' + i] = prop;
             }
         }
-
-        return value;
+        this._config = config;
+        // Lenient ordinal parsing accepts just a number in addition to
+        // number + (possibly) stuff coming from _ordinalParseLenient.
+        this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source);
     }
 
-    function daysInMonth(year, month) {
-        return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
-    }
-
-    function daysInYear(year) {
-        return isLeapYear(year) ? 366 : 365;
+    function mergeConfigs(parentConfig, childConfig) {
+        var res = extend({}, parentConfig), prop;
+        for (prop in childConfig) {
+            if (hasOwnProp(childConfig, prop)) {
+                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                    res[prop] = {};
+                    extend(res[prop], parentConfig[prop]);
+                    extend(res[prop], childConfig[prop]);
+                } else if (childConfig[prop] != null) {
+                    res[prop] = childConfig[prop];
+                } else {
+                    delete res[prop];
+                }
+            }
+        }
+        return res;
     }
 
-    function isLeapYear(year) {
-        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    function Locale(config) {
+        if (config != null) {
+            this.set(config);
+        }
     }
 
-    function checkOverflow(m) {
-        var overflow;
-        if (m._a && m._pf.overflow === -2) {
-            overflow =
-                m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
-                m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
-                m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
-                m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
-                m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
-                m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
-                -1;
+    var keys;
 
-            if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
-                overflow = DATE;
+    if (Object.keys) {
+        keys = Object.keys;
+    } else {
+        keys = function (obj) {
+            var i, res = [];
+            for (i in obj) {
+                if (hasOwnProp(obj, i)) {
+                    res.push(i);
+                }
             }
-
-            m._pf.overflow = overflow;
-        }
+            return res;
+        };
     }
 
-    function initializeParsingFlags(config) {
-        config._pf = {
-            empty : false,
-            unusedTokens : [],
-            unusedInput : [],
-            overflow : -2,
-            charsLeftOver : 0,
-            nullInput : false,
-            invalidMonth : null,
-            invalidFormat : false,
-            userInvalidated : false,
-            iso: false
-        };
+    // internal storage for locale config files
+    var locales = {};
+    var globalLocale;
+
+    function normalizeLocale(key) {
+        return key ? key.toLowerCase().replace('_', '-') : key;
     }
 
-    function isValid(m) {
-        if (m._isValid == null) {
-            m._isValid = !isNaN(m._d.getTime()) &&
-                m._pf.overflow < 0 &&
-                !m._pf.empty &&
-                !m._pf.invalidMonth &&
-                !m._pf.nullInput &&
-                !m._pf.invalidFormat &&
-                !m._pf.userInvalidated;
+    // pick the locale from the array
+    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+    function chooseLocale(names) {
+        var i = 0, j, next, locale, split;
 
-            if (m._strict) {
-                m._isValid = m._isValid &&
-                    m._pf.charsLeftOver === 0 &&
-                    m._pf.unusedTokens.length === 0;
+        while (i < names.length) {
+            split = normalizeLocale(names[i]).split('-');
+            j = split.length;
+            next = normalizeLocale(names[i + 1]);
+            next = next ? next.split('-') : null;
+            while (j > 0) {
+                locale = loadLocale(split.slice(0, j).join('-'));
+                if (locale) {
+                    return locale;
+                }
+                if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+                    //the next array item is better than a shallower substring of this one
+                    break;
+                }
+                j--;
             }
+            i++;
         }
-        return m._isValid;
-    }
-
-    function normalizeLanguage(key) {
-        return key ? key.toLowerCase().replace('_', '-') : key;
+        return null;
     }
 
-    // Return a moment from input, that is local/utc/zone equivalent to model.
-    function makeAs(input, model) {
-        return model._isUTC ? moment(input).zone(model._offset || 0) :
-            moment(input).local();
+    function loadLocale(name) {
+        var oldLocale = null;
+        // TODO: Find a better way to register and load all the locales in Node
+        if (!locales[name] && (typeof module !== 'undefined') &&
+                module && module.exports) {
+            try {
+                oldLocale = globalLocale._abbr;
+                require('./locale/' + name);
+                // because defineLocale currently also sets the global locale, we
+                // want to undo that for lazy loaded locales
+                locale_locales__getSetGlobalLocale(oldLocale);
+            } catch (e) { }
+        }
+        return locales[name];
     }
 
-    /************************************
-        Languages
-    ************************************/
+    // This function will load locale and then set the global locale.  If
+    // no arguments are passed in, it will simply return the current global
+    // locale key.
+    function locale_locales__getSetGlobalLocale (key, values) {
+        var data;
+        if (key) {
+            if (isUndefined(values)) {
+                data = locale_locales__getLocale(key);
+            }
+            else {
+                data = defineLocale(key, values);
+            }
 
+            if (data) {
+                // moment.duration._locale = moment._locale = data;
+                globalLocale = data;
+            }
+        }
 
-    extend(Language.prototype, {
+        return globalLocale._abbr;
+    }
 
-        set : function (config) {
-            var prop, i;
-            for (i in config) {
-                prop = config[i];
-                if (typeof prop === 'function') {
-                    this[i] = prop;
+    function defineLocale (name, config) {
+        if (config !== null) {
+            config.abbr = name;
+            if (locales[name] != null) {
+                deprecateSimple('defineLocaleOverride',
+                        'use moment.updateLocale(localeName, config) to change ' +
+                        'an existing locale. moment.defineLocale(localeName, ' +
+                        'config) should only be used for creating a new locale');
+                config = mergeConfigs(locales[name]._config, config);
+            } else if (config.parentLocale != null) {
+                if (locales[config.parentLocale] != null) {
+                    config = mergeConfigs(locales[config.parentLocale]._config, config);
                 } else {
-                    this['_' + i] = prop;
+                    // treat as if there is no base config
+                    deprecateSimple('parentLocaleUndefined',
+                            'specified parentLocale is not defined yet');
                 }
             }
-        },
-
-        _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
-        months : function (m) {
-            return this._months[m.month()];
-        },
+            locales[name] = new Locale(config);
 
-        _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
-        monthsShort : function (m) {
-            return this._monthsShort[m.month()];
-        },
+            // backwards compat for now: also set the locale
+            locale_locales__getSetGlobalLocale(name);
 
-        monthsParse : function (monthName) {
-            var i, mom, regex;
+            return locales[name];
+        } else {
+            // useful for testing
+            delete locales[name];
+            return null;
+        }
+    }
 
-            if (!this._monthsParse) {
-                this._monthsParse = [];
+    function updateLocale(name, config) {
+        if (config != null) {
+            var locale;
+            if (locales[name] != null) {
+                config = mergeConfigs(locales[name]._config, config);
             }
+            locale = new Locale(config);
+            locale.parentLocale = locales[name];
+            locales[name] = locale;
 
-            for (i = 0; i < 12; i++) {
-                // make the regex if we don't have it already
-                if (!this._monthsParse[i]) {
-                    mom = moment.utc([2000, i]);
-                    regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
-                    this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
-                }
-                // test the regex
-                if (this._monthsParse[i].test(monthName)) {
-                    return i;
+            // backwards compat for now: also set the locale
+            locale_locales__getSetGlobalLocale(name);
+        } else {
+            // pass null for config to unupdate, useful for tests
+            if (locales[name] != null) {
+                if (locales[name].parentLocale != null) {
+                    locales[name] = locales[name].parentLocale;
+                } else if (locales[name] != null) {
+                    delete locales[name];
                 }
             }
-        },
-
-        _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
-        weekdays : function (m) {
-            return this._weekdays[m.day()];
-        },
+        }
+        return locales[name];
+    }
 
-        _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
-        weekdaysShort : function (m) {
-            return this._weekdaysShort[m.day()];
-        },
+    // returns locale data
+    function locale_locales__getLocale (key) {
+        var locale;
 
-        _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
-        weekdaysMin : function (m) {
-            return this._weekdaysMin[m.day()];
-        },
+        if (key && key._locale && key._locale._abbr) {
+            key = key._locale._abbr;
+        }
 
-        weekdaysParse : function (weekdayName) {
-            var i, mom, regex;
+        if (!key) {
+            return globalLocale;
+        }
 
-            if (!this._weekdaysParse) {
-                this._weekdaysParse = [];
+        if (!isArray(key)) {
+            //short-circuit everything else
+            locale = loadLocale(key);
+            if (locale) {
+                return locale;
             }
+            key = [key];
+        }
 
-            for (i = 0; i < 7; i++) {
-                // make the regex if we don't have it already
-                if (!this._weekdaysParse[i]) {
-                    mom = moment([2000, 1]).day(i);
-                    regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
-                    this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
-                }
-                // test the regex
-                if (this._weekdaysParse[i].test(weekdayName)) {
-                    return i;
-                }
-            }
-        },
+        return chooseLocale(key);
+    }
 
-        _longDateFormat : {
-            LT : "h:mm A",
-            L : "MM/DD/YYYY",
-            LL : "MMMM D YYYY",
-            LLL : "MMMM D YYYY LT",
-            LLLL : "dddd, MMMM D YYYY LT"
-        },
-        longDateFormat : function (key) {
-            var output = this._longDateFormat[key];
-            if (!output && this._longDateFormat[key.toUpperCase()]) {
-                output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
-                    return val.slice(1);
-                });
-                this._longDateFormat[key] = output;
-            }
-            return output;
-        },
-
-        isPM : function (input) {
-            // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
-            // Using charAt should be more compatible.
-            return ((input + '').toLowerCase().charAt(0) === 'p');
-        },
+    function locale_locales__listLocales() {
+        return keys(locales);
+    }
 
-        _meridiemParse : /[ap]\.?m?\.?/i,
-        meridiem : function (hours, minutes, isLower) {
-            if (hours > 11) {
-                return isLower ? 'pm' : 'PM';
-            } else {
-                return isLower ? 'am' : 'AM';
-            }
-        },
+    var aliases = {};
 
-        _calendar : {
-            sameDay : '[Today at] LT',
-            nextDay : '[Tomorrow at] LT',
-            nextWeek : 'dddd [at] LT',
-            lastDay : '[Yesterday at] LT',
-            lastWeek : '[Last] dddd [at] LT',
-            sameElse : 'L'
-        },
-        calendar : function (key, mom) {
-            var output = this._calendar[key];
-            return typeof output === 'function' ? output.apply(mom) : output;
-        },
+    function addUnitAlias (unit, shorthand) {
+        var lowerCase = unit.toLowerCase();
+        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+    }
 
-        _relativeTime : {
-            future : "in %s",
-            past : "%s ago",
-            s : "a few seconds",
-            m : "a minute",
-            mm : "%d minutes",
-            h : "an hour",
-            hh : "%d hours",
-            d : "a day",
-            dd : "%d days",
-            M : "a month",
-            MM : "%d months",
-            y : "a year",
-            yy : "%d years"
-        },
-        relativeTime : function (number, withoutSuffix, string, isFuture) {
-            var output = this._relativeTime[string];
-            return (typeof output === 'function') ?
-                output(number, withoutSuffix, string, isFuture) :
-                output.replace(/%d/i, number);
-        },
-        pastFuture : function (diff, output) {
-            var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
-            return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
-        },
+    function normalizeUnits(units) {
+        return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+    }
 
-        ordinal : function (number) {
-            return this._ordinal.replace("%d", number);
-        },
-        _ordinal : "%d",
+    function normalizeObjectUnits(inputObject) {
+        var normalizedInput = {},
+            normalizedProp,
+            prop;
 
-        preparse : function (string) {
-            return string;
-        },
+        for (prop in inputObject) {
+            if (hasOwnProp(inputObject, prop)) {
+                normalizedProp = normalizeUnits(prop);
+                if (normalizedProp) {
+                    normalizedInput[normalizedProp] = inputObject[prop];
+                }
+            }
+        }
 
-        postformat : function (string) {
-            return string;
-        },
+        return normalizedInput;
+    }
 
-        week : function (mom) {
-            return weekOfYear(mom, this._week.dow, this._week.doy).week;
-        },
+    function makeGetSet (unit, keepTime) {
+        return function (value) {
+            if (value != null) {
+                get_set__set(this, unit, value);
+                utils_hooks__hooks.updateOffset(this, keepTime);
+                return this;
+            } else {
+                return get_set__get(this, unit);
+            }
+        };
+    }
 
-        _week : {
-            dow : 0, // Sunday is the first day of the week.
-            doy : 6  // The week that contains Jan 1st is the first week of the year.
-        },
+    function get_set__get (mom, unit) {
+        return mom.isValid() ?
+            mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
+    }
 
-        _invalidDate: 'Invalid date',
-        invalidDate: function () {
-            return this._invalidDate;
+    function get_set__set (mom, unit, value) {
+        if (mom.isValid()) {
+            mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
         }
-    });
+    }
 
-    // Loads a language definition into the `languages` cache.  The function
-    // takes a key and optionally values.  If not in the browser and no values
-    // are provided, it will load the language file module.  As a convenience,
-    // this function also returns the language values.
-    function loadLang(key, values) {
-        values.abbr = key;
-        if (!languages[key]) {
-            languages[key] = new Language();
+    // MOMENTS
+
+    function getSet (units, value) {
+        var unit;
+        if (typeof units === 'object') {
+            for (unit in units) {
+                this.set(unit, units[unit]);
+            }
+        } else {
+            units = normalizeUnits(units);
+            if (isFunction(this[units])) {
+                return this[units](value);
+            }
         }
-        languages[key].set(values);
-        return languages[key];
+        return this;
     }
 
-    // Remove a language from the `languages` cache. Mostly useful in tests.
-    function unloadLang(key) {
-        delete languages[key];
+    function zeroFill(number, targetLength, forceSign) {
+        var absNumber = '' + Math.abs(number),
+            zerosToFill = targetLength - absNumber.length,
+            sign = number >= 0;
+        return (sign ? (forceSign ? '+' : '') : '-') +
+            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
     }
 
-    // Determines which language definition to use and returns it.
-    //
-    // With no parameters, it will return the global language.  If you
-    // pass in a language key, such as 'en', it will return the
-    // definition for 'en', so long as 'en' has already been loaded using
-    // moment.lang.
-    function getLangDefinition(key) {
-        var i = 0, j, lang, next, split,
-            get = function (k) {
-                if (!languages[k] && hasModule) {
-                    try {
-                        require('./lang/' + k);
-                    } catch (e) { }
-                }
-                return languages[k];
-            };
+    var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
 
-        if (!key) {
-            return moment.fn._lang;
-        }
+    var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
 
-        if (!isArray(key)) {
-            //short-circuit everything else
-            lang = get(key);
-            if (lang) {
-                return lang;
-            }
-            key = [key];
-        }
+    var formatFunctions = {};
 
-        //pick the language from the array
-        //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
-        //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
-        while (i < key.length) {
-            split = normalizeLanguage(key[i]).split('-');
-            j = split.length;
-            next = normalizeLanguage(key[i + 1]);
-            next = next ? next.split('-') : null;
-            while (j > 0) {
-                lang = get(split.slice(0, j).join('-'));
-                if (lang) {
-                    return lang;
-                }
-                if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
-                    //the next array item is better than a shallower substring of this one
-                    break;
-                }
-                j--;
-            }
-            i++;
+    var formatTokenFunctions = {};
+
+    // token:    'M'
+    // padded:   ['MM', 2]
+    // ordinal:  'Mo'
+    // callback: function () { this.month() + 1 }
+    function addFormatToken (token, padded, ordinal, callback) {
+        var func = callback;
+        if (typeof callback === 'string') {
+            func = function () {
+                return this[callback]();
+            };
+        }
+        if (token) {
+            formatTokenFunctions[token] = func;
+        }
+        if (padded) {
+            formatTokenFunctions[padded[0]] = function () {
+                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+            };
+        }
+        if (ordinal) {
+            formatTokenFunctions[ordinal] = function () {
+                return this.localeData().ordinal(func.apply(this, arguments), token);
+            };
         }
-        return moment.fn._lang;
     }
 
-    /************************************
-        Formatting
-    ************************************/
-
-
     function removeFormattingTokens(input) {
         if (input.match(/\[[\s\S]/)) {
-            return input.replace(/^\[|\]$/g, "");
+            return input.replace(/^\[|\]$/g, '');
         }
-        return input.replace(/\\/g, "");
+        return input.replace(/\\/g, '');
     }
 
     function makeFormatFunction(format) {
@@ -2400,7 +2168,7 @@ if (typeof define === 'function' && define.amd) {
         }
 
         return function (mom) {
-            var output = "";
+            var output = '', i;
             for (i = 0; i < length; i++) {
                 output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
             }
@@ -2410,25 +2178,21 @@ if (typeof define === 'function' && define.amd) {
 
     // format date using native date object
     function formatMoment(m, format) {
-
         if (!m.isValid()) {
-            return m.lang().invalidDate();
+            return m.localeData().invalidDate();
         }
 
-        format = expandFormat(format, m.lang());
-
-        if (!formatFunctions[format]) {
-            formatFunctions[format] = makeFormatFunction(format);
-        }
+        format = expandFormat(format, m.localeData());
+        formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
 
         return formatFunctions[format](m);
     }
 
-    function expandFormat(format, lang) {
+    function expandFormat(format, locale) {
         var i = 5;
 
         function replaceLongDateFormatTokens(input) {
-            return lang.longDateFormat(input) || input;
+            return locale.longDateFormat(input) || input;
         }
 
         localFormattingTokens.lastIndex = 0;
@@ -2441,3789 +2205,5714 @@ if (typeof define === 'function' && define.amd) {
         return format;
     }
 
+    var match1         = /\d/;            //       0 - 9
+    var match2         = /\d\d/;          //      00 - 99
+    var match3         = /\d{3}/;         //     000 - 999
+    var match4         = /\d{4}/;         //    0000 - 9999
+    var match6         = /[+-]?\d{6}/;    // -999999 - 999999
+    var match1to2      = /\d\d?/;         //       0 - 99
+    var match3to4      = /\d\d\d\d?/;     //     999 - 9999
+    var match5to6      = /\d\d\d\d\d\d?/; //   99999 - 999999
+    var match1to3      = /\d{1,3}/;       //       0 - 999
+    var match1to4      = /\d{1,4}/;       //       0 - 9999
+    var match1to6      = /[+-]?\d{1,6}/;  // -999999 - 999999
 
-    /************************************
-        Parsing
-    ************************************/
-
-
-    // get the regex to find the next token
-    function getParseRegexForToken(token, config) {
-        var a, strict = config._strict;
-        switch (token) {
-        case 'DDDD':
-            return parseTokenThreeDigits;
-        case 'YYYY':
-        case 'GGGG':
-        case 'gggg':
-            return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
-        case 'YYYYYY':
-        case 'YYYYY':
-        case 'GGGGG':
-        case 'ggggg':
-            return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
-        case 'S':
-            if (strict) { return parseTokenOneDigit; }
-            /* falls through */
-        case 'SS':
-            if (strict) { return parseTokenTwoDigits; }
-            /* falls through */
-        case 'SSS':
-        case 'DDD':
-            return strict ? parseTokenThreeDigits : parseTokenOneToThreeDigits;
-        case 'MMM':
-        case 'MMMM':
-        case 'dd':
-        case 'ddd':
-        case 'dddd':
-            return parseTokenWord;
-        case 'a':
-        case 'A':
-            return getLangDefinition(config._l)._meridiemParse;
-        case 'X':
-            return parseTokenTimestampMs;
-        case 'Z':
-        case 'ZZ':
-            return parseTokenTimezone;
-        case 'T':
-            return parseTokenT;
-        case 'SSSS':
-            return parseTokenDigits;
-        case 'MM':
-        case 'DD':
-        case 'YY':
-        case 'GG':
-        case 'gg':
-        case 'HH':
-        case 'hh':
-        case 'mm':
-        case 'ss':
-        case 'ww':
-        case 'WW':
-            return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
-        case 'M':
-        case 'D':
-        case 'd':
-        case 'H':
-        case 'h':
-        case 'm':
-        case 's':
-        case 'w':
-        case 'W':
-        case 'e':
-        case 'E':
-            return strict ? parseTokenOneDigit : parseTokenOneOrTwoDigits;
-        default :
-            a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
-            return a;
-        }
+    var matchUnsigned  = /\d+/;           //       0 - inf
+    var matchSigned    = /[+-]?\d+/;      //    -inf - inf
+
+    var matchOffset    = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
+    var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+
+    var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
+
+    // any word (or two) characters or numbers including two/three word month in arabic.
+    // includes scottish gaelic two word and hyphenated months
+    var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
+
+
+    var regexes = {};
+
+    function addRegexToken (token, regex, strictRegex) {
+        regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
+            return (isStrict && strictRegex) ? strictRegex : regex;
+        };
     }
 
-    function timezoneMinutesFromString(string) {
-        string = string || "";
-        var possibleTzMatches = (string.match(parseTokenTimezone) || []),
-            tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
-            parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
-            minutes = +(parts[1] * 60) + toInt(parts[2]);
+    function getParseRegexForToken (token, config) {
+        if (!hasOwnProp(regexes, token)) {
+            return new RegExp(unescapeFormat(token));
+        }
 
-        return parts[0] === '+' ? -minutes : minutes;
+        return regexes[token](config._strict, config._locale);
     }
 
-    // function to convert string input to date
-    function addTimeToArrayFromToken(token, input, config) {
-        var a, datePartArray = config._a;
+    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+    function unescapeFormat(s) {
+        return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+            return p1 || p2 || p3 || p4;
+        }));
+    }
 
-        switch (token) {
-        // MONTH
-        case 'M' : // fall through to MM
-        case 'MM' :
-            if (input != null) {
-                datePartArray[MONTH] = toInt(input) - 1;
-            }
-            break;
-        case 'MMM' : // fall through to MMMM
-        case 'MMMM' :
-            a = getLangDefinition(config._l).monthsParse(input);
-            // if we didn't find a month name, mark the date as invalid.
-            if (a != null) {
-                datePartArray[MONTH] = a;
-            } else {
-                config._pf.invalidMonth = input;
-            }
-            break;
-        // DAY OF MONTH
-        case 'D' : // fall through to DD
-        case 'DD' :
-            if (input != null) {
-                datePartArray[DATE] = toInt(input);
-            }
-            break;
-        // DAY OF YEAR
-        case 'DDD' : // fall through to DDDD
-        case 'DDDD' :
-            if (input != null) {
-                config._dayOfYear = toInt(input);
-            }
+    function regexEscape(s) {
+        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    }
 
-            break;
-        // YEAR
-        case 'YY' :
-            datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
-            break;
-        case 'YYYY' :
-        case 'YYYYY' :
-        case 'YYYYYY' :
-            datePartArray[YEAR] = toInt(input);
-            break;
-        // AM / PM
-        case 'a' : // fall through to A
-        case 'A' :
-            config._isPm = getLangDefinition(config._l).isPM(input);
-            break;
-        // 24 HOUR
-        case 'H' : // fall through to hh
-        case 'HH' : // fall through to hh
-        case 'h' : // fall through to hh
-        case 'hh' :
-            datePartArray[HOUR] = toInt(input);
-            break;
-        // MINUTE
-        case 'm' : // fall through to mm
-        case 'mm' :
-            datePartArray[MINUTE] = toInt(input);
-            break;
-        // SECOND
-        case 's' : // fall through to ss
-        case 'ss' :
-            datePartArray[SECOND] = toInt(input);
-            break;
-        // MILLISECOND
-        case 'S' :
-        case 'SS' :
-        case 'SSS' :
-        case 'SSSS' :
-            datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
-            break;
-        // UNIX TIMESTAMP WITH MS
-        case 'X':
-            config._d = new Date(parseFloat(input) * 1000);
-            break;
-        // TIMEZONE
-        case 'Z' : // fall through to ZZ
-        case 'ZZ' :
-            config._useUTC = true;
-            config._tzm = timezoneMinutesFromString(input);
-            break;
-        case 'w':
-        case 'ww':
-        case 'W':
-        case 'WW':
-        case 'd':
-        case 'dd':
-        case 'ddd':
-        case 'dddd':
-        case 'e':
-        case 'E':
-            token = token.substr(0, 1);
-            /* falls through */
-        case 'gg':
-        case 'gggg':
-        case 'GG':
-        case 'GGGG':
-        case 'GGGGG':
-            token = token.substr(0, 2);
-            if (input) {
-                config._w = config._w || {};
-                config._w[token] = input;
-            }
-            break;
+    var tokens = {};
+
+    function addParseToken (token, callback) {
+        var i, func = callback;
+        if (typeof token === 'string') {
+            token = [token];
+        }
+        if (typeof callback === 'number') {
+            func = function (input, array) {
+                array[callback] = toInt(input);
+            };
+        }
+        for (i = 0; i < token.length; i++) {
+            tokens[token[i]] = func;
         }
     }
 
-    // convert an array to a date.
-    // the array should mirror the parameters below
-    // note: all values past the year are optional and will default to the lowest possible value.
-    // [year, month, day , hour, minute, second, millisecond]
-    function dateFromConfig(config) {
-        var i, date, input = [], currentDate,
-            yearToUse, fixYear, w, temp, lang, weekday, week;
+    function addWeekParseToken (token, callback) {
+        addParseToken(token, function (input, array, config, token) {
+            config._w = config._w || {};
+            callback(input, config._w, config, token);
+        });
+    }
 
-        if (config._d) {
-            return;
+    function addTimeToArrayFromToken(token, input, config) {
+        if (input != null && hasOwnProp(tokens, token)) {
+            tokens[token](input, config._a, config, token);
         }
+    }
 
-        currentDate = currentDateArray(config);
+    var YEAR = 0;
+    var MONTH = 1;
+    var DATE = 2;
+    var HOUR = 3;
+    var MINUTE = 4;
+    var SECOND = 5;
+    var MILLISECOND = 6;
+    var WEEK = 7;
+    var WEEKDAY = 8;
 
-        //compute day of the year from weeks and weekdays
-        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
-            fixYear = function (val) {
-                var int_val = parseInt(val, 10);
-                return val ?
-                  (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) :
-                  (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]);
-            };
+    var indexOf;
 
-            w = config._w;
-            if (w.GG != null || w.W != null || w.E != null) {
-                temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1);
+    if (Array.prototype.indexOf) {
+        indexOf = Array.prototype.indexOf;
+    } else {
+        indexOf = function (o) {
+            // I know
+            var i;
+            for (i = 0; i < this.length; ++i) {
+                if (this[i] === o) {
+                    return i;
+                }
             }
-            else {
-                lang = getLangDefinition(config._l);
-                weekday = w.d != null ?  parseWeekday(w.d, lang) :
-                  (w.e != null ?  parseInt(w.e, 10) + lang._week.dow : 0);
+            return -1;
+        };
+    }
 
-                week = parseInt(w.w, 10) || 1;
+    function daysInMonth(year, month) {
+        return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+    }
 
-                //if we're parsing 'd', then the low day numbers may be next week
-                if (w.d != null && weekday < lang._week.dow) {
-                    week++;
-                }
+    // FORMATTING
 
-                temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow);
-            }
+    addFormatToken('M', ['MM', 2], 'Mo', function () {
+        return this.month() + 1;
+    });
 
-            config._a[YEAR] = temp.year;
-            config._dayOfYear = temp.dayOfYear;
-        }
+    addFormatToken('MMM', 0, 0, function (format) {
+        return this.localeData().monthsShort(this, format);
+    });
 
-        //if the day of the year is set, figure out what it is
-        if (config._dayOfYear) {
-            yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR];
+    addFormatToken('MMMM', 0, 0, function (format) {
+        return this.localeData().months(this, format);
+    });
 
-            if (config._dayOfYear > daysInYear(yearToUse)) {
-                config._pf._overflowDayOfYear = true;
-            }
+    // ALIASES
 
-            date = makeUTCDate(yearToUse, 0, config._dayOfYear);
-            config._a[MONTH] = date.getUTCMonth();
-            config._a[DATE] = date.getUTCDate();
-        }
+    addUnitAlias('month', 'M');
 
-        // Default to current date.
-        // * if no year, month, day of month are given, default to today
-        // * if day of month is given, default month and year
-        // * if month is given, default only year
-        // * if year is given, don't default anything
-        for (i = 0; i < 3 && config._a[i] == null; ++i) {
-            config._a[i] = input[i] = currentDate[i];
-        }
+    // PARSING
 
-        // Zero out whatever was not defaulted, including time
-        for (; i < 7; i++) {
-            config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+    addRegexToken('M',    match1to2);
+    addRegexToken('MM',   match1to2, match2);
+    addRegexToken('MMM',  function (isStrict, locale) {
+        return locale.monthsShortRegex(isStrict);
+    });
+    addRegexToken('MMMM', function (isStrict, locale) {
+        return locale.monthsRegex(isStrict);
+    });
+
+    addParseToken(['M', 'MM'], function (input, array) {
+        array[MONTH] = toInt(input) - 1;
+    });
+
+    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+        var month = config._locale.monthsParse(input, token, config._strict);
+        // if we didn't find a month name, mark the date as invalid.
+        if (month != null) {
+            array[MONTH] = month;
+        } else {
+            getParsingFlags(config).invalidMonth = input;
         }
+    });
 
-        // add the offsets to the time to be parsed so that we can have a clean array for checking isValid
-        input[HOUR] += toInt((config._tzm || 0) / 60);
-        input[MINUTE] += toInt((config._tzm || 0) % 60);
+    // LOCALES
 
-        config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
+    var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/;
+    var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+    function localeMonths (m, format) {
+        return isArray(this._months) ? this._months[m.month()] :
+            this._months[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
     }
 
-    function dateFromObject(config) {
-        var normalizedInput;
+    var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+    function localeMonthsShort (m, format) {
+        return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
+            this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
+    }
 
-        if (config._d) {
-            return;
+    function units_month__handleStrictParse(monthName, format, strict) {
+        var i, ii, mom, llc = monthName.toLocaleLowerCase();
+        if (!this._monthsParse) {
+            // this is not used
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+            for (i = 0; i < 12; ++i) {
+                mom = create_utc__createUTC([2000, i]);
+                this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
+                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+            }
         }
 
-        normalizedInput = normalizeObjectUnits(config._i);
-        config._a = [
-            normalizedInput.year,
-            normalizedInput.month,
-            normalizedInput.day,
-            normalizedInput.hour,
-            normalizedInput.minute,
-            normalizedInput.second,
-            normalizedInput.millisecond
-        ];
-
-        dateFromConfig(config);
-    }
-
-    function currentDateArray(config) {
-        var now = new Date();
-        if (config._useUTC) {
-            return [
-                now.getUTCFullYear(),
-                now.getUTCMonth(),
-                now.getUTCDate()
-            ];
+        if (strict) {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
         } else {
-            return [now.getFullYear(), now.getMonth(), now.getDate()];
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
         }
     }
 
-    // date from string and format string
-    function makeDateFromStringAndFormat(config) {
-
-        config._a = [];
-        config._pf.empty = true;
+    function localeMonthsParse (monthName, format, strict) {
+        var i, mom, regex;
 
-        // This array is used to make a Date, either with `new Date` or `Date.UTC`
-        var lang = getLangDefinition(config._l),
-            string = '' + config._i,
-            i, parsedInput, tokens, token, skipped,
-            stringLength = string.length,
-            totalParsedInputLength = 0;
+        if (this._monthsParseExact) {
+            return units_month__handleStrictParse.call(this, monthName, format, strict);
+        }
 
-        tokens = expandFormat(config._f, lang).match(formattingTokens) || [];
+        if (!this._monthsParse) {
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+        }
 
-        for (i = 0; i < tokens.length; i++) {
-            token = tokens[i];
-            parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
-            if (parsedInput) {
-                skipped = string.substr(0, string.indexOf(parsedInput));
-                if (skipped.length > 0) {
-                    config._pf.unusedInput.push(skipped);
-                }
-                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
-                totalParsedInputLength += parsedInput.length;
+        // TODO: add sorting
+        // Sorting makes sure if one month (or abbr) is a prefix of another
+        // see sorting in computeMonthsParse
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = create_utc__createUTC([2000, i]);
+            if (strict && !this._longMonthsParse[i]) {
+                this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
+                this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
             }
-            // don't parse if it's not a known token
-            if (formatTokenFunctions[token]) {
-                if (parsedInput) {
-                    config._pf.empty = false;
-                }
-                else {
-                    config._pf.unusedTokens.push(token);
-                }
-                addTimeToArrayFromToken(token, parsedInput, config);
+            if (!strict && !this._monthsParse[i]) {
+                regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
             }
-            else if (config._strict && !parsedInput) {
-                config._pf.unusedTokens.push(token);
+            // test the regex
+            if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+                return i;
+            } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+                return i;
+            } else if (!strict && this._monthsParse[i].test(monthName)) {
+                return i;
             }
         }
+    }
 
-        // add remaining unparsed input length to the string
-        config._pf.charsLeftOver = stringLength - totalParsedInputLength;
-        if (string.length > 0) {
-            config._pf.unusedInput.push(string);
-        }
+    // MOMENTS
 
-        // handle am pm
-        if (config._isPm && config._a[HOUR] < 12) {
-            config._a[HOUR] += 12;
+    function setMonth (mom, value) {
+        var dayOfMonth;
+
+        if (!mom.isValid()) {
+            // No op
+            return mom;
         }
-        // if is 12 am, change hours to 0
-        if (config._isPm === false && config._a[HOUR] === 12) {
-            config._a[HOUR] = 0;
+
+        if (typeof value === 'string') {
+            if (/^\d+$/.test(value)) {
+                value = toInt(value);
+            } else {
+                value = mom.localeData().monthsParse(value);
+                // TODO: Another silent failure?
+                if (typeof value !== 'number') {
+                    return mom;
+                }
+            }
         }
 
-        dateFromConfig(config);
-        checkOverflow(config);
+        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+        return mom;
     }
 
-    function unescapeFormat(s) {
-        return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
-            return p1 || p2 || p3 || p4;
-        });
+    function getSetMonth (value) {
+        if (value != null) {
+            setMonth(this, value);
+            utils_hooks__hooks.updateOffset(this, true);
+            return this;
+        } else {
+            return get_set__get(this, 'Month');
+        }
     }
 
-    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
-    function regexpEscape(s) {
-        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    function getDaysInMonth () {
+        return daysInMonth(this.year(), this.month());
     }
 
-    // date from string and array of format strings
-    function makeDateFromStringAndArray(config) {
-        var tempConfig,
-            bestMoment,
-
-            scoreToBeat,
-            i,
-            currentScore;
+    var defaultMonthsShortRegex = matchWord;
+    function monthsShortRegex (isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsShortStrictRegex;
+            } else {
+                return this._monthsShortRegex;
+            }
+        } else {
+            return this._monthsShortStrictRegex && isStrict ?
+                this._monthsShortStrictRegex : this._monthsShortRegex;
+        }
+    }
 
-        if (config._f.length === 0) {
-            config._pf.invalidFormat = true;
-            config._d = new Date(NaN);
-            return;
+    var defaultMonthsRegex = matchWord;
+    function monthsRegex (isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsStrictRegex;
+            } else {
+                return this._monthsRegex;
+            }
+        } else {
+            return this._monthsStrictRegex && isStrict ?
+                this._monthsStrictRegex : this._monthsRegex;
         }
+    }
 
-        for (i = 0; i < config._f.length; i++) {
-            currentScore = 0;
-            tempConfig = extend({}, config);
-            initializeParsingFlags(tempConfig);
-            tempConfig._f = config._f[i];
-            makeDateFromStringAndFormat(tempConfig);
+    function computeMonthsParse () {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
 
-            if (!isValid(tempConfig)) {
-                continue;
-            }
+        var shortPieces = [], longPieces = [], mixedPieces = [],
+            i, mom;
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = create_utc__createUTC([2000, i]);
+            shortPieces.push(this.monthsShort(mom, ''));
+            longPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.monthsShort(mom, ''));
+        }
+        // Sorting makes sure if one month (or abbr) is a prefix of another it
+        // will match the longer piece.
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+        for (i = 0; i < 12; i++) {
+            shortPieces[i] = regexEscape(shortPieces[i]);
+            longPieces[i] = regexEscape(longPieces[i]);
+            mixedPieces[i] = regexEscape(mixedPieces[i]);
+        }
 
-            // if there is any input that was not parsed add a penalty for that format
-            currentScore += tempConfig._pf.charsLeftOver;
+        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._monthsShortRegex = this._monthsRegex;
+        this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+        this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+    }
 
-            //or tokens
-            currentScore += tempConfig._pf.unusedTokens.length * 10;
+    function checkOverflow (m) {
+        var overflow;
+        var a = m._a;
 
-            tempConfig._pf.score = currentScore;
+        if (a && getParsingFlags(m).overflow === -2) {
+            overflow =
+                a[MONTH]       < 0 || a[MONTH]       > 11  ? MONTH :
+                a[DATE]        < 1 || a[DATE]        > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
+                a[HOUR]        < 0 || a[HOUR]        > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
+                a[MINUTE]      < 0 || a[MINUTE]      > 59  ? MINUTE :
+                a[SECOND]      < 0 || a[SECOND]      > 59  ? SECOND :
+                a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
+                -1;
 
-            if (scoreToBeat == null || currentScore < scoreToBeat) {
-                scoreToBeat = currentScore;
-                bestMoment = tempConfig;
+            if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+                overflow = DATE;
+            }
+            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+                overflow = WEEK;
+            }
+            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+                overflow = WEEKDAY;
             }
+
+            getParsingFlags(m).overflow = overflow;
         }
 
-        extend(config, bestMoment || tempConfig);
+        return m;
     }
 
+    // iso 8601 regex
+    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+    var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/;
+    var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/;
+
+    var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
+
+    var isoDates = [
+        ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+        ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+        ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+        ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+        ['YYYY-DDD', /\d{4}-\d{3}/],
+        ['YYYY-MM', /\d{4}-\d\d/, false],
+        ['YYYYYYMMDD', /[+-]\d{10}/],
+        ['YYYYMMDD', /\d{8}/],
+        // YYYYMM is NOT allowed by the standard
+        ['GGGG[W]WWE', /\d{4}W\d{3}/],
+        ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+        ['YYYYDDD', /\d{7}/]
+    ];
+
+    // iso time formats and regexes
+    var isoTimes = [
+        ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+        ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+        ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+        ['HH:mm', /\d\d:\d\d/],
+        ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+        ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+        ['HHmmss', /\d\d\d\d\d\d/],
+        ['HHmm', /\d\d\d\d/],
+        ['HH', /\d\d/]
+    ];
+
+    var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+
     // date from iso format
-    function makeDateFromString(config) {
-        var i,
+    function configFromISO(config) {
+        var i, l,
             string = config._i,
-            match = isoRegex.exec(string);
+            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+            allowTime, dateFormat, timeFormat, tzFormat;
 
         if (match) {
-            config._pf.iso = true;
-            for (i = 4; i > 0; i--) {
-                if (match[i]) {
-                    // match[5] should be "T" or undefined
-                    config._f = isoDates[i - 1] + (match[6] || " ");
+            getParsingFlags(config).iso = true;
+
+            for (i = 0, l = isoDates.length; i < l; i++) {
+                if (isoDates[i][1].exec(match[1])) {
+                    dateFormat = isoDates[i][0];
+                    allowTime = isoDates[i][2] !== false;
                     break;
                 }
             }
-            for (i = 0; i < 4; i++) {
-                if (isoTimes[i][1].exec(string)) {
-                    config._f += isoTimes[i][0];
-                    break;
+            if (dateFormat == null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[3]) {
+                for (i = 0, l = isoTimes.length; i < l; i++) {
+                    if (isoTimes[i][1].exec(match[3])) {
+                        // match[2] should be 'T' or space
+                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                        break;
+                    }
+                }
+                if (timeFormat == null) {
+                    config._isValid = false;
+                    return;
                 }
             }
-            if (string.match(parseTokenTimezone)) {
-                config._f += "Z";
+            if (!allowTime && timeFormat != null) {
+                config._isValid = false;
+                return;
             }
-            makeDateFromStringAndFormat(config);
-        }
-        else {
-            config._d = new Date(string);
+            if (match[4]) {
+                if (tzRegex.exec(match[4])) {
+                    tzFormat = 'Z';
+                } else {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+            configFromStringAndFormat(config);
+        } else {
+            config._isValid = false;
         }
     }
 
-    function makeDateFromInput(config) {
-        var input = config._i,
-            matched = aspNetJsonRegex.exec(input);
+    // date from iso format or fallback
+    function configFromString(config) {
+        var matched = aspNetJsonRegex.exec(config._i);
 
-        if (input === undefined) {
-            config._d = new Date();
-        } else if (matched) {
+        if (matched !== null) {
             config._d = new Date(+matched[1]);
-        } else if (typeof input === 'string') {
-            makeDateFromString(config);
-        } else if (isArray(input)) {
-            config._a = input.slice(0);
-            dateFromConfig(config);
-        } else if (isDate(input)) {
-            config._d = new Date(+input);
-        } else if (typeof(input) === 'object') {
-            dateFromObject(config);
-        } else {
-            config._d = new Date(input);
+            return;
+        }
+
+        configFromISO(config);
+        if (config._isValid === false) {
+            delete config._isValid;
+            utils_hooks__hooks.createFromInputFallback(config);
         }
     }
 
-    function makeDate(y, m, d, h, M, s, ms) {
+    utils_hooks__hooks.createFromInputFallback = deprecate(
+        'moment construction falls back to js Date. This is ' +
+        'discouraged and will be removed in upcoming major ' +
+        'release. Please refer to ' +
+        'https://github.com/moment/moment/issues/1407 for more info.',
+        function (config) {
+            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+        }
+    );
+
+    function createDate (y, m, d, h, M, s, ms) {
         //can't just apply() to create a date:
         //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
         var date = new Date(y, m, d, h, M, s, ms);
 
-        //the date constructor doesn't accept years < 1970
-        if (y < 1970) {
+        //the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
             date.setFullYear(y);
         }
         return date;
     }
 
-    function makeUTCDate(y) {
+    function createUTCDate (y) {
         var date = new Date(Date.UTC.apply(null, arguments));
-        if (y < 1970) {
+
+        //the Date.UTC function remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
             date.setUTCFullYear(y);
         }
         return date;
     }
 
-    function parseWeekday(input, language) {
-        if (typeof input === 'string') {
-            if (!isNaN(input)) {
-                input = parseInt(input, 10);
-            }
-            else {
-                input = language.weekdaysParse(input);
-                if (typeof input !== 'number') {
-                    return null;
-                }
-            }
-        }
-        return input;
-    }
+    // FORMATTING
 
-    /************************************
-        Relative Time
-    ************************************/
+    addFormatToken('Y', 0, 0, function () {
+        var y = this.year();
+        return y <= 9999 ? '' + y : '+' + y;
+    });
+
+    addFormatToken(0, ['YY', 2], 0, function () {
+        return this.year() % 100;
+    });
 
+    addFormatToken(0, ['YYYY',   4],       0, 'year');
+    addFormatToken(0, ['YYYYY',  5],       0, 'year');
+    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
 
-    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
-    function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
-        return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+    // ALIASES
+
+    addUnitAlias('year', 'y');
+
+    // PARSING
+
+    addRegexToken('Y',      matchSigned);
+    addRegexToken('YY',     match1to2, match2);
+    addRegexToken('YYYY',   match1to4, match4);
+    addRegexToken('YYYYY',  match1to6, match6);
+    addRegexToken('YYYYYY', match1to6, match6);
+
+    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+    addParseToken('YYYY', function (input, array) {
+        array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input);
+    });
+    addParseToken('YY', function (input, array) {
+        array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input);
+    });
+    addParseToken('Y', function (input, array) {
+        array[YEAR] = parseInt(input, 10);
+    });
+
+    // HELPERS
+
+    function daysInYear(year) {
+        return isLeapYear(year) ? 366 : 365;
     }
 
-    function relativeTime(milliseconds, withoutSuffix, lang) {
-        var seconds = round(Math.abs(milliseconds) / 1000),
-            minutes = round(seconds / 60),
-            hours = round(minutes / 60),
-            days = round(hours / 24),
-            years = round(days / 365),
-            args = seconds < 45 && ['s', seconds] ||
-                minutes === 1 && ['m'] ||
-                minutes < 45 && ['mm', minutes] ||
-                hours === 1 && ['h'] ||
-                hours < 22 && ['hh', hours] ||
-                days === 1 && ['d'] ||
-                days <= 25 && ['dd', days] ||
-                days <= 45 && ['M'] ||
-                days < 345 && ['MM', round(days / 30)] ||
-                years === 1 && ['y'] || ['yy', years];
-        args[2] = withoutSuffix;
-        args[3] = milliseconds > 0;
-        args[4] = lang;
-        return substituteTimeAgo.apply({}, args);
+    function isLeapYear(year) {
+        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
     }
 
+    // HOOKS
 
-    /************************************
-        Week of Year
-    ************************************/
+    utils_hooks__hooks.parseTwoDigitYear = function (input) {
+        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+    };
 
+    // MOMENTS
 
-    // firstDayOfWeek       0 = sun, 6 = sat
-    //                      the day of the week that starts the week
-    //                      (usually sunday or monday)
-    // firstDayOfWeekOfYear 0 = sun, 6 = sat
-    //                      the first week is the week that contains the first
-    //                      of this day of the week
-    //                      (eg. ISO weeks use thursday (4))
-    function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
-        var end = firstDayOfWeekOfYear - firstDayOfWeek,
-            daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
-            adjustedMoment;
+    var getSetYear = makeGetSet('FullYear', true);
 
+    function getIsLeapYear () {
+        return isLeapYear(this.year());
+    }
 
-        if (daysToDayOfWeek > end) {
-            daysToDayOfWeek -= 7;
-        }
+    // start-of-first-week - start-of-year
+    function firstWeekOffset(year, dow, doy) {
+        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+            fwd = 7 + dow - doy,
+            // first-week day local weekday -- which local weekday is fwd
+            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
 
-        if (daysToDayOfWeek < end - 7) {
-            daysToDayOfWeek += 7;
+        return -fwdlw + fwd - 1;
+    }
+
+    //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+        var localWeekday = (7 + weekday - dow) % 7,
+            weekOffset = firstWeekOffset(year, dow, doy),
+            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+            resYear, resDayOfYear;
+
+        if (dayOfYear <= 0) {
+            resYear = year - 1;
+            resDayOfYear = daysInYear(resYear) + dayOfYear;
+        } else if (dayOfYear > daysInYear(year)) {
+            resYear = year + 1;
+            resDayOfYear = dayOfYear - daysInYear(year);
+        } else {
+            resYear = year;
+            resDayOfYear = dayOfYear;
         }
 
-        adjustedMoment = moment(mom).add('d', daysToDayOfWeek);
         return {
-            week: Math.ceil(adjustedMoment.dayOfYear() / 7),
-            year: adjustedMoment.year()
+            year: resYear,
+            dayOfYear: resDayOfYear
         };
     }
 
-    //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
-    function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
-        // The only solid way to create an iso date from year is to use
-        // a string format (Date.UTC handles only years > 1900). Don't ask why
-        // it doesn't need Z at the end.
-        var d = new Date(leftZeroFill(year, 6, true) + '-01-01').getUTCDay(),
-            daysToAdd, dayOfYear;
+    function weekOfYear(mom, dow, doy) {
+        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+            resWeek, resYear;
 
-        weekday = weekday != null ? weekday : firstDayOfWeek;
-        daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0);
-        dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
+        if (week < 1) {
+            resYear = mom.year() - 1;
+            resWeek = week + weeksInYear(resYear, dow, doy);
+        } else if (week > weeksInYear(mom.year(), dow, doy)) {
+            resWeek = week - weeksInYear(mom.year(), dow, doy);
+            resYear = mom.year() + 1;
+        } else {
+            resYear = mom.year();
+            resWeek = week;
+        }
 
         return {
-            year: dayOfYear > 0 ? year : year - 1,
-            dayOfYear: dayOfYear > 0 ?  dayOfYear : daysInYear(year - 1) + dayOfYear
+            week: resWeek,
+            year: resYear
         };
     }
 
-    /************************************
-        Top Level Functions
-    ************************************/
+    function weeksInYear(year, dow, doy) {
+        var weekOffset = firstWeekOffset(year, dow, doy),
+            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+    }
 
-    function makeMoment(config) {
-        var input = config._i,
-            format = config._f;
+    // Pick the first defined of two or three arguments.
+    function defaults(a, b, c) {
+        if (a != null) {
+            return a;
+        }
+        if (b != null) {
+            return b;
+        }
+        return c;
+    }
 
-        if (typeof config._pf === 'undefined') {
-            initializeParsingFlags(config);
+    function currentDateArray(config) {
+        // hooks is actually the exported moment object
+        var nowValue = new Date(utils_hooks__hooks.now());
+        if (config._useUTC) {
+            return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
         }
+        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+    }
+
+    // convert an array to a date.
+    // the array should mirror the parameters below
+    // note: all values past the year are optional and will default to the lowest possible value.
+    // [year, month, day , hour, minute, second, millisecond]
+    function configFromArray (config) {
+        var i, date, input = [], currentDate, yearToUse;
 
-        if (input === null) {
-            return moment.invalid({nullInput: true});
+        if (config._d) {
+            return;
         }
 
-        if (typeof input === 'string') {
-            config._i = input = getLangDefinition().preparse(input);
+        currentDate = currentDateArray(config);
+
+        //compute day of the year from weeks and weekdays
+        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+            dayOfYearFromWeekInfo(config);
         }
 
-        if (moment.isMoment(input)) {
-            config = extend({}, input);
+        //if the day of the year is set, figure out what it is
+        if (config._dayOfYear) {
+            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
 
-            config._d = new Date(+input._d);
-        } else if (format) {
-            if (isArray(format)) {
-                makeDateFromStringAndArray(config);
-            } else {
-                makeDateFromStringAndFormat(config);
+            if (config._dayOfYear > daysInYear(yearToUse)) {
+                getParsingFlags(config)._overflowDayOfYear = true;
             }
-        } else {
-            makeDateFromInput(config);
+
+            date = createUTCDate(yearToUse, 0, config._dayOfYear);
+            config._a[MONTH] = date.getUTCMonth();
+            config._a[DATE] = date.getUTCDate();
         }
 
-        return new Moment(config);
-    }
+        // Default to current date.
+        // * if no year, month, day of month are given, default to today
+        // * if day of month is given, default month and year
+        // * if month is given, default only year
+        // * if year is given, don't default anything
+        for (i = 0; i < 3 && config._a[i] == null; ++i) {
+            config._a[i] = input[i] = currentDate[i];
+        }
 
-    moment = function (input, format, lang, strict) {
-        if (typeof(lang) === "boolean") {
-            strict = lang;
-            lang = undefined;
+        // Zero out whatever was not defaulted, including time
+        for (; i < 7; i++) {
+            config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
         }
-        return makeMoment({
-            _i : input,
-            _f : format,
-            _l : lang,
-            _strict : strict,
-            _isUTC : false
-        });
-    };
 
-    // creating with utc
-    moment.utc = function (input, format, lang, strict) {
-        var m;
+        // Check for 24:00:00.000
+        if (config._a[HOUR] === 24 &&
+                config._a[MINUTE] === 0 &&
+                config._a[SECOND] === 0 &&
+                config._a[MILLISECOND] === 0) {
+            config._nextDay = true;
+            config._a[HOUR] = 0;
+        }
 
-        if (typeof(lang) === "boolean") {
-            strict = lang;
-            lang = undefined;
+        config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+        // Apply timezone offset from input. The actual utcOffset can be changed
+        // with parseZone.
+        if (config._tzm != null) {
+            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
         }
-        m = makeMoment({
-            _useUTC : true,
-            _isUTC : true,
-            _l : lang,
-            _i : input,
-            _f : format,
-            _strict : strict
-        }).utc();
 
-        return m;
-    };
+        if (config._nextDay) {
+            config._a[HOUR] = 24;
+        }
+    }
 
-    // creating with unix timestamp (in seconds)
-    moment.unix = function (input) {
-        return moment(input * 1000);
-    };
+    function dayOfYearFromWeekInfo(config) {
+        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
 
-    // duration
-    moment.duration = function (input, key) {
-        var duration = input,
-            // matching against regexp is expensive, do it on demand
-            match = null,
-            sign,
-            ret,
-            parseIso;
+        w = config._w;
+        if (w.GG != null || w.W != null || w.E != null) {
+            dow = 1;
+            doy = 4;
 
-        if (moment.isDuration(input)) {
-            duration = {
-                ms: input._milliseconds,
-                d: input._days,
-                M: input._months
-            };
-        } else if (typeof input === 'number') {
-            duration = {};
-            if (key) {
-                duration[key] = input;
+            // TODO: We need to take the current isoWeekYear, but that depends on
+            // how we interpret now (local, utc, fixed offset). So create
+            // a now version of current config (take local/utc/offset flags, and
+            // create now).
+            weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year);
+            week = defaults(w.W, 1);
+            weekday = defaults(w.E, 1);
+            if (weekday < 1 || weekday > 7) {
+                weekdayOverflow = true;
+            }
+        } else {
+            dow = config._locale._week.dow;
+            doy = config._locale._week.doy;
+
+            weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year);
+            week = defaults(w.w, 1);
+
+            if (w.d != null) {
+                // weekday -- low day numbers are considered next week
+                weekday = w.d;
+                if (weekday < 0 || weekday > 6) {
+                    weekdayOverflow = true;
+                }
+            } else if (w.e != null) {
+                // local weekday -- counting starts from begining of week
+                weekday = w.e + dow;
+                if (w.e < 0 || w.e > 6) {
+                    weekdayOverflow = true;
+                }
             } else {
-                duration.milliseconds = input;
+                // default to begining of week
+                weekday = dow;
             }
-        } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
-            sign = (match[1] === "-") ? -1 : 1;
-            duration = {
-                y: 0,
-                d: toInt(match[DATE]) * sign,
-                h: toInt(match[HOUR]) * sign,
-                m: toInt(match[MINUTE]) * sign,
-                s: toInt(match[SECOND]) * sign,
-                ms: toInt(match[MILLISECOND]) * sign
-            };
-        } else if (!!(match = isoDurationRegex.exec(input))) {
-            sign = (match[1] === "-") ? -1 : 1;
-            parseIso = function (inp) {
-                // We'd normally use ~~inp for this, but unfortunately it also
-                // converts floats to ints.
-                // inp may be undefined, so careful calling replace on it.
-                var res = inp && parseFloat(inp.replace(',', '.'));
-                // apply sign while we're at it
-                return (isNaN(res) ? 0 : res) * sign;
-            };
-            duration = {
-                y: parseIso(match[2]),
-                M: parseIso(match[3]),
-                d: parseIso(match[4]),
-                h: parseIso(match[5]),
-                m: parseIso(match[6]),
-                s: parseIso(match[7]),
-                w: parseIso(match[8])
-            };
         }
+        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+            getParsingFlags(config)._overflowWeeks = true;
+        } else if (weekdayOverflow != null) {
+            getParsingFlags(config)._overflowWeekday = true;
+        } else {
+            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+            config._a[YEAR] = temp.year;
+            config._dayOfYear = temp.dayOfYear;
+        }
+    }
 
-        ret = new Duration(duration);
+    // constant that refers to the ISO standard
+    utils_hooks__hooks.ISO_8601 = function () {};
 
-        if (moment.isDuration(input) && input.hasOwnProperty('_lang')) {
-            ret._lang = input._lang;
+    // date from string and format string
+    function configFromStringAndFormat(config) {
+        // TODO: Move this to another part of the creation flow to prevent circular deps
+        if (config._f === utils_hooks__hooks.ISO_8601) {
+            configFromISO(config);
+            return;
         }
 
-        return ret;
-    };
-
-    // version number
-    moment.version = VERSION;
+        config._a = [];
+        getParsingFlags(config).empty = true;
 
-    // default format
-    moment.defaultFormat = isoFormat;
+        // This array is used to make a Date, either with `new Date` or `Date.UTC`
+        var string = '' + config._i,
+            i, parsedInput, tokens, token, skipped,
+            stringLength = string.length,
+            totalParsedInputLength = 0;
 
-    // This function will be called whenever a moment is mutated.
-    // It is intended to keep the offset in sync with the timezone.
-    moment.updateOffset = function () {};
+        tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
 
-    // This function will load languages and then set the global language.  If
-    // no arguments are passed in, it will simply return the current global
-    // language key.
-    moment.lang = function (key, values) {
-        var r;
-        if (!key) {
-            return moment.fn._lang._abbr;
-        }
-        if (values) {
-            loadLang(normalizeLanguage(key), values);
-        } else if (values === null) {
-            unloadLang(key);
-            key = 'en';
-        } else if (!languages[key]) {
-            getLangDefinition(key);
-        }
-        r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
-        return r._abbr;
-    };
+        for (i = 0; i < tokens.length; i++) {
+            token = tokens[i];
+            parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+            // console.log('token', token, 'parsedInput', parsedInput,
+            //         'regex', getParseRegexForToken(token, config));
+            if (parsedInput) {
+                skipped = string.substr(0, string.indexOf(parsedInput));
+                if (skipped.length > 0) {
+                    getParsingFlags(config).unusedInput.push(skipped);
+                }
+                string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+                totalParsedInputLength += parsedInput.length;
+            }
+            // don't parse if it's not a known token
+            if (formatTokenFunctions[token]) {
+                if (parsedInput) {
+                    getParsingFlags(config).empty = false;
+                }
+                else {
+                    getParsingFlags(config).unusedTokens.push(token);
+                }
+                addTimeToArrayFromToken(token, parsedInput, config);
+            }
+            else if (config._strict && !parsedInput) {
+                getParsingFlags(config).unusedTokens.push(token);
+            }
+        }
 
-    // returns language data
-    moment.langData = function (key) {
-        if (key && key._lang && key._lang._abbr) {
-            key = key._lang._abbr;
+        // add remaining unparsed input length to the string
+        getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+        if (string.length > 0) {
+            getParsingFlags(config).unusedInput.push(string);
         }
-        return getLangDefinition(key);
-    };
 
-    // compare moment object
-    moment.isMoment = function (obj) {
-        return obj instanceof Moment;
-    };
+        // clear _12h flag if hour is <= 12
+        if (getParsingFlags(config).bigHour === true &&
+                config._a[HOUR] <= 12 &&
+                config._a[HOUR] > 0) {
+            getParsingFlags(config).bigHour = undefined;
+        }
 
-    // for typechecking Duration objects
-    moment.isDuration = function (obj) {
-        return obj instanceof Duration;
-    };
+        getParsingFlags(config).parsedDateParts = config._a.slice(0);
+        getParsingFlags(config).meridiem = config._meridiem;
+        // handle meridiem
+        config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
 
-    for (i = lists.length - 1; i >= 0; --i) {
-        makeList(lists[i]);
+        configFromArray(config);
+        checkOverflow(config);
     }
 
-    moment.normalizeUnits = function (units) {
-        return normalizeUnits(units);
-    };
 
-    moment.invalid = function (flags) {
-        var m = moment.utc(NaN);
-        if (flags != null) {
-            extend(m._pf, flags);
+    function meridiemFixWrap (locale, hour, meridiem) {
+        var isPm;
+
+        if (meridiem == null) {
+            // nothing to do
+            return hour;
         }
-        else {
-            m._pf.userInvalidated = true;
+        if (locale.meridiemHour != null) {
+            return locale.meridiemHour(hour, meridiem);
+        } else if (locale.isPM != null) {
+            // Fallback
+            isPm = locale.isPM(meridiem);
+            if (isPm && hour < 12) {
+                hour += 12;
+            }
+            if (!isPm && hour === 12) {
+                hour = 0;
+            }
+            return hour;
+        } else {
+            // this is not supposed to happen
+            return hour;
         }
+    }
 
-        return m;
-    };
-
-    moment.parseZone = function (input) {
-        return moment(input).parseZone();
-    };
-
-    /************************************
-        Moment Prototype
-    ************************************/
+    // date from string and array of format strings
+    function configFromStringAndArray(config) {
+        var tempConfig,
+            bestMoment,
 
+            scoreToBeat,
+            i,
+            currentScore;
 
-    extend(moment.fn = Moment.prototype, {
+        if (config._f.length === 0) {
+            getParsingFlags(config).invalidFormat = true;
+            config._d = new Date(NaN);
+            return;
+        }
 
-        clone : function () {
-            return moment(this);
-        },
+        for (i = 0; i < config._f.length; i++) {
+            currentScore = 0;
+            tempConfig = copyConfig({}, config);
+            if (config._useUTC != null) {
+                tempConfig._useUTC = config._useUTC;
+            }
+            tempConfig._f = config._f[i];
+            configFromStringAndFormat(tempConfig);
 
-        valueOf : function () {
-            return +this._d + ((this._offset || 0) * 60000);
-        },
+            if (!valid__isValid(tempConfig)) {
+                continue;
+            }
 
-        unix : function () {
-            return Math.floor(+this / 1000);
-        },
+            // if there is any input that was not parsed add a penalty for that format
+            currentScore += getParsingFlags(tempConfig).charsLeftOver;
 
-        toString : function () {
-            return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
-        },
+            //or tokens
+            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
 
-        toDate : function () {
-            return this._offset ? new Date(+this) : this._d;
-        },
+            getParsingFlags(tempConfig).score = currentScore;
 
-        toISOString : function () {
-            var m = moment(this).utc();
-            if (0 < m.year() && m.year() <= 9999) {
-                return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
-            } else {
-                return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+            if (scoreToBeat == null || currentScore < scoreToBeat) {
+                scoreToBeat = currentScore;
+                bestMoment = tempConfig;
             }
-        },
-
-        toArray : function () {
-            var m = this;
-            return [
-                m.year(),
-                m.month(),
-                m.date(),
-                m.hours(),
-                m.minutes(),
-                m.seconds(),
-                m.milliseconds()
-            ];
-        },
+        }
 
-        isValid : function () {
-            return isValid(this);
-        },
+        extend(config, bestMoment || tempConfig);
+    }
 
-        isDSTShifted : function () {
+    function configFromObject(config) {
+        if (config._d) {
+            return;
+        }
 
-            if (this._a) {
-                return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
-            }
+        var i = normalizeObjectUnits(config._i);
+        config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
+            return obj && parseInt(obj, 10);
+        });
 
-            return false;
-        },
+        configFromArray(config);
+    }
 
-        parsingFlags : function () {
-            return extend({}, this._pf);
-        },
+    function createFromConfig (config) {
+        var res = new Moment(checkOverflow(prepareConfig(config)));
+        if (res._nextDay) {
+            // Adding is smart enough around DST
+            res.add(1, 'd');
+            res._nextDay = undefined;
+        }
 
-        invalidAt: function () {
-            return this._pf.overflow;
-        },
+        return res;
+    }
 
-        utc : function () {
-            return this.zone(0);
-        },
+    function prepareConfig (config) {
+        var input = config._i,
+            format = config._f;
 
-        local : function () {
-            this.zone(0);
-            this._isUTC = false;
-            return this;
-        },
+        config._locale = config._locale || locale_locales__getLocale(config._l);
 
-        format : function (inputString) {
-            var output = formatMoment(this, inputString || moment.defaultFormat);
-            return this.lang().postformat(output);
-        },
+        if (input === null || (format === undefined && input === '')) {
+            return valid__createInvalid({nullInput: true});
+        }
 
-        add : function (input, val) {
-            var dur;
-            // switch args to support add('s', 1) and add(1, 's')
-            if (typeof input === 'string') {
-                dur = moment.duration(+val, input);
-            } else {
-                dur = moment.duration(input, val);
-            }
-            addOrSubtractDurationFromMoment(this, dur, 1);
-            return this;
-        },
+        if (typeof input === 'string') {
+            config._i = input = config._locale.preparse(input);
+        }
 
-        subtract : function (input, val) {
-            var dur;
-            // switch args to support subtract('s', 1) and subtract(1, 's')
-            if (typeof input === 'string') {
-                dur = moment.duration(+val, input);
-            } else {
-                dur = moment.duration(input, val);
-            }
-            addOrSubtractDurationFromMoment(this, dur, -1);
-            return this;
-        },
+        if (isMoment(input)) {
+            return new Moment(checkOverflow(input));
+        } else if (isArray(format)) {
+            configFromStringAndArray(config);
+        } else if (format) {
+            configFromStringAndFormat(config);
+        } else if (isDate(input)) {
+            config._d = input;
+        } else {
+            configFromInput(config);
+        }
 
-        diff : function (input, units, asFloat) {
-            var that = makeAs(input, this),
-                zoneDiff = (this.zone() - that.zone()) * 6e4,
-                diff, output;
+        if (!valid__isValid(config)) {
+            config._d = null;
+        }
 
-            units = normalizeUnits(units);
+        return config;
+    }
 
-            if (units === 'year' || units === 'month') {
-                // average number of days in the months in the given dates
-                diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
-                // difference in months
-                output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
-                // adjust by taking difference in days, average number of days
-                // and dst in the given months.
-                output += ((this - moment(this).startOf('month')) -
-                        (that - moment(that).startOf('month'))) / diff;
-                // same as above but with zones, to negate all dst
-                output -= ((this.zone() - moment(this).startOf('month').zone()) -
-                        (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff;
-                if (units === 'year') {
-                    output = output / 12;
-                }
-            } else {
-                diff = (this - that);
-                output = units === 'second' ? diff / 1e3 : // 1000
-                    units === 'minute' ? diff / 6e4 : // 1000 * 60
-                    units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
-                    units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
-                    units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
-                    diff;
-            }
-            return asFloat ? output : absRound(output);
-        },
+    function configFromInput(config) {
+        var input = config._i;
+        if (input === undefined) {
+            config._d = new Date(utils_hooks__hooks.now());
+        } else if (isDate(input)) {
+            config._d = new Date(input.valueOf());
+        } else if (typeof input === 'string') {
+            configFromString(config);
+        } else if (isArray(input)) {
+            config._a = map(input.slice(0), function (obj) {
+                return parseInt(obj, 10);
+            });
+            configFromArray(config);
+        } else if (typeof(input) === 'object') {
+            configFromObject(config);
+        } else if (typeof(input) === 'number') {
+            // from milliseconds
+            config._d = new Date(input);
+        } else {
+            utils_hooks__hooks.createFromInputFallback(config);
+        }
+    }
 
-        from : function (time, withoutSuffix) {
-            return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
-        },
+    function createLocalOrUTC (input, format, locale, strict, isUTC) {
+        var c = {};
 
-        fromNow : function (withoutSuffix) {
-            return this.from(moment(), withoutSuffix);
-        },
+        if (typeof(locale) === 'boolean') {
+            strict = locale;
+            locale = undefined;
+        }
+        // object construction must be done this way.
+        // https://github.com/moment/moment/issues/1423
+        c._isAMomentObject = true;
+        c._useUTC = c._isUTC = isUTC;
+        c._l = locale;
+        c._i = input;
+        c._f = format;
+        c._strict = strict;
 
-        calendar : function () {
-            // We want to compare the start of today, vs this.
-            // Getting start-of-today depends on whether we're zone'd or not.
-            var sod = makeAs(moment(), this).startOf('day'),
-                diff = this.diff(sod, 'days', true),
-                format = diff < -6 ? 'sameElse' :
-                    diff < -1 ? 'lastWeek' :
-                    diff < 0 ? 'lastDay' :
-                    diff < 1 ? 'sameDay' :
-                    diff < 2 ? 'nextDay' :
-                    diff < 7 ? 'nextWeek' : 'sameElse';
-            return this.format(this.lang().calendar(format, this));
-        },
+        return createFromConfig(c);
+    }
 
-        isLeapYear : function () {
-            return isLeapYear(this.year());
-        },
+    function local__createLocal (input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, false);
+    }
 
-        isDST : function () {
-            return (this.zone() < this.clone().month(0).zone() ||
-                this.zone() < this.clone().month(5).zone());
-        },
+    var prototypeMin = deprecate(
+         'moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
+         function () {
+             var other = local__createLocal.apply(null, arguments);
+             if (this.isValid() && other.isValid()) {
+                 return other < this ? this : other;
+             } else {
+                 return valid__createInvalid();
+             }
+         }
+     );
 
-        day : function (input) {
-            var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
-            if (input != null) {
-                input = parseWeekday(input, this.lang());
-                return this.add({ d : input - day });
+    var prototypeMax = deprecate(
+        'moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
+        function () {
+            var other = local__createLocal.apply(null, arguments);
+            if (this.isValid() && other.isValid()) {
+                return other > this ? this : other;
             } else {
-                return day;
+                return valid__createInvalid();
             }
-        },
-
-        month : function (input) {
-            var utc = this._isUTC ? 'UTC' : '',
-                dayOfMonth;
-
-            if (input != null) {
-                if (typeof input === 'string') {
-                    input = this.lang().monthsParse(input);
-                    if (typeof input !== 'number') {
-                        return this;
-                    }
-                }
-
-                dayOfMonth = this.date();
-                this.date(1);
-                this._d['set' + utc + 'Month'](input);
-                this.date(Math.min(dayOfMonth, this.daysInMonth()));
+        }
+    );
 
-                moment.updateOffset(this);
-                return this;
-            } else {
-                return this._d['get' + utc + 'Month']();
+    // Pick a moment m from moments so that m[fn](other) is true for all
+    // other. This relies on the function fn to be transitive.
+    //
+    // moments should either be an array of moment objects or an array, whose
+    // first element is an array of moment objects.
+    function pickBy(fn, moments) {
+        var res, i;
+        if (moments.length === 1 && isArray(moments[0])) {
+            moments = moments[0];
+        }
+        if (!moments.length) {
+            return local__createLocal();
+        }
+        res = moments[0];
+        for (i = 1; i < moments.length; ++i) {
+            if (!moments[i].isValid() || moments[i][fn](res)) {
+                res = moments[i];
             }
-        },
+        }
+        return res;
+    }
 
-        startOf: function (units) {
-            units = normalizeUnits(units);
-            // the following switch intentionally omits break keywords
-            // to utilize falling through the cases.
-            switch (units) {
-            case 'year':
-                this.month(0);
-                /* falls through */
-            case 'month':
-                this.date(1);
-                /* falls through */
-            case 'week':
-            case 'isoWeek':
-            case 'day':
-                this.hours(0);
-                /* falls through */
-            case 'hour':
-                this.minutes(0);
-                /* falls through */
-            case 'minute':
-                this.seconds(0);
-                /* falls through */
-            case 'second':
-                this.milliseconds(0);
-                /* falls through */
-            }
-
-            // weeks are a special case
-            if (units === 'week') {
-                this.weekday(0);
-            } else if (units === 'isoWeek') {
-                this.isoWeekday(1);
-            }
+    // TODO: Use [].sort instead?
+    function min () {
+        var args = [].slice.call(arguments, 0);
 
-            return this;
-        },
+        return pickBy('isBefore', args);
+    }
 
-        endOf: function (units) {
-            units = normalizeUnits(units);
-            return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1);
-        },
+    function max () {
+        var args = [].slice.call(arguments, 0);
 
-        isAfter: function (input, units) {
-            units = typeof units !== 'undefined' ? units : 'millisecond';
-            return +this.clone().startOf(units) > +moment(input).startOf(units);
-        },
+        return pickBy('isAfter', args);
+    }
 
-        isBefore: function (input, units) {
-            units = typeof units !== 'undefined' ? units : 'millisecond';
-            return +this.clone().startOf(units) < +moment(input).startOf(units);
-        },
+    var now = function () {
+        return Date.now ? Date.now() : +(new Date());
+    };
 
-        isSame: function (input, units) {
-            units = units || 'ms';
-            return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
-        },
+    function Duration (duration) {
+        var normalizedInput = normalizeObjectUnits(duration),
+            years = normalizedInput.year || 0,
+            quarters = normalizedInput.quarter || 0,
+            months = normalizedInput.month || 0,
+            weeks = normalizedInput.week || 0,
+            days = normalizedInput.day || 0,
+            hours = normalizedInput.hour || 0,
+            minutes = normalizedInput.minute || 0,
+            seconds = normalizedInput.second || 0,
+            milliseconds = normalizedInput.millisecond || 0;
 
-        min: function (other) {
-            other = moment.apply(null, arguments);
-            return other < this ? this : other;
-        },
+        // representation for dateAddRemove
+        this._milliseconds = +milliseconds +
+            seconds * 1e3 + // 1000
+            minutes * 6e4 + // 1000 * 60
+            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+        // Because of dateAddRemove treats 24 hours as different from a
+        // day when working around DST, we need to store them separately
+        this._days = +days +
+            weeks * 7;
+        // It is impossible translate months into days without knowing
+        // which months you are are talking about, so we have to store
+        // it separately.
+        this._months = +months +
+            quarters * 3 +
+            years * 12;
 
-        max: function (other) {
-            other = moment.apply(null, arguments);
-            return other > this ? this : other;
-        },
+        this._data = {};
 
-        zone : function (input) {
-            var offset = this._offset || 0;
-            if (input != null) {
-                if (typeof input === "string") {
-                    input = timezoneMinutesFromString(input);
-                }
-                if (Math.abs(input) < 16) {
-                    input = input * 60;
-                }
-                this._offset = input;
-                this._isUTC = true;
-                if (offset !== input) {
-                    addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true);
-                }
-            } else {
-                return this._isUTC ? offset : this._d.getTimezoneOffset();
-            }
-            return this;
-        },
+        this._locale = locale_locales__getLocale();
 
-        zoneAbbr : function () {
-            return this._isUTC ? "UTC" : "";
-        },
+        this._bubble();
+    }
 
-        zoneName : function () {
-            return this._isUTC ? "Coordinated Universal Time" : "";
-        },
+    function isDuration (obj) {
+        return obj instanceof Duration;
+    }
 
-        parseZone : function () {
-            if (this._tzm) {
-                this.zone(this._tzm);
-            } else if (typeof this._i === 'string') {
-                this.zone(this._i);
-            }
-            return this;
-        },
+    // FORMATTING
 
-        hasAlignedHourOffset : function (input) {
-            if (!input) {
-                input = 0;
-            }
-            else {
-                input = moment(input).zone();
+    function offset (token, separator) {
+        addFormatToken(token, 0, 0, function () {
+            var offset = this.utcOffset();
+            var sign = '+';
+            if (offset < 0) {
+                offset = -offset;
+                sign = '-';
             }
+            return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+        });
+    }
 
-            return (this.zone() - input) % 60 === 0;
-        },
+    offset('Z', ':');
+    offset('ZZ', '');
 
-        daysInMonth : function () {
-            return daysInMonth(this.year(), this.month());
-        },
+    // PARSING
 
-        dayOfYear : function (input) {
-            var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
-            return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
-        },
+    addRegexToken('Z',  matchShortOffset);
+    addRegexToken('ZZ', matchShortOffset);
+    addParseToken(['Z', 'ZZ'], function (input, array, config) {
+        config._useUTC = true;
+        config._tzm = offsetFromString(matchShortOffset, input);
+    });
 
-        quarter : function () {
-            return Math.ceil((this.month() + 1.0) / 3.0);
-        },
+    // HELPERS
 
-        weekYear : function (input) {
-            var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year;
-            return input == null ? year : this.add("y", (input - year));
-        },
+    // timezone chunker
+    // '+10:00' > ['10',  '00']
+    // '-1530'  > ['-15', '30']
+    var chunkOffset = /([\+\-]|\d\d)/gi;
 
-        isoWeekYear : function (input) {
-            var year = weekOfYear(this, 1, 4).year;
-            return input == null ? year : this.add("y", (input - year));
-        },
+    function offsetFromString(matcher, string) {
+        var matches = ((string || '').match(matcher) || []);
+        var chunk   = matches[matches.length - 1] || [];
+        var parts   = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+        var minutes = +(parts[1] * 60) + toInt(parts[2]);
 
-        week : function (input) {
-            var week = this.lang().week(this);
-            return input == null ? week : this.add("d", (input - week) * 7);
-        },
+        return parts[0] === '+' ? minutes : -minutes;
+    }
 
-        isoWeek : function (input) {
-            var week = weekOfYear(this, 1, 4).week;
-            return input == null ? week : this.add("d", (input - week) * 7);
-        },
+    // Return a moment from input, that is local/utc/zone equivalent to model.
+    function cloneWithOffset(input, model) {
+        var res, diff;
+        if (model._isUTC) {
+            res = model.clone();
+            diff = (isMoment(input) || isDate(input) ? input.valueOf() : local__createLocal(input).valueOf()) - res.valueOf();
+            // Use low-level api, because this fn is low-level api.
+            res._d.setTime(res._d.valueOf() + diff);
+            utils_hooks__hooks.updateOffset(res, false);
+            return res;
+        } else {
+            return local__createLocal(input).local();
+        }
+    }
 
-        weekday : function (input) {
-            var weekday = (this.day() + 7 - this.lang()._week.dow) % 7;
-            return input == null ? weekday : this.add("d", input - weekday);
-        },
+    function getDateOffset (m) {
+        // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+        // https://github.com/moment/moment/pull/1871
+        return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+    }
 
-        isoWeekday : function (input) {
-            // behaves the same as moment#day except
-            // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
-            // as a setter, sunday should belong to the previous week.
-            return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
-        },
+    // HOOKS
 
-        get : function (units) {
-            units = normalizeUnits(units);
-            return this[units]();
-        },
+    // This function will be called whenever a moment is mutated.
+    // It is intended to keep the offset in sync with the timezone.
+    utils_hooks__hooks.updateOffset = function () {};
 
-        set : function (units, value) {
-            units = normalizeUnits(units);
-            if (typeof this[units] === 'function') {
-                this[units](value);
-            }
-            return this;
-        },
+    // MOMENTS
 
-        // If passed a language key, it will set the language for this
-        // instance.  Otherwise, it will return the language configuration
-        // variables for this instance.
-        lang : function (key) {
-            if (key === undefined) {
-                return this._lang;
-            } else {
-                this._lang = getLangDefinition(key);
-                return this;
+    // keepLocalTime = true means only change the timezone, without
+    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+    // +0200, so we adjust the time as needed, to be valid.
+    //
+    // Keeping the time actually adds/subtracts (one hour)
+    // from the actual represented time. That is why we call updateOffset
+    // a second time. In case it wants us to change the offset again
+    // _changeInProgress == true case, then we have to adjust, because
+    // there is no such time in the given timezone.
+    function getSetOffset (input, keepLocalTime) {
+        var offset = this._offset || 0,
+            localAdjust;
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        if (input != null) {
+            if (typeof input === 'string') {
+                input = offsetFromString(matchShortOffset, input);
+            } else if (Math.abs(input) < 16) {
+                input = input * 60;
+            }
+            if (!this._isUTC && keepLocalTime) {
+                localAdjust = getDateOffset(this);
+            }
+            this._offset = input;
+            this._isUTC = true;
+            if (localAdjust != null) {
+                this.add(localAdjust, 'm');
+            }
+            if (offset !== input) {
+                if (!keepLocalTime || this._changeInProgress) {
+                    add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false);
+                } else if (!this._changeInProgress) {
+                    this._changeInProgress = true;
+                    utils_hooks__hooks.updateOffset(this, true);
+                    this._changeInProgress = null;
+                }
             }
+            return this;
+        } else {
+            return this._isUTC ? offset : getDateOffset(this);
         }
-    });
+    }
 
-    // helper for adding shortcuts
-    function makeGetterAndSetter(name, key) {
-        moment.fn[name] = moment.fn[name + 's'] = function (input) {
-            var utc = this._isUTC ? 'UTC' : '';
-            if (input != null) {
-                this._d['set' + utc + key](input);
-                moment.updateOffset(this);
-                return this;
-            } else {
-                return this._d['get' + utc + key]();
+    function getSetZone (input, keepLocalTime) {
+        if (input != null) {
+            if (typeof input !== 'string') {
+                input = -input;
             }
-        };
+
+            this.utcOffset(input, keepLocalTime);
+
+            return this;
+        } else {
+            return -this.utcOffset();
+        }
     }
 
-    // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
-    for (i = 0; i < proxyGettersAndSetters.length; i ++) {
-        makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]);
+    function setOffsetToUTC (keepLocalTime) {
+        return this.utcOffset(0, keepLocalTime);
     }
 
-    // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
-    makeGetterAndSetter('year', 'FullYear');
+    function setOffsetToLocal (keepLocalTime) {
+        if (this._isUTC) {
+            this.utcOffset(0, keepLocalTime);
+            this._isUTC = false;
 
-    // add plural methods
-    moment.fn.days = moment.fn.day;
-    moment.fn.months = moment.fn.month;
-    moment.fn.weeks = moment.fn.week;
-    moment.fn.isoWeeks = moment.fn.isoWeek;
+            if (keepLocalTime) {
+                this.subtract(getDateOffset(this), 'm');
+            }
+        }
+        return this;
+    }
 
-    // add aliased format methods
-    moment.fn.toJSON = moment.fn.toISOString;
+    function setOffsetToParsedOffset () {
+        if (this._tzm) {
+            this.utcOffset(this._tzm);
+        } else if (typeof this._i === 'string') {
+            this.utcOffset(offsetFromString(matchOffset, this._i));
+        }
+        return this;
+    }
 
-    /************************************
-        Duration Prototype
-    ************************************/
+    function hasAlignedHourOffset (input) {
+        if (!this.isValid()) {
+            return false;
+        }
+        input = input ? local__createLocal(input).utcOffset() : 0;
 
+        return (this.utcOffset() - input) % 60 === 0;
+    }
 
-    extend(moment.duration.fn = Duration.prototype, {
+    function isDaylightSavingTime () {
+        return (
+            this.utcOffset() > this.clone().month(0).utcOffset() ||
+            this.utcOffset() > this.clone().month(5).utcOffset()
+        );
+    }
 
-        _bubble : function () {
-            var milliseconds = this._milliseconds,
-                days = this._days,
-                months = this._months,
-                data = this._data,
-                seconds, minutes, hours, years;
+    function isDaylightSavingTimeShifted () {
+        if (!isUndefined(this._isDSTShifted)) {
+            return this._isDSTShifted;
+        }
 
-            // The following code bubbles up values, see the tests for
-            // examples of what that means.
-            data.milliseconds = milliseconds % 1000;
+        var c = {};
 
-            seconds = absRound(milliseconds / 1000);
-            data.seconds = seconds % 60;
+        copyConfig(c, this);
+        c = prepareConfig(c);
 
-            minutes = absRound(seconds / 60);
-            data.minutes = minutes % 60;
+        if (c._a) {
+            var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a);
+            this._isDSTShifted = this.isValid() &&
+                compareArrays(c._a, other.toArray()) > 0;
+        } else {
+            this._isDSTShifted = false;
+        }
 
-            hours = absRound(minutes / 60);
-            data.hours = hours % 24;
+        return this._isDSTShifted;
+    }
 
-            days += absRound(hours / 24);
-            data.days = days % 30;
+    function isLocal () {
+        return this.isValid() ? !this._isUTC : false;
+    }
 
-            months += absRound(days / 30);
-            data.months = months % 12;
+    function isUtcOffset () {
+        return this.isValid() ? this._isUTC : false;
+    }
 
-            years = absRound(months / 12);
-            data.years = years;
-        },
+    function isUtc () {
+        return this.isValid() ? this._isUTC && this._offset === 0 : false;
+    }
 
-        weeks : function () {
-            return absRound(this.days() / 7);
-        },
+    // ASP.NET json date format regex
+    var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/;
 
-        valueOf : function () {
-            return this._milliseconds +
-              this._days * 864e5 +
-              (this._months % 12) * 2592e6 +
-              toInt(this._months / 12) * 31536e6;
-        },
+    // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+    // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+    // and further modified to allow for strings containing both week and day
+    var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;
 
-        humanize : function (withSuffix) {
-            var difference = +this,
-                output = relativeTime(difference, !withSuffix, this.lang());
+    function create__createDuration (input, key) {
+        var duration = input,
+            // matching against regexp is expensive, do it on demand
+            match = null,
+            sign,
+            ret,
+            diffRes;
 
-            if (withSuffix) {
-                output = this.lang().pastFuture(difference, output);
+        if (isDuration(input)) {
+            duration = {
+                ms : input._milliseconds,
+                d  : input._days,
+                M  : input._months
+            };
+        } else if (typeof input === 'number') {
+            duration = {};
+            if (key) {
+                duration[key] = input;
+            } else {
+                duration.milliseconds = input;
             }
+        } else if (!!(match = aspNetRegex.exec(input))) {
+            sign = (match[1] === '-') ? -1 : 1;
+            duration = {
+                y  : 0,
+                d  : toInt(match[DATE])        * sign,
+                h  : toInt(match[HOUR])        * sign,
+                m  : toInt(match[MINUTE])      * sign,
+                s  : toInt(match[SECOND])      * sign,
+                ms : toInt(match[MILLISECOND]) * sign
+            };
+        } else if (!!(match = isoRegex.exec(input))) {
+            sign = (match[1] === '-') ? -1 : 1;
+            duration = {
+                y : parseIso(match[2], sign),
+                M : parseIso(match[3], sign),
+                w : parseIso(match[4], sign),
+                d : parseIso(match[5], sign),
+                h : parseIso(match[6], sign),
+                m : parseIso(match[7], sign),
+                s : parseIso(match[8], sign)
+            };
+        } else if (duration == null) {// checks for null or undefined
+            duration = {};
+        } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
+            diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to));
 
-            return this.lang().postformat(output);
-        },
-
-        add : function (input, val) {
-            // supports only 2.0-style add(1, 's') or add(moment)
-            var dur = moment.duration(input, val);
+            duration = {};
+            duration.ms = diffRes.milliseconds;
+            duration.M = diffRes.months;
+        }
 
-            this._milliseconds += dur._milliseconds;
-            this._days += dur._days;
-            this._months += dur._months;
+        ret = new Duration(duration);
 
-            this._bubble();
+        if (isDuration(input) && hasOwnProp(input, '_locale')) {
+            ret._locale = input._locale;
+        }
 
-            return this;
-        },
+        return ret;
+    }
 
-        subtract : function (input, val) {
-            var dur = moment.duration(input, val);
+    create__createDuration.fn = Duration.prototype;
 
-            this._milliseconds -= dur._milliseconds;
-            this._days -= dur._days;
-            this._months -= dur._months;
+    function parseIso (inp, sign) {
+        // We'd normally use ~~inp for this, but unfortunately it also
+        // converts floats to ints.
+        // inp may be undefined, so careful calling replace on it.
+        var res = inp && parseFloat(inp.replace(',', '.'));
+        // apply sign while we're at it
+        return (isNaN(res) ? 0 : res) * sign;
+    }
 
-            this._bubble();
+    function positiveMomentsDifference(base, other) {
+        var res = {milliseconds: 0, months: 0};
 
-            return this;
-        },
+        res.months = other.month() - base.month() +
+            (other.year() - base.year()) * 12;
+        if (base.clone().add(res.months, 'M').isAfter(other)) {
+            --res.months;
+        }
 
-        get : function (units) {
-            units = normalizeUnits(units);
-            return this[units.toLowerCase() + 's']();
-        },
+        res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
 
-        as : function (units) {
-            units = normalizeUnits(units);
-            return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's']();
-        },
+        return res;
+    }
 
-        lang : moment.fn.lang,
-
-        toIsoString : function () {
-            // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
-            var years = Math.abs(this.years()),
-                months = Math.abs(this.months()),
-                days = Math.abs(this.days()),
-                hours = Math.abs(this.hours()),
-                minutes = Math.abs(this.minutes()),
-                seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
-
-            if (!this.asSeconds()) {
-                // this is the same as C#'s (Noda) and python (isodate)...
-                // but not other JS (goog.date)
-                return 'P0D';
-            }
-
-            return (this.asSeconds() < 0 ? '-' : '') +
-                'P' +
-                (years ? years + 'Y' : '') +
-                (months ? months + 'M' : '') +
-                (days ? days + 'D' : '') +
-                ((hours || minutes || seconds) ? 'T' : '') +
-                (hours ? hours + 'H' : '') +
-                (minutes ? minutes + 'M' : '') +
-                (seconds ? seconds + 'S' : '');
+    function momentsDifference(base, other) {
+        var res;
+        if (!(base.isValid() && other.isValid())) {
+            return {milliseconds: 0, months: 0};
         }
-    });
 
-    function makeDurationGetter(name) {
-        moment.duration.fn[name] = function () {
-            return this._data[name];
-        };
-    }
+        other = cloneWithOffset(other, base);
+        if (base.isBefore(other)) {
+            res = positiveMomentsDifference(base, other);
+        } else {
+            res = positiveMomentsDifference(other, base);
+            res.milliseconds = -res.milliseconds;
+            res.months = -res.months;
+        }
 
-    function makeDurationAsGetter(name, factor) {
-        moment.duration.fn['as' + name] = function () {
-            return +this / factor;
-        };
+        return res;
     }
 
-    for (i in unitMillisecondFactors) {
-        if (unitMillisecondFactors.hasOwnProperty(i)) {
-            makeDurationAsGetter(i, unitMillisecondFactors[i]);
-            makeDurationGetter(i.toLowerCase());
+    function absRound (number) {
+        if (number < 0) {
+            return Math.round(-1 * number) * -1;
+        } else {
+            return Math.round(number);
         }
     }
 
-    makeDurationAsGetter('Weeks', 6048e5);
-    moment.duration.fn.asMonths = function () {
-        return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12;
-    };
+    // TODO: remove 'name' arg after deprecation is removed
+    function createAdder(direction, name) {
+        return function (val, period) {
+            var dur, tmp;
+            //invert the arguments, but complain about it
+            if (period !== null && !isNaN(+period)) {
+                deprecateSimple(name, 'moment().' + name  + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
+                tmp = val; val = period; period = tmp;
+            }
+
+            val = typeof val === 'string' ? +val : val;
+            dur = create__createDuration(val, period);
+            add_subtract__addSubtract(this, dur, direction);
+            return this;
+        };
+    }
 
+    function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) {
+        var milliseconds = duration._milliseconds,
+            days = absRound(duration._days),
+            months = absRound(duration._months);
 
-    /************************************
-        Default Lang
-    ************************************/
+        if (!mom.isValid()) {
+            // No op
+            return;
+        }
 
+        updateOffset = updateOffset == null ? true : updateOffset;
 
-    // Set default language, other languages will inherit from English.
-    moment.lang('en', {
-        ordinal : function (number) {
-            var b = number % 10,
-                output = (toInt(number % 100 / 10) === 1) ? 'th' :
-                (b === 1) ? 'st' :
-                (b === 2) ? 'nd' :
-                (b === 3) ? 'rd' : 'th';
-            return number + output;
+        if (milliseconds) {
+            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
         }
-    });
+        if (days) {
+            get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding);
+        }
+        if (months) {
+            setMonth(mom, get_set__get(mom, 'Month') + months * isAdding);
+        }
+        if (updateOffset) {
+            utils_hooks__hooks.updateOffset(mom, days || months);
+        }
+    }
 
-    /* EMBED_LANGUAGES */
+    var add_subtract__add      = createAdder(1, 'add');
+    var add_subtract__subtract = createAdder(-1, 'subtract');
 
-    /************************************
-        Exposing Moment
-    ************************************/
+    function moment_calendar__calendar (time, formats) {
+        // We want to compare the start of today, vs this.
+        // Getting start-of-today depends on whether we're local/utc/offset or not.
+        var now = time || local__createLocal(),
+            sod = cloneWithOffset(now, this).startOf('day'),
+            diff = this.diff(sod, 'days', true),
+            format = diff < -6 ? 'sameElse' :
+                diff < -1 ? 'lastWeek' :
+                diff < 0 ? 'lastDay' :
+                diff < 1 ? 'sameDay' :
+                diff < 2 ? 'nextDay' :
+                diff < 7 ? 'nextWeek' : 'sameElse';
 
-    function makeGlobal(deprecate) {
-        var warned = false, local_moment = moment;
-        /*global ender:false */
-        if (typeof ender !== 'undefined') {
-            return;
+        var output = formats && (isFunction(formats[format]) ? formats[format]() : formats[format]);
+
+        return this.format(output || this.localeData().calendar(format, this, local__createLocal(now)));
+    }
+
+    function clone () {
+        return new Moment(this);
+    }
+
+    function isAfter (input, units) {
+        var localInput = isMoment(input) ? input : local__createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
         }
-        // here, `this` means `window` in the browser, or `global` on the server
-        // add `moment` as a global object via a string identifier,
-        // for Closure Compiler "advanced" mode
-        if (deprecate) {
-            global.moment = function () {
-                if (!warned && console && console.warn) {
-                    warned = true;
-                    console.warn(
-                            "Accessing Moment through the global scope is " +
-                            "deprecated, and will be removed in an upcoming " +
-                            "release.");
-                }
-                return local_moment.apply(null, arguments);
-            };
-            extend(global.moment, local_moment);
+        units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+        if (units === 'millisecond') {
+            return this.valueOf() > localInput.valueOf();
         } else {
-            global['moment'] = moment;
+            return localInput.valueOf() < this.clone().startOf(units).valueOf();
         }
     }
 
-    // CommonJS module is defined
-    if (hasModule) {
-        module.exports = moment;
-        makeGlobal(true);
-    } else if (typeof define === "function" && define.amd) {
-        define("moment", function (require, exports, module) {
-            if (module.config && module.config() && module.config().noGlobal !== true) {
-                // If user provided noGlobal, he is aware of global
-                makeGlobal(module.config().noGlobal === undefined);
-            }
-
-            return moment;
-        });
-    } else {
-        makeGlobal();
+    function isBefore (input, units) {
+        var localInput = isMoment(input) ? input : local__createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+        if (units === 'millisecond') {
+            return this.valueOf() < localInput.valueOf();
+        } else {
+            return this.clone().endOf(units).valueOf() < localInput.valueOf();
+        }
     }
-}).call(this);
-;/**
- * Parse a text request to a json query object tree
- *
- * @param  {String} string The string to parse
- * @return {Object} The json query tree
- */
-function parseStringToObject(string) {
-;
-/*
-	Default template driver for JS/CC generated parsers running as
-	browser-based JavaScript/ECMAScript applications.
-	
-	WARNING: 	This parser template will not run as console and has lesser
-				features for debugging than the console derivates for the
-				various JavaScript platforms.
-	
-	Features:
-	- Parser trace messages
-	- Integrated panic-mode error recovery
-	
-	Written 2007, 2008 by Jan Max Meyer, J.M.K S.F. Software Technologies
-	
-	This is in the public domain.
-*/
 
-var NODEJS__dbg_withtrace		= false;
-var NODEJS__dbg_string			= new String();
+    function isBetween (from, to, units, inclusivity) {
+        inclusivity = inclusivity || '()';
+        return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
+            (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
+    }
 
-function __NODEJS_dbg_print( text )
-{
-	NODEJS__dbg_string += text + "\n";
-}
+    function isSame (input, units) {
+        var localInput = isMoment(input) ? input : local__createLocal(input),
+            inputMs;
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units || 'millisecond');
+        if (units === 'millisecond') {
+            return this.valueOf() === localInput.valueOf();
+        } else {
+            inputMs = localInput.valueOf();
+            return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
+        }
+    }
 
-function __NODEJS_lex( info )
-{
-	var state		= 0;
-	var match		= -1;
-	var match_pos	= 0;
-	var start		= 0;
-	var pos			= info.offset + 1;
-
-	do
-	{
-		pos--;
-		state = 0;
-		match = -2;
-		start = pos;
-
-		if( info.src.length <= start )
-			return 19;
-
-		do
-		{
-
-switch( state )
-{
-	case 0:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 8 ) || ( info.src.charCodeAt( pos ) >= 10 && info.src.charCodeAt( pos ) <= 31 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || info.src.charCodeAt( pos ) == 59 || ( info.src.charCodeAt( pos ) >= 63 && info.src.charCodeAt( pos ) <= 64 ) || ( info.src.charCodeAt( pos ) >= 66 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 9 ) state = 2;
-		else if( info.src.charCodeAt( pos ) == 40 ) state = 3;
-		else if( info.src.charCodeAt( pos ) == 41 ) state = 4;
-		else if( info.src.charCodeAt( pos ) == 60 || info.src.charCodeAt( pos ) == 62 ) state = 5;
-		else if( info.src.charCodeAt( pos ) == 33 ) state = 11;
-		else if( info.src.charCodeAt( pos ) == 79 ) state = 12;
-		else if( info.src.charCodeAt( pos ) == 32 ) state = 13;
-		else if( info.src.charCodeAt( pos ) == 61 ) state = 14;
-		else if( info.src.charCodeAt( pos ) == 34 ) state = 15;
-		else if( info.src.charCodeAt( pos ) == 65 ) state = 19;
-		else if( info.src.charCodeAt( pos ) == 78 ) state = 20;
-		else state = -1;
-		break;
-
-	case 1:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else state = -1;
-		match = 10;
-		match_pos = pos;
-		break;
-
-	case 2:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else state = -1;
-		match = 1;
-		match_pos = pos;
-		break;
-
-	case 3:
-		state = -1;
-		match = 3;
-		match_pos = pos;
-		break;
-
-	case 4:
-		state = -1;
-		match = 4;
-		match_pos = pos;
-		break;
-
-	case 5:
-		if( info.src.charCodeAt( pos ) == 61 ) state = 14;
-		else state = -1;
-		match = 11;
-		match_pos = pos;
-		break;
-
-	case 6:
-		state = -1;
-		match = 8;
-		match_pos = pos;
-		break;
-
-	case 7:
-		state = -1;
-		match = 9;
-		match_pos = pos;
-		break;
-
-	case 8:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else state = -1;
-		match = 6;
-		match_pos = pos;
-		break;
-
-	case 9:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else state = -1;
-		match = 5;
-		match_pos = pos;
-		break;
-
-	case 10:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else state = -1;
-		match = 7;
-		match_pos = pos;
-		break;
-
-	case 11:
-		if( info.src.charCodeAt( pos ) == 61 ) state = 14;
-		else state = -1;
-		break;
-
-	case 12:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 81 ) || ( info.src.charCodeAt( pos ) >= 83 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else if( info.src.charCodeAt( pos ) == 82 ) state = 8;
-		else state = -1;
-		match = 10;
-		match_pos = pos;
-		break;
-
-	case 13:
-		state = -1;
-		match = 1;
-		match_pos = pos;
-		break;
-
-	case 14:
-		state = -1;
-		match = 11;
-		match_pos = pos;
-		break;
-
-	case 15:
-		if( info.src.charCodeAt( pos ) == 34 ) state = 7;
-	else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) || info.src.charCodeAt( pos ) > 255 ) state = 15;
-		else if( info.src.charCodeAt( pos ) == 92 ) state = 17;
-		else state = -1;
-		break;
-
-	case 16:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 67 ) || ( info.src.charCodeAt( pos ) >= 69 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else if( info.src.charCodeAt( pos ) == 68 ) state = 9;
-		else state = -1;
-		match = 10;
-		match_pos = pos;
-		break;
-
-	case 17:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 15;
-		else state = -1;
-		break;
-
-	case 18:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 83 ) || ( info.src.charCodeAt( pos ) >= 85 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else if( info.src.charCodeAt( pos ) == 84 ) state = 10;
-		else state = -1;
-		match = 10;
-		match_pos = pos;
-		break;
-
-	case 19:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 79 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else if( info.src.charCodeAt( pos ) == 78 ) state = 16;
-		else state = -1;
-		match = 10;
-		match_pos = pos;
-		break;
-
-	case 20:
-		if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 78 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
-		else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
-		else if( info.src.charCodeAt( pos ) == 79 ) state = 18;
-		else state = -1;
-		match = 10;
-		match_pos = pos;
-		break;
+    function isSameOrAfter (input, units) {
+        return this.isSame(input, units) || this.isAfter(input,units);
+    }
 
-}
+    function isSameOrBefore (input, units) {
+        return this.isSame(input, units) || this.isBefore(input,units);
+    }
 
+    function diff (input, units, asFloat) {
+        var that,
+            zoneDelta,
+            delta, output;
 
-			pos++;
+        if (!this.isValid()) {
+            return NaN;
+        }
 
-		}
-		while( state > -1 );
+        that = cloneWithOffset(input, this);
 
-	}
-	while( 1 > -1 && match == 1 );
+        if (!that.isValid()) {
+            return NaN;
+        }
 
-	if( match > -1 )
-	{
-		info.att = info.src.substr( start, match_pos - start );
-		info.offset = match_pos;
-		
+        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
 
-	}
-	else
-	{
-		info.att = new String();
-		match = -1;
-	}
+        units = normalizeUnits(units);
 
-	return match;
-}
+        if (units === 'year' || units === 'month' || units === 'quarter') {
+            output = monthDiff(this, that);
+            if (units === 'quarter') {
+                output = output / 3;
+            } else if (units === 'year') {
+                output = output / 12;
+            }
+        } else {
+            delta = this - that;
+            output = units === 'second' ? delta / 1e3 : // 1000
+                units === 'minute' ? delta / 6e4 : // 1000 * 60
+                units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60
+                units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
+                units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
+                delta;
+        }
+        return asFloat ? output : absFloor(output);
+    }
+
+    function monthDiff (a, b) {
+        // difference in months
+        var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+            // b is in (anchor - 1 month, anchor + 1 month)
+            anchor = a.clone().add(wholeMonthDiff, 'months'),
+            anchor2, adjust;
+
+        if (b - anchor < 0) {
+            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor - anchor2);
+        } else {
+            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor2 - anchor);
+        }
 
+        //check for negative zero, return zero if negative zero
+        return -(wholeMonthDiff + adjust) || 0;
+    }
 
-function __NODEJS_parse( src, err_off, err_la )
-{
-	var		sstack			= new Array();
-	var		vstack			= new Array();
-	var 	err_cnt			= 0;
-	var		act;
-	var		go;
-	var		la;
-	var		rval;
-	var 	parseinfo		= new Function( "", "var offset; var src; var att;" );
-	var		info			= new parseinfo();
-	
-/* Pop-Table */
-var pop_tab = new Array(
-	new Array( 0/* begin' */, 1 ),
-	new Array( 13/* begin */, 1 ),
-	new Array( 12/* search_text */, 1 ),
-	new Array( 12/* search_text */, 2 ),
-	new Array( 12/* search_text */, 3 ),
-	new Array( 14/* and_expression */, 1 ),
-	new Array( 14/* and_expression */, 3 ),
-	new Array( 15/* boolean_expression */, 2 ),
-	new Array( 15/* boolean_expression */, 1 ),
-	new Array( 16/* expression */, 3 ),
-	new Array( 16/* expression */, 2 ),
-	new Array( 16/* expression */, 1 ),
-	new Array( 17/* value */, 2 ),
-	new Array( 17/* value */, 1 ),
-	new Array( 18/* string */, 1 ),
-	new Array( 18/* string */, 1 )
-);
-
-/* Action-Table */
-var act_tab = new Array(
-	/* State 0 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
-	/* State 1 */ new Array( 19/* "$" */,0 ),
-	/* State 2 */ new Array( 19/* "$" */,-1 ),
-	/* State 3 */ new Array( 6/* "OR" */,14 , 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 , 19/* "$" */,-2 , 4/* "RIGHT_PARENTHESE" */,-2 ),
-	/* State 4 */ new Array( 5/* "AND" */,16 , 19/* "$" */,-5 , 7/* "NOT" */,-5 , 3/* "LEFT_PARENTHESE" */,-5 , 8/* "COLUMN" */,-5 , 11/* "OPERATOR" */,-5 , 10/* "WORD" */,-5 , 9/* "STRING" */,-5 , 6/* "OR" */,-5 , 4/* "RIGHT_PARENTHESE" */,-5 ),
-	/* State 5 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
-	/* State 6 */ new Array( 19/* "$" */,-8 , 7/* "NOT" */,-8 , 3/* "LEFT_PARENTHESE" */,-8 , 8/* "COLUMN" */,-8 , 11/* "OPERATOR" */,-8 , 10/* "WORD" */,-8 , 9/* "STRING" */,-8 , 6/* "OR" */,-8 , 5/* "AND" */,-8 , 4/* "RIGHT_PARENTHESE" */,-8 ),
-	/* State 7 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
-	/* State 8 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
-	/* State 9 */ new Array( 19/* "$" */,-11 , 7/* "NOT" */,-11 , 3/* "LEFT_PARENTHESE" */,-11 , 8/* "COLUMN" */,-11 , 11/* "OPERATOR" */,-11 , 10/* "WORD" */,-11 , 9/* "STRING" */,-11 , 6/* "OR" */,-11 , 5/* "AND" */,-11 , 4/* "RIGHT_PARENTHESE" */,-11 ),
-	/* State 10 */ new Array( 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
-	/* State 11 */ new Array( 19/* "$" */,-13 , 7/* "NOT" */,-13 , 3/* "LEFT_PARENTHESE" */,-13 , 8/* "COLUMN" */,-13 , 11/* "OPERATOR" */,-13 , 10/* "WORD" */,-13 , 9/* "STRING" */,-13 , 6/* "OR" */,-13 , 5/* "AND" */,-13 , 4/* "RIGHT_PARENTHESE" */,-13 ),
-	/* State 12 */ new Array( 19/* "$" */,-14 , 7/* "NOT" */,-14 , 3/* "LEFT_PARENTHESE" */,-14 , 8/* "COLUMN" */,-14 , 11/* "OPERATOR" */,-14 , 10/* "WORD" */,-14 , 9/* "STRING" */,-14 , 6/* "OR" */,-14 , 5/* "AND" */,-14 , 4/* "RIGHT_PARENTHESE" */,-14 ),
-	/* State 13 */ new Array( 19/* "$" */,-15 , 7/* "NOT" */,-15 , 3/* "LEFT_PARENTHESE" */,-15 , 8/* "COLUMN" */,-15 , 11/* "OPERATOR" */,-15 , 10/* "WORD" */,-15 , 9/* "STRING" */,-15 , 6/* "OR" */,-15 , 5/* "AND" */,-15 , 4/* "RIGHT_PARENTHESE" */,-15 ),
-	/* State 14 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
-	/* State 15 */ new Array( 19/* "$" */,-3 , 4/* "RIGHT_PARENTHESE" */,-3 ),
-	/* State 16 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
-	/* State 17 */ new Array( 19/* "$" */,-7 , 7/* "NOT" */,-7 , 3/* "LEFT_PARENTHESE" */,-7 , 8/* "COLUMN" */,-7 , 11/* "OPERATOR" */,-7 , 10/* "WORD" */,-7 , 9/* "STRING" */,-7 , 6/* "OR" */,-7 , 5/* "AND" */,-7 , 4/* "RIGHT_PARENTHESE" */,-7 ),
-	/* State 18 */ new Array( 4/* "RIGHT_PARENTHESE" */,23 ),
-	/* State 19 */ new Array( 19/* "$" */,-10 , 7/* "NOT" */,-10 , 3/* "LEFT_PARENTHESE" */,-10 , 8/* "COLUMN" */,-10 , 11/* "OPERATOR" */,-10 , 10/* "WORD" */,-10 , 9/* "STRING" */,-10 , 6/* "OR" */,-10 , 5/* "AND" */,-10 , 4/* "RIGHT_PARENTHESE" */,-10 ),
-	/* State 20 */ new Array( 19/* "$" */,-12 , 7/* "NOT" */,-12 , 3/* "LEFT_PARENTHESE" */,-12 , 8/* "COLUMN" */,-12 , 11/* "OPERATOR" */,-12 , 10/* "WORD" */,-12 , 9/* "STRING" */,-12 , 6/* "OR" */,-12 , 5/* "AND" */,-12 , 4/* "RIGHT_PARENTHESE" */,-12 ),
-	/* State 21 */ new Array( 19/* "$" */,-4 , 4/* "RIGHT_PARENTHESE" */,-4 ),
-	/* State 22 */ new Array( 19/* "$" */,-6 , 7/* "NOT" */,-6 , 3/* "LEFT_PARENTHESE" */,-6 , 8/* "COLUMN" */,-6 , 11/* "OPERATOR" */,-6 , 10/* "WORD" */,-6 , 9/* "STRING" */,-6 , 6/* "OR" */,-6 , 4/* "RIGHT_PARENTHESE" */,-6 ),
-	/* State 23 */ new Array( 19/* "$" */,-9 , 7/* "NOT" */,-9 , 3/* "LEFT_PARENTHESE" */,-9 , 8/* "COLUMN" */,-9 , 11/* "OPERATOR" */,-9 , 10/* "WORD" */,-9 , 9/* "STRING" */,-9 , 6/* "OR" */,-9 , 5/* "AND" */,-9 , 4/* "RIGHT_PARENTHESE" */,-9 )
-);
-
-/* Goto-Table */
-var goto_tab = new Array(
-	/* State 0 */ new Array( 13/* begin */,1 , 12/* search_text */,2 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
-	/* State 1 */ new Array(  ),
-	/* State 2 */ new Array(  ),
-	/* State 3 */ new Array( 12/* search_text */,15 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
-	/* State 4 */ new Array(  ),
-	/* State 5 */ new Array( 16/* expression */,17 , 17/* value */,9 , 18/* string */,11 ),
-	/* State 6 */ new Array(  ),
-	/* State 7 */ new Array( 12/* search_text */,18 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
-	/* State 8 */ new Array( 16/* expression */,19 , 17/* value */,9 , 18/* string */,11 ),
-	/* State 9 */ new Array(  ),
-	/* State 10 */ new Array( 18/* string */,20 ),
-	/* State 11 */ new Array(  ),
-	/* State 12 */ new Array(  ),
-	/* State 13 */ new Array(  ),
-	/* State 14 */ new Array( 12/* search_text */,21 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
-	/* State 15 */ new Array(  ),
-	/* State 16 */ new Array( 14/* and_expression */,22 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
-	/* State 17 */ new Array(  ),
-	/* State 18 */ new Array(  ),
-	/* State 19 */ new Array(  ),
-	/* State 20 */ new Array(  ),
-	/* State 21 */ new Array(  ),
-	/* State 22 */ new Array(  ),
-	/* State 23 */ new Array(  )
-);
-
-
-
-/* Symbol labels */
-var labels = new Array(
-	"begin'" /* Non-terminal symbol */,
-	"WHITESPACE" /* Terminal symbol */,
-	"WHITESPACE" /* Terminal symbol */,
-	"LEFT_PARENTHESE" /* Terminal symbol */,
-	"RIGHT_PARENTHESE" /* Terminal symbol */,
-	"AND" /* Terminal symbol */,
-	"OR" /* Terminal symbol */,
-	"NOT" /* Terminal symbol */,
-	"COLUMN" /* Terminal symbol */,
-	"STRING" /* Terminal symbol */,
-	"WORD" /* Terminal symbol */,
-	"OPERATOR" /* Terminal symbol */,
-	"search_text" /* Non-terminal symbol */,
-	"begin" /* Non-terminal symbol */,
-	"and_expression" /* Non-terminal symbol */,
-	"boolean_expression" /* Non-terminal symbol */,
-	"expression" /* Non-terminal symbol */,
-	"value" /* Non-terminal symbol */,
-	"string" /* Non-terminal symbol */,
-	"$" /* Terminal symbol */
-);
-
-
-	
-	info.offset = 0;
-	info.src = src;
-	info.att = new String();
-	
-	if( !err_off )
-		err_off	= new Array();
-	if( !err_la )
-	err_la = new Array();
-	
-	sstack.push( 0 );
-	vstack.push( 0 );
-	
-	la = __NODEJS_lex( info );
-
-	while( true )
-	{
-		act = 25;
-		for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
-		{
-			if( act_tab[sstack[sstack.length-1]][i] == la )
-			{
-				act = act_tab[sstack[sstack.length-1]][i+1];
-				break;
-			}
-		}
-
-		if( NODEJS__dbg_withtrace && sstack.length > 0 )
-		{
-			__NODEJS_dbg_print( "\nState " + sstack[sstack.length-1] + "\n" +
-							"\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" +
-							"\tAction: " + act + "\n" + 
-							"\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ?
-									"..." : "" ) + "\"\n" +
-							"\tStack: " + sstack.join() + "\n" +
-							"\tValue stack: " + vstack.join() + "\n" );
-		}
-		
-			
-		//Panic-mode: Try recovery when parse-error occurs!
-		if( act == 25 )
-		{
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] );
-			
-			err_cnt++;
-			err_off.push( info.offset - info.att.length );			
-			err_la.push( new Array() );
-			for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
-				err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] );
-			
-			//Remember the original stack!
-			var rsstack = new Array();
-			var rvstack = new Array();
-			for( var i = 0; i < sstack.length; i++ )
-			{
-				rsstack[i] = sstack[i];
-				rvstack[i] = vstack[i];
-			}
-			
-			while( act == 25 && la != 19 )
-			{
-				if( NODEJS__dbg_withtrace )
-					__NODEJS_dbg_print( "\tError recovery\n" +
-									"Current lookahead: " + labels[la] + " (" + info.att + ")\n" +
-									"Action: " + act + "\n\n" );
-				if( la == -1 )
-					info.offset++;
-					
-				while( act == 25 && sstack.length > 0 )
-				{
-					sstack.pop();
-					vstack.pop();
-					
-					if( sstack.length == 0 )
-						break;
-						
-					act = 25;
-					for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
-					{
-						if( act_tab[sstack[sstack.length-1]][i] == la )
-						{
-							act = act_tab[sstack[sstack.length-1]][i+1];
-							break;
-						}
-					}
-				}
-				
-				if( act != 25 )
-					break;
-				
-				for( var i = 0; i < rsstack.length; i++ )
-				{
-					sstack.push( rsstack[i] );
-					vstack.push( rvstack[i] );
-				}
-				
-				la = __NODEJS_lex( info );
-			}
-			
-			if( act == 25 )
-			{
-				if( NODEJS__dbg_withtrace )
-					__NODEJS_dbg_print( "\tError recovery failed, terminating parse process..." );
-				break;
-			}
-
-
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "\tError recovery succeeded, continuing" );
-		}
-		
-		/*
-		if( act == 25 )
-			break;
-		*/
-		
-		
-		//Shift
-		if( act > 0 )
-		{			
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" );
-		
-			sstack.push( act );
-			vstack.push( info.att );
-			
-			la = __NODEJS_lex( info );
-			
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" );
-		}
-		//Reduce
-		else
-		{		
-			act *= -1;
-			
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "Reducing by producution: " + act );
-			
-			rval = void(0);
-			
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "\tPerforming semantic action..." );
-			
-switch( act )
-{
-	case 0:
-	{
-		rval = vstack[ vstack.length - 1 ];
-	}
-	break;
-	case 1:
-	{
-		 result = vstack[ vstack.length - 1 ]; 
-	}
-	break;
-	case 2:
-	{
-		 rval = vstack[ vstack.length - 1 ]; 
-	}
-	break;
-	case 3:
-	{
-		 rval = mkComplexQuery('OR',[vstack[ vstack.length - 2 ],vstack[ vstack.length - 1 ]]); 
-	}
-	break;
-	case 4:
-	{
-		 rval = mkComplexQuery('OR',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); 
-	}
-	break;
-	case 5:
-	{
-		 rval = vstack[ vstack.length - 1 ] ; 
-	}
-	break;
-	case 6:
-	{
-		 rval = mkComplexQuery('AND',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); 
-	}
-	break;
-	case 7:
-	{
-		 rval = mkNotQuery(vstack[ vstack.length - 1 ]); 
-	}
-	break;
-	case 8:
-	{
-		 rval = vstack[ vstack.length - 1 ]; 
-	}
-	break;
-	case 9:
-	{
-		 rval = vstack[ vstack.length - 2 ]; 
-	}
-	break;
-	case 10:
-	{
-		 simpleQuerySetKey(vstack[ vstack.length - 1 ],vstack[ vstack.length - 2 ].split(':').slice(0,-1).join(':')); rval = vstack[ vstack.length - 1 ]; 
-	}
-	break;
-	case 11:
-	{
-		 rval = vstack[ vstack.length - 1 ]; 
-	}
-	break;
-	case 12:
-	{
-		 vstack[ vstack.length - 1 ].operator = vstack[ vstack.length - 2 ] ; rval = vstack[ vstack.length - 1 ]; 
-	}
-	break;
-	case 13:
-	{
-		 rval = vstack[ vstack.length - 1 ]; 
-	}
-	break;
-	case 14:
-	{
-		 rval = mkSimpleQuery('',vstack[ vstack.length - 1 ]); 
-	}
-	break;
-	case 15:
-	{
-		 rval = mkSimpleQuery('',vstack[ vstack.length - 1 ].split('"').slice(1,-1).join('"')); 
-	}
-	break;
-}
+    utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+    utils_hooks__hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
 
+    function toString () {
+        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+    }
 
+    function moment_format__toISOString () {
+        var m = this.clone().utc();
+        if (0 < m.year() && m.year() <= 9999) {
+            if (isFunction(Date.prototype.toISOString)) {
+                // native implementation is ~50x faster, use it when we can
+                return this.toDate().toISOString();
+            } else {
+                return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+            }
+        } else {
+            return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+        }
+    }
 
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." );
-				
-			for( var i = 0; i < pop_tab[act][1]; i++ )
-			{
-				sstack.pop();
-				vstack.pop();
-			}
-									
-			go = -1;
-			for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 )
-			{
-				if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] )
-				{
-					go = goto_tab[sstack[sstack.length-1]][i+1];
-					break;
-				}
-			}
-			
-			if( act == 0 )
-				break;
-				
-			if( NODEJS__dbg_withtrace )
-				__NODEJS_dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] );
-				
-			sstack.push( go );
-			vstack.push( rval );			
-		}
-		
-		if( NODEJS__dbg_withtrace )
-		{		
-			alert( NODEJS__dbg_string );
-			NODEJS__dbg_string = new String();
-		}
-	}
-
-	if( NODEJS__dbg_withtrace )
-	{
-		__NODEJS_dbg_print( "\nParse complete." );
-		alert( NODEJS__dbg_string );
-	}
-	
-	return err_cnt;
-}
+    function format (inputString) {
+        if (!inputString) {
+            inputString = this.isUtc() ? utils_hooks__hooks.defaultFormatUtc : utils_hooks__hooks.defaultFormat;
+        }
+        var output = formatMoment(this, inputString);
+        return this.localeData().postformat(output);
+    }
 
+    function from (time, withoutSuffix) {
+        if (this.isValid() &&
+                ((isMoment(time) && time.isValid()) ||
+                 local__createLocal(time).isValid())) {
+            return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
+    }
 
+    function fromNow (withoutSuffix) {
+        return this.from(local__createLocal(), withoutSuffix);
+    }
 
-var arrayExtend = function () {
-  var j, i, newlist = [], list_list = arguments;
-  for (j = 0; j < list_list.length; j += 1) {
-    for (i = 0; i < list_list[j].length; i += 1) {
-      newlist.push(list_list[j][i]);
+    function to (time, withoutSuffix) {
+        if (this.isValid() &&
+                ((isMoment(time) && time.isValid()) ||
+                 local__createLocal(time).isValid())) {
+            return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
     }
-  }
-  return newlist;
 
-}, mkSimpleQuery = function (key, value, operator) {
-  var object = {"type": "simple", "key": key, "value": value};
-  if (operator !== undefined) {
-    object.operator = operator;
-  }
-  return object;
+    function toNow (withoutSuffix) {
+        return this.to(local__createLocal(), withoutSuffix);
+    }
 
-}, mkNotQuery = function (query) {
-  if (query.operator === "NOT") {
-    return query.query_list[0];
-  }
-  return {"type": "complex", "operator": "NOT", "query_list": [query]};
+    // If passed a locale key, it will set the locale for this
+    // instance.  Otherwise, it will return the locale configuration
+    // variables for this instance.
+    function locale (key) {
+        var newLocaleData;
 
-}, mkComplexQuery = function (operator, query_list) {
-  var i, query_list2 = [];
-  for (i = 0; i < query_list.length; i += 1) {
-    if (query_list[i].operator === operator) {
-      query_list2 = arrayExtend(query_list2, query_list[i].query_list);
-    } else {
-      query_list2.push(query_list[i]);
+        if (key === undefined) {
+            return this._locale._abbr;
+        } else {
+            newLocaleData = locale_locales__getLocale(key);
+            if (newLocaleData != null) {
+                this._locale = newLocaleData;
+            }
+            return this;
+        }
     }
-  }
-  return {type:"complex",operator:operator,query_list:query_list2};
 
-}, simpleQuerySetKey = function (query, key) {
-  var i;
-  if (query.type === "complex") {
-    for (i = 0; i < query.query_list.length; ++i) {
-      simpleQuerySetKey (query.query_list[i],key);
+    var lang = deprecate(
+        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+        function (key) {
+            if (key === undefined) {
+                return this.localeData();
+            } else {
+                return this.locale(key);
+            }
+        }
+    );
+
+    function localeData () {
+        return this._locale;
     }
-    return true;
-  }
-  if (query.type === "simple" && !query.key) {
-    query.key = key;
-    return true;
-  }
-  return false;
-},
-  error_offsets = [],
-  error_lookaheads = [],
-  error_count = 0,
-  result;
 
-if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) {
-  var i;
-  for (i = 0; i < error_count; i += 1) {
-    throw new Error("Parse error near \"" +
-                    string.substr(error_offsets[i]) +
-                    "\", expecting \"" +
-                    error_lookaheads[i].join() + "\"");
-  }
-}
+    function startOf (units) {
+        units = normalizeUnits(units);
+        // the following switch intentionally omits break keywords
+        // to utilize falling through the cases.
+        switch (units) {
+        case 'year':
+            this.month(0);
+            /* falls through */
+        case 'quarter':
+        case 'month':
+            this.date(1);
+            /* falls through */
+        case 'week':
+        case 'isoWeek':
+        case 'day':
+        case 'date':
+            this.hours(0);
+            /* falls through */
+        case 'hour':
+            this.minutes(0);
+            /* falls through */
+        case 'minute':
+            this.seconds(0);
+            /* falls through */
+        case 'second':
+            this.milliseconds(0);
+        }
 
-;  return result;
-} // parseStringToObject
+        // weeks are a special case
+        if (units === 'week') {
+            this.weekday(0);
+        }
+        if (units === 'isoWeek') {
+            this.isoWeekday(1);
+        }
 
-;/*global RSVP, window, parseStringToObject*/
-/*jslint nomen: true, maxlen: 90*/
-(function (RSVP, window, parseStringToObject) {
-  "use strict";
+        // quarters are also special
+        if (units === 'quarter') {
+            this.month(Math.floor(this.month() / 3) * 3);
+        }
 
-  var query_class_dict = {},
-    regexp_escape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g,
-    regexp_percent = /%/g,
-    regexp_underscore = /_/g,
-    regexp_operator = /^(?:AND|OR|NOT)$/i,
-    regexp_comparaison = /^(?:!?=|<=?|>=?)$/i;
+        return this;
+    }
 
-  /**
-   * Convert metadata values to array of strings. ex:
-   *
-   *     "a" -> ["a"],
-   *     {"content": "a"} -> ["a"]
-   *
-   * @param  {Any} value The metadata value
-   * @return {Array} The value in string array format
-   */
-  function metadataValueToStringArray(value) {
-    var i, new_value = [];
-    if (value === undefined) {
-      return undefined;
+    function endOf (units) {
+        units = normalizeUnits(units);
+        if (units === undefined || units === 'millisecond') {
+            return this;
+        }
+
+        // 'date' is an alias for 'day', so it should be considered as such.
+        if (units === 'date') {
+            units = 'day';
+        }
+
+        return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
     }
-    if (!Array.isArray(value)) {
-      value = [value];
+
+    function to_type__valueOf () {
+        return this._d.valueOf() - ((this._offset || 0) * 60000);
     }
-    for (i = 0; i < value.length; i += 1) {
-      if (typeof value[i] === 'object') {
-        new_value[i] = value[i].content;
-      } else {
-        new_value[i] = value[i];
-      }
+
+    function unix () {
+        return Math.floor(this.valueOf() / 1000);
     }
-    return new_value;
-  }
 
-  /**
-   * A sort function to sort items by key
-   *
-   * @param  {String} key The key to sort on
-   * @param  {String} [way="ascending"] 'ascending' or 'descending'
-   * @return {Function} The sort function
-   */
-  function sortFunction(key, way) {
-    var result;
-    if (way === 'descending') {
-      result = 1;
-    } else if (way === 'ascending') {
-      result = -1;
-    } else {
-      throw new TypeError("Query.sortFunction(): " +
-                          "Argument 2 must be 'ascending' or 'descending'");
+    function toDate () {
+        return this._offset ? new Date(this.valueOf()) : this._d;
     }
-    return function (a, b) {
-      // this comparison is 5 times faster than json comparison
-      var i, l;
-      a = metadataValueToStringArray(a[key]) || [];
-      b = metadataValueToStringArray(b[key]) || [];
-      l = a.length > b.length ? a.length : b.length;
-      for (i = 0; i < l; i += 1) {
-        if (a[i] === undefined) {
-          return result;
-        }
-        if (b[i] === undefined) {
-          return -result;
-        }
-        if (a[i] > b[i]) {
-          return -result;
-        }
-        if (a[i] < b[i]) {
-          return result;
-        }
-      }
-      return 0;
-    };
-  }
 
-  /**
-   * Sort a list of items, according to keys and directions.
-   *
-   * @param  {Array} sort_on_option List of couples [key, direction]
-   * @param  {Array} list The item list to sort
-   * @return {Array} The filtered list
-   */
-  function sortOn(sort_on_option, list) {
-    var sort_index;
-    if (!Array.isArray(sort_on_option)) {
-      throw new TypeError("jioquery.sortOn(): " +
-                          "Argument 1 is not of type 'array'");
+    function toArray () {
+        var m = this;
+        return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
     }
-    for (sort_index = sort_on_option.length - 1; sort_index >= 0;
-         sort_index -= 1) {
-      list.sort(sortFunction(
-        sort_on_option[sort_index][0],
-        sort_on_option[sort_index][1]
-      ));
+
+    function toObject () {
+        var m = this;
+        return {
+            years: m.year(),
+            months: m.month(),
+            date: m.date(),
+            hours: m.hours(),
+            minutes: m.minutes(),
+            seconds: m.seconds(),
+            milliseconds: m.milliseconds()
+        };
     }
-    return list;
-  }
 
-  /**
-   * Limit a list of items, according to index and length.
-   *
-   * @param  {Array} limit_option A couple [from, length]
-   * @param  {Array} list The item list to limit
-   * @return {Array} The filtered list
-   */
-  function limit(limit_option, list) {
-    if (!Array.isArray(limit_option)) {
-      throw new TypeError("jioquery.limit(): " +
-                          "Argument 1 is not of type 'array'");
+    function toJSON () {
+        // new Date(NaN).toJSON() === null
+        return this.isValid() ? this.toISOString() : null;
     }
-    if (!Array.isArray(list)) {
-      throw new TypeError("jioquery.limit(): " +
-                          "Argument 2 is not of type 'array'");
+
+    function moment_valid__isValid () {
+        return valid__isValid(this);
     }
-    list.splice(0, limit_option[0]);
-    if (limit_option[1]) {
-      list.splice(limit_option[1]);
+
+    function parsingFlags () {
+        return extend({}, getParsingFlags(this));
     }
-    return list;
-  }
 
-  /**
-   * Filter a list of items, modifying them to select only wanted keys.
-   *
-   * @param  {Array} select_option Key list to keep
-   * @param  {Array} list The item list to filter
-   * @return {Array} The filtered list
-   */
-  function select(select_option, list) {
-    var i, j, new_item;
-    if (!Array.isArray(select_option)) {
-      throw new TypeError("jioquery.select(): " +
-                          "Argument 1 is not of type Array");
+    function invalidAt () {
+        return getParsingFlags(this).overflow;
     }
-    if (!Array.isArray(list)) {
-      throw new TypeError("jioquery.select(): " +
-                          "Argument 2 is not of type Array");
+
+    function creationData() {
+        return {
+            input: this._i,
+            format: this._f,
+            locale: this._locale,
+            isUTC: this._isUTC,
+            strict: this._strict
+        };
     }
-    for (i = 0; i < list.length; i += 1) {
-      new_item = {};
-      for (j = 0; j < select_option.length; j += 1) {
-        if (list[i].hasOwnProperty([select_option[j]])) {
-          new_item[select_option[j]] = list[i][select_option[j]];
-        }
-      }
-      for (j in new_item) {
-        if (new_item.hasOwnProperty(j)) {
-          list[i] = new_item;
-          break;
-        }
-      }
+
+    // FORMATTING
+
+    addFormatToken(0, ['gg', 2], 0, function () {
+        return this.weekYear() % 100;
+    });
+
+    addFormatToken(0, ['GG', 2], 0, function () {
+        return this.isoWeekYear() % 100;
+    });
+
+    function addWeekYearFormatToken (token, getter) {
+        addFormatToken(0, [token, token.length], 0, getter);
     }
-    return list;
-  }
 
-  /**
-   * The query to use to filter a list of objects.
-   * This is an abstract class.
-   *
-   * @class Query
-   * @constructor
-   */
-  function Query() {
+    addWeekYearFormatToken('gggg',     'weekYear');
+    addWeekYearFormatToken('ggggg',    'weekYear');
+    addWeekYearFormatToken('GGGG',  'isoWeekYear');
+    addWeekYearFormatToken('GGGGG', 'isoWeekYear');
 
-    /**
-     * Called before parsing the query. Must be overridden!
-     *
-     * @method onParseStart
-     * @param  {Object} object The object shared in the parse process
-     * @param  {Object} option Some option gave in parse()
-     */
-  //   this.onParseStart = emptyFunction;
+    // ALIASES
 
-    /**
-     * Called when parsing a simple query. Must be overridden!
-     *
-     * @method onParseSimpleQuery
-     * @param  {Object} object The object shared in the parse process
-     * @param  {Object} option Some option gave in parse()
-     */
-  //   this.onParseSimpleQuery = emptyFunction;
+    addUnitAlias('weekYear', 'gg');
+    addUnitAlias('isoWeekYear', 'GG');
 
-    /**
-     * Called when parsing a complex query. Must be overridden!
-     *
-     * @method onParseComplexQuery
-     * @param  {Object} object The object shared in the parse process
-     * @param  {Object} option Some option gave in parse()
-     */
-  //   this.onParseComplexQuery = emptyFunction;
+    // PARSING
 
-    /**
-     * Called after parsing the query. Must be overridden!
-     *
-     * @method onParseEnd
-     * @param  {Object} object The object shared in the parse process
-     * @param  {Object} option Some option gave in parse()
-     */
-  //   this.onParseEnd = emptyFunction;
+    addRegexToken('G',      matchSigned);
+    addRegexToken('g',      matchSigned);
+    addRegexToken('GG',     match1to2, match2);
+    addRegexToken('gg',     match1to2, match2);
+    addRegexToken('GGGG',   match1to4, match4);
+    addRegexToken('gggg',   match1to4, match4);
+    addRegexToken('GGGGG',  match1to6, match6);
+    addRegexToken('ggggg',  match1to6, match6);
 
-    return;
-  }
+    addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
+        week[token.substr(0, 2)] = toInt(input);
+    });
 
-  /**
-   * Filter the item list with matching item only
-   *
-   * @method exec
-   * @param  {Array} item_list The list of object
-   * @param  {Object} [option] Some operation option
-   * @param  {Array} [option.select_list] A object keys to retrieve
-   * @param  {Array} [option.sort_on] Couples of object keys and "ascending"
-   *                 or "descending"
-   * @param  {Array} [option.limit] Couple of integer, first is an index and
-   *                 second is the length.
-   */
-  Query.prototype.exec = function (item_list, option) {
-    if (!Array.isArray(item_list)) {
-      throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
+    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+        week[token] = utils_hooks__hooks.parseTwoDigitYear(input);
+    });
+
+    // MOMENTS
+
+    function getSetWeekYear (input) {
+        return getSetWeekYearHelper.call(this,
+                input,
+                this.week(),
+                this.weekday(),
+                this.localeData()._week.dow,
+                this.localeData()._week.doy);
     }
-    if (option === undefined) {
-      option = {};
+
+    function getSetISOWeekYear (input) {
+        return getSetWeekYearHelper.call(this,
+                input, this.isoWeek(), this.isoWeekday(), 1, 4);
     }
-    if (typeof option !== 'object') {
-      throw new TypeError("Query().exec(): " +
-                          "Optional argument 2 is not of type 'object'");
+
+    function getISOWeeksInYear () {
+        return weeksInYear(this.year(), 1, 4);
     }
-    var context = this,
-      i;
-    for (i = item_list.length - 1; i >= 0; i -= 1) {
-      if (!context.match(item_list[i])) {
-        item_list.splice(i, 1);
-      }
+
+    function getWeeksInYear () {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
     }
 
-    if (option.sort_on) {
-      sortOn(option.sort_on, item_list);
+    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+        var weeksTarget;
+        if (input == null) {
+            return weekOfYear(this, dow, doy).year;
+        } else {
+            weeksTarget = weeksInYear(input, dow, doy);
+            if (week > weeksTarget) {
+                week = weeksTarget;
+            }
+            return setWeekAll.call(this, input, week, weekday, dow, doy);
+        }
     }
 
-    if (option.limit) {
-      limit(option.limit, item_list);
+    function setWeekAll(weekYear, week, weekday, dow, doy) {
+        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
+
+        this.year(date.getUTCFullYear());
+        this.month(date.getUTCMonth());
+        this.date(date.getUTCDate());
+        return this;
     }
 
-    select(option.select_list || [], item_list);
+    // FORMATTING
 
-    return new RSVP.Queue()
-      .push(function () {
-        return item_list;
-      });
-  };
+    addFormatToken('Q', 0, 'Qo', 'quarter');
 
-  /**
-   * Test if an item matches this query
-   *
-   * @method match
-   * @param  {Object} item The object to test
-   * @return {Boolean} true if match, false otherwise
-   */
-  Query.prototype.match = function () {
-    return true;
-  };
+    // ALIASES
 
-  /**
-   * Browse the Query in deep calling parser method in each step.
-   *
-   * `onParseStart` is called first, on end `onParseEnd` is called.
-   * It starts from the simple queries at the bottom of the tree calling the
-   * parser method `onParseSimpleQuery`, and go up calling the
-   * `onParseComplexQuery` method.
-   *
-   * @method parse
-   * @param  {Object} option Any options you want (except 'parsed')
-   * @return {Any} The parse result
-   */
-  Query.prototype.parse = function (option) {
-    var that = this,
-      object;
-    /**
-     * The recursive parser.
-     *
-     * @param  {Object} object The object shared in the parse process
-     * @param  {Object} options Some options usable in the parseMethods
-     * @return {Any} The parser result
-     */
-    function recParse(object, option) {
-      var query = object.parsed,
-        queue = new RSVP.Queue(),
-        i;
+    addUnitAlias('quarter', 'Q');
 
-      function enqueue(j) {
-        queue
-          .push(function () {
-            object.parsed = query.query_list[j];
-            return recParse(object, option);
-          })
-          .push(function () {
-            query.query_list[j] = object.parsed;
-          });
-      }
+    // PARSING
 
-      if (query.type === "complex") {
+    addRegexToken('Q', match1);
+    addParseToken('Q', function (input, array) {
+        array[MONTH] = (toInt(input) - 1) * 3;
+    });
 
+    // MOMENTS
 
-        for (i = 0; i < query.query_list.length; i += 1) {
-          enqueue(i);
-        }
+    function getSetQuarter (input) {
+        return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+    }
 
-        return queue
-          .push(function () {
-            object.parsed = query;
-            return that.onParseComplexQuery(object, option);
-          });
+    // FORMATTING
 
-      }
-      if (query.type === "simple") {
-        return that.onParseSimpleQuery(object, option);
-      }
+    addFormatToken('w', ['ww', 2], 'wo', 'week');
+    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+    // ALIASES
+
+    addUnitAlias('week', 'w');
+    addUnitAlias('isoWeek', 'W');
+
+    // PARSING
+
+    addRegexToken('w',  match1to2);
+    addRegexToken('ww', match1to2, match2);
+    addRegexToken('W',  match1to2);
+    addRegexToken('WW', match1to2, match2);
+
+    addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
+        week[token.substr(0, 1)] = toInt(input);
+    });
+
+    // HELPERS
+
+    // LOCALES
+
+    function localeWeek (mom) {
+        return weekOfYear(mom, this._week.dow, this._week.doy).week;
     }
-    object = {
-      parsed: JSON.parse(JSON.stringify(that.serialized()))
+
+    var defaultLocaleWeek = {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
     };
-    return new RSVP.Queue()
-      .push(function () {
-        return that.onParseStart(object, option);
-      })
-      .push(function () {
-        return recParse(object, option);
-      })
-      .push(function () {
-        return that.onParseEnd(object, option);
-      })
-      .push(function () {
-        return object.parsed;
-      });
 
-  };
+    function localeFirstDayOfWeek () {
+        return this._week.dow;
+    }
 
-  /**
-   * Convert this query to a parsable string.
-   *
-   * @method toString
-   * @return {String} The string version of this query
-   */
-  Query.prototype.toString = function () {
-    return "";
-  };
+    function localeFirstDayOfYear () {
+        return this._week.doy;
+    }
 
-  /**
-   * Convert this query to an jsonable object in order to be remake thanks to
-   * QueryFactory class.
-   *
-   * @method serialized
-   * @return {Object} The jsonable object
-   */
-  Query.prototype.serialized = function () {
-    return undefined;
-  };
+    // MOMENTS
 
-  /**
-   * Provides static methods to create Query object
-   *
-   * @class QueryFactory
-   */
-  function QueryFactory() {
-    return;
-  }
+    function getSetWeek (input) {
+        var week = this.localeData().week(this);
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
 
-  /**
-   * Escapes regexp special chars from a string.
-   *
-   * @param  {String} string The string to escape
-   * @return {String} The escaped string
-   */
-  function stringEscapeRegexpCharacters(string) {
-    return string.replace(regexp_escape, "\\$&");
-  }
+    function getSetISOWeek (input) {
+        var week = weekOfYear(this, 1, 4).week;
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
 
-  /**
-   * Inherits the prototype methods from one constructor into another. The
-   * prototype of `constructor` will be set to a new object created from
-   * `superConstructor`.
-   *
-   * @param  {Function} constructor The constructor which inherits the super one
-   * @param  {Function} superConstructor The super constructor
-   */
-  function inherits(constructor, superConstructor) {
-    constructor.super_ = superConstructor;
-    constructor.prototype = Object.create(superConstructor.prototype, {
-      "constructor": {
-        "configurable": true,
-        "enumerable": false,
-        "writable": true,
-        "value": constructor
-      }
-    });
-  }
+    // FORMATTING
 
-  /**
-   * Convert a search text to a regexp.
-   *
-   * @param  {String} string The string to convert
-   * @param  {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_"
-   * @return {RegExp} The search text regexp
-   */
-  function searchTextToRegExp(string, use_wildcard_characters) {
-    if (typeof string !== 'string') {
-      throw new TypeError("jioquery.searchTextToRegExp(): " +
-                          "Argument 1 is not of type 'string'");
-    }
-    if (use_wildcard_characters === false) {
-      return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
-    }
-    return new RegExp("^" + stringEscapeRegexpCharacters(string)
-      .replace(regexp_percent, '.*')
-      .replace(regexp_underscore, '.') + "$");
-  }
+    addFormatToken('D', ['DD', 2], 'Do', 'date');
 
-  /**
-   * The ComplexQuery inherits from Query, and compares one or several metadata
-   * values.
-   *
-   * @class ComplexQuery
-   * @extends Query
-   * @param  {Object} [spec={}] The specifications
-   * @param  {String} [spec.operator="AND"] The compare method to use
-   * @param  {String} spec.key The metadata key
-   * @param  {String} spec.value The value of the metadata to compare
-   */
-  function ComplexQuery(spec, key_schema) {
-    Query.call(this);
+    // ALIASES
 
-    /**
-     * Logical operator to use to compare object values
-     *
-     * @attribute operator
-     * @type String
-     * @default "AND"
-     * @optional
-     */
-    this.operator = spec.operator;
+    addUnitAlias('date', 'D');
 
-    /**
-     * The sub Query list which are used to query an item.
-     *
-     * @attribute query_list
-     * @type Array
-     * @default []
-     * @optional
-     */
-    this.query_list = spec.query_list || [];
-    this.query_list = this.query_list.map(
-      // decorate the map to avoid sending the index as key_schema argument
-      function (o) { return QueryFactory.create(o, key_schema); }
-    );
+    // PARSING
 
-  }
-  inherits(ComplexQuery, Query);
+    addRegexToken('D',  match1to2);
+    addRegexToken('DD', match1to2, match2);
+    addRegexToken('Do', function (isStrict, locale) {
+        return isStrict ? locale._ordinalParse : locale._ordinalParseLenient;
+    });
 
-  ComplexQuery.prototype.operator = "AND";
-  ComplexQuery.prototype.type = "complex";
+    addParseToken(['D', 'DD'], DATE);
+    addParseToken('Do', function (input, array) {
+        array[DATE] = toInt(input.match(match1to2)[0], 10);
+    });
 
-  /**
-   * #crossLink "Query/match:method"
-   */
-  ComplexQuery.prototype.match = function (item) {
-    var operator = this.operator;
-    if (!(regexp_operator.test(operator))) {
-      operator = "AND";
-    }
-    return this[operator.toUpperCase()](item);
-  };
+    // MOMENTS
 
-  /**
-   * #crossLink "Query/toString:method"
-   */
-  ComplexQuery.prototype.toString = function () {
-    var str_list = [], this_operator = this.operator;
-    if (this.operator === "NOT") {
-      str_list.push("NOT (");
-      str_list.push(this.query_list[0].toString());
-      str_list.push(")");
-      return str_list.join(" ");
-    }
-    this.query_list.forEach(function (query) {
-      str_list.push("(");
-      str_list.push(query.toString());
-      str_list.push(")");
-      str_list.push(this_operator);
+    var getSetDayOfMonth = makeGetSet('Date', true);
+
+    // FORMATTING
+
+    addFormatToken('d', 0, 'do', 'day');
+
+    addFormatToken('dd', 0, 0, function (format) {
+        return this.localeData().weekdaysMin(this, format);
     });
-    str_list.length -= 1;
-    return str_list.join(" ");
-  };
 
-  /**
-   * #crossLink "Query/serialized:method"
-   */
-  ComplexQuery.prototype.serialized = function () {
-    var s = {
-      "type": "complex",
-      "operator": this.operator,
-      "query_list": []
-    };
-    this.query_list.forEach(function (query) {
-      s.query_list.push(
-        typeof query.toJSON === "function" ? query.toJSON() : query
-      );
+    addFormatToken('ddd', 0, 0, function (format) {
+        return this.localeData().weekdaysShort(this, format);
     });
-    return s;
-  };
-  ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized;
 
-  /**
-   * Comparison operator, test if all sub queries match the
-   * item value
-   *
-   * @method AND
-   * @param  {Object} item The item to match
-   * @return {Boolean} true if all match, false otherwise
-   */
-  ComplexQuery.prototype.AND = function (item) {
-    var result = true,
-      i = 0;
+    addFormatToken('dddd', 0, 0, function (format) {
+        return this.localeData().weekdays(this, format);
+    });
 
-    while (result && (i !== this.query_list.length)) {
-      result = this.query_list[i].match(item);
-      i += 1;
-    }
-    return result;
+    addFormatToken('e', 0, 0, 'weekday');
+    addFormatToken('E', 0, 0, 'isoWeekday');
 
-  };
+    // ALIASES
 
-  /**
-   * Comparison operator, test if one of the sub queries matches the
-   * item value
-   *
-   * @method OR
-   * @param  {Object} item The item to match
-   * @return {Boolean} true if one match, false otherwise
-   */
-  ComplexQuery.prototype.OR = function (item) {
-    var result = false,
-      i = 0;
+    addUnitAlias('day', 'd');
+    addUnitAlias('weekday', 'e');
+    addUnitAlias('isoWeekday', 'E');
 
-    while ((!result) && (i !== this.query_list.length)) {
-      result = this.query_list[i].match(item);
-      i += 1;
-    }
+    // PARSING
 
-    return result;
-  };
+    addRegexToken('d',    match1to2);
+    addRegexToken('e',    match1to2);
+    addRegexToken('E',    match1to2);
+    addRegexToken('dd',   function (isStrict, locale) {
+        return locale.weekdaysMinRegex(isStrict);
+    });
+    addRegexToken('ddd',   function (isStrict, locale) {
+        return locale.weekdaysShortRegex(isStrict);
+    });
+    addRegexToken('dddd',   function (isStrict, locale) {
+        return locale.weekdaysRegex(isStrict);
+    });
 
-  /**
-   * Comparison operator, test if the sub query does not match the
-   * item value
-   *
-   * @method NOT
-   * @param  {Object} item The item to match
-   * @return {Boolean} true if one match, false otherwise
-   */
-  ComplexQuery.prototype.NOT = function (item) {
-    return !this.query_list[0].match(item);
-  };
+    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+        var weekday = config._locale.weekdaysParse(input, token, config._strict);
+        // if we didn't get a weekday name, mark the date as invalid
+        if (weekday != null) {
+            week.d = weekday;
+        } else {
+            getParsingFlags(config).invalidWeekday = input;
+        }
+    });
 
-  /**
-   * Creates Query object from a search text string or a serialized version
-   * of a Query.
-   *
-   * @method create
-   * @static
-   * @param  {Object,String} object The search text or the serialized version
-   *         of a Query
-   * @return {Query} A Query object
-   */
-  QueryFactory.create = function (object, key_schema) {
-    if (object === "") {
-      return new Query();
-    }
-    if (typeof object === "string") {
-      object = parseStringToObject(object);
+    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+        week[token] = toInt(input);
+    });
+
+    // HELPERS
+
+    function parseWeekday(input, locale) {
+        if (typeof input !== 'string') {
+            return input;
+        }
+
+        if (!isNaN(input)) {
+            return parseInt(input, 10);
+        }
+
+        input = locale.weekdaysParse(input);
+        if (typeof input === 'number') {
+            return input;
+        }
+
+        return null;
     }
-    if (typeof (object || {}).type === "string" &&
-        query_class_dict[object.type]) {
-      return new query_class_dict[object.type](object, key_schema);
+
+    // LOCALES
+
+    var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+    function localeWeekdays (m, format) {
+        return isArray(this._weekdays) ? this._weekdays[m.day()] :
+            this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
     }
-    throw new TypeError("QueryFactory.create(): " +
-                        "Argument 1 is not a search text or a parsable object");
-  };
 
-  function objectToSearchText(query) {
-    var str_list = [];
-    if (query.type === "complex") {
-      str_list.push("(");
-      (query.query_list || []).forEach(function (sub_query) {
-        str_list.push(objectToSearchText(sub_query));
-        str_list.push(query.operator);
-      });
-      str_list.length -= 1;
-      str_list.push(")");
-      return str_list.join(" ");
+    var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+    function localeWeekdaysShort (m) {
+        return this._weekdaysShort[m.day()];
     }
-    if (query.type === "simple") {
-      return (query.key ? query.key + ": " : "") +
-        (query.operator || "") + ' "' + query.value + '"';
+
+    var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+    function localeWeekdaysMin (m) {
+        return this._weekdaysMin[m.day()];
     }
-    throw new TypeError("This object is not a query");
-  }
 
-  function checkKeySchema(key_schema) {
-    var prop;
+    function day_of_week__handleStrictParse(weekdayName, format, strict) {
+        var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._minWeekdaysParse = [];
 
-    if (key_schema !== undefined) {
-      if (typeof key_schema !== 'object') {
-        throw new TypeError("SimpleQuery().create(): " +
-                            "key_schema is not of type 'object'");
-      }
-      // key_set is mandatory
-      if (key_schema.key_set === undefined) {
-        throw new TypeError("SimpleQuery().create(): " +
-                            "key_schema has no 'key_set' property");
-      }
-      for (prop in key_schema) {
-        if (key_schema.hasOwnProperty(prop)) {
-          switch (prop) {
-          case 'key_set':
-          case 'cast_lookup':
-          case 'match_lookup':
-            break;
-          default:
-            throw new TypeError("SimpleQuery().create(): " +
-                               "key_schema has unknown property '" + prop + "'");
-          }
+            for (i = 0; i < 7; ++i) {
+                mom = create_utc__createUTC([2000, 1]).day(i);
+                this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
+                this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
+                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+            }
         }
-      }
-    }
-  }
 
-  /**
-   * The SimpleQuery inherits from Query, and compares one metadata value
-   *
-   * @class SimpleQuery
-   * @extends Query
-   * @param  {Object} [spec={}] The specifications
-   * @param  {String} [spec.operator="="] The compare method to use
-   * @param  {String} spec.key The metadata key
-   * @param  {String} spec.value The value of the metadata to compare
-   */
-  function SimpleQuery(spec, key_schema) {
-    Query.call(this);
+        if (strict) {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        }
+    }
 
-    checkKeySchema(key_schema);
+    function localeWeekdaysParse (weekdayName, format, strict) {
+        var i, mom, regex;
 
-    this._key_schema = key_schema || {};
+        if (this._weekdaysParseExact) {
+            return day_of_week__handleStrictParse.call(this, weekdayName, format, strict);
+        }
 
-    /**
-     * Operator to use to compare object values
-     *
-     * @attribute operator
-     * @type String
-     * @optional
-     */
-    this.operator = spec.operator;
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._minWeekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._fullWeekdaysParse = [];
+        }
 
-    /**
-     * Key of the object which refers to the value to compare
-     *
-     * @attribute key
-     * @type String
-     */
-    this.key = spec.key;
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
 
-    /**
-     * Value is used to do the comparison with the object value
-     *
-     * @attribute value
-     * @type String
-     */
-    this.value = spec.value;
+            mom = create_utc__createUTC([2000, 1]).day(i);
+            if (strict && !this._fullWeekdaysParse[i]) {
+                this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
+                this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
+                this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
+            }
+            if (!this._weekdaysParse[i]) {
+                regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+            }
+            // test the regex
+            if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
+                return i;
+            } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
+                return i;
+            } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
+                return i;
+            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+                return i;
+            }
+        }
+    }
 
-  }
-  inherits(SimpleQuery, Query);
+    // MOMENTS
 
-  SimpleQuery.prototype.type = "simple";
+    function getSetDayOfWeek (input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+        if (input != null) {
+            input = parseWeekday(input, this.localeData());
+            return this.add(input - day, 'd');
+        } else {
+            return day;
+        }
+    }
 
-  function checkKey(key) {
-    var prop;
+    function getSetLocaleDayOfWeek (input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+        return input == null ? weekday : this.add(input - weekday, 'd');
+    }
 
-    if (key.read_from === undefined) {
-      throw new TypeError("Custom key is missing the read_from property");
+    function getSetISODayOfWeek (input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        // behaves the same as moment#day except
+        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+        // as a setter, sunday should belong to the previous week.
+        return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
     }
 
-    for (prop in key) {
-      if (key.hasOwnProperty(prop)) {
-        switch (prop) {
-        case 'read_from':
-        case 'cast_to':
-        case 'equal_match':
-          break;
-        default:
-          throw new TypeError("Custom key has unknown property '" +
-                              prop + "'");
+    var defaultWeekdaysRegex = matchWord;
+    function weekdaysRegex (isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysStrictRegex;
+            } else {
+                return this._weekdaysRegex;
+            }
+        } else {
+            return this._weekdaysStrictRegex && isStrict ?
+                this._weekdaysStrictRegex : this._weekdaysRegex;
         }
-      }
     }
-  }
 
-  /**
-   * #crossLink "Query/match:method"
-   */
-  SimpleQuery.prototype.match = function (item) {
-    var object_value = null,
-      equal_match = null,
-      cast_to = null,
-      matchMethod = null,
-      operator = this.operator,
-      value = null,
-      key = this.key;
+    var defaultWeekdaysShortRegex = matchWord;
+    function weekdaysShortRegex (isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysShortStrictRegex;
+            } else {
+                return this._weekdaysShortRegex;
+            }
+        } else {
+            return this._weekdaysShortStrictRegex && isStrict ?
+                this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
+        }
+    }
 
-    if (!(regexp_comparaison.test(operator))) {
-      // `operator` is not correct, we have to change it to "like" or "="
-      if (regexp_percent.test(this.value)) {
-        // `value` contains a non escaped `%`
-        operator = "like";
-      } else {
-        // `value` does not contain non escaped `%`
-        operator = "=";
-      }
+    var defaultWeekdaysMinRegex = matchWord;
+    function weekdaysMinRegex (isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysMinStrictRegex;
+            } else {
+                return this._weekdaysMinRegex;
+            }
+        } else {
+            return this._weekdaysMinStrictRegex && isStrict ?
+                this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
+        }
     }
 
-    matchMethod = this[operator];
 
-    if (this._key_schema.key_set && this._key_schema.key_set[key] !== undefined) {
-      key = this._key_schema.key_set[key];
+    function computeWeekdaysParse () {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
+
+        var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
+            i, mom, minp, shortp, longp;
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+            mom = create_utc__createUTC([2000, 1]).day(i);
+            minp = this.weekdaysMin(mom, '');
+            shortp = this.weekdaysShort(mom, '');
+            longp = this.weekdays(mom, '');
+            minPieces.push(minp);
+            shortPieces.push(shortp);
+            longPieces.push(longp);
+            mixedPieces.push(minp);
+            mixedPieces.push(shortp);
+            mixedPieces.push(longp);
+        }
+        // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+        // will match the longer piece.
+        minPieces.sort(cmpLenRev);
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+        for (i = 0; i < 7; i++) {
+            shortPieces[i] = regexEscape(shortPieces[i]);
+            longPieces[i] = regexEscape(longPieces[i]);
+            mixedPieces[i] = regexEscape(mixedPieces[i]);
+        }
+
+        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._weekdaysShortRegex = this._weekdaysRegex;
+        this._weekdaysMinRegex = this._weekdaysRegex;
+
+        this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+        this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+        this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
     }
 
-    if (typeof key === 'object') {
-      checkKey(key);
-      object_value = item[key.read_from];
+    // FORMATTING
 
-      equal_match = key.equal_match;
+    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
 
-      // equal_match can be a string
-      if (typeof equal_match === 'string') {
-        // XXX raise error if equal_match not in match_lookup
-        equal_match = this._key_schema.match_lookup[equal_match];
-      }
+    // ALIASES
 
-      // equal_match overrides the default '=' operator
-      if (equal_match !== undefined) {
-        matchMethod = (operator === "=" || operator === "like" ?
-                       equal_match : matchMethod);
-      }
+    addUnitAlias('dayOfYear', 'DDD');
 
-      value = this.value;
-      cast_to = key.cast_to;
-      if (cast_to) {
-        // cast_to can be a string
-        if (typeof cast_to === 'string') {
-          // XXX raise error if cast_to not in cast_lookup
-          cast_to = this._key_schema.cast_lookup[cast_to];
-        }
+    // PARSING
 
-        try {
-          value = cast_to(value);
-        } catch (e) {
-          value = undefined;
-        }
+    addRegexToken('DDD',  match1to3);
+    addRegexToken('DDDD', match3);
+    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+        config._dayOfYear = toInt(input);
+    });
 
-        try {
-          object_value = cast_to(object_value);
-        } catch (e) {
-          object_value = undefined;
-        }
-      }
-    } else {
-      object_value = item[key];
-      value = this.value;
-    }
-    if (object_value === undefined || value === undefined) {
-      return false;
+    // HELPERS
+
+    // MOMENTS
+
+    function getSetDayOfYear (input) {
+        var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
+        return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
     }
-    return matchMethod(object_value, value);
-  };
 
-  /**
-   * #crossLink "Query/toString:method"
-   */
-  SimpleQuery.prototype.toString = function () {
-    return (this.key ? this.key + ":" : "") +
-      (this.operator ? " " + this.operator : "") + ' "' + this.value + '"';
-  };
+    // FORMATTING
 
-  /**
-   * #crossLink "Query/serialized:method"
-   */
-  SimpleQuery.prototype.serialized = function () {
-    var object = {
-      "type": "simple",
-      "key": this.key,
-      "value": this.value
-    };
-    if (this.operator !== undefined) {
-      object.operator = this.operator;
+    function hFormat() {
+        return this.hours() % 12 || 12;
     }
-    return object;
-  };
-  SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized;
 
-  /**
-   * Comparison operator, test if this query value matches the item value
-   *
-   * @method =
-   * @param  {String} object_value The value to compare
-   * @param  {String} comparison_value The comparison value
-   * @return {Boolean} true if match, false otherwise
-   */
-  SimpleQuery.prototype["="] = function (object_value, comparison_value) {
-    var value, i;
-    if (!Array.isArray(object_value)) {
-      object_value = [object_value];
+    function kFormat() {
+        return this.hours() || 24;
     }
-    for (i = 0; i < object_value.length; i += 1) {
-      value = object_value[i];
-      if (typeof value === 'object' && value.hasOwnProperty('content')) {
-        value = value.content;
-      }
-      if (typeof value.cmp === "function") {
-        return (value.cmp(comparison_value) === 0);
-      }
-      if (comparison_value.toString() === value.toString()) {
-        return true;
-      }
+
+    addFormatToken('H', ['HH', 2], 0, 'hour');
+    addFormatToken('h', ['hh', 2], 0, hFormat);
+    addFormatToken('k', ['kk', 2], 0, kFormat);
+
+    addFormatToken('hmm', 0, 0, function () {
+        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('hmmss', 0, 0, function () {
+        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2);
+    });
+
+    addFormatToken('Hmm', 0, 0, function () {
+        return '' + this.hours() + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('Hmmss', 0, 0, function () {
+        return '' + this.hours() + zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2);
+    });
+
+    function meridiem (token, lowercase) {
+        addFormatToken(token, 0, 0, function () {
+            return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
+        });
     }
-    return false;
-  };
 
-  /**
-   * Comparison operator, test if this query value matches the item value
-   *
-   * @method like
-   * @param  {String} object_value The value to compare
-   * @param  {String} comparison_value The comparison value
-   * @return {Boolean} true if match, false otherwise
-   */
-  SimpleQuery.prototype.like = function (object_value, comparison_value) {
-    var value, i;
-    if (!Array.isArray(object_value)) {
-      object_value = [object_value];
-    }
-    for (i = 0; i < object_value.length; i += 1) {
-      value = object_value[i];
-      if (typeof value === 'object' && value.hasOwnProperty('content')) {
-        value = value.content;
-      }
-      if (typeof value.cmp === "function") {
-        return (value.cmp(comparison_value) === 0);
-      }
-      if (
-        searchTextToRegExp(comparison_value.toString()).test(value.toString())
-      ) {
-        return true;
-      }
-    }
-    return false;
-  };
+    meridiem('a', true);
+    meridiem('A', false);
 
-  /**
-   * Comparison operator, test if this query value does not match the item value
-   *
-   * @method !=
-   * @param  {String} object_value The value to compare
-   * @param  {String} comparison_value The comparison value
-   * @return {Boolean} true if not match, false otherwise
-   */
-  SimpleQuery.prototype["!="] = function (object_value, comparison_value) {
-    var value, i;
-    if (!Array.isArray(object_value)) {
-      object_value = [object_value];
-    }
-    for (i = 0; i < object_value.length; i += 1) {
-      value = object_value[i];
-      if (typeof value === 'object' && value.hasOwnProperty('content')) {
-        value = value.content;
-      }
-      if (typeof value.cmp === "function") {
-        return (value.cmp(comparison_value) !== 0);
-      }
-      if (comparison_value.toString() === value.toString()) {
-        return false;
-      }
-    }
-    return true;
-  };
+    // ALIASES
 
-  /**
-   * Comparison operator, test if this query value is lower than the item value
-   *
-   * @method <
-   * @param  {Number, String} object_value The value to compare
-   * @param  {Number, String} comparison_value The comparison value
-   * @return {Boolean} true if lower, false otherwise
-   */
-  SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
-    var value;
-    if (!Array.isArray(object_value)) {
-      object_value = [object_value];
-    }
-    value = object_value[0];
-    if (typeof value === 'object' && value.hasOwnProperty('content')) {
-      value = value.content;
-    }
-    if (typeof value.cmp === "function") {
-      return (value.cmp(comparison_value) < 0);
-    }
-    return (value < comparison_value);
-  };
+    addUnitAlias('hour', 'h');
 
-  /**
-   * Comparison operator, test if this query value is equal or lower than the
-   * item value
-   *
-   * @method <=
-   * @param  {Number, String} object_value The value to compare
-   * @param  {Number, String} comparison_value The comparison value
-   * @return {Boolean} true if equal or lower, false otherwise
-   */
-  SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
-    var value;
-    if (!Array.isArray(object_value)) {
-      object_value = [object_value];
-    }
-    value = object_value[0];
-    if (typeof value === 'object' && value.hasOwnProperty('content')) {
-      value = value.content;
-    }
-    if (typeof value.cmp === "function") {
-      return (value.cmp(comparison_value) <= 0);
-    }
-    return (value <= comparison_value);
-  };
+    // PARSING
 
-  /**
-   * Comparison operator, test if this query value is greater than the item
-   * value
-   *
-   * @method >
-   * @param  {Number, String} object_value The value to compare
-   * @param  {Number, String} comparison_value The comparison value
-   * @return {Boolean} true if greater, false otherwise
-   */
-  SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
-    var value;
-    if (!Array.isArray(object_value)) {
-      object_value = [object_value];
+    function matchMeridiem (isStrict, locale) {
+        return locale._meridiemParse;
     }
-    value = object_value[0];
-    if (typeof value === 'object' && value.hasOwnProperty('content')) {
-      value = value.content;
-    }
-    if (typeof value.cmp === "function") {
-      return (value.cmp(comparison_value) > 0);
-    }
-    return (value > comparison_value);
-  };
 
-  /**
-   * Comparison operator, test if this query value is equal or greater than the
-   * item value
-   *
-   * @method >=
-   * @param  {Number, String} object_value The value to compare
-   * @param  {Number, String} comparison_value The comparison value
-   * @return {Boolean} true if equal or greater, false otherwise
-   */
-  SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
-    var value;
-    if (!Array.isArray(object_value)) {
-      object_value = [object_value];
-    }
-    value = object_value[0];
-    if (typeof value === 'object' && value.hasOwnProperty('content')) {
-      value = value.content;
+    addRegexToken('a',  matchMeridiem);
+    addRegexToken('A',  matchMeridiem);
+    addRegexToken('H',  match1to2);
+    addRegexToken('h',  match1to2);
+    addRegexToken('HH', match1to2, match2);
+    addRegexToken('hh', match1to2, match2);
+
+    addRegexToken('hmm', match3to4);
+    addRegexToken('hmmss', match5to6);
+    addRegexToken('Hmm', match3to4);
+    addRegexToken('Hmmss', match5to6);
+
+    addParseToken(['H', 'HH'], HOUR);
+    addParseToken(['a', 'A'], function (input, array, config) {
+        config._isPm = config._locale.isPM(input);
+        config._meridiem = input;
+    });
+    addParseToken(['h', 'hh'], function (input, array, config) {
+        array[HOUR] = toInt(input);
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmmss', function (input, array, config) {
+        var pos1 = input.length - 4;
+        var pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('Hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+    });
+    addParseToken('Hmmss', function (input, array, config) {
+        var pos1 = input.length - 4;
+        var pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+    });
+
+    // LOCALES
+
+    function localeIsPM (input) {
+        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+        // Using charAt should be more compatible.
+        return ((input + '').toLowerCase().charAt(0) === 'p');
     }
-    if (typeof value.cmp === "function") {
-      return (value.cmp(comparison_value) >= 0);
+
+    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
+    function localeMeridiem (hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'pm' : 'PM';
+        } else {
+            return isLower ? 'am' : 'AM';
+        }
     }
-    return (value >= comparison_value);
-  };
 
-  query_class_dict.simple = SimpleQuery;
-  query_class_dict.complex = ComplexQuery;
 
-  Query.parseStringToObject = parseStringToObject;
-  Query.objectToSearchText = objectToSearchText;
+    // MOMENTS
 
-  window.Query = Query;
-  window.SimpleQuery = SimpleQuery;
-  window.ComplexQuery = ComplexQuery;
-  window.QueryFactory = QueryFactory;
+    // Setting the hour should keep the time, because the user explicitly
+    // specified which hour he wants. So trying to maintain the same hour (in
+    // a new timezone) makes sense. Adding/subtracting hours does not follow
+    // this rule.
+    var getSetHour = makeGetSet('Hours', true);
 
-}(RSVP, window, parseStringToObject));
-;/*global window, moment */
-/*jslint nomen: true, maxlen: 200*/
-(function (window, moment) {
-  "use strict";
+    // FORMATTING
 
-//   /**
-//    * Add a secured (write permission denied) property to an object.
-//    *
-//    * @param  {Object} object The object to fill
-//    * @param  {String} key The object key where to store the property
-//    * @param  {Any} value The value to store
-//    */
-//   function _export(key, value) {
-//     Object.defineProperty(to_export, key, {
-//       "configurable": false,
-//       "enumerable": true,
-//       "writable": false,
-//       "value": value
-//     });
-//   }
+    addFormatToken('m', ['mm', 2], 0, 'minute');
 
-  var YEAR = 'year',
-    MONTH = 'month',
-    DAY = 'day',
-    HOUR = 'hour',
-    MIN = 'minute',
-    SEC = 'second',
-    MSEC = 'millisecond',
-    precision_grade = {
-      'year': 0,
-      'month': 1,
-      'day': 2,
-      'hour': 3,
-      'minute': 4,
-      'second': 5,
-      'millisecond': 6
-    },
-    lesserPrecision = function (p1, p2) {
-      return (precision_grade[p1] < precision_grade[p2]) ? p1 : p2;
-    },
-    JIODate;
+    // ALIASES
 
+    addUnitAlias('minute', 'm');
 
-  JIODate = function (str) {
-    // in case of forgotten 'new'
-    if (!(this instanceof JIODate)) {
-      return new JIODate(str);
-    }
+    // PARSING
 
-    if (str instanceof JIODate) {
-      this.mom = str.mom.clone();
-      this._precision = str._precision;
-      return;
-    }
+    addRegexToken('m',  match1to2);
+    addRegexToken('mm', match1to2, match2);
+    addParseToken(['m', 'mm'], MINUTE);
 
-    if (str === undefined) {
-      this.mom = moment();
-      this.setPrecision(MSEC);
-      return;
-    }
+    // MOMENTS
 
-    this.mom = null;
-    this._str = str;
+    var getSetMinute = makeGetSet('Minutes', false);
 
-    // http://www.w3.org/TR/NOTE-datetime
-    // http://dotat.at/tmp/ISO_8601-2004_E.pdf
+    // FORMATTING
 
-    // XXX these regexps fail to detect many invalid dates.
+    addFormatToken('s', ['ss', 2], 0, 'second');
 
-    if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+\-][0-2]\d:[0-5]\d|Z)/)
-          || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d/)) {
-      // ISO, milliseconds
-      this.mom = moment(str);
-      this.setPrecision(MSEC);
-    } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/)
-          || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/)) {
-      // ISO, seconds
-      this.mom = moment(str);
-      this.setPrecision(SEC);
-    } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/)
-          || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d/)) {
-      // ISO, minutes
-      this.mom = moment(str);
-      this.setPrecision(MIN);
-    } else if (str.match(/\d\d\d\d-\d\d-\d\d \d\d/)) {
-      this.mom = moment(str);
-      this.setPrecision(HOUR);
-    } else if (str.match(/\d\d\d\d-\d\d-\d\d/)) {
-      this.mom = moment(str);
-      this.setPrecision(DAY);
-    } else if (str.match(/\d\d\d\d-\d\d/)) {
-      this.mom = moment(str);
-      this.setPrecision(MONTH);
-    } else if (str.match(/\d\d\d\d/)) {
-      this.mom = moment(str);
-      this.setPrecision(YEAR);
-    }
+    // ALIASES
 
-    if (!this.mom) {
-      throw new Error("Cannot parse: " + str);
-    }
+    addUnitAlias('second', 's');
 
-  };
+    // PARSING
 
+    addRegexToken('s',  match1to2);
+    addRegexToken('ss', match1to2, match2);
+    addParseToken(['s', 'ss'], SECOND);
 
-  JIODate.prototype.setPrecision = function (prec) {
-    this._precision = prec;
-  };
+    // MOMENTS
 
+    var getSetSecond = makeGetSet('Seconds', false);
 
-  JIODate.prototype.getPrecision = function () {
-    return this._precision;
-  };
+    // FORMATTING
 
+    addFormatToken('S', 0, 0, function () {
+        return ~~(this.millisecond() / 100);
+    });
 
-  JIODate.prototype.cmp = function (other) {
-    var m1 = this.mom,
-      m2 = other.mom,
-      p = lesserPrecision(this._precision, other._precision);
-    return m1.isBefore(m2, p) ? -1 : (m1.isSame(m2, p) ? 0 : +1);
-  };
+    addFormatToken(0, ['SS', 2], 0, function () {
+        return ~~(this.millisecond() / 10);
+    });
 
+    addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+    addFormatToken(0, ['SSSS', 4], 0, function () {
+        return this.millisecond() * 10;
+    });
+    addFormatToken(0, ['SSSSS', 5], 0, function () {
+        return this.millisecond() * 100;
+    });
+    addFormatToken(0, ['SSSSSS', 6], 0, function () {
+        return this.millisecond() * 1000;
+    });
+    addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+        return this.millisecond() * 10000;
+    });
+    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+        return this.millisecond() * 100000;
+    });
+    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+        return this.millisecond() * 1000000;
+    });
 
-  JIODate.prototype.toPrecisionString = function (precision) {
-    var fmt;
 
-    precision = precision || this._precision;
+    // ALIASES
 
-    fmt = {
-      'millisecond': 'YYYY-MM-DD HH:mm:ss.SSS',
-      'second': 'YYYY-MM-DD HH:mm:ss',
-      'minute': 'YYYY-MM-DD HH:mm',
-      'hour': 'YYYY-MM-DD HH',
-      'day': 'YYYY-MM-DD',
-      'month': 'YYYY-MM',
-      'year': 'YYYY'
-    }[precision];
+    addUnitAlias('millisecond', 'ms');
 
-    if (!fmt) {
-      throw new TypeError("Unsupported precision value '" + precision + "'");
+    // PARSING
+
+    addRegexToken('S',    match1to3, match1);
+    addRegexToken('SS',   match1to3, match2);
+    addRegexToken('SSS',  match1to3, match3);
+
+    var token;
+    for (token = 'SSSS'; token.length <= 9; token += 'S') {
+        addRegexToken(token, matchUnsigned);
     }
 
-    return this.mom.format(fmt);
-  };
+    function parseMs(input, array) {
+        array[MILLISECOND] = toInt(('0.' + input) * 1000);
+    }
 
+    for (token = 'S'; token.length <= 9; token += 'S') {
+        addParseToken(token, parseMs);
+    }
+    // MOMENTS
 
-  JIODate.prototype.toString = function () {
-    return this._str;
-  };
+    var getSetMillisecond = makeGetSet('Milliseconds', false);
 
+    // FORMATTING
 
-//   _export('JIODate', JIODate);
-// 
-//   _export('YEAR', YEAR);
-//   _export('MONTH', MONTH);
-//   _export('DAY', DAY);
-//   _export('HOUR', HOUR);
-//   _export('MIN', MIN);
-//   _export('SEC', SEC);
-//   _export('MSEC', MSEC);
+    addFormatToken('z',  0, 0, 'zoneAbbr');
+    addFormatToken('zz', 0, 0, 'zoneName');
+
+    // MOMENTS
+
+    function getZoneAbbr () {
+        return this._isUTC ? 'UTC' : '';
+    }
+
+    function getZoneName () {
+        return this._isUTC ? 'Coordinated Universal Time' : '';
+    }
+
+    var momentPrototype__proto = Moment.prototype;
+
+    momentPrototype__proto.add               = add_subtract__add;
+    momentPrototype__proto.calendar          = moment_calendar__calendar;
+    momentPrototype__proto.clone             = clone;
+    momentPrototype__proto.diff              = diff;
+    momentPrototype__proto.endOf             = endOf;
+    momentPrototype__proto.format            = format;
+    momentPrototype__proto.from              = from;
+    momentPrototype__proto.fromNow           = fromNow;
+    momentPrototype__proto.to                = to;
+    momentPrototype__proto.toNow             = toNow;
+    momentPrototype__proto.get               = getSet;
+    momentPrototype__proto.invalidAt         = invalidAt;
+    momentPrototype__proto.isAfter           = isAfter;
+    momentPrototype__proto.isBefore          = isBefore;
+    momentPrototype__proto.isBetween         = isBetween;
+    momentPrototype__proto.isSame            = isSame;
+    momentPrototype__proto.isSameOrAfter     = isSameOrAfter;
+    momentPrototype__proto.isSameOrBefore    = isSameOrBefore;
+    momentPrototype__proto.isValid           = moment_valid__isValid;
+    momentPrototype__proto.lang              = lang;
+    momentPrototype__proto.locale            = locale;
+    momentPrototype__proto.localeData        = localeData;
+    momentPrototype__proto.max               = prototypeMax;
+    momentPrototype__proto.min               = prototypeMin;
+    momentPrototype__proto.parsingFlags      = parsingFlags;
+    momentPrototype__proto.set               = getSet;
+    momentPrototype__proto.startOf           = startOf;
+    momentPrototype__proto.subtract          = add_subtract__subtract;
+    momentPrototype__proto.toArray           = toArray;
+    momentPrototype__proto.toObject          = toObject;
+    momentPrototype__proto.toDate            = toDate;
+    momentPrototype__proto.toISOString       = moment_format__toISOString;
+    momentPrototype__proto.toJSON            = toJSON;
+    momentPrototype__proto.toString          = toString;
+    momentPrototype__proto.unix              = unix;
+    momentPrototype__proto.valueOf           = to_type__valueOf;
+    momentPrototype__proto.creationData      = creationData;
 
-  window.jiodate = {
-    JIODate: JIODate,
-    YEAR: YEAR,
-    MONTH: MONTH,
-    DAY: DAY,
-    HOUR: HOUR,
-    MIN: MIN,
-    SEC: SEC,
-    MSEC: MSEC
-  };
-}(window, moment));
-;/*global window, RSVP, Blob, XMLHttpRequest, QueryFactory, Query, atob,
-  FileReader, ArrayBuffer, Uint8Array */
-(function (window, RSVP, Blob, QueryFactory, Query, atob,
-           FileReader, ArrayBuffer, Uint8Array) {
-  "use strict";
+    // Year
+    momentPrototype__proto.year       = getSetYear;
+    momentPrototype__proto.isLeapYear = getIsLeapYear;
 
-  var util = {},
-    jIO;
+    // Week Year
+    momentPrototype__proto.weekYear    = getSetWeekYear;
+    momentPrototype__proto.isoWeekYear = getSetISOWeekYear;
 
-  function jIOError(message, status_code) {
-    if ((message !== undefined) && (typeof message !== "string")) {
-      throw new TypeError('You must pass a string.');
+    // Quarter
+    momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter;
+
+    // Month
+    momentPrototype__proto.month       = getSetMonth;
+    momentPrototype__proto.daysInMonth = getDaysInMonth;
+
+    // Week
+    momentPrototype__proto.week           = momentPrototype__proto.weeks        = getSetWeek;
+    momentPrototype__proto.isoWeek        = momentPrototype__proto.isoWeeks     = getSetISOWeek;
+    momentPrototype__proto.weeksInYear    = getWeeksInYear;
+    momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear;
+
+    // Day
+    momentPrototype__proto.date       = getSetDayOfMonth;
+    momentPrototype__proto.day        = momentPrototype__proto.days             = getSetDayOfWeek;
+    momentPrototype__proto.weekday    = getSetLocaleDayOfWeek;
+    momentPrototype__proto.isoWeekday = getSetISODayOfWeek;
+    momentPrototype__proto.dayOfYear  = getSetDayOfYear;
+
+    // Hour
+    momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour;
+
+    // Minute
+    momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute;
+
+    // Second
+    momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond;
+
+    // Millisecond
+    momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond;
+
+    // Offset
+    momentPrototype__proto.utcOffset            = getSetOffset;
+    momentPrototype__proto.utc                  = setOffsetToUTC;
+    momentPrototype__proto.local                = setOffsetToLocal;
+    momentPrototype__proto.parseZone            = setOffsetToParsedOffset;
+    momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset;
+    momentPrototype__proto.isDST                = isDaylightSavingTime;
+    momentPrototype__proto.isDSTShifted         = isDaylightSavingTimeShifted;
+    momentPrototype__proto.isLocal              = isLocal;
+    momentPrototype__proto.isUtcOffset          = isUtcOffset;
+    momentPrototype__proto.isUtc                = isUtc;
+    momentPrototype__proto.isUTC                = isUtc;
+
+    // Timezone
+    momentPrototype__proto.zoneAbbr = getZoneAbbr;
+    momentPrototype__proto.zoneName = getZoneName;
+
+    // Deprecations
+    momentPrototype__proto.dates  = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
+    momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
+    momentPrototype__proto.years  = deprecate('years accessor is deprecated. Use year instead', getSetYear);
+    momentPrototype__proto.zone   = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone);
+
+    var momentPrototype = momentPrototype__proto;
+
+    function moment__createUnix (input) {
+        return local__createLocal(input * 1000);
+    }
+
+    function moment__createInZone () {
+        return local__createLocal.apply(null, arguments).parseZone();
+    }
+
+    var defaultCalendar = {
+        sameDay : '[Today at] LT',
+        nextDay : '[Tomorrow at] LT',
+        nextWeek : 'dddd [at] LT',
+        lastDay : '[Yesterday at] LT',
+        lastWeek : '[Last] dddd [at] LT',
+        sameElse : 'L'
+    };
+
+    function locale_calendar__calendar (key, mom, now) {
+        var output = this._calendar[key];
+        return isFunction(output) ? output.call(mom, now) : output;
     }
-    this.message = message || "Default Message";
-    this.status_code = status_code || 500;
-  }
-  jIOError.prototype = new Error();
-  jIOError.prototype.constructor = jIOError;
-  util.jIOError = jIOError;
 
-  /**
-   * Send request with XHR and return a promise. xhr.onload: The promise is
-   * resolved when the status code is lower than 400 with the xhr object as
-   * first parameter. xhr.onerror: reject with xhr object as first
-   * parameter. xhr.onprogress: notifies the xhr object.
-   *
-   * @param  {Object} param The parameters
-   * @param  {String} [param.type="GET"] The request method
-   * @param  {String} [param.dataType=""] The data type to retrieve
-   * @param  {String} param.url The url
-   * @param  {Any} [param.data] The data to send
-   * @param  {Function} [param.beforeSend] A function called just before the
-   *    send request. The first parameter of this function is the XHR object.
-   * @return {Promise} The promise
-   */
-  function ajax(param) {
-    var xhr = new XMLHttpRequest();
-    return new RSVP.Promise(function (resolve, reject, notify) {
-      var k;
-      xhr.open(param.type || "GET", param.url, true);
-      xhr.responseType = param.dataType || "";
-      if (typeof param.headers === 'object' && param.headers !== null) {
-        for (k in param.headers) {
-          if (param.headers.hasOwnProperty(k)) {
-            xhr.setRequestHeader(k, param.headers[k]);
-          }
-        }
-      }
-      xhr.addEventListener("load", function (e) {
-        if (e.target.status >= 400) {
-          return reject(e);
-        }
-        resolve(e);
-      });
-      xhr.addEventListener("error", reject);
-      xhr.addEventListener("progress", notify);
-      if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
-        for (k in param.xhrFields) {
-          if (param.xhrFields.hasOwnProperty(k)) {
-            xhr[k] = param.xhrFields[k];
-          }
-        }
-      }
-      if (typeof param.beforeSend === 'function') {
-        param.beforeSend(xhr);
-      }
-      xhr.send(param.data);
-    }, function () {
-      xhr.abort();
-    });
-  }
-  util.ajax = ajax;
+    var defaultLongDateFormat = {
+        LTS  : 'h:mm:ss A',
+        LT   : 'h:mm A',
+        L    : 'MM/DD/YYYY',
+        LL   : 'MMMM D, YYYY',
+        LLL  : 'MMMM D, YYYY h:mm A',
+        LLLL : 'dddd, MMMM D, YYYY h:mm A'
+    };
 
-  function readBlobAsText(blob, encoding) {
-    var fr = new FileReader();
-    return new RSVP.Promise(function (resolve, reject, notify) {
-      fr.addEventListener("load", resolve);
-      fr.addEventListener("error", reject);
-      fr.addEventListener("progress", notify);
-      fr.readAsText(blob, encoding);
-    }, function () {
-      fr.abort();
-    });
-  }
-  util.readBlobAsText = readBlobAsText;
+    function longDateFormat (key) {
+        var format = this._longDateFormat[key],
+            formatUpper = this._longDateFormat[key.toUpperCase()];
 
-  function readBlobAsArrayBuffer(blob) {
-    var fr = new FileReader();
-    return new RSVP.Promise(function (resolve, reject, notify) {
-      fr.addEventListener("load", resolve);
-      fr.addEventListener("error", reject);
-      fr.addEventListener("progress", notify);
-      fr.readAsArrayBuffer(blob);
-    }, function () {
-      fr.abort();
-    });
-  }
-  util.readBlobAsArrayBuffer = readBlobAsArrayBuffer;
+        if (format || !formatUpper) {
+            return format;
+        }
 
-  function readBlobAsDataURL(blob) {
-    var fr = new FileReader();
-    return new RSVP.Promise(function (resolve, reject, notify) {
-      fr.addEventListener("load", resolve);
-      fr.addEventListener("error", reject);
-      fr.addEventListener("progress", notify);
-      fr.readAsDataURL(blob);
-    }, function () {
-      fr.abort();
-    });
-  }
-  util.readBlobAsDataURL = readBlobAsDataURL;
+        this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
+            return val.slice(1);
+        });
 
-  // https://gist.github.com/davoclavo/4424731
-  function dataURItoBlob(dataURI) {
-    // convert base64 to raw binary data held in a string
-    var byteString = atob(dataURI.split(',')[1]),
-    // separate out the mime component
-      mimeString = dataURI.split(',')[0].split(':')[1],
-    // write the bytes of the string to an ArrayBuffer
-      arrayBuffer = new ArrayBuffer(byteString.length),
-      _ia = new Uint8Array(arrayBuffer),
-      i;
-    mimeString = mimeString.slice(0, mimeString.length - ";base64".length);
-    for (i = 0; i < byteString.length; i += 1) {
-      _ia[i] = byteString.charCodeAt(i);
+        return this._longDateFormat[key];
     }
-    return new Blob([arrayBuffer], {type: mimeString});
-  }
 
-  util.dataURItoBlob = dataURItoBlob;
+    var defaultInvalidDate = 'Invalid date';
 
-  // tools
-  function checkId(argument_list, storage, method_name) {
-    if (typeof argument_list[0] !== 'string' || argument_list[0] === '') {
-      throw new jIO.util.jIOError(
-        "Document id must be a non empty string on '" + storage.__type +
-          "." + method_name + "'.",
-        400
-      );
+    function invalidDate () {
+        return this._invalidDate;
     }
-  }
 
-  function checkAttachmentId(argument_list, storage, method_name) {
-    if (typeof argument_list[1] !== 'string' || argument_list[1] === '') {
-      throw new jIO.util.jIOError(
-        "Attachment id must be a non empty string on '" + storage.__type +
-          "." + method_name + "'.",
-        400
-      );
+    var defaultOrdinal = '%d';
+    var defaultOrdinalParse = /\d{1,2}/;
+
+    function ordinal (number) {
+        return this._ordinal.replace('%d', number);
     }
-  }
 
-  function declareMethod(klass, name, precondition_function, post_function) {
-    klass.prototype[name] = function () {
-      var argument_list = arguments,
-        context = this,
-        precondition_result;
+    function preParsePostFormat (string) {
+        return string;
+    }
 
-      return new RSVP.Queue()
-        .push(function () {
-          if (precondition_function !== undefined) {
-            return precondition_function.apply(
-              context.__storage,
-              [argument_list, context, name]
-            );
-          }
-        })
-        .push(function (result) {
-          var storage_method = context.__storage[name];
-          precondition_result = result;
-          if (storage_method === undefined) {
-            throw new jIO.util.jIOError(
-              "Capacity '" + name + "' is not implemented on '" +
-                context.__type + "'",
-              501
-            );
-          }
-          return storage_method.apply(
-            context.__storage,
-            argument_list
-          );
-        })
-        .push(function (result) {
-          if (post_function !== undefined) {
-            return post_function.call(
-              context,
-              argument_list,
-              result,
-              precondition_result
-            );
-          }
-          return result;
-        });
+    var defaultRelativeTime = {
+        future : 'in %s',
+        past   : '%s ago',
+        s  : 'a few seconds',
+        m  : 'a minute',
+        mm : '%d minutes',
+        h  : 'an hour',
+        hh : '%d hours',
+        d  : 'a day',
+        dd : '%d days',
+        M  : 'a month',
+        MM : '%d months',
+        y  : 'a year',
+        yy : '%d years'
     };
-    // Allow chain
-    return this;
-  }
 
+    function relative__relativeTime (number, withoutSuffix, string, isFuture) {
+        var output = this._relativeTime[string];
+        return (isFunction(output)) ?
+            output(number, withoutSuffix, string, isFuture) :
+            output.replace(/%d/i, number);
+    }
+
+    function pastFuture (diff, output) {
+        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+    }
+
+    var prototype__proto = Locale.prototype;
+
+    prototype__proto._calendar       = defaultCalendar;
+    prototype__proto.calendar        = locale_calendar__calendar;
+    prototype__proto._longDateFormat = defaultLongDateFormat;
+    prototype__proto.longDateFormat  = longDateFormat;
+    prototype__proto._invalidDate    = defaultInvalidDate;
+    prototype__proto.invalidDate     = invalidDate;
+    prototype__proto._ordinal        = defaultOrdinal;
+    prototype__proto.ordinal         = ordinal;
+    prototype__proto._ordinalParse   = defaultOrdinalParse;
+    prototype__proto.preparse        = preParsePostFormat;
+    prototype__proto.postformat      = preParsePostFormat;
+    prototype__proto._relativeTime   = defaultRelativeTime;
+    prototype__proto.relativeTime    = relative__relativeTime;
+    prototype__proto.pastFuture      = pastFuture;
+    prototype__proto.set             = locale_set__set;
+
+    // Month
+    prototype__proto.months            =        localeMonths;
+    prototype__proto._months           = defaultLocaleMonths;
+    prototype__proto.monthsShort       =        localeMonthsShort;
+    prototype__proto._monthsShort      = defaultLocaleMonthsShort;
+    prototype__proto.monthsParse       =        localeMonthsParse;
+    prototype__proto._monthsRegex      = defaultMonthsRegex;
+    prototype__proto.monthsRegex       = monthsRegex;
+    prototype__proto._monthsShortRegex = defaultMonthsShortRegex;
+    prototype__proto.monthsShortRegex  = monthsShortRegex;
+
+    // Week
+    prototype__proto.week = localeWeek;
+    prototype__proto._week = defaultLocaleWeek;
+    prototype__proto.firstDayOfYear = localeFirstDayOfYear;
+    prototype__proto.firstDayOfWeek = localeFirstDayOfWeek;
+
+    // Day of Week
+    prototype__proto.weekdays       =        localeWeekdays;
+    prototype__proto._weekdays      = defaultLocaleWeekdays;
+    prototype__proto.weekdaysMin    =        localeWeekdaysMin;
+    prototype__proto._weekdaysMin   = defaultLocaleWeekdaysMin;
+    prototype__proto.weekdaysShort  =        localeWeekdaysShort;
+    prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort;
+    prototype__proto.weekdaysParse  =        localeWeekdaysParse;
+
+    prototype__proto._weekdaysRegex      = defaultWeekdaysRegex;
+    prototype__proto.weekdaysRegex       =        weekdaysRegex;
+    prototype__proto._weekdaysShortRegex = defaultWeekdaysShortRegex;
+    prototype__proto.weekdaysShortRegex  =        weekdaysShortRegex;
+    prototype__proto._weekdaysMinRegex   = defaultWeekdaysMinRegex;
+    prototype__proto.weekdaysMinRegex    =        weekdaysMinRegex;
+
+    // Hours
+    prototype__proto.isPM = localeIsPM;
+    prototype__proto._meridiemParse = defaultLocaleMeridiemParse;
+    prototype__proto.meridiem = localeMeridiem;
+
+    function lists__get (format, index, field, setter) {
+        var locale = locale_locales__getLocale();
+        var utc = create_utc__createUTC().set(setter, index);
+        return locale[field](utc, format);
+    }
+
+    function listMonthsImpl (format, index, field) {
+        if (typeof format === 'number') {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+
+        if (index != null) {
+            return lists__get(format, index, field, 'month');
+        }
+
+        var i;
+        var out = [];
+        for (i = 0; i < 12; i++) {
+            out[i] = lists__get(format, i, field, 'month');
+        }
+        return out;
+    }
+
+    // ()
+    // (5)
+    // (fmt, 5)
+    // (fmt)
+    // (true)
+    // (true, 5)
+    // (true, fmt, 5)
+    // (true, fmt)
+    function listWeekdaysImpl (localeSorted, format, index, field) {
+        if (typeof localeSorted === 'boolean') {
+            if (typeof format === 'number') {
+                index = format;
+                format = undefined;
+            }
 
+            format = format || '';
+        } else {
+            format = localeSorted;
+            index = format;
+            localeSorted = false;
 
+            if (typeof format === 'number') {
+                index = format;
+                format = undefined;
+            }
 
-  /////////////////////////////////////////////////////////////////
-  // jIO Storage Proxy
-  /////////////////////////////////////////////////////////////////
-  function JioProxyStorage(type, storage) {
-    if (!(this instanceof JioProxyStorage)) {
-      return new JioProxyStorage();
-    }
-    this.__type = type;
-    this.__storage = storage;
-  }
+            format = format || '';
+        }
 
-  declareMethod(JioProxyStorage, "put", checkId, function (argument_list) {
-    return argument_list[0];
-  });
-  declareMethod(JioProxyStorage, "get", checkId);
-  declareMethod(JioProxyStorage, "bulk");
-  declareMethod(JioProxyStorage, "remove", checkId, function (argument_list) {
-    return argument_list[0];
-  });
+        var locale = locale_locales__getLocale(),
+            shift = localeSorted ? locale._week.dow : 0;
 
-  JioProxyStorage.prototype.post = function () {
-    var context = this,
-      argument_list = arguments;
-    return new RSVP.Queue()
-      .push(function () {
-        var storage_method = context.__storage.post;
-        if (storage_method === undefined) {
-          throw new jIO.util.jIOError(
-            "Capacity 'post' is not implemented on '" + context.__type + "'",
-            501
-          );
+        if (index != null) {
+            return lists__get(format, (index + shift) % 7, field, 'day');
         }
-        return context.__storage.post.apply(context.__storage, argument_list);
-      });
-  };
-
-  declareMethod(JioProxyStorage, 'putAttachment', function (argument_list,
-                                                            storage,
-                                                            method_name) {
-    checkId(argument_list, storage, method_name);
-    checkAttachmentId(argument_list, storage, method_name);
 
-    var options = argument_list[3] || {};
+        var i;
+        var out = [];
+        for (i = 0; i < 7; i++) {
+            out[i] = lists__get(format, (i + shift) % 7, field, 'day');
+        }
+        return out;
+    }
 
-    if (typeof argument_list[2] === 'string') {
-      argument_list[2] = new Blob([argument_list[2]], {
-        "type": options._content_type || options._mimetype ||
-                "text/plain;charset=utf-8"
-      });
-    } else if (!(argument_list[2] instanceof Blob)) {
-      throw new jIO.util.jIOError(
-        'Attachment content is not a blob',
-        400
-      );
+    function lists__listMonths (format, index) {
+        return listMonthsImpl(format, index, 'months');
     }
-  });
 
-  declareMethod(JioProxyStorage, 'removeAttachment', function (argument_list,
-                                                               storage,
-                                                               method_name) {
-    checkId(argument_list, storage, method_name);
-    checkAttachmentId(argument_list, storage, method_name);
-  });
+    function lists__listMonthsShort (format, index) {
+        return listMonthsImpl(format, index, 'monthsShort');
+    }
 
-  declareMethod(JioProxyStorage, 'getAttachment', function (argument_list,
-                                                            storage,
-                                                            method_name) {
-    var result = "blob";
-//     if (param.storage_spec.type !== "indexeddb" &&
-//         param.storage_spec.type !== "dav" &&
-//         (param.kwargs._start !== undefined
-//          || param.kwargs._end !== undefined)) {
-//       restCommandRejecter(param, [
-//         'bad_request',
-//         'unsupport',
-//         '_start, _end not support'
-//       ]);
-//       return false;
-//     }
-    checkId(argument_list, storage, method_name);
-    checkAttachmentId(argument_list, storage, method_name);
-    // Drop optional parameters, which are only used in postfunction
-    if (argument_list[2] !== undefined) {
-      result = argument_list[2].format || result;
-      delete argument_list[2].format;
+    function lists__listWeekdays (localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
     }
-    return result;
-  }, function (argument_list, blob, convert) {
-    var result;
-    if (!(blob instanceof Blob)) {
-      throw new jIO.util.jIOError(
-        "'getAttachment' (" + argument_list[0] + " , " +
-          argument_list[1] + ") on '" + this.__type +
-          "' does not return a Blob.",
-        501
-      );
+
+    function lists__listWeekdaysShort (localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
     }
-    if (convert === "blob") {
-      result = blob;
-    } else if (convert === "data_url") {
-      result = new RSVP.Queue()
-        .push(function () {
-          return jIO.util.readBlobAsDataURL(blob);
-        })
-        .push(function (evt) {
-          return evt.target.result;
-        });
-    } else if (convert === "array_buffer") {
-      result = new RSVP.Queue()
-        .push(function () {
-          return jIO.util.readBlobAsArrayBuffer(blob);
-        })
-        .push(function (evt) {
-          return evt.target.result;
-        });
-    } else if (convert === "text") {
-      result = new RSVP.Queue()
-        .push(function () {
-          return jIO.util.readBlobAsText(blob);
-        })
-        .push(function (evt) {
-          return evt.target.result;
-        });
-    } else if (convert === "json") {
-      result = new RSVP.Queue()
-        .push(function () {
-          return jIO.util.readBlobAsText(blob);
-        })
-        .push(function (evt) {
-          return JSON.parse(evt.target.result);
-        });
-    } else {
-      throw new jIO.util.jIOError(
-        this.__type + ".getAttachment format: '" + convert +
-          "' is not supported",
-        400
-      );
+
+    function lists__listWeekdaysMin (localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
     }
-    return result;
-  });
 
-  JioProxyStorage.prototype.buildQuery = function () {
-    var storage_method = this.__storage.buildQuery,
-      context = this,
-      argument_list = arguments;
-    if (storage_method === undefined) {
-      throw new jIO.util.jIOError(
-        "Capacity 'buildQuery' is not implemented on '" + this.__type + "'",
-        501
-      );
+    locale_locales__getSetGlobalLocale('en', {
+        ordinalParse: /\d{1,2}(th|st|nd|rd)/,
+        ordinal : function (number) {
+            var b = number % 10,
+                output = (toInt(number % 100 / 10) === 1) ? 'th' :
+                (b === 1) ? 'st' :
+                (b === 2) ? 'nd' :
+                (b === 3) ? 'rd' : 'th';
+            return number + output;
+        }
+    });
+
+    // Side effect imports
+    utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale);
+    utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale);
+
+    var mathAbs = Math.abs;
+
+    function duration_abs__abs () {
+        var data           = this._data;
+
+        this._milliseconds = mathAbs(this._milliseconds);
+        this._days         = mathAbs(this._days);
+        this._months       = mathAbs(this._months);
+
+        data.milliseconds  = mathAbs(data.milliseconds);
+        data.seconds       = mathAbs(data.seconds);
+        data.minutes       = mathAbs(data.minutes);
+        data.hours         = mathAbs(data.hours);
+        data.months        = mathAbs(data.months);
+        data.years         = mathAbs(data.years);
+
+        return this;
     }
-    return new RSVP.Queue()
-      .push(function () {
-        return storage_method.apply(
-          context.__storage,
-          argument_list
-        );
-      });
-  };
 
-  JioProxyStorage.prototype.hasCapacity = function (name) {
-    var storage_method = this.__storage.hasCapacity,
-      capacity_method = this.__storage[name];
-    if (capacity_method !== undefined) {
-      return true;
+    function duration_add_subtract__addSubtract (duration, input, value, direction) {
+        var other = create__createDuration(input, value);
+
+        duration._milliseconds += direction * other._milliseconds;
+        duration._days         += direction * other._days;
+        duration._months       += direction * other._months;
+
+        return duration._bubble();
     }
-    if ((storage_method === undefined) ||
-        !storage_method.apply(this.__storage, arguments)) {
-      throw new jIO.util.jIOError(
-        "Capacity '" + name + "' is not implemented on '" + this.__type + "'",
-        501
-      );
+
+    // supports only 2.0-style add(1, 's') or add(duration)
+    function duration_add_subtract__add (input, value) {
+        return duration_add_subtract__addSubtract(this, input, value, 1);
     }
-    return true;
-  };
 
-  JioProxyStorage.prototype.allDocs = function (options) {
-    var context = this;
-    if (options === undefined) {
-      options = {};
+    // supports only 2.0-style subtract(1, 's') or subtract(duration)
+    function duration_add_subtract__subtract (input, value) {
+        return duration_add_subtract__addSubtract(this, input, value, -1);
     }
-    return new RSVP.Queue()
-      .push(function () {
-        if (context.hasCapacity("list") &&
-            ((options.query === undefined) || context.hasCapacity("query")) &&
-            ((options.sort_on === undefined) || context.hasCapacity("sort")) &&
-            ((options.select_list === undefined) ||
-             context.hasCapacity("select")) &&
-            ((options.include_docs === undefined) ||
-             context.hasCapacity("include")) &&
-            ((options.limit === undefined) || context.hasCapacity("limit"))) {
-          return context.buildQuery(options);
+
+    function absCeil (number) {
+        if (number < 0) {
+            return Math.floor(number);
+        } else {
+            return Math.ceil(number);
         }
-      })
-      .push(function (result) {
-        return {
-          data: {
-            rows: result,
-            total_rows: result.length
-          }
-        };
-      });
-  };
+    }
 
-  declareMethod(JioProxyStorage, "allAttachments", checkId);
-  declareMethod(JioProxyStorage, "repair");
+    function bubble () {
+        var milliseconds = this._milliseconds;
+        var days         = this._days;
+        var months       = this._months;
+        var data         = this._data;
+        var seconds, minutes, hours, years, monthsFromDays;
 
-  JioProxyStorage.prototype.repair = function () {
-    var context = this,
-      argument_list = arguments;
-    return new RSVP.Queue()
-      .push(function () {
-        var storage_method = context.__storage.repair;
-        if (storage_method !== undefined) {
-          return context.__storage.repair.apply(context.__storage,
-                                                argument_list);
+        // if we have a mix of positive and negative values, bubble down first
+        // check: https://github.com/moment/moment/issues/2166
+        if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
+                (milliseconds <= 0 && days <= 0 && months <= 0))) {
+            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+            days = 0;
+            months = 0;
         }
-      });
-  };
 
-  /////////////////////////////////////////////////////////////////
-  // Storage builder
-  /////////////////////////////////////////////////////////////////
-  function JioBuilder() {
-    if (!(this instanceof JioBuilder)) {
-      return new JioBuilder();
-    }
-    this.__storage_types = {};
-  }
+        // The following code bubbles up values, see the tests for
+        // examples of what that means.
+        data.milliseconds = milliseconds % 1000;
 
-  JioBuilder.prototype.createJIO = function (storage_spec, util) {
+        seconds           = absFloor(milliseconds / 1000);
+        data.seconds      = seconds % 60;
 
-    if (typeof storage_spec.type !== 'string') {
-      throw new TypeError("Invalid storage description");
-    }
-    if (!this.__storage_types[storage_spec.type]) {
-      throw new TypeError("Unknown storage '" + storage_spec.type + "'");
-    }
+        minutes           = absFloor(seconds / 60);
+        data.minutes      = minutes % 60;
 
-    return new JioProxyStorage(
-      storage_spec.type,
-      new this.__storage_types[storage_spec.type](storage_spec, util)
-    );
+        hours             = absFloor(minutes / 60);
+        data.hours        = hours % 24;
 
-  };
+        days += absFloor(hours / 24);
 
-  JioBuilder.prototype.addStorage = function (type, Constructor) {
-    if (typeof type !== 'string') {
-      throw new TypeError(
-        "jIO.addStorage(): Argument 1 is not of type 'string'"
-      );
+        // convert days to months
+        monthsFromDays = absFloor(daysToMonths(days));
+        months += monthsFromDays;
+        days -= absCeil(monthsToDays(monthsFromDays));
+
+        // 12 months -> 1 year
+        years = absFloor(months / 12);
+        months %= 12;
+
+        data.days   = days;
+        data.months = months;
+        data.years  = years;
+
+        return this;
     }
-    if (typeof Constructor !== 'function') {
-      throw new TypeError("jIO.addStorage(): " +
-                          "Argument 2 is not of type 'function'");
+
+    function daysToMonths (days) {
+        // 400 years have 146097 days (taking into account leap year rules)
+        // 400 years have 12 months === 4800
+        return days * 4800 / 146097;
+    }
+
+    function monthsToDays (months) {
+        // the reverse of daysToMonths
+        return months * 146097 / 4800;
+    }
+
+    function as (units) {
+        var days;
+        var months;
+        var milliseconds = this._milliseconds;
+
+        units = normalizeUnits(units);
+
+        if (units === 'month' || units === 'year') {
+            days   = this._days   + milliseconds / 864e5;
+            months = this._months + daysToMonths(days);
+            return units === 'month' ? months : months / 12;
+        } else {
+            // handle milliseconds separately because of floating point math errors (issue #1867)
+            days = this._days + Math.round(monthsToDays(this._months));
+            switch (units) {
+                case 'week'   : return days / 7     + milliseconds / 6048e5;
+                case 'day'    : return days         + milliseconds / 864e5;
+                case 'hour'   : return days * 24    + milliseconds / 36e5;
+                case 'minute' : return days * 1440  + milliseconds / 6e4;
+                case 'second' : return days * 86400 + milliseconds / 1000;
+                // Math.floor prevents floating point math errors here
+                case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
+                default: throw new Error('Unknown unit ' + units);
+            }
+        }
+    }
+
+    // TODO: Use this.as('ms')?
+    function duration_as__valueOf () {
+        return (
+            this._milliseconds +
+            this._days * 864e5 +
+            (this._months % 12) * 2592e6 +
+            toInt(this._months / 12) * 31536e6
+        );
+    }
+
+    function makeAs (alias) {
+        return function () {
+            return this.as(alias);
+        };
+    }
+
+    var asMilliseconds = makeAs('ms');
+    var asSeconds      = makeAs('s');
+    var asMinutes      = makeAs('m');
+    var asHours        = makeAs('h');
+    var asDays         = makeAs('d');
+    var asWeeks        = makeAs('w');
+    var asMonths       = makeAs('M');
+    var asYears        = makeAs('y');
+
+    function duration_get__get (units) {
+        units = normalizeUnits(units);
+        return this[units + 's']();
+    }
+
+    function makeGetter(name) {
+        return function () {
+            return this._data[name];
+        };
+    }
+
+    var milliseconds = makeGetter('milliseconds');
+    var seconds      = makeGetter('seconds');
+    var minutes      = makeGetter('minutes');
+    var hours        = makeGetter('hours');
+    var days         = makeGetter('days');
+    var months       = makeGetter('months');
+    var years        = makeGetter('years');
+
+    function weeks () {
+        return absFloor(this.days() / 7);
+    }
+
+    var round = Math.round;
+    var thresholds = {
+        s: 45,  // seconds to minute
+        m: 45,  // minutes to hour
+        h: 22,  // hours to day
+        d: 26,  // days to month
+        M: 11   // months to year
+    };
+
+    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+    }
+
+    function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) {
+        var duration = create__createDuration(posNegDuration).abs();
+        var seconds  = round(duration.as('s'));
+        var minutes  = round(duration.as('m'));
+        var hours    = round(duration.as('h'));
+        var days     = round(duration.as('d'));
+        var months   = round(duration.as('M'));
+        var years    = round(duration.as('y'));
+
+        var a = seconds < thresholds.s && ['s', seconds]  ||
+                minutes <= 1           && ['m']           ||
+                minutes < thresholds.m && ['mm', minutes] ||
+                hours   <= 1           && ['h']           ||
+                hours   < thresholds.h && ['hh', hours]   ||
+                days    <= 1           && ['d']           ||
+                days    < thresholds.d && ['dd', days]    ||
+                months  <= 1           && ['M']           ||
+                months  < thresholds.M && ['MM', months]  ||
+                years   <= 1           && ['y']           || ['yy', years];
+
+        a[2] = withoutSuffix;
+        a[3] = +posNegDuration > 0;
+        a[4] = locale;
+        return substituteTimeAgo.apply(null, a);
+    }
+
+    // This function allows you to set a threshold for relative time strings
+    function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) {
+        if (thresholds[threshold] === undefined) {
+            return false;
+        }
+        if (limit === undefined) {
+            return thresholds[threshold];
+        }
+        thresholds[threshold] = limit;
+        return true;
+    }
+
+    function humanize (withSuffix) {
+        var locale = this.localeData();
+        var output = duration_humanize__relativeTime(this, !withSuffix, locale);
+
+        if (withSuffix) {
+            output = locale.pastFuture(+this, output);
+        }
+
+        return locale.postformat(output);
+    }
+
+    var iso_string__abs = Math.abs;
+
+    function iso_string__toISOString() {
+        // for ISO strings we do not use the normal bubbling rules:
+        //  * milliseconds bubble up until they become hours
+        //  * days do not bubble at all
+        //  * months bubble up until they become years
+        // This is because there is no context-free conversion between hours and days
+        // (think of clock changes)
+        // and also not between days and months (28-31 days per month)
+        var seconds = iso_string__abs(this._milliseconds) / 1000;
+        var days         = iso_string__abs(this._days);
+        var months       = iso_string__abs(this._months);
+        var minutes, hours, years;
+
+        // 3600 seconds -> 60 minutes -> 1 hour
+        minutes           = absFloor(seconds / 60);
+        hours             = absFloor(minutes / 60);
+        seconds %= 60;
+        minutes %= 60;
+
+        // 12 months -> 1 year
+        years  = absFloor(months / 12);
+        months %= 12;
+
+
+        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+        var Y = years;
+        var M = months;
+        var D = days;
+        var h = hours;
+        var m = minutes;
+        var s = seconds;
+        var total = this.asSeconds();
+
+        if (!total) {
+            // this is the same as C#'s (Noda) and python (isodate)...
+            // but not other JS (goog.date)
+            return 'P0D';
+        }
+
+        return (total < 0 ? '-' : '') +
+            'P' +
+            (Y ? Y + 'Y' : '') +
+            (M ? M + 'M' : '') +
+            (D ? D + 'D' : '') +
+            ((h || m || s) ? 'T' : '') +
+            (h ? h + 'H' : '') +
+            (m ? m + 'M' : '') +
+            (s ? s + 'S' : '');
+    }
+
+    var duration_prototype__proto = Duration.prototype;
+
+    duration_prototype__proto.abs            = duration_abs__abs;
+    duration_prototype__proto.add            = duration_add_subtract__add;
+    duration_prototype__proto.subtract       = duration_add_subtract__subtract;
+    duration_prototype__proto.as             = as;
+    duration_prototype__proto.asMilliseconds = asMilliseconds;
+    duration_prototype__proto.asSeconds      = asSeconds;
+    duration_prototype__proto.asMinutes      = asMinutes;
+    duration_prototype__proto.asHours        = asHours;
+    duration_prototype__proto.asDays         = asDays;
+    duration_prototype__proto.asWeeks        = asWeeks;
+    duration_prototype__proto.asMonths       = asMonths;
+    duration_prototype__proto.asYears        = asYears;
+    duration_prototype__proto.valueOf        = duration_as__valueOf;
+    duration_prototype__proto._bubble        = bubble;
+    duration_prototype__proto.get            = duration_get__get;
+    duration_prototype__proto.milliseconds   = milliseconds;
+    duration_prototype__proto.seconds        = seconds;
+    duration_prototype__proto.minutes        = minutes;
+    duration_prototype__proto.hours          = hours;
+    duration_prototype__proto.days           = days;
+    duration_prototype__proto.weeks          = weeks;
+    duration_prototype__proto.months         = months;
+    duration_prototype__proto.years          = years;
+    duration_prototype__proto.humanize       = humanize;
+    duration_prototype__proto.toISOString    = iso_string__toISOString;
+    duration_prototype__proto.toString       = iso_string__toISOString;
+    duration_prototype__proto.toJSON         = iso_string__toISOString;
+    duration_prototype__proto.locale         = locale;
+    duration_prototype__proto.localeData     = localeData;
+
+    // Deprecations
+    duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString);
+    duration_prototype__proto.lang = lang;
+
+    // Side effect imports
+
+    // FORMATTING
+
+    addFormatToken('X', 0, 0, 'unix');
+    addFormatToken('x', 0, 0, 'valueOf');
+
+    // PARSING
+
+    addRegexToken('x', matchSigned);
+    addRegexToken('X', matchTimestamp);
+    addParseToken('X', function (input, array, config) {
+        config._d = new Date(parseFloat(input, 10) * 1000);
+    });
+    addParseToken('x', function (input, array, config) {
+        config._d = new Date(toInt(input));
+    });
+
+    // Side effect imports
+
+
+    utils_hooks__hooks.version = '2.13.0';
+
+    setHookCallback(local__createLocal);
+
+    utils_hooks__hooks.fn                    = momentPrototype;
+    utils_hooks__hooks.min                   = min;
+    utils_hooks__hooks.max                   = max;
+    utils_hooks__hooks.now                   = now;
+    utils_hooks__hooks.utc                   = create_utc__createUTC;
+    utils_hooks__hooks.unix                  = moment__createUnix;
+    utils_hooks__hooks.months                = lists__listMonths;
+    utils_hooks__hooks.isDate                = isDate;
+    utils_hooks__hooks.locale                = locale_locales__getSetGlobalLocale;
+    utils_hooks__hooks.invalid               = valid__createInvalid;
+    utils_hooks__hooks.duration              = create__createDuration;
+    utils_hooks__hooks.isMoment              = isMoment;
+    utils_hooks__hooks.weekdays              = lists__listWeekdays;
+    utils_hooks__hooks.parseZone             = moment__createInZone;
+    utils_hooks__hooks.localeData            = locale_locales__getLocale;
+    utils_hooks__hooks.isDuration            = isDuration;
+    utils_hooks__hooks.monthsShort           = lists__listMonthsShort;
+    utils_hooks__hooks.weekdaysMin           = lists__listWeekdaysMin;
+    utils_hooks__hooks.defineLocale          = defineLocale;
+    utils_hooks__hooks.updateLocale          = updateLocale;
+    utils_hooks__hooks.locales               = locale_locales__listLocales;
+    utils_hooks__hooks.weekdaysShort         = lists__listWeekdaysShort;
+    utils_hooks__hooks.normalizeUnits        = normalizeUnits;
+    utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold;
+    utils_hooks__hooks.prototype             = momentPrototype;
+
+    var _moment = utils_hooks__hooks;
+
+    return _moment;
+
+}));;/**
+ * Parse a text request to a json query object tree
+ *
+ * @param  {String} string The string to parse
+ * @return {Object} The json query tree
+ */
+function parseStringToObject(string) {
+
+var arrayExtend = function () {
+  var j, i, newlist = [], list_list = arguments;
+  for (j = 0; j < list_list.length; j += 1) {
+    for (i = 0; i < list_list[j].length; i += 1) {
+      newlist.push(list_list[j][i]);
+    }
+  }
+  return newlist;
+
+}, mkSimpleQuery = function (key, value, operator) {
+  var object = {"type": "simple", "key": key, "value": value};
+  if (operator !== undefined) {
+    object.operator = operator;
+  }
+  return object;
+
+}, mkNotQuery = function (query) {
+  if (query.operator === "NOT") {
+    return query.query_list[0];
+  }
+  return {"type": "complex", "operator": "NOT", "query_list": [query]};
+
+}, mkComplexQuery = function (operator, query_list) {
+  var i, query_list2 = [];
+  for (i = 0; i < query_list.length; i += 1) {
+    if (query_list[i].operator === operator) {
+      query_list2 = arrayExtend(query_list2, query_list[i].query_list);
+    } else {
+      query_list2.push(query_list[i]);
+    }
+  }
+  return {type:"complex",operator:operator,query_list:query_list2};
+
+}, simpleQuerySetKey = function (query, key) {
+  var i;
+  if (query.type === "complex") {
+    for (i = 0; i < query.query_list.length; ++i) {
+      simpleQuerySetKey (query.query_list[i],key);
+    }
+    return true;
+  }
+  if (query.type === "simple" && !query.key) {
+    query.key = key;
+    return true;
+  }
+  return false;
+},
+  error_offsets = [],
+  error_lookaheads = [],
+  error_count = 0,
+  result;
+;/* parser generated by jison 0.4.16 */
+/*
+  Returns a Parser object of the following structure:
+
+  Parser: {
+    yy: {}
+  }
+
+  Parser.prototype: {
+    yy: {},
+    trace: function(),
+    symbols_: {associative list: name ==> number},
+    terminals_: {associative list: number ==> name},
+    productions_: [...],
+    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
+    table: [...],
+    defaultActions: {...},
+    parseError: function(str, hash),
+    parse: function(input),
+
+    lexer: {
+        EOF: 1,
+        parseError: function(str, hash),
+        setInput: function(input),
+        input: function(),
+        unput: function(str),
+        more: function(),
+        less: function(n),
+        pastInput: function(),
+        upcomingInput: function(),
+        showPosition: function(),
+        test_match: function(regex_match_array, rule_index),
+        next: function(),
+        lex: function(),
+        begin: function(condition),
+        popState: function(),
+        _currentRules: function(),
+        topState: function(),
+        pushState: function(condition),
+
+        options: {
+            ranges: boolean           (optional: true ==> token location info will include a .range[] member)
+            flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
+            backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
+        },
+
+        performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
+        rules: [...],
+        conditions: {associative list: name ==> set},
+    }
+  }
+
+
+  token location info (@$, _$, etc.): {
+    first_line: n,
+    last_line: n,
+    first_column: n,
+    last_column: n,
+    range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
+  }
+
+
+  the parseError function receives a 'hash' object with these members for lexer and parser errors: {
+    text:        (matched text)
+    token:       (the produced terminal token, if any)
+    line:        (yylineno)
+  }
+  while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
+    loc:         (yylloc)
+    expected:    (string describing the set of expected tokens)
+    recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
+  }
+*/
+var parser = (function(){
+var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,7],$V2=[1,8],$V3=[1,10],$V4=[1,12],$V5=[1,6,7,15],$V6=[1,6,7,9,12,14,15,16,19,21],$V7=[1,6,7,9,11,12,14,15,16,19,21],$V8=[2,17];
+var parser = {trace: function trace() { },
+yy: {},
+symbols_: {"error":2,"begin":3,"search_text":4,"end":5,"EOF":6,"NEWLINE":7,"and_expression":8,"OR":9,"boolean_expression":10,"AND":11,"NOT":12,"expression":13,"LEFT_PARENTHESE":14,"RIGHT_PARENTHESE":15,"WORD":16,"DEFINITION":17,"value":18,"OPERATOR":19,"string":20,"QUOTE":21,"QUOTED_STRING":22,"$accept":0,"$end":1},
+terminals_: {2:"error",6:"EOF",7:"NEWLINE",9:"OR",11:"AND",12:"NOT",14:"LEFT_PARENTHESE",15:"RIGHT_PARENTHESE",16:"WORD",17:"DEFINITION",19:"OPERATOR",21:"QUOTE",22:"QUOTED_STRING"},
+productions_: [0,[3,2],[5,0],[5,1],[5,1],[4,1],[4,2],[4,3],[8,1],[8,3],[10,2],[10,1],[13,3],[13,3],[13,1],[18,2],[18,1],[20,1],[20,3]],
+performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
+/* this == yyval */
+
+var $0 = $$.length - 1;
+switch (yystate) {
+case 1:
+ return $$[$0-1]; 
+break;
+case 5: case 8: case 11: case 14: case 16:
+ this.$ = $$[$0]; 
+break;
+case 6:
+ this.$ = mkComplexQuery('OR', [$$[$0-1], $$[$0]]); 
+break;
+case 7:
+ this.$ = mkComplexQuery('OR', [$$[$0-2], $$[$0]]); 
+break;
+case 9:
+ this.$ = mkComplexQuery('AND', [$$[$0-2], $$[$0]]); 
+break;
+case 10:
+ this.$ = mkNotQuery($$[$0]); 
+break;
+case 12:
+ this.$ = $$[$0-1]; 
+break;
+case 13:
+ simpleQuerySetKey($$[$0], $$[$0-2]); this.$ = $$[$0]; 
+break;
+case 15:
+ $$[$0].operator = $$[$0-1] ; this.$ = $$[$0]; 
+break;
+case 17:
+ this.$ = mkSimpleQuery('', $$[$0]); 
+break;
+case 18:
+ this.$ = mkSimpleQuery('', $$[$0-1]); 
+break;
+}
+},
+table: [{3:1,4:2,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},{1:[3]},{1:[2,2],5:13,6:[1,14],7:[1,15]},o($V5,[2,5],{8:3,10:4,13:6,18:9,20:11,4:16,9:[1,17],12:$V0,14:$V1,16:$V2,19:$V3,21:$V4}),o($V6,[2,8],{11:[1,18]}),{13:19,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,11]),{4:20,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,$V8,{17:[1,21]}),o($V7,[2,14]),{16:[1,23],20:22,21:$V4},o($V7,[2,16]),{22:[1,24]},{1:[2,1]},{1:[2,3]},{1:[2,4]},o($V5,[2,6]),{4:25,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},{8:26,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,10]),{15:[1,27]},{13:28,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,15]),o($V7,$V8),{21:[1,29]},o($V5,[2,7]),o($V6,[2,9]),o($V7,[2,12]),o($V7,[2,13]),o($V7,[2,18])],
+defaultActions: {13:[2,1],14:[2,3],15:[2,4]},
+parseError: function parseError(str, hash) {
+    if (hash.recoverable) {
+        this.trace(str);
+    } else {
+        function _parseError (msg, hash) {
+            this.message = msg;
+            this.hash = hash;
+        }
+        _parseError.prototype = new Error();
+
+        throw new _parseError(str, hash);
+    }
+},
+parse: function parse(input) {
+    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+    var args = lstack.slice.call(arguments, 1);
+    var lexer = Object.create(this.lexer);
+    var sharedState = { yy: {} };
+    for (var k in this.yy) {
+        if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
+            sharedState.yy[k] = this.yy[k];
+        }
+    }
+    lexer.setInput(input, sharedState.yy);
+    sharedState.yy.lexer = lexer;
+    sharedState.yy.parser = this;
+    if (typeof lexer.yylloc == 'undefined') {
+        lexer.yylloc = {};
+    }
+    var yyloc = lexer.yylloc;
+    lstack.push(yyloc);
+    var ranges = lexer.options && lexer.options.ranges;
+    if (typeof sharedState.yy.parseError === 'function') {
+        this.parseError = sharedState.yy.parseError;
+    } else {
+        this.parseError = Object.getPrototypeOf(this).parseError;
+    }
+    function popStack(n) {
+        stack.length = stack.length - 2 * n;
+        vstack.length = vstack.length - n;
+        lstack.length = lstack.length - n;
+    }
+    _token_stack:
+        var lex = function () {
+            var token;
+            token = lexer.lex() || EOF;
+            if (typeof token !== 'number') {
+                token = self.symbols_[token] || token;
+            }
+            return token;
+        };
+    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+    while (true) {
+        state = stack[stack.length - 1];
+        if (this.defaultActions[state]) {
+            action = this.defaultActions[state];
+        } else {
+            if (symbol === null || typeof symbol == 'undefined') {
+                symbol = lex();
+            }
+            action = table[state] && table[state][symbol];
+        }
+                    if (typeof action === 'undefined' || !action.length || !action[0]) {
+                var errStr = '';
+                expected = [];
+                for (p in table[state]) {
+                    if (this.terminals_[p] && p > TERROR) {
+                        expected.push('\'' + this.terminals_[p] + '\'');
+                    }
+                }
+                if (lexer.showPosition) {
+                    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
+                } else {
+                    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
+                }
+                this.parseError(errStr, {
+                    text: lexer.match,
+                    token: this.terminals_[symbol] || symbol,
+                    line: lexer.yylineno,
+                    loc: yyloc,
+                    expected: expected
+                });
+            }
+        if (action[0] instanceof Array && action.length > 1) {
+            throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
+        }
+        switch (action[0]) {
+        case 1:
+            stack.push(symbol);
+            vstack.push(lexer.yytext);
+            lstack.push(lexer.yylloc);
+            stack.push(action[1]);
+            symbol = null;
+            if (!preErrorSymbol) {
+                yyleng = lexer.yyleng;
+                yytext = lexer.yytext;
+                yylineno = lexer.yylineno;
+                yyloc = lexer.yylloc;
+                if (recovering > 0) {
+                    recovering--;
+                }
+            } else {
+                symbol = preErrorSymbol;
+                preErrorSymbol = null;
+            }
+            break;
+        case 2:
+            len = this.productions_[action[1]][1];
+            yyval.$ = vstack[vstack.length - len];
+            yyval._$ = {
+                first_line: lstack[lstack.length - (len || 1)].first_line,
+                last_line: lstack[lstack.length - 1].last_line,
+                first_column: lstack[lstack.length - (len || 1)].first_column,
+                last_column: lstack[lstack.length - 1].last_column
+            };
+            if (ranges) {
+                yyval._$.range = [
+                    lstack[lstack.length - (len || 1)].range[0],
+                    lstack[lstack.length - 1].range[1]
+                ];
+            }
+            r = this.performAction.apply(yyval, [
+                yytext,
+                yyleng,
+                yylineno,
+                sharedState.yy,
+                action[1],
+                vstack,
+                lstack
+            ].concat(args));
+            if (typeof r !== 'undefined') {
+                return r;
+            }
+            if (len) {
+                stack = stack.slice(0, -1 * len * 2);
+                vstack = vstack.slice(0, -1 * len);
+                lstack = lstack.slice(0, -1 * len);
+            }
+            stack.push(this.productions_[action[1]][0]);
+            vstack.push(yyval.$);
+            lstack.push(yyval._$);
+            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+            stack.push(newState);
+            break;
+        case 3:
+            return true;
+        }
+    }
+    return true;
+}};
+/* generated by jison-lex 0.3.4 */
+var lexer = (function(){
+var lexer = ({
+
+EOF:1,
+
+parseError:function parseError(str, hash) {
+        if (this.yy.parser) {
+            this.yy.parser.parseError(str, hash);
+        } else {
+            throw new Error(str);
+        }
+    },
+
+// resets the lexer, sets new input
+setInput:function (input, yy) {
+        this.yy = yy || this.yy || {};
+        this._input = input;
+        this._more = this._backtrack = this.done = false;
+        this.yylineno = this.yyleng = 0;
+        this.yytext = this.matched = this.match = '';
+        this.conditionStack = ['INITIAL'];
+        this.yylloc = {
+            first_line: 1,
+            first_column: 0,
+            last_line: 1,
+            last_column: 0
+        };
+        if (this.options.ranges) {
+            this.yylloc.range = [0,0];
+        }
+        this.offset = 0;
+        return this;
+    },
+
+// consumes and returns one char from the input
+input:function () {
+        var ch = this._input[0];
+        this.yytext += ch;
+        this.yyleng++;
+        this.offset++;
+        this.match += ch;
+        this.matched += ch;
+        var lines = ch.match(/(?:\r\n?|\n).*/g);
+        if (lines) {
+            this.yylineno++;
+            this.yylloc.last_line++;
+        } else {
+            this.yylloc.last_column++;
+        }
+        if (this.options.ranges) {
+            this.yylloc.range[1]++;
+        }
+
+        this._input = this._input.slice(1);
+        return ch;
+    },
+
+// unshifts one char (or a string) into the input
+unput:function (ch) {
+        var len = ch.length;
+        var lines = ch.split(/(?:\r\n?|\n)/g);
+
+        this._input = ch + this._input;
+        this.yytext = this.yytext.substr(0, this.yytext.length - len);
+        //this.yyleng -= len;
+        this.offset -= len;
+        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+        this.match = this.match.substr(0, this.match.length - 1);
+        this.matched = this.matched.substr(0, this.matched.length - 1);
+
+        if (lines.length - 1) {
+            this.yylineno -= lines.length - 1;
+        }
+        var r = this.yylloc.range;
+
+        this.yylloc = {
+            first_line: this.yylloc.first_line,
+            last_line: this.yylineno + 1,
+            first_column: this.yylloc.first_column,
+            last_column: lines ?
+                (lines.length === oldLines.length ? this.yylloc.first_column : 0)
+                 + oldLines[oldLines.length - lines.length].length - lines[0].length :
+              this.yylloc.first_column - len
+        };
+
+        if (this.options.ranges) {
+            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+        }
+        this.yyleng = this.yytext.length;
+        return this;
+    },
+
+// When called from action, caches matched text and appends it on next action
+more:function () {
+        this._more = true;
+        return this;
+    },
+
+// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
+reject:function () {
+        if (this.options.backtrack_lexer) {
+            this._backtrack = true;
+        } else {
+            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
+                text: "",
+                token: null,
+                line: this.yylineno
+            });
+
+        }
+        return this;
+    },
+
+// retain first n characters of the match
+less:function (n) {
+        this.unput(this.match.slice(n));
+    },
+
+// displays already matched input, i.e. for error messages
+pastInput:function () {
+        var past = this.matched.substr(0, this.matched.length - this.match.length);
+        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+    },
+
+// displays upcoming input, i.e. for error messages
+upcomingInput:function () {
+        var next = this.match;
+        if (next.length < 20) {
+            next += this._input.substr(0, 20-next.length);
+        }
+        return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
+    },
+
+// displays the character position where the lexing error occurred, i.e. for error messages
+showPosition:function () {
+        var pre = this.pastInput();
+        var c = new Array(pre.length + 1).join("-");
+        return pre + this.upcomingInput() + "\n" + c + "^";
+    },
+
+// test the lexed token: return FALSE when not a match, otherwise return token
+test_match:function (match, indexed_rule) {
+        var token,
+            lines,
+            backup;
+
+        if (this.options.backtrack_lexer) {
+            // save context
+            backup = {
+                yylineno: this.yylineno,
+                yylloc: {
+                    first_line: this.yylloc.first_line,
+                    last_line: this.last_line,
+                    first_column: this.yylloc.first_column,
+                    last_column: this.yylloc.last_column
+                },
+                yytext: this.yytext,
+                match: this.match,
+                matches: this.matches,
+                matched: this.matched,
+                yyleng: this.yyleng,
+                offset: this.offset,
+                _more: this._more,
+                _input: this._input,
+                yy: this.yy,
+                conditionStack: this.conditionStack.slice(0),
+                done: this.done
+            };
+            if (this.options.ranges) {
+                backup.yylloc.range = this.yylloc.range.slice(0);
+            }
+        }
+
+        lines = match[0].match(/(?:\r\n?|\n).*/g);
+        if (lines) {
+            this.yylineno += lines.length;
+        }
+        this.yylloc = {
+            first_line: this.yylloc.last_line,
+            last_line: this.yylineno + 1,
+            first_column: this.yylloc.last_column,
+            last_column: lines ?
+                         lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
+                         this.yylloc.last_column + match[0].length
+        };
+        this.yytext += match[0];
+        this.match += match[0];
+        this.matches = match;
+        this.yyleng = this.yytext.length;
+        if (this.options.ranges) {
+            this.yylloc.range = [this.offset, this.offset += this.yyleng];
+        }
+        this._more = false;
+        this._backtrack = false;
+        this._input = this._input.slice(match[0].length);
+        this.matched += match[0];
+        token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
+        if (this.done && this._input) {
+            this.done = false;
+        }
+        if (token) {
+            return token;
+        } else if (this._backtrack) {
+            // recover context
+            for (var k in backup) {
+                this[k] = backup[k];
+            }
+            return false; // rule action called reject() implying the next rule should be tested instead.
+        }
+        return false;
+    },
+
+// return next match in input
+next:function () {
+        if (this.done) {
+            return this.EOF;
+        }
+        if (!this._input) {
+            this.done = true;
+        }
+
+        var token,
+            match,
+            tempMatch,
+            index;
+        if (!this._more) {
+            this.yytext = '';
+            this.match = '';
+        }
+        var rules = this._currentRules();
+        for (var i = 0; i < rules.length; i++) {
+            tempMatch = this._input.match(this.rules[rules[i]]);
+            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+                match = tempMatch;
+                index = i;
+                if (this.options.backtrack_lexer) {
+                    token = this.test_match(tempMatch, rules[i]);
+                    if (token !== false) {
+                        return token;
+                    } else if (this._backtrack) {
+                        match = false;
+                        continue; // rule action called reject() implying a rule MISmatch.
+                    } else {
+                        // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+                        return false;
+                    }
+                } else if (!this.options.flex) {
+                    break;
+                }
+            }
+        }
+        if (match) {
+            token = this.test_match(match, rules[index]);
+            if (token !== false) {
+                return token;
+            }
+            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
+            return false;
+        }
+        if (this._input === "") {
+            return this.EOF;
+        } else {
+            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
+                text: "",
+                token: null,
+                line: this.yylineno
+            });
+        }
+    },
+
+// return next match that has a token
+lex:function lex() {
+        var r = this.next();
+        if (r) {
+            return r;
+        } else {
+            return this.lex();
+        }
+    },
+
+// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
+begin:function begin(condition) {
+        this.conditionStack.push(condition);
+    },
+
+// pop the previously active lexer condition state off the condition stack
+popState:function popState() {
+        var n = this.conditionStack.length - 1;
+        if (n > 0) {
+            return this.conditionStack.pop();
+        } else {
+            return this.conditionStack[0];
+        }
+    },
+
+// produce the lexer rule set which is active for the currently active lexer condition state
+_currentRules:function _currentRules() {
+        if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
+            return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
+        } else {
+            return this.conditions["INITIAL"].rules;
+        }
+    },
+
+// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
+topState:function topState(n) {
+        n = this.conditionStack.length - 1 - Math.abs(n || 0);
+        if (n >= 0) {
+            return this.conditionStack[n];
+        } else {
+            return "INITIAL";
+        }
+    },
+
+// alias for begin(condition)
+pushState:function pushState(condition) {
+        this.begin(condition);
+    },
+
+// return the number of states currently on the stack
+stateStackSize:function stateStackSize() {
+        return this.conditionStack.length;
+    },
+options: {},
+performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+var YYSTATE=YY_START;
+switch($avoiding_name_collisions) {
+case 0:this.begin("letsquote"); return "QUOTE";
+break;
+case 1:this.popState(); this.begin("endquote"); return "QUOTED_STRING";
+break;
+case 2:this.popState(); return "QUOTE";
+break;
+case 3:/* skip whitespace */
+break;
+case 4:return "LEFT_PARENTHESE";
+break;
+case 5:return "RIGHT_PARENTHESE";
+break;
+case 6:return "AND";
+break;
+case 7:return "OR";
+break;
+case 8:return "NOT";
+break;
+case 9:return "DEFINITION";
+break;
+case 10:return 19;
+break;
+case 11:return 16;
+break;
+case 12:return 6;
+break;
+}
+},
+rules: [/^(?:")/,/^(?:(\\"|[^"])*)/,/^(?:")/,/^(?:[^\S]+)/,/^(?:\()/,/^(?:\))/,/^(?:AND\b)/,/^(?:OR\b)/,/^(?:NOT\b)/,/^(?::)/,/^(?:(!?=|<=?|>=?))/,/^(?:[^\s\n"():><!=]+)/,/^(?:$)/],
+conditions: {"endquote":{"rules":[2],"inclusive":false},"letsquote":{"rules":[1],"inclusive":false},"INITIAL":{"rules":[0,3,4,5,6,7,8,9,10,11,12],"inclusive":true}}
+});
+return lexer;
+})();
+parser.lexer = lexer;
+function Parser () {
+  this.yy = {};
+}
+Parser.prototype = parser;parser.Parser = Parser;
+return new Parser;
+})();;  return parser.parse(string);
+} // parseStringToObject
+
+;/*global RSVP, window, parseStringToObject*/
+/*jslint nomen: true, maxlen: 90*/
+(function (RSVP, window, parseStringToObject) {
+  "use strict";
+
+  var query_class_dict = {},
+    regexp_escape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g,
+    regexp_percent = /%/g,
+    regexp_underscore = /_/g,
+    regexp_operator = /^(?:AND|OR|NOT)$/i,
+    regexp_comparaison = /^(?:!?=|<=?|>=?)$/i;
+
+  /**
+   * Convert metadata values to array of strings. ex:
+   *
+   *     "a" -> ["a"],
+   *     {"content": "a"} -> ["a"]
+   *
+   * @param  {Any} value The metadata value
+   * @return {Array} The value in string array format
+   */
+  function metadataValueToStringArray(value) {
+    var i, new_value = [];
+    if (value === undefined) {
+      return undefined;
+    }
+    if (!Array.isArray(value)) {
+      value = [value];
+    }
+    for (i = 0; i < value.length; i += 1) {
+      if (typeof value[i] === 'object') {
+        new_value[i] = value[i].content;
+      } else {
+        new_value[i] = value[i];
+      }
+    }
+    return new_value;
+  }
+
+  /**
+   * A sort function to sort items by key
+   *
+   * @param  {String} key The key to sort on
+   * @param  {String} [way="ascending"] 'ascending' or 'descending'
+   * @return {Function} The sort function
+   */
+  function sortFunction(key, way) {
+    var result;
+    if (way === 'descending') {
+      result = 1;
+    } else if (way === 'ascending') {
+      result = -1;
+    } else {
+      throw new TypeError("Query.sortFunction(): " +
+                          "Argument 2 must be 'ascending' or 'descending'");
+    }
+    return function (a, b) {
+      // this comparison is 5 times faster than json comparison
+      var i, l;
+      a = metadataValueToStringArray(a[key]) || [];
+      b = metadataValueToStringArray(b[key]) || [];
+      l = a.length > b.length ? a.length : b.length;
+      for (i = 0; i < l; i += 1) {
+        if (a[i] === undefined) {
+          return result;
+        }
+        if (b[i] === undefined) {
+          return -result;
+        }
+        if (a[i] > b[i]) {
+          return -result;
+        }
+        if (a[i] < b[i]) {
+          return result;
+        }
+      }
+      return 0;
+    };
+  }
+
+  /**
+   * Sort a list of items, according to keys and directions.
+   *
+   * @param  {Array} sort_on_option List of couples [key, direction]
+   * @param  {Array} list The item list to sort
+   * @return {Array} The filtered list
+   */
+  function sortOn(sort_on_option, list) {
+    var sort_index;
+    if (!Array.isArray(sort_on_option)) {
+      throw new TypeError("jioquery.sortOn(): " +
+                          "Argument 1 is not of type 'array'");
+    }
+    for (sort_index = sort_on_option.length - 1; sort_index >= 0;
+         sort_index -= 1) {
+      list.sort(sortFunction(
+        sort_on_option[sort_index][0],
+        sort_on_option[sort_index][1]
+      ));
+    }
+    return list;
+  }
+
+  /**
+   * Limit a list of items, according to index and length.
+   *
+   * @param  {Array} limit_option A couple [from, length]
+   * @param  {Array} list The item list to limit
+   * @return {Array} The filtered list
+   */
+  function limit(limit_option, list) {
+    if (!Array.isArray(limit_option)) {
+      throw new TypeError("jioquery.limit(): " +
+                          "Argument 1 is not of type 'array'");
+    }
+    if (!Array.isArray(list)) {
+      throw new TypeError("jioquery.limit(): " +
+                          "Argument 2 is not of type 'array'");
+    }
+    list.splice(0, limit_option[0]);
+    if (limit_option[1]) {
+      list.splice(limit_option[1]);
+    }
+    return list;
+  }
+
+  /**
+   * Filter a list of items, modifying them to select only wanted keys.
+   *
+   * @param  {Array} select_option Key list to keep
+   * @param  {Array} list The item list to filter
+   * @return {Array} The filtered list
+   */
+  function select(select_option, list) {
+    var i, j, new_item;
+    if (!Array.isArray(select_option)) {
+      throw new TypeError("jioquery.select(): " +
+                          "Argument 1 is not of type Array");
+    }
+    if (!Array.isArray(list)) {
+      throw new TypeError("jioquery.select(): " +
+                          "Argument 2 is not of type Array");
+    }
+    for (i = 0; i < list.length; i += 1) {
+      new_item = {};
+      for (j = 0; j < select_option.length; j += 1) {
+        if (list[i].hasOwnProperty([select_option[j]])) {
+          new_item[select_option[j]] = list[i][select_option[j]];
+        }
+      }
+      for (j in new_item) {
+        if (new_item.hasOwnProperty(j)) {
+          list[i] = new_item;
+          break;
+        }
+      }
+    }
+    return list;
+  }
+
+  /**
+   * The query to use to filter a list of objects.
+   * This is an abstract class.
+   *
+   * @class Query
+   * @constructor
+   */
+  function Query() {
+
+    /**
+     * Called before parsing the query. Must be overridden!
+     *
+     * @method onParseStart
+     * @param  {Object} object The object shared in the parse process
+     * @param  {Object} option Some option gave in parse()
+     */
+  //   this.onParseStart = emptyFunction;
+
+    /**
+     * Called when parsing a simple query. Must be overridden!
+     *
+     * @method onParseSimpleQuery
+     * @param  {Object} object The object shared in the parse process
+     * @param  {Object} option Some option gave in parse()
+     */
+  //   this.onParseSimpleQuery = emptyFunction;
+
+    /**
+     * Called when parsing a complex query. Must be overridden!
+     *
+     * @method onParseComplexQuery
+     * @param  {Object} object The object shared in the parse process
+     * @param  {Object} option Some option gave in parse()
+     */
+  //   this.onParseComplexQuery = emptyFunction;
+
+    /**
+     * Called after parsing the query. Must be overridden!
+     *
+     * @method onParseEnd
+     * @param  {Object} object The object shared in the parse process
+     * @param  {Object} option Some option gave in parse()
+     */
+  //   this.onParseEnd = emptyFunction;
+
+    return;
+  }
+
+  /**
+   * Filter the item list with matching item only
+   *
+   * @method exec
+   * @param  {Array} item_list The list of object
+   * @param  {Object} [option] Some operation option
+   * @param  {Array} [option.select_list] A object keys to retrieve
+   * @param  {Array} [option.sort_on] Couples of object keys and "ascending"
+   *                 or "descending"
+   * @param  {Array} [option.limit] Couple of integer, first is an index and
+   *                 second is the length.
+   */
+  Query.prototype.exec = function (item_list, option) {
+    if (!Array.isArray(item_list)) {
+      throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
+    }
+    if (option === undefined) {
+      option = {};
+    }
+    if (typeof option !== 'object') {
+      throw new TypeError("Query().exec(): " +
+                          "Optional argument 2 is not of type 'object'");
+    }
+    var context = this,
+      i;
+    for (i = item_list.length - 1; i >= 0; i -= 1) {
+      if (!context.match(item_list[i])) {
+        item_list.splice(i, 1);
+      }
+    }
+
+    if (option.sort_on) {
+      sortOn(option.sort_on, item_list);
+    }
+
+    if (option.limit) {
+      limit(option.limit, item_list);
+    }
+
+    select(option.select_list || [], item_list);
+
+    return new RSVP.Queue()
+      .push(function () {
+        return item_list;
+      });
+  };
+
+  /**
+   * Test if an item matches this query
+   *
+   * @method match
+   * @param  {Object} item The object to test
+   * @return {Boolean} true if match, false otherwise
+   */
+  Query.prototype.match = function () {
+    return true;
+  };
+
+  /**
+   * Browse the Query in deep calling parser method in each step.
+   *
+   * `onParseStart` is called first, on end `onParseEnd` is called.
+   * It starts from the simple queries at the bottom of the tree calling the
+   * parser method `onParseSimpleQuery`, and go up calling the
+   * `onParseComplexQuery` method.
+   *
+   * @method parse
+   * @param  {Object} option Any options you want (except 'parsed')
+   * @return {Any} The parse result
+   */
+  Query.prototype.parse = function (option) {
+    var that = this,
+      object;
+    /**
+     * The recursive parser.
+     *
+     * @param  {Object} object The object shared in the parse process
+     * @param  {Object} options Some options usable in the parseMethods
+     * @return {Any} The parser result
+     */
+    function recParse(object, option) {
+      var query = object.parsed,
+        queue = new RSVP.Queue(),
+        i;
+
+      function enqueue(j) {
+        queue
+          .push(function () {
+            object.parsed = query.query_list[j];
+            return recParse(object, option);
+          })
+          .push(function () {
+            query.query_list[j] = object.parsed;
+          });
+      }
+
+      if (query.type === "complex") {
+
+
+        for (i = 0; i < query.query_list.length; i += 1) {
+          enqueue(i);
+        }
+
+        return queue
+          .push(function () {
+            object.parsed = query;
+            return that.onParseComplexQuery(object, option);
+          });
+
+      }
+      if (query.type === "simple") {
+        return that.onParseSimpleQuery(object, option);
+      }
+    }
+    object = {
+      parsed: JSON.parse(JSON.stringify(that.serialized()))
+    };
+    return new RSVP.Queue()
+      .push(function () {
+        return that.onParseStart(object, option);
+      })
+      .push(function () {
+        return recParse(object, option);
+      })
+      .push(function () {
+        return that.onParseEnd(object, option);
+      })
+      .push(function () {
+        return object.parsed;
+      });
+
+  };
+
+  /**
+   * Convert this query to a parsable string.
+   *
+   * @method toString
+   * @return {String} The string version of this query
+   */
+  Query.prototype.toString = function () {
+    return "";
+  };
+
+  /**
+   * Convert this query to an jsonable object in order to be remake thanks to
+   * QueryFactory class.
+   *
+   * @method serialized
+   * @return {Object} The jsonable object
+   */
+  Query.prototype.serialized = function () {
+    return undefined;
+  };
+
+  /**
+   * Provides static methods to create Query object
+   *
+   * @class QueryFactory
+   */
+  function QueryFactory() {
+    return;
+  }
+
+  /**
+   * Escapes regexp special chars from a string.
+   *
+   * @param  {String} string The string to escape
+   * @return {String} The escaped string
+   */
+  function stringEscapeRegexpCharacters(string) {
+    return string.replace(regexp_escape, "\\$&");
+  }
+
+  /**
+   * Inherits the prototype methods from one constructor into another. The
+   * prototype of `constructor` will be set to a new object created from
+   * `superConstructor`.
+   *
+   * @param  {Function} constructor The constructor which inherits the super one
+   * @param  {Function} superConstructor The super constructor
+   */
+  function inherits(constructor, superConstructor) {
+    constructor.super_ = superConstructor;
+    constructor.prototype = Object.create(superConstructor.prototype, {
+      "constructor": {
+        "configurable": true,
+        "enumerable": false,
+        "writable": true,
+        "value": constructor
+      }
+    });
+  }
+
+  /**
+   * Convert a search text to a regexp.
+   *
+   * @param  {String} string The string to convert
+   * @param  {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_"
+   * @return {RegExp} The search text regexp
+   */
+  function searchTextToRegExp(string, use_wildcard_characters) {
+    if (typeof string !== 'string') {
+      throw new TypeError("jioquery.searchTextToRegExp(): " +
+                          "Argument 1 is not of type 'string'");
+    }
+    if (use_wildcard_characters === false) {
+      return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
+    }
+    return new RegExp("^" + stringEscapeRegexpCharacters(string)
+      .replace(regexp_percent, '.*')
+      .replace(regexp_underscore, '.') + "$");
+  }
+
+  /**
+   * The ComplexQuery inherits from Query, and compares one or several metadata
+   * values.
+   *
+   * @class ComplexQuery
+   * @extends Query
+   * @param  {Object} [spec={}] The specifications
+   * @param  {String} [spec.operator="AND"] The compare method to use
+   * @param  {String} spec.key The metadata key
+   * @param  {String} spec.value The value of the metadata to compare
+   */
+  function ComplexQuery(spec, key_schema) {
+    Query.call(this);
+
+    /**
+     * Logical operator to use to compare object values
+     *
+     * @attribute operator
+     * @type String
+     * @default "AND"
+     * @optional
+     */
+    this.operator = spec.operator;
+
+    /**
+     * The sub Query list which are used to query an item.
+     *
+     * @attribute query_list
+     * @type Array
+     * @default []
+     * @optional
+     */
+    this.query_list = spec.query_list || [];
+    this.query_list = this.query_list.map(
+      // decorate the map to avoid sending the index as key_schema argument
+      function (o) { return QueryFactory.create(o, key_schema); }
+    );
+
+  }
+  inherits(ComplexQuery, Query);
+
+  ComplexQuery.prototype.operator = "AND";
+  ComplexQuery.prototype.type = "complex";
+
+  /**
+   * #crossLink "Query/match:method"
+   */
+  ComplexQuery.prototype.match = function (item) {
+    var operator = this.operator;
+    if (!(regexp_operator.test(operator))) {
+      operator = "AND";
+    }
+    return this[operator.toUpperCase()](item);
+  };
+
+  /**
+   * #crossLink "Query/toString:method"
+   */
+  ComplexQuery.prototype.toString = function () {
+    var str_list = [], this_operator = this.operator;
+    if (this.operator === "NOT") {
+      str_list.push("NOT (");
+      str_list.push(this.query_list[0].toString());
+      str_list.push(")");
+      return str_list.join(" ");
+    }
+    this.query_list.forEach(function (query) {
+      str_list.push("(");
+      str_list.push(query.toString());
+      str_list.push(")");
+      str_list.push(this_operator);
+    });
+    str_list.length -= 1;
+    return str_list.join(" ");
+  };
+
+  /**
+   * #crossLink "Query/serialized:method"
+   */
+  ComplexQuery.prototype.serialized = function () {
+    var s = {
+      "type": "complex",
+      "operator": this.operator,
+      "query_list": []
+    };
+    this.query_list.forEach(function (query) {
+      s.query_list.push(
+        typeof query.toJSON === "function" ? query.toJSON() : query
+      );
+    });
+    return s;
+  };
+  ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized;
+
+  /**
+   * Comparison operator, test if all sub queries match the
+   * item value
+   *
+   * @method AND
+   * @param  {Object} item The item to match
+   * @return {Boolean} true if all match, false otherwise
+   */
+  ComplexQuery.prototype.AND = function (item) {
+    var result = true,
+      i = 0;
+
+    while (result && (i !== this.query_list.length)) {
+      result = this.query_list[i].match(item);
+      i += 1;
+    }
+    return result;
+
+  };
+
+  /**
+   * Comparison operator, test if one of the sub queries matches the
+   * item value
+   *
+   * @method OR
+   * @param  {Object} item The item to match
+   * @return {Boolean} true if one match, false otherwise
+   */
+  ComplexQuery.prototype.OR = function (item) {
+    var result = false,
+      i = 0;
+
+    while ((!result) && (i !== this.query_list.length)) {
+      result = this.query_list[i].match(item);
+      i += 1;
+    }
+
+    return result;
+  };
+
+  /**
+   * Comparison operator, test if the sub query does not match the
+   * item value
+   *
+   * @method NOT
+   * @param  {Object} item The item to match
+   * @return {Boolean} true if one match, false otherwise
+   */
+  ComplexQuery.prototype.NOT = function (item) {
+    return !this.query_list[0].match(item);
+  };
+
+  /**
+   * Creates Query object from a search text string or a serialized version
+   * of a Query.
+   *
+   * @method create
+   * @static
+   * @param  {Object,String} object The search text or the serialized version
+   *         of a Query
+   * @return {Query} A Query object
+   */
+  QueryFactory.create = function (object, key_schema) {
+    if (object === "") {
+      return new Query();
+    }
+    if (typeof object === "string") {
+      object = parseStringToObject(object);
+    }
+    if (typeof (object || {}).type === "string" &&
+        query_class_dict[object.type]) {
+      return new query_class_dict[object.type](object, key_schema);
+    }
+    throw new TypeError("QueryFactory.create(): " +
+                        "Argument 1 is not a search text or a parsable object");
+  };
+
+  function objectToSearchText(query) {
+    var str_list = [];
+    if (query.type === "complex") {
+      str_list.push("(");
+      (query.query_list || []).forEach(function (sub_query) {
+        str_list.push(objectToSearchText(sub_query));
+        str_list.push(query.operator);
+      });
+      str_list.length -= 1;
+      str_list.push(")");
+      return str_list.join(" ");
+    }
+    if (query.type === "simple") {
+      return (query.key ? query.key + ": " : "") +
+        (query.operator || "") + ' "' + query.value + '"';
+    }
+    throw new TypeError("This object is not a query");
+  }
+
+  function checkKeySchema(key_schema) {
+    var prop;
+
+    if (key_schema !== undefined) {
+      if (typeof key_schema !== 'object') {
+        throw new TypeError("SimpleQuery().create(): " +
+                            "key_schema is not of type 'object'");
+      }
+      // key_set is mandatory
+      if (key_schema.key_set === undefined) {
+        throw new TypeError("SimpleQuery().create(): " +
+                            "key_schema has no 'key_set' property");
+      }
+      for (prop in key_schema) {
+        if (key_schema.hasOwnProperty(prop)) {
+          switch (prop) {
+          case 'key_set':
+          case 'cast_lookup':
+          case 'match_lookup':
+            break;
+          default:
+            throw new TypeError("SimpleQuery().create(): " +
+                               "key_schema has unknown property '" + prop + "'");
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * The SimpleQuery inherits from Query, and compares one metadata value
+   *
+   * @class SimpleQuery
+   * @extends Query
+   * @param  {Object} [spec={}] The specifications
+   * @param  {String} [spec.operator="="] The compare method to use
+   * @param  {String} spec.key The metadata key
+   * @param  {String} spec.value The value of the metadata to compare
+   */
+  function SimpleQuery(spec, key_schema) {
+    Query.call(this);
+
+    checkKeySchema(key_schema);
+
+    this._key_schema = key_schema || {};
+
+    /**
+     * Operator to use to compare object values
+     *
+     * @attribute operator
+     * @type String
+     * @optional
+     */
+    this.operator = spec.operator;
+
+    /**
+     * Key of the object which refers to the value to compare
+     *
+     * @attribute key
+     * @type String
+     */
+    this.key = spec.key;
+
+    /**
+     * Value is used to do the comparison with the object value
+     *
+     * @attribute value
+     * @type String
+     */
+    this.value = spec.value;
+
+  }
+  inherits(SimpleQuery, Query);
+
+  SimpleQuery.prototype.type = "simple";
+
+  function checkKey(key) {
+    var prop;
+
+    if (key.read_from === undefined) {
+      throw new TypeError("Custom key is missing the read_from property");
+    }
+
+    for (prop in key) {
+      if (key.hasOwnProperty(prop)) {
+        switch (prop) {
+        case 'read_from':
+        case 'cast_to':
+        case 'equal_match':
+          break;
+        default:
+          throw new TypeError("Custom key has unknown property '" +
+                              prop + "'");
+        }
+      }
+    }
+  }
+
+  /**
+   * #crossLink "Query/match:method"
+   */
+  SimpleQuery.prototype.match = function (item) {
+    var object_value = null,
+      equal_match = null,
+      cast_to = null,
+      matchMethod = null,
+      operator = this.operator,
+      value = null,
+      key = this.key;
+
+    if (!(regexp_comparaison.test(operator))) {
+      // `operator` is not correct, we have to change it to "like" or "="
+      if (regexp_percent.test(this.value)) {
+        // `value` contains a non escaped `%`
+        operator = "like";
+      } else {
+        // `value` does not contain non escaped `%`
+        operator = "=";
+      }
+    }
+
+    matchMethod = this[operator];
+
+    if (this._key_schema.key_set && this._key_schema.key_set[key] !== undefined) {
+      key = this._key_schema.key_set[key];
+    }
+
+    if (typeof key === 'object') {
+      checkKey(key);
+      object_value = item[key.read_from];
+
+      equal_match = key.equal_match;
+
+      // equal_match can be a string
+      if (typeof equal_match === 'string') {
+        // XXX raise error if equal_match not in match_lookup
+        equal_match = this._key_schema.match_lookup[equal_match];
+      }
+
+      // equal_match overrides the default '=' operator
+      if (equal_match !== undefined) {
+        matchMethod = (operator === "=" || operator === "like" ?
+                       equal_match : matchMethod);
+      }
+
+      value = this.value;
+      cast_to = key.cast_to;
+      if (cast_to) {
+        // cast_to can be a string
+        if (typeof cast_to === 'string') {
+          // XXX raise error if cast_to not in cast_lookup
+          cast_to = this._key_schema.cast_lookup[cast_to];
+        }
+
+        try {
+          value = cast_to(value);
+        } catch (e) {
+          value = undefined;
+        }
+
+        try {
+          object_value = cast_to(object_value);
+        } catch (e) {
+          object_value = undefined;
+        }
+      }
+    } else {
+      object_value = item[key];
+      value = this.value;
+    }
+    if (object_value === undefined || value === undefined) {
+      return false;
+    }
+    return matchMethod(object_value, value);
+  };
+
+  /**
+   * #crossLink "Query/toString:method"
+   */
+  SimpleQuery.prototype.toString = function () {
+    return (this.key ? this.key + ":" : "") +
+      (this.operator ? " " + this.operator : "") + ' "' + this.value + '"';
+  };
+
+  /**
+   * #crossLink "Query/serialized:method"
+   */
+  SimpleQuery.prototype.serialized = function () {
+    var object = {
+      "type": "simple",
+      "key": this.key,
+      "value": this.value
+    };
+    if (this.operator !== undefined) {
+      object.operator = this.operator;
+    }
+    return object;
+  };
+  SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized;
+
+  /**
+   * Comparison operator, test if this query value matches the item value
+   *
+   * @method =
+   * @param  {String} object_value The value to compare
+   * @param  {String} comparison_value The comparison value
+   * @return {Boolean} true if match, false otherwise
+   */
+  SimpleQuery.prototype["="] = function (object_value, comparison_value) {
+    var value, i;
+    if (!Array.isArray(object_value)) {
+      object_value = [object_value];
+    }
+    for (i = 0; i < object_value.length; i += 1) {
+      value = object_value[i];
+      if (typeof value === 'object' && value.hasOwnProperty('content')) {
+        value = value.content;
+      }
+      if (typeof value.cmp === "function") {
+        return (value.cmp(comparison_value) === 0);
+      }
+      if (comparison_value.toString() === value.toString()) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  /**
+   * Comparison operator, test if this query value matches the item value
+   *
+   * @method like
+   * @param  {String} object_value The value to compare
+   * @param  {String} comparison_value The comparison value
+   * @return {Boolean} true if match, false otherwise
+   */
+  SimpleQuery.prototype.like = function (object_value, comparison_value) {
+    var value, i;
+    if (!Array.isArray(object_value)) {
+      object_value = [object_value];
+    }
+    for (i = 0; i < object_value.length; i += 1) {
+      value = object_value[i];
+      if (typeof value === 'object' && value.hasOwnProperty('content')) {
+        value = value.content;
+      }
+      if (typeof value.cmp === "function") {
+        return (value.cmp(comparison_value) === 0);
+      }
+      if (
+        searchTextToRegExp(comparison_value.toString()).test(value.toString())
+      ) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  /**
+   * Comparison operator, test if this query value does not match the item value
+   *
+   * @method !=
+   * @param  {String} object_value The value to compare
+   * @param  {String} comparison_value The comparison value
+   * @return {Boolean} true if not match, false otherwise
+   */
+  SimpleQuery.prototype["!="] = function (object_value, comparison_value) {
+    var value, i;
+    if (!Array.isArray(object_value)) {
+      object_value = [object_value];
+    }
+    for (i = 0; i < object_value.length; i += 1) {
+      value = object_value[i];
+      if (typeof value === 'object' && value.hasOwnProperty('content')) {
+        value = value.content;
+      }
+      if (typeof value.cmp === "function") {
+        return (value.cmp(comparison_value) !== 0);
+      }
+      if (comparison_value.toString() === value.toString()) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  /**
+   * Comparison operator, test if this query value is lower than the item value
+   *
+   * @method <
+   * @param  {Number, String} object_value The value to compare
+   * @param  {Number, String} comparison_value The comparison value
+   * @return {Boolean} true if lower, false otherwise
+   */
+  SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
+    var value;
+    if (!Array.isArray(object_value)) {
+      object_value = [object_value];
+    }
+    value = object_value[0];
+    if (typeof value === 'object' && value.hasOwnProperty('content')) {
+      value = value.content;
+    }
+    if (typeof value.cmp === "function") {
+      return (value.cmp(comparison_value) < 0);
+    }
+    return (value < comparison_value);
+  };
+
+  /**
+   * Comparison operator, test if this query value is equal or lower than the
+   * item value
+   *
+   * @method <=
+   * @param  {Number, String} object_value The value to compare
+   * @param  {Number, String} comparison_value The comparison value
+   * @return {Boolean} true if equal or lower, false otherwise
+   */
+  SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
+    var value;
+    if (!Array.isArray(object_value)) {
+      object_value = [object_value];
+    }
+    value = object_value[0];
+    if (typeof value === 'object' && value.hasOwnProperty('content')) {
+      value = value.content;
+    }
+    if (typeof value.cmp === "function") {
+      return (value.cmp(comparison_value) <= 0);
+    }
+    return (value <= comparison_value);
+  };
+
+  /**
+   * Comparison operator, test if this query value is greater than the item
+   * value
+   *
+   * @method >
+   * @param  {Number, String} object_value The value to compare
+   * @param  {Number, String} comparison_value The comparison value
+   * @return {Boolean} true if greater, false otherwise
+   */
+  SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
+    var value;
+    if (!Array.isArray(object_value)) {
+      object_value = [object_value];
+    }
+    value = object_value[0];
+    if (typeof value === 'object' && value.hasOwnProperty('content')) {
+      value = value.content;
+    }
+    if (typeof value.cmp === "function") {
+      return (value.cmp(comparison_value) > 0);
+    }
+    return (value > comparison_value);
+  };
+
+  /**
+   * Comparison operator, test if this query value is equal or greater than the
+   * item value
+   *
+   * @method >=
+   * @param  {Number, String} object_value The value to compare
+   * @param  {Number, String} comparison_value The comparison value
+   * @return {Boolean} true if equal or greater, false otherwise
+   */
+  SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
+    var value;
+    if (!Array.isArray(object_value)) {
+      object_value = [object_value];
+    }
+    value = object_value[0];
+    if (typeof value === 'object' && value.hasOwnProperty('content')) {
+      value = value.content;
+    }
+    if (typeof value.cmp === "function") {
+      return (value.cmp(comparison_value) >= 0);
+    }
+    return (value >= comparison_value);
+  };
+
+  query_class_dict.simple = SimpleQuery;
+  query_class_dict.complex = ComplexQuery;
+
+  Query.parseStringToObject = parseStringToObject;
+  Query.objectToSearchText = objectToSearchText;
+
+  window.Query = Query;
+  window.SimpleQuery = SimpleQuery;
+  window.ComplexQuery = ComplexQuery;
+  window.QueryFactory = QueryFactory;
+
+}(RSVP, window, parseStringToObject));
+;/*global window, moment */
+/*jslint nomen: true, maxlen: 200*/
+(function (window, moment) {
+  "use strict";
+
+//   /**
+//    * Add a secured (write permission denied) property to an object.
+//    *
+//    * @param  {Object} object The object to fill
+//    * @param  {String} key The object key where to store the property
+//    * @param  {Any} value The value to store
+//    */
+//   function _export(key, value) {
+//     Object.defineProperty(to_export, key, {
+//       "configurable": false,
+//       "enumerable": true,
+//       "writable": false,
+//       "value": value
+//     });
+//   }
+
+  var YEAR = 'year',
+    MONTH = 'month',
+    DAY = 'day',
+    HOUR = 'hour',
+    MIN = 'minute',
+    SEC = 'second',
+    MSEC = 'millisecond',
+    precision_grade = {
+      'year': 0,
+      'month': 1,
+      'day': 2,
+      'hour': 3,
+      'minute': 4,
+      'second': 5,
+      'millisecond': 6
+    },
+    lesserPrecision = function (p1, p2) {
+      return (precision_grade[p1] < precision_grade[p2]) ? p1 : p2;
+    },
+    JIODate;
+
+
+  JIODate = function (str) {
+    // in case of forgotten 'new'
+    if (!(this instanceof JIODate)) {
+      return new JIODate(str);
+    }
+
+    if (str instanceof JIODate) {
+      this.mom = str.mom.clone();
+      this._precision = str._precision;
+      return;
+    }
+
+    if (str === undefined) {
+      this.mom = moment();
+      this.setPrecision(MSEC);
+      return;
+    }
+
+    this.mom = null;
+    this._str = str;
+
+    // http://www.w3.org/TR/NOTE-datetime
+    // http://dotat.at/tmp/ISO_8601-2004_E.pdf
+
+    // XXX these regexps fail to detect many invalid dates.
+
+    if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+\-][0-2]\d:[0-5]\d|Z)/)
+          || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d/)) {
+      // ISO, milliseconds
+      this.mom = moment(str);
+      this.setPrecision(MSEC);
+    } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/)
+          || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/)) {
+      // ISO, seconds
+      this.mom = moment(str);
+      this.setPrecision(SEC);
+    } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/)
+          || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d/)) {
+      // ISO, minutes
+      this.mom = moment(str);
+      this.setPrecision(MIN);
+    } else if (str.match(/\d\d\d\d-\d\d-\d\d \d\d/)) {
+      this.mom = moment(str);
+      this.setPrecision(HOUR);
+    } else if (str.match(/\d\d\d\d-\d\d-\d\d/)) {
+      this.mom = moment(str);
+      this.setPrecision(DAY);
+    } else if (str.match(/\d\d\d\d-\d\d/)) {
+      this.mom = moment(str);
+      this.setPrecision(MONTH);
+    } else if (str.match(/\d\d\d\d/)) {
+      // Creating a moment with only the year will show this deprecation
+      // warning:
+      //
+      // Deprecation warning: moment construction falls back to js Date. This is
+      // discouraged and will be removed in upcoming major release. Please refer
+      // to https://github.com/moment/moment/issues/1407 for more info.
+      //
+      // TL;DR: parsing year-only strings with momentjs falls back to native
+      // Date and it won't correctly represent the year in local time if UTF
+      // offset is negative.
+      //
+      // The solution is to use the format parameter, so momentjs won't fall
+      // back to the native Date and we will have the correct year in local
+      // time.
+      //
+      this.mom = moment(str, 'YYYY');
+      this.setPrecision(YEAR);
+    }
+
+    if (!this.mom) {
+      throw new Error("Cannot parse: " + str);
+    }
+
+  };
+
+
+  JIODate.prototype.setPrecision = function (prec) {
+    this._precision = prec;
+  };
+
+
+  JIODate.prototype.getPrecision = function () {
+    return this._precision;
+  };
+
+
+  JIODate.prototype.cmp = function (other) {
+    var m1 = this.mom,
+      m2 = other.mom,
+      p = lesserPrecision(this._precision, other._precision);
+    return m1.isBefore(m2, p) ? -1 : (m1.isSame(m2, p) ? 0 : +1);
+  };
+
+
+  JIODate.prototype.toPrecisionString = function (precision) {
+    var fmt;
+
+    precision = precision || this._precision;
+
+    fmt = {
+      'millisecond': 'YYYY-MM-DD HH:mm:ss.SSS',
+      'second': 'YYYY-MM-DD HH:mm:ss',
+      'minute': 'YYYY-MM-DD HH:mm',
+      'hour': 'YYYY-MM-DD HH',
+      'day': 'YYYY-MM-DD',
+      'month': 'YYYY-MM',
+      'year': 'YYYY'
+    }[precision];
+
+    if (!fmt) {
+      throw new TypeError("Unsupported precision value '" + precision + "'");
+    }
+
+    return this.mom.format(fmt);
+  };
+
+
+  JIODate.prototype.toString = function () {
+    return this._str;
+  };
+
+
+//   _export('JIODate', JIODate);
+// 
+//   _export('YEAR', YEAR);
+//   _export('MONTH', MONTH);
+//   _export('DAY', DAY);
+//   _export('HOUR', HOUR);
+//   _export('MIN', MIN);
+//   _export('SEC', SEC);
+//   _export('MSEC', MSEC);
+
+  window.jiodate = {
+    JIODate: JIODate,
+    YEAR: YEAR,
+    MONTH: MONTH,
+    DAY: DAY,
+    HOUR: HOUR,
+    MIN: MIN,
+    SEC: SEC,
+    MSEC: MSEC
+  };
+}(window, moment));
+;/*global window, RSVP, Blob, XMLHttpRequest, QueryFactory, Query, atob,
+  FileReader, ArrayBuffer, Uint8Array */
+(function (window, RSVP, Blob, QueryFactory, Query, atob,
+           FileReader, ArrayBuffer, Uint8Array) {
+  "use strict";
+
+  var util = {},
+    jIO;
+
+  function jIOError(message, status_code) {
+    if ((message !== undefined) && (typeof message !== "string")) {
+      throw new TypeError('You must pass a string.');
+    }
+    this.message = message || "Default Message";
+    this.status_code = status_code || 500;
+  }
+  jIOError.prototype = new Error();
+  jIOError.prototype.constructor = jIOError;
+  util.jIOError = jIOError;
+
+  /**
+   * Send request with XHR and return a promise. xhr.onload: The promise is
+   * resolved when the status code is lower than 400 with the xhr object as
+   * first parameter. xhr.onerror: reject with xhr object as first
+   * parameter. xhr.onprogress: notifies the xhr object.
+   *
+   * @param  {Object} param The parameters
+   * @param  {String} [param.type="GET"] The request method
+   * @param  {String} [param.dataType=""] The data type to retrieve
+   * @param  {String} param.url The url
+   * @param  {Any} [param.data] The data to send
+   * @param  {Function} [param.beforeSend] A function called just before the
+   *    send request. The first parameter of this function is the XHR object.
+   * @return {Promise} The promise
+   */
+  function ajax(param) {
+    var xhr = new XMLHttpRequest();
+    return new RSVP.Promise(function (resolve, reject, notify) {
+      var k;
+      xhr.open(param.type || "GET", param.url, true);
+      xhr.responseType = param.dataType || "";
+      if (typeof param.headers === 'object' && param.headers !== null) {
+        for (k in param.headers) {
+          if (param.headers.hasOwnProperty(k)) {
+            xhr.setRequestHeader(k, param.headers[k]);
+          }
+        }
+      }
+      xhr.addEventListener("load", function (e) {
+        if (e.target.status >= 400) {
+          return reject(e);
+        }
+        resolve(e);
+      });
+      xhr.addEventListener("error", reject);
+      xhr.addEventListener("progress", notify);
+      if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
+        for (k in param.xhrFields) {
+          if (param.xhrFields.hasOwnProperty(k)) {
+            xhr[k] = param.xhrFields[k];
+          }
+        }
+      }
+      if (typeof param.beforeSend === 'function') {
+        param.beforeSend(xhr);
+      }
+      xhr.send(param.data);
+    }, function () {
+      xhr.abort();
+    });
+  }
+  util.ajax = ajax;
+
+  function readBlobAsText(blob, encoding) {
+    var fr = new FileReader();
+    return new RSVP.Promise(function (resolve, reject, notify) {
+      fr.addEventListener("load", resolve);
+      fr.addEventListener("error", reject);
+      fr.addEventListener("progress", notify);
+      fr.readAsText(blob, encoding);
+    }, function () {
+      fr.abort();
+    });
+  }
+  util.readBlobAsText = readBlobAsText;
+
+  function readBlobAsArrayBuffer(blob) {
+    var fr = new FileReader();
+    return new RSVP.Promise(function (resolve, reject, notify) {
+      fr.addEventListener("load", resolve);
+      fr.addEventListener("error", reject);
+      fr.addEventListener("progress", notify);
+      fr.readAsArrayBuffer(blob);
+    }, function () {
+      fr.abort();
+    });
+  }
+  util.readBlobAsArrayBuffer = readBlobAsArrayBuffer;
+
+  function readBlobAsDataURL(blob) {
+    var fr = new FileReader();
+    return new RSVP.Promise(function (resolve, reject, notify) {
+      fr.addEventListener("load", resolve);
+      fr.addEventListener("error", reject);
+      fr.addEventListener("progress", notify);
+      fr.readAsDataURL(blob);
+    }, function () {
+      fr.abort();
+    });
+  }
+  util.readBlobAsDataURL = readBlobAsDataURL;
+
+  // https://gist.github.com/davoclavo/4424731
+  function dataURItoBlob(dataURI) {
+    // convert base64 to raw binary data held in a string
+    var byteString = atob(dataURI.split(',')[1]),
+    // separate out the mime component
+      mimeString = dataURI.split(',')[0].split(':')[1],
+    // write the bytes of the string to an ArrayBuffer
+      arrayBuffer = new ArrayBuffer(byteString.length),
+      _ia = new Uint8Array(arrayBuffer),
+      i;
+    mimeString = mimeString.slice(0, mimeString.length - ";base64".length);
+    for (i = 0; i < byteString.length; i += 1) {
+      _ia[i] = byteString.charCodeAt(i);
+    }
+    return new Blob([arrayBuffer], {type: mimeString});
+  }
+
+  util.dataURItoBlob = dataURItoBlob;
+
+  // tools
+  function checkId(argument_list, storage, method_name) {
+    if (typeof argument_list[0] !== 'string' || argument_list[0] === '') {
+      throw new jIO.util.jIOError(
+        "Document id must be a non empty string on '" + storage.__type +
+          "." + method_name + "'.",
+        400
+      );
+    }
+  }
+
+  function checkAttachmentId(argument_list, storage, method_name) {
+    if (typeof argument_list[1] !== 'string' || argument_list[1] === '') {
+      throw new jIO.util.jIOError(
+        "Attachment id must be a non empty string on '" + storage.__type +
+          "." + method_name + "'.",
+        400
+      );
+    }
+  }
+
+  function declareMethod(klass, name, precondition_function, post_function) {
+    klass.prototype[name] = function () {
+      var argument_list = arguments,
+        context = this,
+        precondition_result;
+
+      return new RSVP.Queue()
+        .push(function () {
+          if (precondition_function !== undefined) {
+            return precondition_function.apply(
+              context.__storage,
+              [argument_list, context, name]
+            );
+          }
+        })
+        .push(function (result) {
+          var storage_method = context.__storage[name];
+          precondition_result = result;
+          if (storage_method === undefined) {
+            throw new jIO.util.jIOError(
+              "Capacity '" + name + "' is not implemented on '" +
+                context.__type + "'",
+              501
+            );
+          }
+          return storage_method.apply(
+            context.__storage,
+            argument_list
+          );
+        })
+        .push(function (result) {
+          if (post_function !== undefined) {
+            return post_function.call(
+              context,
+              argument_list,
+              result,
+              precondition_result
+            );
+          }
+          return result;
+        });
+    };
+    // Allow chain
+    return this;
+  }
+
+
+
+
+  /////////////////////////////////////////////////////////////////
+  // jIO Storage Proxy
+  /////////////////////////////////////////////////////////////////
+  function JioProxyStorage(type, storage) {
+    if (!(this instanceof JioProxyStorage)) {
+      return new JioProxyStorage();
+    }
+    this.__type = type;
+    this.__storage = storage;
+  }
+
+  declareMethod(JioProxyStorage, "put", checkId, function (argument_list) {
+    return argument_list[0];
+  });
+  declareMethod(JioProxyStorage, "get", checkId);
+  declareMethod(JioProxyStorage, "bulk");
+  declareMethod(JioProxyStorage, "remove", checkId, function (argument_list) {
+    return argument_list[0];
+  });
+
+  JioProxyStorage.prototype.post = function () {
+    var context = this,
+      argument_list = arguments;
+    return new RSVP.Queue()
+      .push(function () {
+        var storage_method = context.__storage.post;
+        if (storage_method === undefined) {
+          throw new jIO.util.jIOError(
+            "Capacity 'post' is not implemented on '" + context.__type + "'",
+            501
+          );
+        }
+        return context.__storage.post.apply(context.__storage, argument_list);
+      });
+  };
+
+  declareMethod(JioProxyStorage, 'putAttachment', function (argument_list,
+                                                            storage,
+                                                            method_name) {
+    checkId(argument_list, storage, method_name);
+    checkAttachmentId(argument_list, storage, method_name);
+
+    var options = argument_list[3] || {};
+
+    if (typeof argument_list[2] === 'string') {
+      argument_list[2] = new Blob([argument_list[2]], {
+        "type": options._content_type || options._mimetype ||
+                "text/plain;charset=utf-8"
+      });
+    } else if (!(argument_list[2] instanceof Blob)) {
+      throw new jIO.util.jIOError(
+        'Attachment content is not a blob',
+        400
+      );
+    }
+  });
+
+  declareMethod(JioProxyStorage, 'removeAttachment', function (argument_list,
+                                                               storage,
+                                                               method_name) {
+    checkId(argument_list, storage, method_name);
+    checkAttachmentId(argument_list, storage, method_name);
+  });
+
+  declareMethod(JioProxyStorage, 'getAttachment', function (argument_list,
+                                                            storage,
+                                                            method_name) {
+    var result = "blob";
+//     if (param.storage_spec.type !== "indexeddb" &&
+//         param.storage_spec.type !== "dav" &&
+//         (param.kwargs._start !== undefined
+//          || param.kwargs._end !== undefined)) {
+//       restCommandRejecter(param, [
+//         'bad_request',
+//         'unsupport',
+//         '_start, _end not support'
+//       ]);
+//       return false;
+//     }
+    checkId(argument_list, storage, method_name);
+    checkAttachmentId(argument_list, storage, method_name);
+    // Drop optional parameters, which are only used in postfunction
+    if (argument_list[2] !== undefined) {
+      result = argument_list[2].format || result;
+      delete argument_list[2].format;
+    }
+    return result;
+  }, function (argument_list, blob, convert) {
+    var result;
+    if (!(blob instanceof Blob)) {
+      throw new jIO.util.jIOError(
+        "'getAttachment' (" + argument_list[0] + " , " +
+          argument_list[1] + ") on '" + this.__type +
+          "' does not return a Blob.",
+        501
+      );
+    }
+    if (convert === "blob") {
+      result = blob;
+    } else if (convert === "data_url") {
+      result = new RSVP.Queue()
+        .push(function () {
+          return jIO.util.readBlobAsDataURL(blob);
+        })
+        .push(function (evt) {
+          return evt.target.result;
+        });
+    } else if (convert === "array_buffer") {
+      result = new RSVP.Queue()
+        .push(function () {
+          return jIO.util.readBlobAsArrayBuffer(blob);
+        })
+        .push(function (evt) {
+          return evt.target.result;
+        });
+    } else if (convert === "text") {
+      result = new RSVP.Queue()
+        .push(function () {
+          return jIO.util.readBlobAsText(blob);
+        })
+        .push(function (evt) {
+          return evt.target.result;
+        });
+    } else if (convert === "json") {
+      result = new RSVP.Queue()
+        .push(function () {
+          return jIO.util.readBlobAsText(blob);
+        })
+        .push(function (evt) {
+          return JSON.parse(evt.target.result);
+        });
+    } else {
+      throw new jIO.util.jIOError(
+        this.__type + ".getAttachment format: '" + convert +
+          "' is not supported",
+        400
+      );
+    }
+    return result;
+  });
+
+  JioProxyStorage.prototype.buildQuery = function () {
+    var storage_method = this.__storage.buildQuery,
+      context = this,
+      argument_list = arguments;
+    if (storage_method === undefined) {
+      throw new jIO.util.jIOError(
+        "Capacity 'buildQuery' is not implemented on '" + this.__type + "'",
+        501
+      );
+    }
+    return new RSVP.Queue()
+      .push(function () {
+        return storage_method.apply(
+          context.__storage,
+          argument_list
+        );
+      });
+  };
+
+  JioProxyStorage.prototype.hasCapacity = function (name) {
+    var storage_method = this.__storage.hasCapacity,
+      capacity_method = this.__storage[name];
+    if (capacity_method !== undefined) {
+      return true;
+    }
+    if ((storage_method === undefined) ||
+        !storage_method.apply(this.__storage, arguments)) {
+      throw new jIO.util.jIOError(
+        "Capacity '" + name + "' is not implemented on '" + this.__type + "'",
+        501
+      );
+    }
+    return true;
+  };
+
+  JioProxyStorage.prototype.allDocs = function (options) {
+    var context = this;
+    if (options === undefined) {
+      options = {};
+    }
+    return new RSVP.Queue()
+      .push(function () {
+        if (context.hasCapacity("list") &&
+            ((options.query === undefined) || context.hasCapacity("query")) &&
+            ((options.sort_on === undefined) || context.hasCapacity("sort")) &&
+            ((options.select_list === undefined) ||
+             context.hasCapacity("select")) &&
+            ((options.include_docs === undefined) ||
+             context.hasCapacity("include")) &&
+            ((options.limit === undefined) || context.hasCapacity("limit"))) {
+          return context.buildQuery(options);
+        }
+      })
+      .push(function (result) {
+        return {
+          data: {
+            rows: result,
+            total_rows: result.length
+          }
+        };
+      });
+  };
+
+  declareMethod(JioProxyStorage, "allAttachments", checkId);
+  declareMethod(JioProxyStorage, "repair");
+
+  JioProxyStorage.prototype.repair = function () {
+    var context = this,
+      argument_list = arguments;
+    return new RSVP.Queue()
+      .push(function () {
+        var storage_method = context.__storage.repair;
+        if (storage_method !== undefined) {
+          return context.__storage.repair.apply(context.__storage,
+                                                argument_list);
+        }
+      });
+  };
+
+  /////////////////////////////////////////////////////////////////
+  // Storage builder
+  /////////////////////////////////////////////////////////////////
+  function JioBuilder() {
+    if (!(this instanceof JioBuilder)) {
+      return new JioBuilder();
+    }
+    this.__storage_types = {};
+  }
+
+  JioBuilder.prototype.createJIO = function (storage_spec, util) {
+
+    if (typeof storage_spec.type !== 'string') {
+      throw new TypeError("Invalid storage description");
+    }
+    if (!this.__storage_types[storage_spec.type]) {
+      throw new TypeError("Unknown storage '" + storage_spec.type + "'");
+    }
+
+    return new JioProxyStorage(
+      storage_spec.type,
+      new this.__storage_types[storage_spec.type](storage_spec, util)
+    );
+
+  };
+
+  JioBuilder.prototype.addStorage = function (type, Constructor) {
+    if (typeof type !== 'string') {
+      throw new TypeError(
+        "jIO.addStorage(): Argument 1 is not of type 'string'"
+      );
+    }
+    if (typeof Constructor !== 'function') {
+      throw new TypeError("jIO.addStorage(): " +
+                          "Argument 2 is not of type 'function'");
     }
     if (this.__storage_types[type] !== undefined) {
       throw new TypeError("jIO.addStorage(): Storage type already exists");
@@ -6469,7 +8158,7 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
         var ceilHeapSize = function (v) {
             // The asm.js spec says:
             // The heap object's byteLength must be either
-            // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
+            // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
             // Also, byteLengths smaller than 2^16 are deprecated.
             var p;
             // If v is smaller than 2^16, the smallest possible solution
@@ -6900,7 +8589,9 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
                                          id, options);
           }
           // Already exists on destination
-          throw new jIO.util.jIOError("Conflict on '" + id + "'",
+          throw new jIO.util.jIOError("Conflict on '" + id + "': " +
+                                      JSON.stringify(doc) + " !== " +
+                                      JSON.stringify(remote_doc),
                                       409);
         });
     }
@@ -6971,11 +8662,12 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
     }
 
     function checkSignatureDifference(queue, source, destination, id,
-                                      conflict_force, conflict_ignore) {
+                                      conflict_force, conflict_ignore,
+                                      getMethod) {
       queue
         .push(function () {
           return RSVP.all([
-            source.get(id),
+            getMethod(id),
             context._signature_sub_storage.get(id)
           ]);
         })
@@ -7004,7 +8696,9 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
                     return;
                   }
                   if (conflict_force !== true) {
-                    throw new jIO.util.jIOError("Conflict on '" + id + "'",
+                    throw new jIO.util.jIOError("Conflict on '" + id + "': " +
+                                                JSON.stringify(doc) + " !== " +
+                                                JSON.stringify(remote_doc),
                                                 409);
                   }
                 }
@@ -7023,6 +8717,35 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
         });
     }
 
+    function checkBulkSignatureDifference(queue, source, destination, id_list,
+                                          conflict_force, conflict_ignore) {
+      queue
+        .push(function () {
+          return source.bulk(id_list);
+        })
+        .push(function (result_list) {
+          var i,
+            sub_queue = new RSVP.Queue();
+
+          function getResult(j) {
+            return function (id) {
+              if (id !== id_list[j].parameter_list[0]) {
+                throw new Error("Does not access expected ID " + id);
+              }
+              return result_list[j];
+            };
+          }
+
+          for (i = 0; i < result_list.length; i += 1) {
+            checkSignatureDifference(sub_queue, source, destination,
+                               id_list[i].parameter_list[0],
+                               conflict_force, conflict_ignore,
+                               getResult(i));
+          }
+          return sub_queue;
+        });
+    }
+
     function pushStorage(source, destination, options) {
       var queue = new RSVP.Queue();
       if (!options.hasOwnProperty("use_post")) {
@@ -7039,6 +8762,7 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
           var i,
             local_dict = {},
             new_list = [],
+            change_list = [],
             signature_dict = {},
             key;
           for (i = 0; i < result_list[0].data.total_rows; i += 1) {
@@ -7081,9 +8805,17 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
             if (signature_dict.hasOwnProperty(key)) {
               if (local_dict.hasOwnProperty(key)) {
                 if (options.check_modification === true) {
-                  checkSignatureDifference(queue, source, destination, key,
-                                           options.conflict_force,
-                                           options.conflict_ignore);
+                  if (options.use_bulk_get === true) {
+                    change_list.push({
+                      method: "get",
+                      parameter_list: [key]
+                    });
+                  } else {
+                    checkSignatureDifference(queue, source, destination, key,
+                                             options.conflict_force,
+                                             options.conflict_ignore,
+                                             source.get.bind(source));
+                  }
                 }
               } else {
                 if (options.check_deletion === true) {
@@ -7092,6 +8824,12 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
               }
             }
           }
+          if ((options.use_bulk_get === true) && (change_list.length !== 0)) {
+            checkBulkSignatureDifference(queue, source, destination,
+                                         change_list,
+                                         options.conflict_force,
+                                         options.conflict_ignore);
+          }
         });
     }
 
@@ -7181,300 +8919,688 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
       });
   };
 
-  jIO.addStorage('replicate', ReplicateStorage);
+  jIO.addStorage('replicate', ReplicateStorage);
+
+}(jIO, RSVP, Rusha));
+;/*
+ * Copyright 2015, Nexedi SA
+ * Released under the LGPL license.
+ * http://www.gnu.org/licenses/lgpl.html
+ */
+
+/*jslint nomen: true*/
+/*global Rusha*/
+
+/**
+ * JIO Sha Storage. Type = 'sha'.
+ */
+
+(function (Rusha) {
+  "use strict";
+
+  var rusha = new Rusha();
+
+  function ShaStorage(spec) {
+    this._sub_storage = jIO.createJIO(spec.sub_storage);
+  }
+
+  ShaStorage.prototype.post = function (param) {
+    return this._sub_storage.put(
+      rusha.digestFromString(JSON.stringify(param)),
+      param
+    );
+  };
+
+  ShaStorage.prototype.get = function () {
+    return this._sub_storage.get.apply(this._sub_storage, arguments);
+  };
+  ShaStorage.prototype.remove = function () {
+    return this._sub_storage.remove.apply(this._sub_storage, arguments);
+  };
+  ShaStorage.prototype.hasCapacity = function () {
+    return this._sub_storage.hasCapacity.apply(this._sub_storage, arguments);
+  };
+  ShaStorage.prototype.buildQuery = function () {
+    return this._sub_storage.buildQuery.apply(this._sub_storage, arguments);
+  };
+  ShaStorage.prototype.getAttachment = function () {
+    return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
+  };
+  ShaStorage.prototype.putAttachment = function () {
+    return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+  };
+  ShaStorage.prototype.removeAttachment = function () {
+    return this._sub_storage.removeAttachment.apply(this._sub_storage,
+                                                    arguments);
+  };
+  ShaStorage.prototype.allAttachments = function () {
+    return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
+  };
+  ShaStorage.prototype.repair = function () {
+    return this._sub_storage.repair.apply(this._sub_storage, arguments);
+  };
+
+  jIO.addStorage('sha', ShaStorage);
+
+}(Rusha));
+;/*jslint nomen: true*/
+(function (jIO) {
+  "use strict";
+
+  /**
+   * The jIO UUIDStorage extension
+   *
+   * @class UUIDStorage
+   * @constructor
+   */
+  function UUIDStorage(spec) {
+    this._sub_storage = jIO.createJIO(spec.sub_storage);
+  }
+
+  UUIDStorage.prototype.get = function () {
+    return this._sub_storage.get.apply(this._sub_storage, arguments);
+  };
+  UUIDStorage.prototype.allAttachments = function () {
+    return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
+  };
+  UUIDStorage.prototype.post = function (param) {
+
+    function S4() {
+      return ('0000' + Math.floor(
+        Math.random() * 0x10000 /* 65536 */
+      ).toString(16)).slice(-4);
+    }
+
+    var id = S4() + S4() + "-" +
+      S4() + "-" +
+      S4() + "-" +
+      S4() + "-" +
+      S4() + S4() + S4();
+
+    return this.put(id, param);
+  };
+  UUIDStorage.prototype.put = function () {
+    return this._sub_storage.put.apply(this._sub_storage, arguments);
+  };
+  UUIDStorage.prototype.remove = function () {
+    return this._sub_storage.remove.apply(this._sub_storage, arguments);
+  };
+  UUIDStorage.prototype.getAttachment = function () {
+    return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
+  };
+  UUIDStorage.prototype.putAttachment = function () {
+    return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+  };
+  UUIDStorage.prototype.removeAttachment = function () {
+    return this._sub_storage.removeAttachment.apply(this._sub_storage,
+                                                    arguments);
+  };
+  UUIDStorage.prototype.repair = function () {
+    return this._sub_storage.repair.apply(this._sub_storage, arguments);
+  };
+  UUIDStorage.prototype.hasCapacity = function (name) {
+    return this._sub_storage.hasCapacity(name);
+  };
+  UUIDStorage.prototype.buildQuery = function () {
+    return this._sub_storage.buildQuery.apply(this._sub_storage,
+                                              arguments);
+  };
+
+  jIO.addStorage('uuid', UUIDStorage);
 
-}(jIO, RSVP, Rusha));
+}(jIO));
 ;/*
- * Copyright 2015, Nexedi SA
+ * Copyright 2013, Nexedi SA
  * Released under the LGPL license.
  * http://www.gnu.org/licenses/lgpl.html
  */
 
 /*jslint nomen: true*/
-/*global Rusha*/
+/*global jIO, RSVP*/
 
 /**
- * JIO Sha Storage. Type = 'sha'.
+ * JIO Memory Storage. Type = 'memory'.
+ * Memory browser "database" storage.
+ *
+ * Storage Description:
+ *
+ *     {
+ *       "type": "memory"
+ *     }
+ *
+ * @class MemoryStorage
  */
 
-(function (Rusha) {
+(function (jIO, JSON, RSVP) {
   "use strict";
 
-  var rusha = new Rusha();
-
-  function ShaStorage(spec) {
-    this._sub_storage = jIO.createJIO(spec.sub_storage);
+  /**
+   * The JIO MemoryStorage extension
+   *
+   * @class MemoryStorage
+   * @constructor
+   */
+  function MemoryStorage() {
+    this._database = {};
   }
 
-  ShaStorage.prototype.post = function (param) {
-    return this._sub_storage.put(
-      rusha.digestFromString(JSON.stringify(param)),
-      param
-    );
+  MemoryStorage.prototype.put = function (id, metadata) {
+    if (!this._database.hasOwnProperty(id)) {
+      this._database[id] = {
+        attachments: {}
+      };
+    }
+    this._database[id].doc = JSON.stringify(metadata);
+    return id;
   };
 
-  ShaStorage.prototype.get = function () {
-    return this._sub_storage.get.apply(this._sub_storage, arguments);
-  };
-  ShaStorage.prototype.remove = function () {
-    return this._sub_storage.remove.apply(this._sub_storage, arguments);
+  MemoryStorage.prototype.get = function (id) {
+    try {
+      return JSON.parse(this._database[id].doc);
+    } catch (error) {
+      if (error instanceof TypeError) {
+        throw new jIO.util.jIOError(
+          "Cannot find document: " + id,
+          404
+        );
+      }
+      throw error;
+    }
   };
-  ShaStorage.prototype.hasCapacity = function () {
-    return this._sub_storage.hasCapacity.apply(this._sub_storage, arguments);
+
+  MemoryStorage.prototype.allAttachments = function (id) {
+    var key,
+      attachments = {};
+    try {
+      for (key in this._database[id].attachments) {
+        if (this._database[id].attachments.hasOwnProperty(key)) {
+          attachments[key] = {};
+        }
+      }
+    } catch (error) {
+      if (error instanceof TypeError) {
+        throw new jIO.util.jIOError(
+          "Cannot find document: " + id,
+          404
+        );
+      }
+      throw error;
+    }
+    return attachments;
   };
-  ShaStorage.prototype.buildQuery = function () {
-    return this._sub_storage.buildQuery.apply(this._sub_storage, arguments);
+
+  MemoryStorage.prototype.remove = function (id) {
+    delete this._database[id];
+    return id;
   };
-  ShaStorage.prototype.getAttachment = function () {
-    return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
+
+  MemoryStorage.prototype.getAttachment = function (id, name) {
+    try {
+      var result = this._database[id].attachments[name];
+      if (result === undefined) {
+        throw new jIO.util.jIOError(
+          "Cannot find attachment: " + id + " , " + name,
+          404
+        );
+      }
+      return jIO.util.dataURItoBlob(result);
+    } catch (error) {
+      if (error instanceof TypeError) {
+        throw new jIO.util.jIOError(
+          "Cannot find attachment: " + id + " , " + name,
+          404
+        );
+      }
+      throw error;
+    }
   };
-  ShaStorage.prototype.putAttachment = function () {
-    return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+
+  MemoryStorage.prototype.putAttachment = function (id, name, blob) {
+    var attachment_dict;
+    try {
+      attachment_dict = this._database[id].attachments;
+    } catch (error) {
+      if (error instanceof TypeError) {
+        throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+      }
+      throw error;
+    }
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.readBlobAsDataURL(blob);
+      })
+      .push(function (evt) {
+        attachment_dict[name] = evt.target.result;
+      });
   };
-  ShaStorage.prototype.removeAttachment = function () {
-    return this._sub_storage.removeAttachment.apply(this._sub_storage,
-                                                    arguments);
+
+  MemoryStorage.prototype.removeAttachment = function (id, name) {
+    try {
+      delete this._database[id].attachments[name];
+    } catch (error) {
+      if (error instanceof TypeError) {
+        throw new jIO.util.jIOError(
+          "Cannot find document: " + id,
+          404
+        );
+      }
+      throw error;
+    }
   };
-  ShaStorage.prototype.allAttachments = function () {
-    return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
+
+
+  MemoryStorage.prototype.hasCapacity = function (name) {
+    return ((name === "list") || (name === "include"));
   };
-  ShaStorage.prototype.repair = function () {
-    return this._sub_storage.repair.apply(this._sub_storage, arguments);
+
+  MemoryStorage.prototype.buildQuery = function (options) {
+    var rows = [],
+      i;
+    for (i in this._database) {
+      if (this._database.hasOwnProperty(i)) {
+        if (options.include_docs === true) {
+          rows.push({
+            id: i,
+            value: {},
+            doc: JSON.parse(this._database[i].doc)
+          });
+        } else {
+          rows.push({
+            id: i,
+            value: {}
+          });
+        }
+
+      }
+    }
+    return rows;
   };
 
-  jIO.addStorage('sha', ShaStorage);
+  jIO.addStorage('memory', MemoryStorage);
 
-}(Rusha));
+}(jIO, JSON, RSVP));
 ;/*jslint nomen: true*/
-(function (jIO) {
+/*global RSVP, Blob, LZString, DOMException*/
+(function (RSVP, Blob, LZString, DOMException) {
   "use strict";
 
   /**
-   * The jIO UUIDStorage extension
+   * The jIO ZipStorage extension
    *
-   * @class UUIDStorage
+   * @class ZipStorage
    * @constructor
    */
-  function UUIDStorage(spec) {
+
+  var MIME_TYPE = "application/x-jio-utf16_lz_string";
+
+  function ZipStorage(spec) {
     this._sub_storage = jIO.createJIO(spec.sub_storage);
   }
 
-  UUIDStorage.prototype.get = function () {
-    return this._sub_storage.get.apply(this._sub_storage, arguments);
+  ZipStorage.prototype.get = function () {
+    return this._sub_storage.get.apply(this._sub_storage,
+                                        arguments);
   };
-  UUIDStorage.prototype.allAttachments = function () {
-    return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
+
+  ZipStorage.prototype.post = function () {
+    return this._sub_storage.post.apply(this._sub_storage,
+                                        arguments);
+  };
+
+  ZipStorage.prototype.put = function () {
+    return this._sub_storage.put.apply(this._sub_storage,
+                                       arguments);
+  };
+
+  ZipStorage.prototype.remove = function () {
+    return this._sub_storage.remove.apply(this._sub_storage,
+                                          arguments);
+  };
+
+  ZipStorage.prototype.hasCapacity = function () {
+    return this._sub_storage.hasCapacity.apply(this._sub_storage,
+                                               arguments);
+  };
+
+  ZipStorage.prototype.buildQuery = function () {
+    return this._sub_storage.buildQuery.apply(this._sub_storage,
+                                              arguments);
+  };
+
+  ZipStorage.prototype.getAttachment = function (id, name) {
+    var that = this;
+    return that._sub_storage.getAttachment(id, name)
+      .push(function (blob) {
+        if (blob.type !== MIME_TYPE) {
+          return blob;
+        }
+        return new RSVP.Queue()
+          .push(function () {
+            return jIO.util.readBlobAsText(blob, 'utf16');
+          })
+          .push(function (evt) {
+            var result =
+              LZString.decompressFromUTF16(evt.target.result);
+            if (result === '') {
+              return blob;
+            }
+            try {
+              return jIO.util.dataURItoBlob(
+                result
+              );
+            } catch (error) {
+              if (error instanceof DOMException) {
+                return blob;
+              }
+              throw error;
+            }
+          });
+      });
   };
-  UUIDStorage.prototype.post = function (param) {
-
-    function S4() {
-      return ('0000' + Math.floor(
-        Math.random() * 0x10000 /* 65536 */
-      ).toString(16)).slice(-4);
-    }
 
-    var id = S4() + S4() + "-" +
-      S4() + "-" +
-      S4() + "-" +
-      S4() + "-" +
-      S4() + S4() + S4();
+  function myEndsWith(str, query) {
+    return (str.indexOf(query) === str.length - query.length);
+  }
 
-    return this.put(id, param);
-  };
-  UUIDStorage.prototype.put = function () {
-    return this._sub_storage.put.apply(this._sub_storage, arguments);
-  };
-  UUIDStorage.prototype.remove = function () {
-    return this._sub_storage.remove.apply(this._sub_storage, arguments);
-  };
-  UUIDStorage.prototype.getAttachment = function () {
-    return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
-  };
-  UUIDStorage.prototype.putAttachment = function () {
-    return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+  ZipStorage.prototype.putAttachment = function (id, name, blob) {
+    var that = this;
+    if ((blob.type.indexOf("text/") === 0) || myEndsWith(blob.type, "xml") ||
+        myEndsWith(blob.type, "json")) {
+      return new RSVP.Queue()
+        .push(function () {
+          return jIO.util.readBlobAsDataURL(blob);
+        })
+        .push(function (data) {
+          var result = LZString.compressToUTF16(data.target.result);
+          blob = new Blob([result],
+                          {type: MIME_TYPE});
+          return that._sub_storage.putAttachment(id, name, blob);
+        });
+    }
+    return this._sub_storage.putAttachment.apply(this._sub_storage,
+                                                 arguments);
   };
-  UUIDStorage.prototype.removeAttachment = function () {
+
+  ZipStorage.prototype.removeAttachment = function () {
     return this._sub_storage.removeAttachment.apply(this._sub_storage,
                                                     arguments);
   };
-  UUIDStorage.prototype.repair = function () {
-    return this._sub_storage.repair.apply(this._sub_storage, arguments);
-  };
-  UUIDStorage.prototype.hasCapacity = function (name) {
-    return this._sub_storage.hasCapacity(name);
-  };
-  UUIDStorage.prototype.buildQuery = function () {
-    return this._sub_storage.buildQuery.apply(this._sub_storage,
-                                              arguments);
-  };
 
-  jIO.addStorage('uuid', UUIDStorage);
+  ZipStorage.prototype.allAttachments = function () {
+    return this._sub_storage.allAttachments.apply(this._sub_storage,
+                                                  arguments);
+  };
 
-}(jIO));
+  jIO.addStorage('zip', ZipStorage);
+}(RSVP, Blob, LZString, DOMException));
 ;/*
  * Copyright 2013, Nexedi SA
  * Released under the LGPL license.
  * http://www.gnu.org/licenses/lgpl.html
  */
-
-/*jslint nomen: true*/
-/*global jIO*/
-
 /**
- * JIO Memory Storage. Type = 'memory'.
- * Memory browser "database" storage.
- *
- * Storage Description:
- *
- *     {
- *       "type": "memory"
- *     }
- *
- * @class MemoryStorage
+ * JIO Dropbox Storage. Type = "dropbox".
+ * Dropbox "database" storage.
  */
+/*global Blob, jIO, RSVP, UriTemplate*/
+/*jslint nomen: true*/
 
-(function (jIO) {
+(function (jIO, RSVP, Blob, UriTemplate) {
   "use strict";
+  var UPLOAD_URL = "https://content.dropboxapi.com/1/files_put/" +
+      "{+root}{+id}{+name}{?access_token}",
+    upload_template = UriTemplate.parse(UPLOAD_URL),
+    CREATE_DIR_URL = "https://api.dropboxapi.com/1/fileops/create_folder" +
+      "{?access_token,root,path}",
+    create_dir_template = UriTemplate.parse(CREATE_DIR_URL),
+    REMOVE_URL = "https://api.dropboxapi.com/1/fileops/delete/" +
+      "{?access_token,root,path}",
+    remote_template = UriTemplate.parse(REMOVE_URL),
+    GET_URL = "https://content.dropboxapi.com/1/files" +
+      "{/root,id}{+name}{?access_token}",
+    get_template = UriTemplate.parse(GET_URL),
+    //LIST_URL = 'https://api.dropboxapi.com/1/metadata/sandbox/';
+    METADATA_URL = "https://api.dropboxapi.com/1/metadata" +
+      "{/root}{+id}{?access_token}",
+    metadata_template = UriTemplate.parse(METADATA_URL);
+
+  function restrictDocumentId(id) {
+    if (id.indexOf("/") !== 0) {
+      throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)",
+                                  400);
+    }
+    if (id.lastIndexOf("/") !== (id.length - 1)) {
+      throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)",
+                                  400);
+    }
+    return id;
+  }
+
+  function restrictAttachmentId(id) {
+    if (id.indexOf("/") !== -1) {
+      throw new jIO.util.jIOError("attachment " + id + " is forbidden",
+                                  400);
+    }
+  }
 
   /**
-   * The JIO MemoryStorage extension
+   * The JIO Dropbox Storage extension
    *
-   * @class MemoryStorage
+   * @class DropboxStorage
    * @constructor
    */
-  function MemoryStorage() {
-    this._database = {};
+  function DropboxStorage(spec) {
+    if (typeof spec.access_token !== 'string' || !spec.access_token) {
+      throw new TypeError("Access Token' must be a string " +
+                          "which contains more than one character.");
+    }
+    if (typeof spec.root !== 'string' || !spec.root ||
+        (spec.root !== "dropbox" && spec.root !== "sandbox")) {
+      throw new TypeError("root must be 'dropbox' or 'sandbox'");
+    }
+    this._access_token = spec.access_token;
+    this._root = spec.root;
   }
 
-  MemoryStorage.prototype.put = function (id, metadata) {
-    if (!this._database.hasOwnProperty(id)) {
-      this._database[id] = {
-        attachments: {}
-      };
+  DropboxStorage.prototype.put = function (id, param) {
+    var that = this;
+    id = restrictDocumentId(id);
+    if (Object.getOwnPropertyNames(param).length > 0) {
+      // Reject if param has some properties
+      throw new jIO.util.jIOError("Can not store properties: " +
+                                  Object.getOwnPropertyNames(param), 400);
     }
-    this._database[id].doc = metadata;
-    return id;
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          type: "POST",
+          url: create_dir_template.expand({
+            access_token: that._access_token,
+            root: that._root,
+            path: id
+          })
+        });
+      })
+      .push(undefined, function (err) {
+        if ((err.target !== undefined) &&
+            (err.target.status === 405)) {
+          // Directory already exists, no need to fail
+          return;
+        }
+        throw err;
+      });
   };
 
-  MemoryStorage.prototype.get = function (id) {
-    try {
-      return this._database[id].doc;
-    } catch (error) {
-      if (error instanceof TypeError) {
-        throw new jIO.util.jIOError(
-          "Cannot find document: " + id,
-          404
-        );
-      }
-      throw error;
+  DropboxStorage.prototype.remove = function (id) {
+    id = restrictDocumentId(id);
+    return jIO.util.ajax({
+      type: "POST",
+      url: remote_template.expand({
+        access_token: this._access_token,
+        root: this._root,
+        path: id
+      })
+    });
+  };
+
+  DropboxStorage.prototype.get = function (id) {
+    var that = this;
+
+    if (id === "/") {
+      return {};
     }
+    id = restrictDocumentId(id);
+
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          type: "GET",
+          url: metadata_template.expand({
+            access_token: that._access_token,
+            root: that._root,
+            id: id
+          })
+        });
+      })
+      .push(function (evt) {
+        var obj = JSON.parse(evt.target.response ||
+                             evt.target.responseText);
+        if (obj.is_dir) {
+          return {};
+        }
+        throw new jIO.util.jIOError("Not a directory: " + id, 404);
+      }, function (error) {
+        if (error.target !== undefined && error.target.status === 404) {
+          throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+        }
+        throw error;
+      });
   };
 
-  MemoryStorage.prototype.allAttachments = function (id) {
-    var key,
-      attachments = {};
-    try {
-      for (key in this._database[id].attachments) {
-        if (this._database[id].attachments.hasOwnProperty(key)) {
-          attachments[key] = {};
+  DropboxStorage.prototype.allAttachments = function (id) {
+
+    var that = this;
+    id = restrictDocumentId(id);
+
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          type: "GET",
+          url: metadata_template.expand({
+            access_token: that._access_token,
+            root: that._root,
+            id: id
+          })
+        });
+      })
+      .push(function (evt) {
+        var obj = JSON.parse(evt.target.response || evt.target.responseText),
+          i,
+          result = {};
+        if (!obj.is_dir) {
+          throw new jIO.util.jIOError("Not a directory: " + id, 404);
         }
-      }
-    } catch (error) {
-      if (error instanceof TypeError) {
-        throw new jIO.util.jIOError(
-          "Cannot find document: " + id,
-          404
-        );
-      }
-      throw error;
-    }
-    return attachments;
+        for (i = 0; i < obj.contents.length; i += 1) {
+          if (!obj.contents[i].is_dir) {
+            result[obj.contents[i].path.split("/").pop()] = {};
+          }
+        }
+        return result;
+      }, function (error) {
+        if (error.target !== undefined && error.target.status === 404) {
+          throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+        }
+        throw error;
+      });
   };
 
-  MemoryStorage.prototype.remove = function (id) {
-    delete this._database[id];
-    return id;
-  };
+  //currently, putAttachment will fail with files larger than 150MB,
+  //due to the Dropbox API. the API provides the "chunked_upload" method
+  //to pass this limit, but upload process becomes more complex to implement.
+  //
+  //putAttachment will also create a folder if you try to put an attachment
+  //to an inexisting foler.
 
-  MemoryStorage.prototype.getAttachment = function (id, name) {
-    try {
-      var result = this._database[id].attachments[name];
-      if (result === undefined) {
-        throw new jIO.util.jIOError(
-          "Cannot find attachment: " + id + " , " + name,
-          404
-        );
-      }
-      return result;
-    } catch (error) {
-      if (error instanceof TypeError) {
-        throw new jIO.util.jIOError(
-          "Cannot find attachment: " + id + " , " + name,
-          404
-        );
-      }
-      throw error;
-    }
-  };
+  DropboxStorage.prototype.putAttachment = function (id, name, blob) {
+    id = restrictDocumentId(id);
+    restrictAttachmentId(name);
 
-  MemoryStorage.prototype.putAttachment = function (id, name, blob) {
-    var attachment_dict;
-    try {
-      attachment_dict = this._database[id].attachments;
-    } catch (error) {
-      if (error instanceof TypeError) {
-        throw new jIO.util.jIOError("Cannot find document: " + id, 404);
-      }
-      throw error;
-    }
-    attachment_dict[name] = blob;
+    return jIO.util.ajax({
+      type: "PUT",
+      url: upload_template.expand({
+        root: this._root,
+        id: id,
+        name: name,
+        access_token: this._access_token
+      }),
+      dataType: blob.type,
+      data: blob
+    });
   };
 
-  MemoryStorage.prototype.removeAttachment = function (id, name) {
-    try {
-      delete this._database[id].attachments[name];
-    } catch (error) {
-      if (error instanceof TypeError) {
-        throw new jIO.util.jIOError(
-          "Cannot find document: " + id,
-          404
+  DropboxStorage.prototype.getAttachment = function (id, name) {
+    var that = this;
+
+    id = restrictDocumentId(id);
+    restrictAttachmentId(name);
+
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          type: "GET",
+          dataType: "blob",
+          url: get_template.expand({
+            root: that._root,
+            id: id,
+            name: name,
+            access_token: that._access_token
+          })
+        });
+      })
+      .push(function (evt) {
+        return new Blob(
+          [evt.target.response || evt.target.responseText],
+          {"type": evt.target.getResponseHeader('Content-Type') ||
+            "application/octet-stream"}
         );
-      }
-      throw error;
-    }
+      }, function (error) {
+        if (error.target !== undefined && error.target.status === 404) {
+          throw new jIO.util.jIOError("Cannot find attachment: " +
+                                      id + ", " + name, 404);
+        }
+        throw error;
+      });
   };
 
+  //removeAttachment removes also directories.(due to Dropbox API)
 
-  MemoryStorage.prototype.hasCapacity = function (name) {
-    return ((name === "list") || (name === "include"));
-  };
+  DropboxStorage.prototype.removeAttachment = function (id, name) {
+    var that = this;
+    id = restrictDocumentId(id);
+    restrictAttachmentId(name);
 
-  MemoryStorage.prototype.buildQuery = function (options) {
-    var rows = [],
-      i;
-    for (i in this._database) {
-      if (this._database.hasOwnProperty(i)) {
-        if (options.include_docs === true) {
-          rows.push({
-            id: i,
-            value: {},
-            doc: this._database[i]
-          });
-        } else {
-          rows.push({
-            id: i,
-            value: {}
-          });
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          type: "POST",
+          url: remote_template.expand({
+            access_token: that._access_token,
+            root: that._root,
+            path: id + name
+          })
+        });
+      }).push(undefined, function (error) {
+        if (error.target !== undefined && error.target.status === 404) {
+          throw new jIO.util.jIOError("Cannot find attachment: " +
+                                      id + ", " + name, 404);
         }
-
-      }
-    }
-    return rows;
+        throw error;
+      });
   };
 
-  jIO.addStorage('memory', MemoryStorage);
+  jIO.addStorage('dropbox', DropboxStorage);
 
-}(jIO));
+}(jIO, RSVP, Blob, UriTemplate));
 ;/*
  * Copyright 2013, Nexedi SA
  * Released under the LGPL license.
@@ -7482,3143 +9608,3197 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
  */
 
 /*jslint nomen: true*/
-/*global jIO, sessionStorage, localStorage, RSVP */
+/*global jIO, RSVP, DOMParser, Blob */
 
-/**
- * JIO Local Storage. Type = 'local'.
- * Local browser "database" storage.
- *
- * Storage Description:
- *
- *     {
- *       "type": "local",
- *       "sessiononly": false
- *     }
- *
- * @class LocalStorage
- */
+// JIO Dav Storage Description :
+// {
+//   type: "dav",
+//   url: {string},
+//   basic_login: {string} // Basic authentication
+// }
 
-(function (jIO, sessionStorage, localStorage, RSVP) {
+// NOTE: to get the authentication type ->
+// curl --verbose  -X OPTION http://domain/
+// In the headers: "WWW-Authenticate: Basic realm="DAV-upload"
+
+(function (jIO, RSVP, DOMParser, Blob) {
   "use strict";
 
-  function LocalStorage(spec) {
-    if (spec.sessiononly === true) {
-      this._storage = sessionStorage;
-    } else {
-      this._storage = localStorage;
+  function ajax(storage, options) {
+    if (options === undefined) {
+      options = {};
+    }
+    if (storage._authorization !== undefined) {
+      if (options.headers === undefined) {
+        options.headers = {};
+      }
+      options.headers.Authorization = storage._authorization;
     }
+
+    if (storage._with_credentials !== undefined) {
+      if (options.xhrFields === undefined) {
+        options.xhrFields = {};
+      }
+      options.xhrFields.withCredentials = storage._with_credentials;
+    }
+//       if (start !== undefined) {
+//         if (end !== undefined) {
+//           headers.Range = "bytes=" + start + "-" + end;
+//         } else {
+//           headers.Range = "bytes=" + start + "-";
+//         }
+//       }
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax(options);
+      });
   }
 
   function restrictDocumentId(id) {
-    if (id !== "/") {
-      throw new jIO.util.jIOError("id " + id + " is forbidden (!== /)",
+    if (id.indexOf("/") !== 0) {
+      throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)",
+                                  400);
+    }
+    if (id.lastIndexOf("/") !== (id.length - 1)) {
+      throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)",
+                                  400);
+    }
+    return id;
+  }
+
+  function restrictAttachmentId(id) {
+    if (id.indexOf("/") !== -1) {
+      throw new jIO.util.jIOError("attachment " + id + " is forbidden",
                                   400);
     }
   }
 
-  LocalStorage.prototype.get = function (id) {
-    restrictDocumentId(id);
-    return {};
-  };
-
-  LocalStorage.prototype.allAttachments = function (id) {
-    restrictDocumentId(id);
-
-    var attachments = {},
-      key;
-
-    for (key in this._storage) {
-      if (this._storage.hasOwnProperty(key)) {
-        attachments[key] = {};
-      }
+  /**
+   * The JIO WebDAV Storage extension
+   *
+   * @class DavStorage
+   * @constructor
+   */
+  function DavStorage(spec) {
+    if (typeof spec.url !== 'string') {
+      throw new TypeError("DavStorage 'url' is not of type string");
+    }
+    this._url = spec.url;
+    // XXX digest login
+    if (typeof spec.basic_login === 'string') {
+      this._authorization = "Basic " + spec.basic_login;
+    }
+    this._with_credentials = spec.with_credentials;
+  }
+
+  DavStorage.prototype.put = function (id, param) {
+    var that = this;
+    id = restrictDocumentId(id);
+    if (Object.getOwnPropertyNames(param).length > 0) {
+      // Reject if param has some properties
+      throw new jIO.util.jIOError("Can not store properties: " +
+                                  Object.getOwnPropertyNames(param), 400);
     }
-    return attachments;
+    return new RSVP.Queue()
+      .push(function () {
+        return ajax(that, {
+          type: "MKCOL",
+          url: that._url + id
+        });
+      })
+      .push(undefined, function (err) {
+        if ((err.target !== undefined) &&
+            (err.target.status === 405)) {
+          return;
+        }
+        throw err;
+      });
   };
 
-  LocalStorage.prototype.getAttachment = function (id, name) {
-    restrictDocumentId(id);
-
-    var textstring = this._storage.getItem(name);
-
-    if (textstring === null) {
-      throw new jIO.util.jIOError(
-        "Cannot find attachment " + name,
-        404
-      );
-    }
-    return jIO.util.dataURItoBlob(textstring);
+  DavStorage.prototype.remove = function (id) {
+    id = restrictDocumentId(id);
+    return ajax(this, {
+      type: "DELETE",
+      url: this._url + id
+    });
   };
 
-  LocalStorage.prototype.putAttachment = function (id, name, blob) {
+  DavStorage.prototype.get = function (id) {
     var context = this;
-    restrictDocumentId(id);
+    id = restrictDocumentId(id);
 
-    // the document already exists
-    // download data
     return new RSVP.Queue()
       .push(function () {
-        return jIO.util.readBlobAsDataURL(blob);
+        return ajax(context, {
+          type: "PROPFIND",
+          url: context._url + id,
+          dataType: "text",
+          headers: {
+            // Increasing this value is a performance killer
+            Depth: "1"
+          }
+        });
       })
-      .push(function (e) {
-        context._storage.setItem(name, e.target.result);
+      .push(function () {
+        return {};
+      }, function (error) {
+        if ((error.target !== undefined) &&
+            (error.target.status === 404)) {
+          throw new jIO.util.jIOError("Cannot find document", 404);
+        }
+        throw error;
       });
   };
 
-  LocalStorage.prototype.removeAttachment = function (id, name) {
-    restrictDocumentId(id);
-    return this._storage.removeItem(name);
-  };
-
+  DavStorage.prototype.allAttachments = function (id) {
 
-  LocalStorage.prototype.hasCapacity = function (name) {
-    return (name === "list");
-  };
+    var context = this;
+    id = restrictDocumentId(id);
 
-  LocalStorage.prototype.buildQuery = function () {
-    return [{
-      id: "/",
-      value: {}
-    }];
-  };
+    return new RSVP.Queue()
+      .push(function () {
+        return ajax(context, {
+          type: "PROPFIND",
+          url: context._url + id,
+          dataType: "text",
+          headers: {
+            // Increasing this value is a performance killer
+            Depth: "1"
+          }
+        });
+      })
 
-  jIO.addStorage('local', LocalStorage);
 
-}(jIO, sessionStorage, localStorage, RSVP));
-;/*jslint nomen: true*/
-/*global RSVP, Blob, LZString, DOMException*/
-(function (RSVP, Blob, LZString, DOMException) {
-  "use strict";
+      .push(function (response) {
+        // Extract all meta informations and return them to JSON
 
-  /**
-   * The jIO ZipStorage extension
-   *
-   * @class ZipStorage
-   * @constructor
-   */
+        var i,
+          attachment = {},
+          id,
+          attachment_list = new DOMParser().parseFromString(
+            response.target.responseText,
+            "text/xml"
+          ).querySelectorAll(
+            "D\\:response, response"
+          );
 
-  var MIME_TYPE = "application/x-jio-utf16_lz_string";
+        // exclude parent folder and browse
+        for (i = 1; i < attachment_list.length; i += 1) {
+          // XXX Only get files for now
+          id = attachment_list[i].querySelector("D\\:href, href").
+            textContent.split('/').slice(-1)[0];
+          // XXX Ugly
+          if ((id !== undefined) && (id !== "")) {
+            attachment[id] = {};
+          }
+        }
+        return attachment;
 
-  function ZipStorage(spec) {
-    this._sub_storage = jIO.createJIO(spec.sub_storage);
-  }
+      }, function (error) {
+        if ((error.target !== undefined) &&
+            (error.target.status === 404)) {
+          throw new jIO.util.jIOError("Cannot find document", 404);
+        }
+        throw error;
+      });
 
-  ZipStorage.prototype.get = function () {
-    return this._sub_storage.get.apply(this._sub_storage,
-                                        arguments);
   };
 
-  ZipStorage.prototype.post = function () {
-    return this._sub_storage.post.apply(this._sub_storage,
-                                        arguments);
-  };
 
-  ZipStorage.prototype.put = function () {
-    return this._sub_storage.put.apply(this._sub_storage,
-                                       arguments);
-  };
+  DavStorage.prototype.putAttachment = function (id, name, blob) {
+    var that = this;
+    id = restrictDocumentId(id);
+    restrictAttachmentId(name);
 
-  ZipStorage.prototype.remove = function () {
-    return this._sub_storage.remove.apply(this._sub_storage,
-                                          arguments);
+    return new RSVP.Queue()
+      .push(function () {
+        return ajax(that, {
+          type: "PUT",
+          url: that._url + id + name,
+          data: blob
+        });
+      })
+      .push(undefined, function (error) {
+        if (error.target.status === 403 || error.target.status === 424) {
+          throw new jIO.util.jIOError("Cannot access subdocument", 404);
+        }
+        throw error;
+      });
   };
 
-  ZipStorage.prototype.hasCapacity = function () {
-    return this._sub_storage.hasCapacity.apply(this._sub_storage,
-                                               arguments);
-  };
+  DavStorage.prototype.getAttachment = function (id, name) {
+    var context = this;
+    id = restrictDocumentId(id);
+    restrictAttachmentId(name);
+
+    return new RSVP.Queue()
+      .push(function () {
+        return ajax(context, {
+          type: "GET",
+          url: context._url + id + name,
+          dataType: "blob"
+        });
+      })
+      .push(function (response) {
+        return new Blob(
+          [response.target.response || response.target.responseText],
+          {"type": response.target.getResponseHeader('Content-Type') ||
+                   "application/octet-stream"}
+        );
+      }, function (error) {
+        if ((error.target !== undefined) &&
+            (error.target.status === 404)) {
+          throw new jIO.util.jIOError("Cannot find attachment: "
+                                      + id + " , " + name,
+                                      404);
+        }
+        throw error;
+      });
 
-  ZipStorage.prototype.buildQuery = function () {
-    return this._sub_storage.buildQuery.apply(this._sub_storage,
-                                              arguments);
   };
 
-  ZipStorage.prototype.getAttachment = function (id, name) {
-    var that = this;
-    return that._sub_storage.getAttachment(id, name)
-      .push(function (blob) {
-        if (blob.type !== MIME_TYPE) {
-          return blob;
-        }
-        return new RSVP.Queue()
-          .push(function () {
-            return jIO.util.readBlobAsText(blob, 'utf16');
-          })
-          .push(function (evt) {
-            var result =
-              LZString.decompressFromUTF16(evt.target.result);
-            if (result === '') {
-              return blob;
-            }
-            try {
-              return jIO.util.dataURItoBlob(
-                result
-              );
-            } catch (error) {
-              if (error instanceof DOMException) {
-                return blob;
-              }
-              throw error;
-            }
-          });
+  DavStorage.prototype.removeAttachment = function (id, name) {
+    var context = this;
+    id = restrictDocumentId(id);
+    restrictAttachmentId(name);
+
+    return new RSVP.Queue()
+      .push(function () {
+        return ajax(context, {
+          type: "DELETE",
+          url: context._url + id + name
+        });
+      })
+      .push(undefined, function (error) {
+        if ((error.target !== undefined) &&
+            (error.target.status === 404)) {
+          throw new jIO.util.jIOError("Cannot find attachment: "
+                                      + id + " , " + name,
+                                      404);
+        }
+        throw error;
       });
   };
 
-  function myEndsWith(str, query) {
-    return (str.indexOf(query) === str.length - query.length);
-  }
+  // JIO COMMANDS //
 
-  ZipStorage.prototype.putAttachment = function (id, name, blob) {
-    var that = this;
-    if ((blob.type.indexOf("text/") === 0) || myEndsWith(blob.type, "xml") ||
-        myEndsWith(blob.type, "json")) {
-      return new RSVP.Queue()
-        .push(function () {
-          return jIO.util.readBlobAsDataURL(blob);
-        })
-        .push(function (data) {
-          var result = LZString.compressToUTF16(data.target.result);
-          blob = new Blob([result],
-                          {type: MIME_TYPE});
-          return that._sub_storage.putAttachment(id, name, blob);
-        });
-    }
-    return this._sub_storage.putAttachment.apply(this._sub_storage,
-                                                 arguments);
-  };
+  // wedDav methods rfc4918 (short summary)
+  // COPY     Reproduces single resources (files) and collections (directory
+  //          trees). Will overwrite files (if specified by request) but will
+  //          respond 209 (Conflict) if it would overwrite a tree
+  // DELETE   deletes files and directory trees
+  // GET      just the vanilla HTTP/1.1 behaviour
+  // HEAD     ditto
+  // LOCK     locks a resources
+  // MKCOL    creates a directory
+  // MOVE     Moves (rename or copy) a file or a directory tree. Will
+  //          'overwrite' files (if specified by the request) but will respond
+  //          209 (Conflict) if it would overwrite a tree.
+  // OPTIONS  If WebDAV is enabled and available for the path this reports the
+  //          WebDAV extension methods
+  // PROPFIND Retrieves the requested file characteristics, DAV lock status
+  //          and 'dead' properties for individual files, a directory and its
+  //          child files, or a directory tree
+  // PROPPATCHset and remove 'dead' meta-data properties
+  // PUT      Update or create resource or collections
+  // UNLOCK   unlocks a resource
 
-  ZipStorage.prototype.removeAttachment = function () {
-    return this._sub_storage.removeAttachment.apply(this._sub_storage,
-                                                    arguments);
-  };
+  // Notes: all Ajax requests should be CORS (cross-domain)
+  // adding custom headers triggers preflight OPTIONS request!
+  // http://remysharp.com/2011/04/21/getting-cors-working/
 
-  ZipStorage.prototype.allAttachments = function () {
-    return this._sub_storage.allAttachments.apply(this._sub_storage,
-                                                  arguments);
-  };
+  jIO.addStorage('dav', DavStorage);
 
-  jIO.addStorage('zip', ZipStorage);
-}(RSVP, Blob, LZString, DOMException));
+}(jIO, RSVP, DOMParser, Blob));
 ;/*
  * Copyright 2015, Nexedi SA
  * Released under the LGPL license.
  * http://www.gnu.org/licenses/lgpl.html
  */
-
+/**
+ * JIO Google Drive Storage. Type = "gdrive".
+ * Google Drive "database" storage.
+ */
+/*global jIO, Blob, RSVP, UriTemplate, JSON*/
 /*jslint nomen: true*/
-/*global jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer*/
 
-(function (jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer) {
+(function (jIO, Blob, RSVP, UriTemplate, JSON) {
   "use strict";
 
+  var UPLOAD_URL = "https://www.googleapis.com{/upload}/drive/v2/files{/id}" +
+      "{?uploadType,access_token}",
+    upload_template = UriTemplate.parse(UPLOAD_URL),
+    REMOVE_URL = "https://www.googleapis.com/drive/v2/" +
+      "files{/id,trash}{?access_token}",
+    remove_template = UriTemplate.parse(REMOVE_URL),
+    LIST_URL = "https://www.googleapis.com/drive/v2/files" +
+      "?prettyPrint=false{&pageToken}&q=trashed=false" +
+      "&fields=nextPageToken,items(id){&access_token}",
+    list_template = UriTemplate.parse(LIST_URL),
+    GET_URL = "https://www.googleapis.com/drive/v2/files{/id}{?alt}",
+    get_template = UriTemplate.parse(GET_URL);
 
-  // you the cryptography system used by this storage is AES-GCM.
-  // here is an example of how to generate a key to the json format.
+  function handleError(error, id) {
+    if (error.target.status === 404) {
+      throw new jIO.util.jIOError(
+        "Cannot find document: " + id,
+        404
+      );
+    }
+    throw error;
+  }
 
-  // var key,
-  //     jsonKey;
-  // crypto.subtle.generateKey({name: "AES-GCM",length: 256},
-  //                           (true), ["encrypt", "decrypt"])
-  // .then(function(res){key = res;});
-  //
-  // window.crypto.subtle.exportKey("jwk", key)
-  // .then(function(res){jsonKey = val})
-  //
-  //var storage = jIO.createJIO({type: "crypt", key: jsonKey,
-  //                             sub_storage: {...}});
+  function listPage(result, token) {
+    var i,
+      obj;
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          "type": "GET",
+          "url": list_template.expand({
+            pageToken : (result.nextPageToken || ""),
+            access_token: token
+          })
+        });
+      })
+      .push(function (data) {
+        obj = JSON.parse(data.target.response || data.target.responseText);
+        for (i = 0; i < obj.items.length; i += 1) {
+          obj.items[i].value = {};
+          result.push(obj.items[i]);
+        }
+        result.nextPageToken = obj.nextPageToken;
+        return result;
+      }, handleError);
+  }
 
-  // find more informations about this cryptography system on
-  // https://github.com/diafygi/webcrypto-examples#aes-gcm
+  function checkName(name) {
+    if (name !== "enclosure") {
+      throw new jIO.util.jIOError("Only support 'enclosure' attachment", 400);
+    }
+  }
 
   /**
-   * The JIO Cryptography Storage extension
+   * The JIO Google Drive Storage extension
    *
-   * @class CryptStorage
+   * @class GdriveStorage
    * @constructor
    */
-
-  var MIME_TYPE = "application/x-jio-aes-gcm-encryption";
-
-  function CryptStorage(spec) {
-    this._key = spec.key;
-    this._jsonKey = true;
-    this._sub_storage = jIO.createJIO(spec.sub_storage);
+  function GdriveStorage(spec) {
+    if (spec === undefined || spec.access_token === undefined ||
+        typeof spec.access_token !== 'string') {
+      throw new TypeError("Access Token must be a string " +
+                          "which contains more than one character.");
+    }
+    if (spec.trashing !== undefined &&
+        (spec.trashing !== true && spec.trashing !== false)) {
+      throw new TypeError("trashing parameter" +
+                          " must be a boolean (true or false)");
+    }
+    this._trashing = spec.trashing || true;
+    this._access_token = spec.access_token;
+    return;
   }
 
-  function convertKey(that) {
+  function recursiveAllDocs(result, accessToken) {
     return new RSVP.Queue()
       .push(function () {
-        return crypto.subtle.importKey("jwk", that._key,
-                                       "AES-GCM", false,
-                                       ["encrypt", "decrypt"]);
+        return listPage(result, accessToken);
       })
-      .push(function (res) {
-        that._key = res;
-        that._jsonKey = false;
-        return;
-      }, function () {
-        throw new TypeError(
-          "'key' must be a CryptoKey to JSON Web Key format"
-        );
+      .push(function () {
+        if (result.nextPageToken) {
+          return recursiveAllDocs(result, accessToken);
+        }
+        return result;
       });
   }
 
-  CryptStorage.prototype.get = function () {
-    return this._sub_storage.get.apply(this._sub_storage,
-                                       arguments);
+  GdriveStorage.prototype.hasCapacity = function (name) {
+    return (name === "list");
+  };
+
+  GdriveStorage.prototype.buildQuery = function () {
+    return recursiveAllDocs([], this._access_token);
+  };
+
+  function sendMetaData(id, param, token) {
+    var boundary = "-------314159265358979323846";
+
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          "type": id ? "PUT" : "POST",
+          "url": upload_template.expand({
+            access_token: token,
+            id: id || [],
+            upload: id ? [] : "upload",
+            uploadType: "multipart"
+          }),
+          headers: {
+            "Content-Type" : 'multipart/related; boundary="' + boundary + '"'
+          },
+          data: '--' + boundary + '\n' +
+            'Content-Type: application/json; charset=UTF-8\n\n' +
+            JSON.stringify(param) + '\n\n--' + boundary + "--"
+        });
+      })
+      .push(function (result) {
+        var obj = JSON.parse(result.target.responseText);
+
+        return obj.id;
+      },
+            function (error) {handleError(error, id); });
+  }
+
+  GdriveStorage.prototype.put = function (id, param) {
+    return sendMetaData(id, param, this._access_token);
+  };
+
+  GdriveStorage.prototype.post = function (param) {
+    return sendMetaData(undefined, param, this._access_token);
   };
 
-  CryptStorage.prototype.post = function () {
-    return this._sub_storage.post.apply(this._sub_storage,
-                                        arguments);
+  function sendData(id, blob, token) {
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          "type": "PUT",
+          "url": upload_template.expand({
+            access_token: token,
+            upload: "upload",
+            id: id,
+            uploadType: "media"
+          }),
+          data: blob
+        });
+      })
+      .push(function (data) {
+        data = JSON.parse(data.target.responseText);
+        if (data.mimeType === "application/vnd.google-apps.folder") {
+          throw new jIO.util.jIOError("cannot put attachments to folder", 400);
+        }
+        return data;
+      }, function (error) {handleError(error, id); });
+  }
+
+  GdriveStorage.prototype.putAttachment = function (id, name, blob) {
+    checkName(name);
+    return sendData(id, blob, this._access_token);
   };
 
-  CryptStorage.prototype.put = function () {
-    return this._sub_storage.put.apply(this._sub_storage,
-                                       arguments);
+  GdriveStorage.prototype.remove = function (id) {
+    var that  = this;
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          type: that._trashing ? "POST" : "DELETE",
+          url: remove_template.expand({
+            id : id,
+            access_token : that._access_token,
+            trash : that._trashing ? "trash" : []
+          })
+        });
+      })
+      .push(undefined, function (error) {handleError(error, id); });
   };
 
-  CryptStorage.prototype.remove = function () {
-    return this._sub_storage.remove.apply(this._sub_storage,
-                                          arguments);
-  };
+  function getData(id, attach, token) {
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.ajax({
+          type: "GET",
+          dataType: attach ? "blob" : "json",
+          url: get_template.expand({
+            id: id,
+            alt: attach ? "media" : [],
+            access_token: token
+          }),
+          headers: {
+            "Authorization" : "Bearer " + token
+          }
+        });
+      })
+      .push(function (evt) {
+        return evt.target.response ||
+          (attach ? new Blob([evt.target.responseText],
+                             {"type" :
+                              evt.target.responseHeaders["Content-Type"]}) :
+              JSON.parse(evt.target.responseText));
+      }, function (error) {handleError(error, id); });
+  }
 
-  CryptStorage.prototype.hasCapacity = function () {
-    return this._sub_storage.hasCapacity.apply(this._sub_storage,
-                                               arguments);
+  GdriveStorage.prototype.get = function (id) {
+    return getData(id, false, this._access_token);
   };
 
-  CryptStorage.prototype.buildQuery = function () {
-    return this._sub_storage.buildQuery.apply(this._sub_storage,
-                                              arguments);
+  GdriveStorage.prototype.getAttachment = function (id, name) {
+    checkName(name);
+    return getData(id, true, this._access_token);
   };
 
-
-  CryptStorage.prototype.putAttachment = function (id, name, blob) {
-    var initializaton_vector = crypto.getRandomValues(new Uint8Array(12)),
-      that = this;
+  GdriveStorage.prototype.allAttachments = function (id) {
+    var token = this._access_token;
 
     return new RSVP.Queue()
       .push(function () {
-        if (that._jsonKey === true) {
-          return convertKey(that);
-        }
-        return;
-      })
-      .push(function () {
-        return jIO.util.readBlobAsDataURL(blob);
-      })
-      .push(function (dataURL) {
-        //string->arraybuffer
-        var strLen = dataURL.currentTarget.result.length,
-          buf = new ArrayBuffer(strLen),
-          bufView = new Uint8Array(buf),
-          i;
-
-        dataURL = dataURL.currentTarget.result;
-        for (i = 0; i < strLen; i += 1) {
-          bufView[i] = dataURL.charCodeAt(i);
-        }
-        return crypto.subtle.encrypt({
-          name : "AES-GCM",
-          iv : initializaton_vector
-        },
-                                     that._key, buf);
+        return getData(id, false, token);
       })
-      .push(function (coded) {
-        var blob = new Blob([initializaton_vector, coded], {type: MIME_TYPE});
-        return that._sub_storage.putAttachment(id, name, blob);
-      });
-  };
-
-  CryptStorage.prototype.getAttachment = function (id, name) {
-    var that = this;
-
-    return that._sub_storage.getAttachment(id, name)
-      .push(function (blob) {
-        if (blob.type !== MIME_TYPE) {
-          return blob;
+      .push(function (data) {
+        if (data.mimeType === "application/vnd.google-apps.folder") {
+          return {};
         }
-        return new RSVP.Queue()
-          .push(function () {
-            if (that._jsonKey === true) {
-              return convertKey(that);
-            }
-            return;
-          })
-          .push(function () {
-            return jIO.util.readBlobAsArrayBuffer(blob);
-          })
-          .push(function (coded) {
-            var initializaton_vector;
-
-            coded = coded.currentTarget.result;
-            initializaton_vector = new Uint8Array(coded.slice(0, 12));
-            return crypto.subtle.decrypt({
-              name : "AES-GCM",
-              iv : initializaton_vector
-            },
-                                         that._key, coded.slice(12));
-          })
-          .push(function (arr) {
-            //arraybuffer->string
-            arr = String.fromCharCode.apply(null, new Uint8Array(arr));
-            try {
-              return jIO.util.dataURItoBlob(arr);
-            } catch (error) {
-              if (error instanceof DOMException) {
-                return blob;
-              }
-              throw error;
-            }
-          }, function () { return blob; });
+        return {"enclosure": {}};
       });
   };
 
-  CryptStorage.prototype.removeAttachment = function () {
-    return this._sub_storage.removeAttachment.apply(this._sub_storage,
-                                                    arguments);
-  };
-
-  CryptStorage.prototype.allAttachments = function () {
-    return this._sub_storage.allAttachments.apply(this._sub_storage,
-                                                  arguments);
-  };
+  jIO.addStorage('gdrive', GdriveStorage);
 
-  jIO.addStorage('crypt', CryptStorage);
+}(jIO, Blob, RSVP, UriTemplate, JSON));
+;/*jslint nomen: true */
+/*global RSVP*/
 
-}(jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer));
-;/*
- * Copyright 2013, Nexedi SA
- * Released under the LGPL license.
- * http://www.gnu.org/licenses/lgpl.html
- */
 /**
- * JIO Dropbox Storage. Type = "dropbox".
- * Dropbox "database" storage.
+ * JIO Union Storage. Type = 'union'.
+ * This provide a unified access other multiple storage.
+ * New document are created in the first sub storage.
+ * Document are searched in each sub storage until it is found.
+ * 
+ *
+ * Storage Description:
+ *
+ *     {
+ *       "type": "union",
+ *       "storage_list": [
+ *         sub_storage_description_1,
+ *         sub_storage_description_2,
+ *
+ *         sub_storage_description_X,
+ *       ]
+ *     }
+ *
+ * @class UnionStorage
  */
-/*global Blob, jIO, RSVP, UriTemplate*/
-/*jslint nomen: true*/
 
-(function (jIO, RSVP, Blob, UriTemplate) {
+(function (jIO, RSVP) {
   "use strict";
-  var UPLOAD_URL = "https://content.dropboxapi.com/1/files_put/" +
-      "{+root}{+id}{+name}{?access_token}",
-    upload_template = UriTemplate.parse(UPLOAD_URL),
-    CREATE_DIR_URL = "https://api.dropboxapi.com/1/fileops/create_folder" +
-      "{?access_token,root,path}",
-    create_dir_template = UriTemplate.parse(CREATE_DIR_URL),
-    REMOVE_URL = "https://api.dropboxapi.com/1/fileops/delete/" +
-      "{?access_token,root,path}",
-    remote_template = UriTemplate.parse(REMOVE_URL),
-    GET_URL = "https://content.dropboxapi.com/1/files" +
-      "{/root,id}{+name}{?access_token}",
-    get_template = UriTemplate.parse(GET_URL),
-    //LIST_URL = 'https://api.dropboxapi.com/1/metadata/sandbox/';
-    METADATA_URL = "https://api.dropboxapi.com/1/metadata" +
-      "{/root}{+id}{?access_token}",
-    metadata_template = UriTemplate.parse(METADATA_URL);
-
-  function restrictDocumentId(id) {
-    if (id.indexOf("/") !== 0) {
-      throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)",
-                                  400);
-    }
-    if (id.lastIndexOf("/") !== (id.length - 1)) {
-      throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)",
-                                  400);
-    }
-    return id;
-  }
-
-  function restrictAttachmentId(id) {
-    if (id.indexOf("/") !== -1) {
-      throw new jIO.util.jIOError("attachment " + id + " is forbidden",
-                                  400);
-    }
-  }
 
   /**
-   * The JIO Dropbox Storage extension
+   * The JIO UnionStorage extension
    *
-   * @class DropboxStorage
+   * @class UnionStorage
    * @constructor
    */
-  function DropboxStorage(spec) {
-    if (typeof spec.access_token !== 'string' || !spec.access_token) {
-      throw new TypeError("Access Token' must be a string " +
-                          "which contains more than one character.");
+  function UnionStorage(spec) {
+    if (!Array.isArray(spec.storage_list)) {
+      throw new jIO.util.jIOError("storage_list is not an Array", 400);
     }
-    if (typeof spec.root !== 'string' || !spec.root ||
-        (spec.root !== "dropbox" && spec.root !== "sandbox")) {
-      throw new TypeError("root must be 'dropbox' or 'sandbox'");
+    var i;
+    this._storage_list = [];
+    for (i = 0; i < spec.storage_list.length; i += 1) {
+      this._storage_list.push(jIO.createJIO(spec.storage_list[i]));
+    }
+  }
+
+  UnionStorage.prototype._getWithStorageIndex = function () {
+    var i,
+      index = 0,
+      context = this,
+      arg = arguments,
+      result = this._storage_list[0].get.apply(this._storage_list[0], arg);
+
+    function handle404(j) {
+      result
+        .push(undefined, function (error) {
+          if ((error instanceof jIO.util.jIOError) &&
+              (error.status_code === 404)) {
+            return context._storage_list[j].get.apply(context._storage_list[j],
+                                                      arg)
+              .push(function (doc) {
+                index = j;
+                return doc;
+              });
+          }
+          throw error;
+        });
     }
-    this._access_token = spec.access_token;
-    this._root = spec.root;
-  }
 
-  DropboxStorage.prototype.put = function (id, param) {
-    var that = this;
-    id = restrictDocumentId(id);
-    if (Object.getOwnPropertyNames(param).length > 0) {
-      // Reject if param has some properties
-      throw new jIO.util.jIOError("Can not store properties: " +
-                                  Object.getOwnPropertyNames(param), 400);
+    for (i = 1; i < this._storage_list.length; i += 1) {
+      handle404(i);
     }
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          type: "POST",
-          url: create_dir_template.expand({
-            access_token: that._access_token,
-            root: that._root,
-            path: id
-          })
-        });
-      })
-      .push(undefined, function (err) {
-        if ((err.target !== undefined) &&
-            (err.target.status === 405)) {
-          // Directory already exists, no need to fail
-          return;
-        }
-        throw err;
+    return result
+      .push(function (doc) {
+        return [index, doc];
       });
   };
 
-  DropboxStorage.prototype.remove = function (id) {
-    id = restrictDocumentId(id);
-    return jIO.util.ajax({
-      type: "POST",
-      url: remote_template.expand({
-        access_token: this._access_token,
-        root: this._root,
-        path: id
-      })
-    });
+  /*
+   * Get a document
+   * Try on each substorage on after the other
+   */
+  UnionStorage.prototype.get = function () {
+    return this._getWithStorageIndex.apply(this, arguments)
+      .push(function (result) {
+        return result[1];
+      });
   };
 
-  DropboxStorage.prototype.get = function (id) {
-    var that = this;
+  /*
+   * Get attachments list
+   * Try on each substorage on after the other
+   */
+  UnionStorage.prototype.allAttachments = function () {
+    var argument_list = arguments,
+      context = this;
+    return this._getWithStorageIndex.apply(this, arguments)
+      .push(function (result) {
+        var sub_storage = context._storage_list[result[0]];
+        return sub_storage.allAttachments.apply(sub_storage, argument_list);
+      });
+  };
 
-    if (id === "/") {
-      return {};
-    }
-    id = restrictDocumentId(id);
+  /*
+   * Post a document
+   * Simply store on the first substorage
+   */
+  UnionStorage.prototype.post = function () {
+    return this._storage_list[0].post.apply(this._storage_list[0], arguments);
+  };
 
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          type: "GET",
-          url: metadata_template.expand({
-            access_token: that._access_token,
-            root: that._root,
-            id: id
-          })
-        });
-      })
-      .push(function (evt) {
-        var obj = JSON.parse(evt.target.response ||
-                             evt.target.responseText);
-        if (obj.is_dir) {
-          return {};
-        }
-        throw new jIO.util.jIOError("Not a directory: " + id, 404);
-      }, function (error) {
-        if (error.target !== undefined && error.target.status === 404) {
-          throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+  /*
+   * Put a document
+   * Search the document location, and modify it in its storage.
+   */
+  UnionStorage.prototype.put = function () {
+    var arg = arguments,
+      context = this;
+    return this._getWithStorageIndex(arg[0])
+      .push(undefined, function (error) {
+        if ((error instanceof jIO.util.jIOError) &&
+            (error.status_code === 404)) {
+          // Document does not exist, create in first substorage
+          return [0];
         }
         throw error;
+      })
+      .push(function (result) {
+        // Storage found, modify in it directly
+        var sub_storage = context._storage_list[result[0]];
+        return sub_storage.put.apply(sub_storage, arg);
       });
   };
 
-  DropboxStorage.prototype.allAttachments = function (id) {
-
-    var that = this;
-    id = restrictDocumentId(id);
+  /*
+   * Remove a document
+   * Search the document location, and remove it from its storage.
+   */
+  UnionStorage.prototype.remove = function () {
+    var arg = arguments,
+      context = this;
+    return this._getWithStorageIndex(arg[0])
+      .push(function (result) {
+        // Storage found, remove from it directly
+        var sub_storage = context._storage_list[result[0]];
+        return sub_storage.remove.apply(sub_storage, arg);
+      });
+  };
 
+  UnionStorage.prototype.buildQuery = function () {
+    var promise_list = [],
+      i,
+      id_dict = {},
+      len = this._storage_list.length,
+      sub_storage;
+    for (i = 0; i < len; i += 1) {
+      sub_storage = this._storage_list[i];
+      promise_list.push(sub_storage.buildQuery.apply(sub_storage, arguments));
+    }
     return new RSVP.Queue()
       .push(function () {
-        return jIO.util.ajax({
-          type: "GET",
-          url: metadata_template.expand({
-            access_token: that._access_token,
-            root: that._root,
-            id: id
-          })
-        });
+        return RSVP.all(promise_list);
       })
-      .push(function (evt) {
-        var obj = JSON.parse(evt.target.response || evt.target.responseText),
-          i,
-          result = {};
-        if (!obj.is_dir) {
-          throw new jIO.util.jIOError("Not a directory: " + id, 404);
-        }
-        for (i = 0; i < obj.contents.length; i += 1) {
-          if (!obj.contents[i].is_dir) {
-            result[obj.contents[i].path.split("/").pop()] = {};
+      .push(function (result_list) {
+        var result = [],
+          sub_result,
+          sub_result_len,
+          j;
+        len = result_list.length;
+        for (i = 0; i < len; i += 1) {
+          sub_result = result_list[i];
+          sub_result_len = sub_result.length;
+          for (j = 0; j < sub_result_len; j += 1) {
+            if (!id_dict.hasOwnProperty(sub_result[j].id)) {
+              id_dict[sub_result[j].id] = null;
+              result.push(sub_result[j]);
+            }
           }
         }
         return result;
-      }, function (error) {
-        if (error.target !== undefined && error.target.status === 404) {
-          throw new jIO.util.jIOError("Cannot find document: " + id, 404);
-        }
-        throw error;
       });
   };
 
-  //currently, putAttachment will fail with files larger than 150MB,
-  //due to the Dropbox API. the API provides the "chunked_upload" method
-  //to pass this limit, but upload process becomes more complex to implement.
-  //
-  //putAttachment will also create a folder if you try to put an attachment
-  //to an inexisting foler.
-
-  DropboxStorage.prototype.putAttachment = function (id, name, blob) {
-    id = restrictDocumentId(id);
-    restrictAttachmentId(name);
-
-    return jIO.util.ajax({
-      type: "PUT",
-      url: upload_template.expand({
-        root: this._root,
-        id: id,
-        name: name,
-        access_token: this._access_token
-      }),
-      dataType: blob.type,
-      data: blob
-    });
+  UnionStorage.prototype.hasCapacity = function (name) {
+    var i,
+      len,
+      result,
+      sub_storage;
+    if ((name === "list") ||
+            (name === "query") ||
+            (name === "select")) {
+      result = true;
+      len = this._storage_list.length;
+      for (i = 0; i < len; i += 1) {
+        sub_storage = this._storage_list[i];
+        result = result && sub_storage.hasCapacity(name);
+      }
+      return result;
+    }
+    return false;
   };
 
-  DropboxStorage.prototype.getAttachment = function (id, name) {
-    var that = this;
-
-    id = restrictDocumentId(id);
-    restrictAttachmentId(name);
-
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          type: "GET",
-          dataType: "blob",
-          url: get_template.expand({
-            root: that._root,
-            id: id,
-            name: name,
-            access_token: that._access_token
-          })
-        });
-      })
-      .push(function (evt) {
-        return new Blob(
-          [evt.target.response || evt.target.responseText],
-          {"type": evt.target.getResponseHeader('Content-Type') ||
-            "application/octet-stream"}
-        );
-      }, function (error) {
-        if (error.target !== undefined && error.target.status === 404) {
-          throw new jIO.util.jIOError("Cannot find attachment: " +
-                                      id + ", " + name, 404);
-        }
-        throw error;
-      });
+  UnionStorage.prototype.repair = function () {
+    var i,
+      promise_list = [];
+    for (i = 0; i < this._storage_list.length; i += 1) {
+      promise_list.push(this._storage_list[i].repair.apply(
+        this._storage_list[i],
+        arguments
+      ));
+    }
+    return RSVP.all(promise_list);
   };
 
-  //removeAttachment removes also directories.(due to Dropbox API)
+  UnionStorage.prototype.getAttachment = function () {
+    var argument_list = arguments,
+      context = this;
+    return this._getWithStorageIndex.apply(this, arguments)
+      .push(function (result) {
+        var sub_storage = context._storage_list[result[0]];
+        return sub_storage.getAttachment.apply(sub_storage, argument_list);
+      });
+  };
 
-  DropboxStorage.prototype.removeAttachment = function (id, name) {
-    var that = this;
-    id = restrictDocumentId(id);
-    restrictAttachmentId(name);
+  UnionStorage.prototype.putAttachment = function () {
+    var argument_list = arguments,
+      context = this;
+    return this._getWithStorageIndex.apply(this, arguments)
+      .push(function (result) {
+        var sub_storage = context._storage_list[result[0]];
+        return sub_storage.putAttachment.apply(sub_storage, argument_list);
+      });
+  };
 
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          type: "POST",
-          url: remote_template.expand({
-            access_token: that._access_token,
-            root: that._root,
-            path: id + name
-          })
-        });
-      }).push(undefined, function (error) {
-        if (error.target !== undefined && error.target.status === 404) {
-          throw new jIO.util.jIOError("Cannot find attachment: " +
-                                      id + ", " + name, 404);
-        }
-        throw error;
+  UnionStorage.prototype.removeAttachment = function () {
+    var argument_list = arguments,
+      context = this;
+    return this._getWithStorageIndex.apply(this, arguments)
+      .push(function (result) {
+        var sub_storage = context._storage_list[result[0]];
+        return sub_storage.removeAttachment.apply(sub_storage, argument_list);
       });
   };
 
-  jIO.addStorage('dropbox', DropboxStorage);
+  jIO.addStorage('union', UnionStorage);
 
-}(jIO, RSVP, Blob, UriTemplate));
+}(jIO, RSVP));
 ;/*
  * Copyright 2013, Nexedi SA
  * Released under the LGPL license.
  * http://www.gnu.org/licenses/lgpl.html
  */
-
-/*jslint nomen: true*/
-/*global jIO, RSVP, DOMParser, Blob */
-
-// JIO Dav Storage Description :
+// JIO ERP5 Storage Description :
 // {
-//   type: "dav",
-//   url: {string},
-//   basic_login: {string} // Basic authentication
+//   type: "erp5"
+//   url: {string}
 // }
 
-// NOTE: to get the authentication type ->
-// curl --verbose  -X OPTION http://domain/
-// In the headers: "WWW-Authenticate: Basic realm="DAV-upload"
+/*jslint nomen: true, unparam: true */
+/*global jIO, UriTemplate, FormData, RSVP, URI, Blob,
+         SimpleQuery, ComplexQuery*/
 
-(function (jIO, RSVP, DOMParser, Blob) {
+(function (jIO, UriTemplate, FormData, RSVP, URI, Blob,
+           SimpleQuery, ComplexQuery) {
   "use strict";
 
-  function ajax(storage, options) {
-    if (options === undefined) {
-      options = {};
-    }
-    if (storage._authorization !== undefined) {
-      if (options.headers === undefined) {
-        options.headers = {};
-      }
-      options.headers.Authorization = storage._authorization;
-    }
-//       if (start !== undefined) {
-//         if (end !== undefined) {
-//           headers.Range = "bytes=" + start + "-" + end;
-//         } else {
-//           headers.Range = "bytes=" + start + "-";
-//         }
-//       }
+  function getSiteDocument(storage) {
     return new RSVP.Queue()
       .push(function () {
-        return jIO.util.ajax(options);
+        return jIO.util.ajax({
+          "type": "GET",
+          "url": storage._url,
+          "xhrFields": {
+            withCredentials: true
+          }
+        });
+      })
+      .push(function (event) {
+        return JSON.parse(event.target.responseText);
       });
   }
 
-  function restrictDocumentId(id) {
-    if (id.indexOf("/") !== 0) {
-      throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)",
-                                  400);
-    }
-    if (id.lastIndexOf("/") !== (id.length - 1)) {
-      throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)",
-                                  400);
+  function getDocumentAndHateoas(storage, id, options) {
+    if (options === undefined) {
+      options = {};
     }
-    return id;
+    return getSiteDocument(storage)
+      .push(function (site_hal) {
+        // XXX need to get modified metadata
+        return new RSVP.Queue()
+          .push(function () {
+            return jIO.util.ajax({
+              "type": "GET",
+              "url": UriTemplate.parse(site_hal._links.traverse.href)
+                                .expand({
+                  relative_url: id,
+                  view: options._view
+                }),
+              "xhrFields": {
+                withCredentials: true
+              }
+            });
+          })
+          .push(undefined, function (error) {
+            if ((error.target !== undefined) &&
+                (error.target.status === 404)) {
+              throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+            }
+            throw error;
+          });
+      });
   }
 
-  function restrictAttachmentId(id) {
-    if (id.indexOf("/") !== -1) {
-      throw new jIO.util.jIOError("attachment " + id + " is forbidden",
-                                  400);
-    }
+  var allowed_field_dict = {
+    "StringField": null,
+    "EmailField": null,
+    "IntegerField": null,
+    "FloatField": null,
+    "TextAreaField": null
+  };
+
+  function extractPropertyFromFormJSON(json) {
+    return new RSVP.Queue()
+      .push(function () {
+        var form = json._embedded._view,
+          converted_json = {
+            portal_type: json._links.type.name
+          },
+          form_data_json = {},
+          field,
+          key,
+          prefix_length,
+          result;
+
+        if (json._links.hasOwnProperty('parent')) {
+          converted_json.parent_relative_url =
+            new URI(json._links.parent.href).segment(2);
+        }
+
+        form_data_json.form_id = {
+          "key": [form.form_id.key],
+          "default": form.form_id["default"]
+        };
+        // XXX How to store datetime
+        for (key in form) {
+          if (form.hasOwnProperty(key)) {
+            field = form[key];
+            prefix_length = 0;
+            if (key.indexOf('my_') === 0 && field.editable) {
+              prefix_length = 3;
+            }
+            if (key.indexOf('your_') === 0) {
+              prefix_length = 5;
+            }
+            if ((prefix_length !== 0) &&
+                (allowed_field_dict.hasOwnProperty(field.type))) {
+              form_data_json[key.substring(prefix_length)] = {
+                "default": field["default"],
+                "key": field.key
+              };
+              converted_json[key.substring(prefix_length)] = field["default"];
+            }
+          }
+        }
+
+        result = {
+          data: converted_json,
+          form_data: form_data_json
+        };
+        if (form.hasOwnProperty('_actions') &&
+            form._actions.hasOwnProperty('put')) {
+          result.action_href = form._actions.put.href;
+        }
+        return result;
+      });
   }
 
-  /**
-   * The JIO WebDAV Storage extension
-   *
-   * @class DavStorage
-   * @constructor
-   */
-  function DavStorage(spec) {
-    if (typeof spec.url !== 'string') {
-      throw new TypeError("DavStorage 'url' is not of type string");
+  function extractPropertyFromForm(context, id) {
+    return context.getAttachment(id, "view")
+      .push(function (blob) {
+        return jIO.util.readBlobAsText(blob);
+      })
+      .push(function (evt) {
+        return JSON.parse(evt.target.result);
+      })
+      .push(function (json) {
+        return extractPropertyFromFormJSON(json);
+      });
+  }
+
+  // XXX docstring
+  function ERP5Storage(spec) {
+    if (typeof spec.url !== "string" || !spec.url) {
+      throw new TypeError("ERP5 'url' must be a string " +
+                          "which contains more than one character.");
     }
     this._url = spec.url;
-    // XXX digest login
-    if (typeof spec.basic_login === 'string') {
-      this._authorization = "Basic " + spec.basic_login;
-    }
-
+    this._default_view_reference = spec.default_view_reference;
   }
 
-  DavStorage.prototype.put = function (id, param) {
-    var that = this;
-    id = restrictDocumentId(id);
-    if (Object.getOwnPropertyNames(param).length > 0) {
-      // Reject if param has some properties
-      throw new jIO.util.jIOError("Can not store properties: " +
-                                  Object.getOwnPropertyNames(param), 400);
-    }
-    return new RSVP.Queue()
-      .push(function () {
-        return ajax(that, {
-          type: "MKCOL",
-          url: that._url + id
-        });
-      })
-      .push(undefined, function (err) {
-        if ((err.target !== undefined) &&
-            (err.target.status === 405)) {
-          return;
+  function convertJSONToGet(json) {
+    var key,
+      result = json.data;
+    // Remove all ERP5 hateoas links / convert them into jIO ID
+    for (key in result) {
+      if (result.hasOwnProperty(key)) {
+        if (!result[key]) {
+          delete result[key];
         }
-        throw err;
+      }
+    }
+    return result;
+  }
+
+  ERP5Storage.prototype.get = function (id) {
+    return extractPropertyFromForm(this, id)
+      .push(function (result) {
+        return convertJSONToGet(result);
       });
   };
 
-  DavStorage.prototype.remove = function (id) {
-    id = restrictDocumentId(id);
-    return ajax(this, {
-      type: "DELETE",
-      url: this._url + id
-    });
-  };
+  ERP5Storage.prototype.bulk = function (request_list) {
+    var i,
+      storage = this,
+      bulk_list = [];
 
-  DavStorage.prototype.get = function (id) {
-    var context = this;
-    id = restrictDocumentId(id);
 
-    return new RSVP.Queue()
-      .push(function () {
-        return ajax(context, {
-          type: "PROPFIND",
-          url: context._url + id,
-          dataType: "text",
-          headers: {
-            // Increasing this value is a performance killer
-            Depth: "1"
+    for (i = 0; i < request_list.length; i += 1) {
+      if (request_list[i].method !== "get") {
+        throw new Error("ERP5Storage: not supported " +
+                        request_list[i].method + " in bulk");
+      }
+      bulk_list.push({
+        relative_url: request_list[i].parameter_list[0],
+        view: storage._default_view_reference
+      });
+    }
+    return getSiteDocument(storage)
+      .push(function (site_hal) {
+        var form_data = new FormData();
+        form_data.append("bulk_list", JSON.stringify(bulk_list));
+        return jIO.util.ajax({
+          "type": "POST",
+          "url": site_hal._actions.bulk.href,
+          "data": form_data,
+//           "headers": {
+//             "Content-Type": "application/json"
+//           },
+          "xhrFields": {
+            withCredentials: true
           }
         });
       })
-      .push(function () {
-        return {};
-      }, function (error) {
-        if ((error.target !== undefined) &&
-            (error.target.status === 404)) {
-          throw new jIO.util.jIOError("Cannot find document", 404);
+      .push(function (response) {
+        var result_list = [],
+          hateoas = JSON.parse(response.target.responseText);
+
+        function pushResult(json) {
+          return extractPropertyFromFormJSON(json)
+            .push(function (json2) {
+              return convertJSONToGet(json2);
+            });
         }
-        throw error;
+
+        for (i = 0; i < hateoas.result_list.length; i += 1) {
+          result_list.push(pushResult(hateoas.result_list[i]));
+        }
+        return RSVP.all(result_list);
       });
   };
 
-  DavStorage.prototype.allAttachments = function (id) {
-
-    var context = this;
-    id = restrictDocumentId(id);
+  ERP5Storage.prototype.post = function (data) {
+    var context = this,
+      new_id;
 
-    return new RSVP.Queue()
-      .push(function () {
-        return ajax(context, {
-          type: "PROPFIND",
-          url: context._url + id,
-          dataType: "text",
-          headers: {
-            // Increasing this value is a performance killer
-            Depth: "1"
+    return getSiteDocument(this)
+      .push(function (site_hal) {
+        var form_data = new FormData();
+        form_data.append("portal_type", data.portal_type);
+        form_data.append("parent_relative_url", data.parent_relative_url);
+        return jIO.util.ajax({
+          type: "POST",
+          url: site_hal._actions.add.href,
+          data: form_data,
+          xhrFields: {
+            withCredentials: true
           }
         });
       })
+      .push(function (evt) {
+        var location = evt.target.getResponseHeader("X-Location"),
+          uri = new URI(location);
+        new_id = uri.segment(2);
+        return context.put(new_id, data);
+      })
+      .push(function () {
+        return new_id;
+      });
+  };
 
+  ERP5Storage.prototype.put = function (id, data) {
+    var context = this;
 
-      .push(function (response) {
-        // Extract all meta informations and return them to JSON
-
-        var i,
-          attachment = {},
-          id,
-          attachment_list = new DOMParser().parseFromString(
-            response.target.responseText,
-            "text/xml"
-          ).querySelectorAll(
-            "D\\:response, response"
-          );
+    return extractPropertyFromForm(context, id)
+      .push(function (result) {
+        var key,
+          json = result.form_data,
+          form_data = {};
+        form_data[json.form_id.key] = json.form_id["default"];
 
-        // exclude parent folder and browse
-        for (i = 1; i < attachment_list.length; i += 1) {
-          // XXX Only get files for now
-          id = attachment_list[i].querySelector("D\\:href, href").
-            textContent.split('/').slice(-1)[0];
-          // XXX Ugly
-          if ((id !== undefined) && (id !== "")) {
-            attachment[id] = {};
+        // XXX How to store datetime:!!!!!
+        for (key in data) {
+          if (data.hasOwnProperty(key)) {
+            if (key === "form_id") {
+              throw new jIO.util.jIOError(
+                "ERP5: forbidden property: " + key,
+                400
+              );
+            }
+            if ((key !== "portal_type") && (key !== "parent_relative_url")) {
+              if (!json.hasOwnProperty(key)) {
+                throw new jIO.util.jIOError(
+                  "ERP5: can not store property: " + key,
+                  400
+                );
+              }
+              form_data[json[key].key] = data[key];
+            }
           }
         }
-        return attachment;
-
-      }, function (error) {
-        if ((error.target !== undefined) &&
-            (error.target.status === 404)) {
-          throw new jIO.util.jIOError("Cannot find document", 404);
+        if (!result.hasOwnProperty('action_href')) {
+          throw new jIO.util.jIOError(
+            "ERP5: can not modify document: " + id,
+            403
+          );
         }
-        throw error;
+        return context.putAttachment(
+          id,
+          result.action_href,
+          new Blob([JSON.stringify(form_data)], {type: "application/json"})
+        );
       });
-
   };
 
+  ERP5Storage.prototype.allAttachments = function (id) {
+    var context = this;
+    return getDocumentAndHateoas(this, id)
+      .push(function () {
+        if (context._default_view_reference === undefined) {
+          return {
+            links: {}
+          };
+        }
+        return {
+          view: {},
+          links: {}
+        };
+      });
+  };
 
-  DavStorage.prototype.putAttachment = function (id, name, blob) {
-    var that = this;
-    id = restrictDocumentId(id);
-    restrictAttachmentId(name);
+  ERP5Storage.prototype.getAttachment = function (id, action, options) {
+    if (options === undefined) {
+      options = {};
+    }
+    if (action === "view") {
+      if (this._default_view_reference === undefined) {
+        throw new jIO.util.jIOError(
+          "Cannot find attachment view for: " + id,
+          404
+        );
+      }
+      return getDocumentAndHateoas(this, id,
+                                   {"_view": this._default_view_reference})
+        .push(function (response) {
+          var result = JSON.parse(response.target.responseText);
+          // Remove all ERP5 hateoas links / convert them into jIO ID
 
-    return new RSVP.Queue()
-      .push(function () {
-        return ajax(that, {
-          type: "PUT",
-          url: that._url + id + name,
-          data: blob
+          // XXX Change default action to an jio urn with attachment name inside
+          // if Base_edit, do put URN
+          // if others, do post URN (ie, unique new attachment name)
+          // XXX Except this attachment name should be generated when
+          return new Blob(
+            [JSON.stringify(result)],
+            {"type": 'application/hal+json'}
+          );
+        });
+    }
+    if (action === "links") {
+      return getDocumentAndHateoas(this, id)
+        .push(function (response) {
+          return new Blob(
+            [JSON.stringify(JSON.parse(response.target.responseText))],
+            {"type": 'application/hal+json'}
+          );
+        });
+    }
+    if (action.indexOf(this._url) === 0) {
+      return new RSVP.Queue()
+        .push(function () {
+          var start,
+            end,
+            range,
+            request_options = {
+              "type": "GET",
+              "dataType": "blob",
+              "url": action,
+              "xhrFields": {
+                withCredentials: true
+              }
+            };
+          if (options.start !== undefined ||  options.end !== undefined) {
+            start = options.start || 0;
+            end = options.end;
+            if (end !== undefined && end < 0) {
+              throw new jIO.util.jIOError("end must be positive",
+                                          400);
+            }
+            if (start < 0) {
+              range = "bytes=" + start;
+            } else if (end === undefined) {
+              range = "bytes=" + start + "-";
+            } else {
+              if (start > end) {
+                throw new jIO.util.jIOError("start is greater than end",
+                                            400);
+              }
+              range = "bytes=" + start + "-" + end;
+            }
+            request_options.headers = {Range: range};
+          }
+          return jIO.util.ajax(request_options);
+        })
+        .push(function (evt) {
+          if (evt.target.response === undefined) {
+            return new Blob(
+              [evt.target.responseText],
+              {"type": evt.target.getResponseHeader("Content-Type")}
+            );
+          }
+          return evt.target.response;
         });
-      })
-      .push(undefined, function (error) {
-        if (error.target.status === 403 || error.target.status === 424) {
-          throw new jIO.util.jIOError("Cannot access subdocument", 404);
-        }
-        throw error;
-      });
+    }
+    throw new jIO.util.jIOError("ERP5: not support get attachment: " + action,
+                                400);
   };
 
-  DavStorage.prototype.getAttachment = function (id, name) {
-    var context = this;
-    id = restrictDocumentId(id);
-    restrictAttachmentId(name);
+  ERP5Storage.prototype.putAttachment = function (id, name, blob) {
+    // Assert we use a callable on a document from the ERP5 site
+    if (name.indexOf(this._url) !== 0) {
+      throw new jIO.util.jIOError("Can not store outside ERP5: " +
+                                  name, 400);
+    }
 
     return new RSVP.Queue()
       .push(function () {
-        return ajax(context, {
-          type: "GET",
-          url: context._url + id + name,
-          dataType: "blob"
-        });
+        return jIO.util.readBlobAsText(blob);
       })
-      .push(function (response) {
-        return new Blob(
-          [response.target.response || response.target.responseText],
-          {"type": response.target.getResponseHeader('Content-Type') ||
-                   "application/octet-stream"}
-        );
-      }, function (error) {
-        if ((error.target !== undefined) &&
-            (error.target.status === 404)) {
-          throw new jIO.util.jIOError("Cannot find attachment: "
-                                      + id + " , " + name,
-                                      404);
+      .push(function (evt) {
+        var form_data = JSON.parse(evt.target.result),
+          data = new FormData(),
+          array,
+          i,
+          key,
+          value;
+        for (key in form_data) {
+          if (form_data.hasOwnProperty(key)) {
+            if (Array.isArray(form_data[key])) {
+              array = form_data[key];
+            } else {
+              array = [form_data[key]];
+            }
+            for (i = 0; i < array.length; i += 1) {
+              value = array[i];
+              if (typeof value === "object") {
+                data.append(key, jIO.util.dataURItoBlob(value.url),
+                            value.file_name);
+              } else {
+                data.append(key, value);
+              }
+            }
+          }
         }
-        throw error;
+        return jIO.util.ajax({
+          "type": "POST",
+          "url": name,
+          "data": data,
+          "xhrFields": {
+            withCredentials: true
+          }
+        });
       });
+  };
 
+  ERP5Storage.prototype.hasCapacity = function (name) {
+    return ((name === "list") || (name === "query") ||
+            (name === "select") || (name === "limit") ||
+            (name === "sort"));
   };
 
-  DavStorage.prototype.removeAttachment = function (id, name) {
-    var context = this;
-    id = restrictDocumentId(id);
-    restrictAttachmentId(name);
+  function isSingleLocalRoles(parsed_query) {
+    if ((parsed_query instanceof SimpleQuery) &&
+        (parsed_query.key === 'local_roles')) {
+      // local_roles:"Assignee"
+      return parsed_query.value;
+    }
+  }
 
-    return new RSVP.Queue()
-      .push(function () {
-        return ajax(context, {
-          type: "DELETE",
-          url: context._url + id + name
-        });
-      })
-      .push(undefined, function (error) {
-        if ((error.target !== undefined) &&
-            (error.target.status === 404)) {
-          throw new jIO.util.jIOError("Cannot find attachment: "
-                                      + id + " , " + name,
-                                      404);
-        }
-        throw error;
-      });
-  };
+  function isMultipleLocalRoles(parsed_query) {
+    var i,
+      sub_query,
+      is_multiple = true,
+      local_role_list = [];
+    if ((parsed_query instanceof ComplexQuery) &&
+        (parsed_query.operator === 'OR')) {
 
-  // JIO COMMANDS //
+      for (i = 0; i < parsed_query.query_list.length; i += 1) {
+        sub_query = parsed_query.query_list[i];
+        if ((sub_query instanceof SimpleQuery) &&
+            (sub_query.key === 'local_roles')) {
+          local_role_list.push(sub_query.value);
+        } else {
+          is_multiple = false;
+        }
+      }
+      if (is_multiple) {
+        // local_roles:"Assignee" OR local_roles:"Assignor"
+        return local_role_list;
+      }
+    }
+  }
 
-  // wedDav methods rfc4918 (short summary)
-  // COPY     Reproduces single resources (files) and collections (directory
-  //          trees). Will overwrite files (if specified by request) but will
-  //          respond 209 (Conflict) if it would overwrite a tree
-  // DELETE   deletes files and directory trees
-  // GET      just the vanilla HTTP/1.1 behaviour
-  // HEAD     ditto
-  // LOCK     locks a resources
-  // MKCOL    creates a directory
-  // MOVE     Moves (rename or copy) a file or a directory tree. Will
-  //          'overwrite' files (if specified by the request) but will respond
-  //          209 (Conflict) if it would overwrite a tree.
-  // OPTIONS  If WebDAV is enabled and available for the path this reports the
-  //          WebDAV extension methods
-  // PROPFIND Retrieves the requested file characteristics, DAV lock status
-  //          and 'dead' properties for individual files, a directory and its
-  //          child files, or a directory tree
-  // PROPPATCHset and remove 'dead' meta-data properties
-  // PUT      Update or create resource or collections
-  // UNLOCK   unlocks a resource
+  ERP5Storage.prototype.buildQuery = function (options) {
+//     if (typeof options.query !== "string") {
+//       options.query = (options.query ?
+//                        jIO.Query.objectToSearchText(options.query) :
+//                        undefined);
+//     }
+    return getSiteDocument(this)
+      .push(function (site_hal) {
+        var query = options.query,
+          i,
+          parsed_query,
+          sub_query,
+          result_list,
+          local_roles,
+          tmp_list = [];
+        if (options.query) {
+          parsed_query = jIO.QueryFactory.create(options.query);
 
-  // Notes: all Ajax requests should be CORS (cross-domain)
-  // adding custom headers triggers preflight OPTIONS request!
-  // http://remysharp.com/2011/04/21/getting-cors-working/
+          result_list = isSingleLocalRoles(parsed_query);
+          if (result_list) {
+            query = undefined;
+            local_roles = result_list;
+          } else {
 
-  jIO.addStorage('dav', DavStorage);
+            result_list = isMultipleLocalRoles(parsed_query);
+            if (result_list) {
+              query = undefined;
+              local_roles = result_list;
+            } else if ((parsed_query instanceof ComplexQuery) &&
+                       (parsed_query.operator === 'AND')) {
 
-}(jIO, RSVP, DOMParser, Blob));
-;/*
- * Copyright 2015, Nexedi SA
- * Released under the LGPL license.
- * http://www.gnu.org/licenses/lgpl.html
- */
-/**
- * JIO Google Drive Storage. Type = "gdrive".
- * Google Drive "database" storage.
- */
-/*global jIO, Blob, RSVP, UriTemplate, JSON*/
-/*jslint nomen: true*/
+              // portal_type:"Person" AND local_roles:"Assignee"
+              for (i = 0; i < parsed_query.query_list.length; i += 1) {
+                sub_query = parsed_query.query_list[i];
 
-(function (jIO, Blob, RSVP, UriTemplate, JSON) {
-  "use strict";
+                result_list = isSingleLocalRoles(sub_query);
+                if (result_list) {
+                  local_roles = result_list;
+                  parsed_query.query_list.splice(i, 1);
+                  query = jIO.Query.objectToSearchText(parsed_query);
+                  i = parsed_query.query_list.length;
+                } else {
+                  result_list = isMultipleLocalRoles(sub_query);
+                  if (result_list) {
+                    local_roles = result_list;
+                    parsed_query.query_list.splice(i, 1);
+                    query = jIO.Query.objectToSearchText(parsed_query);
+                    i = parsed_query.query_list.length;
+                  }
+                }
+              }
+            }
 
-  var UPLOAD_URL = "https://www.googleapis.com{/upload}/drive/v2/files{/id}" +
-      "{?uploadType,access_token}",
-    upload_template = UriTemplate.parse(UPLOAD_URL),
-    REMOVE_URL = "https://www.googleapis.com/drive/v2/" +
-      "files{/id,trash}{?access_token}",
-    remove_template = UriTemplate.parse(REMOVE_URL),
-    LIST_URL = "https://www.googleapis.com/drive/v2/files" +
-      "?prettyPrint=false{&pageToken}&q=trashed=false" +
-      "&fields=nextPageToken,items(id){&access_token}",
-    list_template = UriTemplate.parse(LIST_URL),
-    GET_URL = "https://www.googleapis.com/drive/v2/files{/id}{?alt}",
-    get_template = UriTemplate.parse(GET_URL);
+          }
+        }
 
-  function handleError(error, id) {
-    if (error.target.status === 404) {
-      throw new jIO.util.jIOError(
-        "Cannot find document: " + id,
-        404
-      );
-    }
-    throw error;
-  }
+        if (options.sort_on) {
+          for (i = 0; i < options.sort_on.length; i += 1) {
+            tmp_list.push(JSON.stringify(options.sort_on[i]));
+          }
+          options.sort_on = tmp_list;
+        }
 
-  function listPage(result, token) {
-    var i,
-      obj;
-    return new RSVP.Queue()
-      .push(function () {
         return jIO.util.ajax({
           "type": "GET",
-          "url": list_template.expand({
-            pageToken : (result.nextPageToken || ""),
-            access_token: token
-          })
+          "url": UriTemplate.parse(site_hal._links.raw_search.href)
+                            .expand({
+              query: query,
+              // XXX Force erp5 to return embedded document
+              select_list: options.select_list || ["title", "reference"],
+              limit: options.limit,
+              sort_on: options.sort_on,
+              local_roles: local_roles
+            }),
+          "xhrFields": {
+            withCredentials: true
+          }
         });
       })
-      .push(function (data) {
-        obj = JSON.parse(data.target.response || data.target.responseText);
-        for (i = 0; i < obj.items.length; i += 1) {
-          obj.items[i].value = {};
-          result.push(obj.items[i]);
+      .push(function (response) {
+        return JSON.parse(response.target.responseText);
+      })
+      .push(function (catalog_json) {
+        var data = catalog_json._embedded.contents,
+          count = data.length,
+          i,
+          uri,
+          item,
+          result = [];
+        for (i = 0; i < count; i += 1) {
+          item = data[i];
+          uri = new URI(item._links.self.href);
+          delete item._links;
+          result.push({
+            id: uri.segment(2),
+            value: item
+          });
         }
-        result.nextPageToken = obj.nextPageToken;
         return result;
-      }, handleError);
-  }
+      });
+  };
 
-  function checkName(name) {
-    if (name !== "enclosure") {
-      throw new jIO.util.jIOError("Only support 'enclosure' attachment", 400);
-    }
-  }
+  jIO.addStorage("erp5", ERP5Storage);
+
+}(jIO, UriTemplate, FormData, RSVP, URI, Blob,
+  SimpleQuery, ComplexQuery));
+;/*jslint nomen: true*/
+/*global RSVP*/
+(function (jIO, RSVP) {
+  "use strict";
 
   /**
-   * The JIO Google Drive Storage extension
+   * The jIO QueryStorage extension
    *
-   * @class GdriveStorage
+   * @class QueryStorage
    * @constructor
    */
-  function GdriveStorage(spec) {
-    if (spec === undefined || spec.access_token === undefined ||
-        typeof spec.access_token !== 'string') {
-      throw new TypeError("Access Token must be a string " +
-                          "which contains more than one character.");
-    }
-    if (spec.trashing !== undefined &&
-        (spec.trashing !== true && spec.trashing !== false)) {
-      throw new TypeError("trashing parameter" +
-                          " must be a boolean (true or false)");
-    }
-    this._trashing = spec.trashing || true;
-    this._access_token = spec.access_token;
-    return;
-  }
-
-  function recursiveAllDocs(result, accessToken) {
-    return new RSVP.Queue()
-      .push(function () {
-        return listPage(result, accessToken);
-      })
-      .push(function () {
-        if (result.nextPageToken) {
-          return recursiveAllDocs(result, accessToken);
-        }
-        return result;
-      });
+  function QueryStorage(spec) {
+    this._sub_storage = jIO.createJIO(spec.sub_storage);
+    this._key_schema = spec.key_schema;
   }
 
-  GdriveStorage.prototype.hasCapacity = function (name) {
-    return (name === "list");
+  QueryStorage.prototype.get = function () {
+    return this._sub_storage.get.apply(this._sub_storage, arguments);
   };
-
-  GdriveStorage.prototype.buildQuery = function () {
-    return recursiveAllDocs([], this._access_token);
+  QueryStorage.prototype.allAttachments = function () {
+    return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
+  };
+  QueryStorage.prototype.post = function () {
+    return this._sub_storage.post.apply(this._sub_storage, arguments);
+  };
+  QueryStorage.prototype.put = function () {
+    return this._sub_storage.put.apply(this._sub_storage, arguments);
+  };
+  QueryStorage.prototype.remove = function () {
+    return this._sub_storage.remove.apply(this._sub_storage, arguments);
+  };
+  QueryStorage.prototype.getAttachment = function () {
+    return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
+  };
+  QueryStorage.prototype.putAttachment = function () {
+    return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
+  };
+  QueryStorage.prototype.removeAttachment = function () {
+    return this._sub_storage.removeAttachment.apply(this._sub_storage,
+                                                    arguments);
+  };
+  QueryStorage.prototype.repair = function () {
+    return this._sub_storage.repair.apply(this._sub_storage, arguments);
   };
 
-  function sendMetaData(id, param, token) {
-    var boundary = "-------314159265358979323846";
-
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          "type": id ? "PUT" : "POST",
-          "url": upload_template.expand({
-            access_token: token,
-            id: id || [],
-            upload: id ? [] : "upload",
-            uploadType: "multipart"
-          }),
-          headers: {
-            "Content-Type" : 'multipart/related; boundary="' + boundary + '"'
-          },
-          data: '--' + boundary + '\n' +
-            'Content-Type: application/json; charset=UTF-8\n\n' +
-            JSON.stringify(param) + '\n\n--' + boundary + "--"
-        });
-      })
-      .push(function (result) {
-        var obj = JSON.parse(result.target.responseText);
-
-        return obj.id;
-      },
-            function (error) {handleError(error, id); });
-  }
+  QueryStorage.prototype.hasCapacity = function (name) {
+    var this_storage_capacity_list = ["limit",
+                                      "sort",
+                                      "select",
+                                      "query"];
 
-  GdriveStorage.prototype.put = function (id, param) {
-    return sendMetaData(id, param, this._access_token);
+    if (this_storage_capacity_list.indexOf(name) !== -1) {
+      return true;
+    }
+    if (name === "list") {
+      return this._sub_storage.hasCapacity(name);
+    }
+    return false;
   };
+  QueryStorage.prototype.buildQuery = function (options) {
+    var substorage = this._sub_storage,
+      context = this,
+      sub_options = {},
+      is_manual_query_needed = false,
+      is_manual_include_needed = false;
 
-  GdriveStorage.prototype.post = function (param) {
-    return sendMetaData(undefined, param, this._access_token);
-  };
+    if (substorage.hasCapacity("list")) {
 
-  function sendData(id, blob, token) {
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          "type": "PUT",
-          "url": upload_template.expand({
-            access_token: token,
-            upload: "upload",
-            id: id,
-            uploadType: "media"
-          }),
-          data: blob
-        });
-      })
-      .push(function (data) {
-        data = JSON.parse(data.target.responseText);
-        if (data.mimeType === "application/vnd.google-apps.folder") {
-          throw new jIO.util.jIOError("cannot put attachments to folder", 400);
+      // Can substorage handle the queries if needed?
+      try {
+        if (((options.query === undefined) ||
+             (substorage.hasCapacity("query"))) &&
+            ((options.sort_on === undefined) ||
+             (substorage.hasCapacity("sort"))) &&
+            ((options.select_list === undefined) ||
+             (substorage.hasCapacity("select"))) &&
+            ((options.limit === undefined) ||
+             (substorage.hasCapacity("limit")))) {
+          sub_options.query = options.query;
+          sub_options.sort_on = options.sort_on;
+          sub_options.select_list = options.select_list;
+          sub_options.limit = options.limit;
         }
-        return data;
-      }, function (error) {handleError(error, id); });
-  }
+      } catch (error) {
+        if ((error instanceof jIO.util.jIOError) &&
+            (error.status_code === 501)) {
+          is_manual_query_needed = true;
+        } else {
+          throw error;
+        }
+      }
 
-  GdriveStorage.prototype.putAttachment = function (id, name, blob) {
-    checkName(name);
-    return sendData(id, blob, this._access_token);
-  };
+      // Can substorage include the docs if needed?
+      try {
+        if ((is_manual_query_needed ||
+            (options.include_docs === true)) &&
+            (substorage.hasCapacity("include"))) {
+          sub_options.include_docs = true;
+        }
+      } catch (error) {
+        if ((error instanceof jIO.util.jIOError) &&
+            (error.status_code === 501)) {
+          is_manual_include_needed = true;
+        } else {
+          throw error;
+        }
+      }
 
-  GdriveStorage.prototype.remove = function (id) {
-    var that  = this;
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          type: that._trashing ? "POST" : "DELETE",
-          url: remove_template.expand({
-            id : id,
-            access_token : that._access_token,
-            trash : that._trashing ? "trash" : []
-          })
-        });
-      })
-      .push(undefined, function (error) {handleError(error, id); });
-  };
+      return substorage.buildQuery(sub_options)
 
-  function getData(id, attach, token) {
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          type: "GET",
-          dataType: attach ? "blob" : "json",
-          url: get_template.expand({
-            id: id,
-            alt: attach ? "media" : [],
-            access_token: token
-          }),
-          headers: {
-            "Authorization" : "Bearer " + token
+        // Include docs if needed
+        .push(function (result) {
+          var include_query_list = [result],
+            len,
+            i;
+
+          function safeGet(j) {
+            var id = result[j].id;
+            return substorage.get(id)
+              .push(function (doc) {
+                // XXX Can delete user data!
+                doc._id = id;
+                return doc;
+              }, function (error) {
+                // Document may have been dropped after listing
+                if ((error instanceof jIO.util.jIOError) &&
+                    (error.status_code === 404)) {
+                  return;
+                }
+                throw error;
+              });
           }
-        });
-      })
-      .push(function (evt) {
-        return evt.target.response ||
-          (attach ? new Blob([evt.target.responseText],
-                             {"type" :
-                              evt.target.responseHeaders["Content-Type"]}) :
-              JSON.parse(evt.target.responseText));
-      }, function (error) {handleError(error, id); });
-  }
-
-  GdriveStorage.prototype.get = function (id) {
-    return getData(id, false, this._access_token);
-  };
 
-  GdriveStorage.prototype.getAttachment = function (id, name) {
-    checkName(name);
-    return getData(id, true, this._access_token);
-  };
+          if (is_manual_include_needed) {
+            len = result.length;
+            for (i = 0; i < len; i += 1) {
+              include_query_list.push(safeGet(i));
+            }
+            result = RSVP.all(include_query_list);
+          }
+          return result;
+        })
+        .push(function (result) {
+          var original_result,
+            len,
+            i;
+          if (is_manual_include_needed) {
+            original_result = result[0];
+            len = original_result.length;
+            for (i = 0; i < len; i += 1) {
+              original_result[i].doc = result[i + 1];
+            }
+            result = original_result;
+          }
+          return result;
 
-  GdriveStorage.prototype.allAttachments = function (id) {
-    var token = this._access_token;
+        })
 
-    return new RSVP.Queue()
-      .push(function () {
-        return getData(id, false, token);
-      })
-      .push(function (data) {
-        if (data.mimeType === "application/vnd.google-apps.folder") {
-          return {};
-        }
-        return {"enclosure": {}};
-      });
-  };
+        // Manual query if needed
+        .push(function (result) {
+          var data_rows = [],
+            len,
+            i;
+          if (is_manual_query_needed) {
+            len = result.length;
+            for (i = 0; i < len; i += 1) {
+              result[i].doc.__id = result[i].id;
+              data_rows.push(result[i].doc);
+            }
+            if (options.select_list) {
+              options.select_list.push("__id");
+            }
+            result = jIO.QueryFactory.create(options.query || "",
+                                             context._key_schema).
+              exec(data_rows, options);
+          }
+          return result;
+        })
 
-  jIO.addStorage('gdrive', GdriveStorage);
+        // reconstruct filtered rows, preserving the order from docs
+        .push(function (result) {
+          var new_result = [],
+            element,
+            len,
+            i;
+          if (is_manual_query_needed) {
+            len = result.length;
+            for (i = 0; i < len; i += 1) {
+              element = {
+                id: result[i].__id,
+                value: options.select_list ? result[i] : {},
+                doc: {}
+              };
+              if (options.select_list) {
+                // Does not work if user manually request __id
+                delete element.value.__id;
+              }
+              if (options.include_docs) {
+                // XXX To implement
+                throw new Error("QueryStorage does not support include docs");
+              }
+              new_result.push(element);
+            }
+            result = new_result;
+          }
+          return result;
+        });
 
-}(jIO, Blob, RSVP, UriTemplate, JSON));
-;/*jslint nomen: true */
-/*global RSVP*/
+    }
+  };
 
-/**
- * JIO Union Storage. Type = 'union'.
- * This provide a unified access other multiple storage.
- * New document are created in the first sub storage.
- * Document are searched in each sub storage until it is found.
- * 
- *
- * Storage Description:
- *
- *     {
- *       "type": "union",
- *       "storage_list": [
- *         sub_storage_description_1,
- *         sub_storage_description_2,
- *
- *         sub_storage_description_X,
- *       ]
- *     }
- *
- * @class UnionStorage
- */
+  jIO.addStorage('query', QueryStorage);
 
-(function (jIO, RSVP) {
+}(jIO, RSVP));
+;/*jslint nomen: true*/
+/*global RSVP, Blob*/
+(function (jIO, RSVP, Blob) {
   "use strict";
 
   /**
-   * The JIO UnionStorage extension
+   * The jIO FileSystemBridgeStorage extension
    *
-   * @class UnionStorage
+   * @class FileSystemBridgeStorage
    * @constructor
    */
-  function UnionStorage(spec) {
-    if (!Array.isArray(spec.storage_list)) {
-      throw new jIO.util.jIOError("storage_list is not an Array", 400);
-    }
-    var i;
-    this._storage_list = [];
-    for (i = 0; i < spec.storage_list.length; i += 1) {
-      this._storage_list.push(jIO.createJIO(spec.storage_list[i]));
-    }
+  function FileSystemBridgeStorage(spec) {
+    this._sub_storage = jIO.createJIO(spec.sub_storage);
   }
+  var DOCUMENT_EXTENSION = ".json",
+    DOCUMENT_KEY = "/.jio_documents/",
+    ROOT = "/";
 
-  UnionStorage.prototype._getWithStorageIndex = function () {
-    var i,
-      index = 0,
-      context = this,
-      arg = arguments,
-      result = this._storage_list[0].get.apply(this._storage_list[0], arg);
+  function endsWith(str, suffix) {
+    return str.indexOf(suffix, str.length - suffix.length) !== -1;
+  }
 
-    function handle404(j) {
-      result
-        .push(undefined, function (error) {
-          if ((error instanceof jIO.util.jIOError) &&
-              (error.status_code === 404)) {
-            return context._storage_list[j].get.apply(context._storage_list[j],
-                                                      arg)
-              .push(function (doc) {
-                index = j;
-                return doc;
-              });
-          }
-          throw error;
-        });
-    }
+  FileSystemBridgeStorage.prototype.get = function (id) {
+    var context = this;
+    return new RSVP.Queue()
 
-    for (i = 1; i < this._storage_list.length; i += 1) {
-      handle404(i);
-    }
-    return result
-      .push(function (doc) {
-        return [index, doc];
-      });
-  };
+      // First, try to get explicit reference to the document
 
-  /*
-   * Get a document
-   * Try on each substorage on after the other
-   */
-  UnionStorage.prototype.get = function () {
-    return this._getWithStorageIndex.apply(this, arguments)
-      .push(function (result) {
-        return result[1];
+      .push(function () {
+        // First get the document itself if it exists
+        return context._sub_storage.getAttachment(
+          DOCUMENT_KEY,
+          id + DOCUMENT_EXTENSION,
+          {format: "json"}
+        );
+      })
+      .push(undefined, function (error) {
+        if ((error instanceof jIO.util.jIOError) &&
+            (error.status_code === 404)) {
+
+          // Second, try to get default attachment
+          return context._sub_storage.allAttachments(ROOT)
+            .push(function (attachment_dict) {
+              if (attachment_dict.hasOwnProperty(id)) {
+                return {};
+              }
+              throw new jIO.util.jIOError("Cannot find document " + id,
+                                          404);
+            });
+        }
+        throw error;
       });
   };
 
-  /*
-   * Get attachments list
-   * Try on each substorage on after the other
-   */
-  UnionStorage.prototype.allAttachments = function () {
-    var argument_list = arguments,
-      context = this;
-    return this._getWithStorageIndex.apply(this, arguments)
-      .push(function (result) {
-        var sub_storage = context._storage_list[result[0]];
-        return sub_storage.allAttachments.apply(sub_storage, argument_list);
+  FileSystemBridgeStorage.prototype.allAttachments = function (id) {
+    var context = this;
+    return context._sub_storage.allAttachments(ROOT)
+      .push(function (attachment_dict) {
+        if (attachment_dict.hasOwnProperty(id)) {
+          return {
+            enclosure: {}
+          };
+        }
+        // Second get the document itself if it exists
+        return context._sub_storage.getAttachment(
+          DOCUMENT_KEY,
+          id + DOCUMENT_EXTENSION
+        )
+          .push(function () {
+            return {};
+          }, function (error) {
+            if ((error instanceof jIO.util.jIOError) &&
+                (error.status_code === 404)) {
+              throw new jIO.util.jIOError("Cannot find document " + id,
+                                          404);
+            }
+            throw error;
+          });
       });
-  };
 
-  /*
-   * Post a document
-   * Simply store on the first substorage
-   */
-  UnionStorage.prototype.post = function () {
-    return this._storage_list[0].post.apply(this._storage_list[0], arguments);
   };
 
-  /*
-   * Put a document
-   * Search the document location, and modify it in its storage.
-   */
-  UnionStorage.prototype.put = function () {
-    var arg = arguments,
-      context = this;
-    return this._getWithStorageIndex(arg[0])
+  FileSystemBridgeStorage.prototype.put = function (doc_id, param) {
+    var context = this;
+    // XXX Handle conflict!
+
+    return context._sub_storage.putAttachment(
+      DOCUMENT_KEY,
+      doc_id + DOCUMENT_EXTENSION,
+      new Blob([JSON.stringify(param)], {type: "application/json"})
+    )
       .push(undefined, function (error) {
         if ((error instanceof jIO.util.jIOError) &&
             (error.status_code === 404)) {
-          // Document does not exist, create in first substorage
-          return [0];
+          return context._sub_storage.put(DOCUMENT_KEY, {})
+            .push(function () {
+              return context._sub_storage.putAttachment(
+                DOCUMENT_KEY,
+                doc_id + DOCUMENT_EXTENSION,
+                new Blob([JSON.stringify(param)],
+                         {type: "application/json"})
+              );
+            });
         }
         throw error;
       })
-      .push(function (result) {
-        // Storage found, modify in it directly
-        var sub_storage = context._storage_list[result[0]];
-        return sub_storage.put.apply(sub_storage, arg);
+      .push(function () {
+        return doc_id;
       });
-  };
 
-  /*
-   * Remove a document
-   * Search the document location, and remove it from its storage.
-   */
-  UnionStorage.prototype.remove = function () {
-    var arg = arguments,
-      context = this;
-    return this._getWithStorageIndex(arg[0])
-      .push(function (result) {
-        // Storage found, remove from it directly
-        var sub_storage = context._storage_list[result[0]];
-        return sub_storage.remove.apply(sub_storage, arg);
-      });
   };
 
-  UnionStorage.prototype.buildQuery = function () {
-    var promise_list = [],
-      i,
-      id_dict = {},
-      len = this._storage_list.length,
-      sub_storage;
-    for (i = 0; i < len; i += 1) {
-      sub_storage = this._storage_list[i];
-      promise_list.push(sub_storage.buildQuery.apply(sub_storage, arguments));
-    }
+  FileSystemBridgeStorage.prototype.remove = function (doc_id) {
+    var context = this,
+      got_error = false;
     return new RSVP.Queue()
+
+      // First, try to remove enclosure
       .push(function () {
-        return RSVP.all(promise_list);
+        return context._sub_storage.removeAttachment(
+          ROOT,
+          doc_id
+        );
       })
-      .push(function (result_list) {
-        var result = [],
-          sub_result,
-          sub_result_len,
-          j;
-        len = result_list.length;
-        for (i = 0; i < len; i += 1) {
-          sub_result = result_list[i];
-          sub_result_len = sub_result.length;
-          for (j = 0; j < sub_result_len; j += 1) {
-            if (!id_dict.hasOwnProperty(sub_result[j].id)) {
-              id_dict[sub_result[j].id] = null;
-              result.push(sub_result[j]);
-            }
-          }
-        }
-        return result;
-      });
-  };
 
-  UnionStorage.prototype.hasCapacity = function (name) {
-    var i,
-      len,
-      result,
-      sub_storage;
-    if ((name === "list") ||
-            (name === "query") ||
-            (name === "select")) {
-      result = true;
-      len = this._storage_list.length;
-      for (i = 0; i < len; i += 1) {
-        sub_storage = this._storage_list[i];
-        result = result && sub_storage.hasCapacity(name);
-      }
-      return result;
-    }
-    return false;
-  };
+      .push(undefined, function (error) {
+        if ((error instanceof jIO.util.jIOError) &&
+            (error.status_code === 404)) {
+          got_error = true;
+          return;
+        }
+        throw error;
+      })
 
-  UnionStorage.prototype.repair = function () {
-    var i,
-      promise_list = [];
-    for (i = 0; i < this._storage_list.length; i += 1) {
-      promise_list.push(this._storage_list[i].repair.apply(
-        this._storage_list[i],
-        arguments
-      ));
-    }
-    return RSVP.all(promise_list);
-  };
+      // Second, try to remove explicit doc
+      .push(function () {
+        return context._sub_storage.removeAttachment(
+          DOCUMENT_KEY,
+          doc_id + DOCUMENT_EXTENSION
+        );
+      })
 
-  UnionStorage.prototype.getAttachment = function () {
-    var argument_list = arguments,
-      context = this;
-    return this._getWithStorageIndex.apply(this, arguments)
-      .push(function (result) {
-        var sub_storage = context._storage_list[result[0]];
-        return sub_storage.getAttachment.apply(sub_storage, argument_list);
+      .push(undefined, function (error) {
+        if ((!got_error) && (error instanceof jIO.util.jIOError) &&
+            (error.status_code === 404)) {
+          return doc_id;
+        }
+        throw error;
       });
-  };
 
-  UnionStorage.prototype.putAttachment = function () {
-    var argument_list = arguments,
-      context = this;
-    return this._getWithStorageIndex.apply(this, arguments)
-      .push(function (result) {
-        var sub_storage = context._storage_list[result[0]];
-        return sub_storage.putAttachment.apply(sub_storage, argument_list);
-      });
   };
 
-  UnionStorage.prototype.removeAttachment = function () {
-    var argument_list = arguments,
-      context = this;
-    return this._getWithStorageIndex.apply(this, arguments)
-      .push(function (result) {
-        var sub_storage = context._storage_list[result[0]];
-        return sub_storage.removeAttachment.apply(sub_storage, argument_list);
-      });
+  FileSystemBridgeStorage.prototype.hasCapacity = function (capacity) {
+    return (capacity === "list");
   };
 
-  jIO.addStorage('union', UnionStorage);
-
-}(jIO, RSVP));
-;/*
- * Copyright 2013, Nexedi SA
- * Released under the LGPL license.
- * http://www.gnu.org/licenses/lgpl.html
- */
-// JIO ERP5 Storage Description :
-// {
-//   type: "erp5"
-//   url: {string}
-// }
-
-/*jslint nomen: true, unparam: true */
-/*global jIO, UriTemplate, FormData, RSVP, URI, Blob,
-         SimpleQuery, ComplexQuery*/
-
-(function (jIO, UriTemplate, FormData, RSVP, URI, Blob,
-           SimpleQuery, ComplexQuery) {
-  "use strict";
-
-  function getSiteDocument(storage) {
+  FileSystemBridgeStorage.prototype.buildQuery = function () {
+    var result_dict = {},
+      context = this;
     return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.ajax({
-          "type": "GET",
-          "url": storage._url,
-          "xhrFields": {
-            withCredentials: true
-          }
-        });
-      })
-      .push(function (event) {
-        return JSON.parse(event.target.responseText);
-      });
-  }
-
-  function getDocumentAndHateoas(storage, id, options) {
-    if (options === undefined) {
-      options = {};
-    }
-    return getSiteDocument(storage)
-      .push(function (site_hal) {
-        // XXX need to get modified metadata
-        return new RSVP.Queue()
-          .push(function () {
-            return jIO.util.ajax({
-              "type": "GET",
-              "url": UriTemplate.parse(site_hal._links.traverse.href)
-                                .expand({
-                  relative_url: id,
-                  view: options._view
-                }),
-              "xhrFields": {
-                withCredentials: true
-              }
-            });
-          })
-          .push(undefined, function (error) {
-            if ((error.target !== undefined) &&
-                (error.target.status === 404)) {
-              throw new jIO.util.jIOError("Cannot find document: " + id, 404);
-            }
-            throw error;
-          });
-      });
-  }
-
-  var allowed_field_dict = {
-    "StringField": null,
-    "EmailField": null,
-    "IntegerField": null,
-    "FloatField": null,
-    "TextAreaField": null
-  };
 
-  function extractPropertyFromFormJSON(json) {
-    return new RSVP.Queue()
-      .push(function () {
-        var form = json._embedded._view,
-          converted_json = {
-            portal_type: json.portal_type
-          },
-          form_data_json = {},
-          field,
-          key,
-          prefix_length;
+      // First, get list of explicit documents
 
-        form_data_json.form_id = {
-          "key": [form.form_id.key],
-          "default": form.form_id["default"]
-        };
-        // XXX How to store datetime
-        for (key in form) {
-          if (form.hasOwnProperty(key)) {
-            field = form[key];
-            prefix_length = 0;
-            if (key.indexOf('my_') === 0 && field.editable) {
-              prefix_length = 3;
-            }
-            if (key.indexOf('your_') === 0) {
-              prefix_length = 5;
-            }
-            if ((prefix_length !== 0) &&
-                (allowed_field_dict.hasOwnProperty(field.type))) {
-              form_data_json[key.substring(prefix_length)] = {
-                "default": field["default"],
-                "key": field.key
-              };
-              converted_json[key.substring(prefix_length)] = field["default"];
+      .push(function () {
+        return context._sub_storage.allAttachments(DOCUMENT_KEY);
+      })
+      .push(function (result) {
+        var key;
+        for (key in result) {
+          if (result.hasOwnProperty(key)) {
+            if (endsWith(key, DOCUMENT_EXTENSION)) {
+              result_dict[key.substring(
+                0,
+                key.length - DOCUMENT_EXTENSION.length
+              )] = null;
             }
           }
         }
+      }, function (error) {
+        if ((error instanceof jIO.util.jIOError) &&
+            (error.status_code === 404)) {
+          return;
+        }
+        throw error;
+      })
 
-        return {
-          action_href: form._actions.put.href,
-          data: converted_json,
-          form_data: form_data_json
-        };
-      });
-  }
+      // Second, get list of enclosure
 
-  function extractPropertyFromForm(context, id) {
-    return context.getAttachment(id, "view")
-      .push(function (blob) {
-        return jIO.util.readBlobAsText(blob);
+      .push(function () {
+        return context._sub_storage.allAttachments(ROOT);
       })
-      .push(function (evt) {
-        return JSON.parse(evt.target.result);
+      .push(function (result) {
+        var key;
+        for (key in result) {
+          if (result.hasOwnProperty(key)) {
+            result_dict[key] = null;
+          }
+        }
       })
-      .push(function (json) {
-        return extractPropertyFromFormJSON(json);
-      });
-  }
 
-  // XXX docstring
-  function ERP5Storage(spec) {
-    if (typeof spec.url !== "string" || !spec.url) {
-      throw new TypeError("ERP5 'url' must be a string " +
-                          "which contains more than one character.");
-    }
-    this._url = spec.url;
-    this._default_view_reference = spec.default_view_reference;
-  }
+      // Finally, build the result
 
-  function convertJSONToGet(json) {
-    var key,
-      result = json.data;
-    // Remove all ERP5 hateoas links / convert them into jIO ID
-    for (key in result) {
-      if (result.hasOwnProperty(key)) {
-        if (!result[key]) {
-          delete result[key];
+      .push(function () {
+        var result = [],
+          key;
+        for (key in result_dict) {
+          if (result_dict.hasOwnProperty(key)) {
+            result.push({
+              id: key,
+              value: {}
+            });
+          }
         }
-      }
+        return result;
+      });
+
+  };
+
+  FileSystemBridgeStorage.prototype.getAttachment = function (id, name) {
+    if (name !== "enclosure") {
+      throw new jIO.util.jIOError("Only support 'enclosure' attachment",
+                                  400);
     }
-    return result;
-  }
 
-  ERP5Storage.prototype.get = function (id) {
-    return extractPropertyFromForm(this, id)
-      .push(function (result) {
-        return convertJSONToGet(result);
-      });
+    return this._sub_storage.getAttachment(ROOT, id);
   };
 
-  ERP5Storage.prototype.bulk = function (request_list) {
-    var i,
-      storage = this,
-      bulk_list = [];
+  FileSystemBridgeStorage.prototype.putAttachment = function (id, name, blob) {
+    if (name !== "enclosure") {
+      throw new jIO.util.jIOError("Only support 'enclosure' attachment",
+                                  400);
+    }
 
+    return this._sub_storage.putAttachment(
+      ROOT,
+      id,
+      blob
+    );
+  };
 
-    for (i = 0; i < request_list.length; i += 1) {
-      if (request_list[i].method !== "get") {
-        throw new Error("ERP5Storage: not supported " +
-                        request_list[i].method + " in bulk");
-      }
-      bulk_list.push({
-        relative_url: request_list[i].parameter_list[0],
-        view: storage._default_view_reference
-      });
+  FileSystemBridgeStorage.prototype.removeAttachment = function (id, name) {
+    if (name !== "enclosure") {
+      throw new jIO.util.jIOError("Only support 'enclosure' attachment",
+                                  400);
     }
-    return getSiteDocument(storage)
-      .push(function (site_hal) {
-        var form_data = new FormData();
-        form_data.append("bulk_list", JSON.stringify(bulk_list));
-        return jIO.util.ajax({
-          "type": "POST",
-          "url": site_hal._actions.bulk.href,
-          "data": form_data,
-//           "headers": {
-//             "Content-Type": "application/json"
-//           },
-          "xhrFields": {
-            withCredentials: true
-          }
-        });
-      })
-      .push(function (response) {
-        var result_list = [],
-          hateoas = JSON.parse(response.target.responseText);
 
-        function pushResult(json) {
-          json.portal_type = json._links.type.name;
-          return extractPropertyFromFormJSON(json)
-            .push(function (json2) {
-              return convertJSONToGet(json2);
-            });
-        }
+    return this._sub_storage.removeAttachment(ROOT, id);
+  };
 
-        for (i = 0; i < hateoas.result_list.length; i += 1) {
-          result_list.push(pushResult(hateoas.result_list[i]));
-        }
-        return RSVP.all(result_list);
-      });
+  FileSystemBridgeStorage.prototype.repair = function () {
+    return this._sub_storage.repair.apply(this._sub_storage, arguments);
   };
 
-  ERP5Storage.prototype.post = function (data) {
-    var context = this,
-      new_id;
+  jIO.addStorage('drivetojiomapping', FileSystemBridgeStorage);
 
-    return getSiteDocument(this)
-      .push(function (site_hal) {
-        var form_data = new FormData();
-        form_data.append("portal_type", data.portal_type);
-        form_data.append("parent_relative_url", data.parent_relative_url);
-        return jIO.util.ajax({
-          type: "POST",
-          url: site_hal._actions.add.href,
-          data: form_data,
-          xhrFields: {
-            withCredentials: true
-          }
-        });
-      })
-      .push(function (evt) {
-        var location = evt.target.getResponseHeader("X-Location"),
-          uri = new URI(location);
-        new_id = uri.segment(2);
-        return context.put(new_id, data);
-      })
+}(jIO, RSVP, Blob));
+;/*jslint nomen: true*/
+/*global Blob, atob, btoa, RSVP*/
+(function (jIO, Blob, atob, btoa, RSVP) {
+  "use strict";
+
+  /**
+   * The jIO DocumentStorage extension
+   *
+   * @class DocumentStorage
+   * @constructor
+   */
+  function DocumentStorage(spec) {
+    this._sub_storage = jIO.createJIO(spec.sub_storage);
+    this._document_id = spec.document_id;
+    this._repair_attachment = spec.repair_attachment || false;
+  }
+
+  var DOCUMENT_EXTENSION = ".json",
+    DOCUMENT_REGEXP = new RegExp("^jio_document/([\\w=]+)" +
+                                 DOCUMENT_EXTENSION + "$"),
+    ATTACHMENT_REGEXP = new RegExp("^jio_attachment/([\\w=]+)/([\\w=]+)$");
+
+  function getSubAttachmentIdFromParam(id, name) {
+    if (name === undefined) {
+      return 'jio_document/' + btoa(id) + DOCUMENT_EXTENSION;
+    }
+    return 'jio_attachment/' + btoa(id) + "/" + btoa(name);
+  }
+
+  DocumentStorage.prototype.get = function (id) {
+    return this._sub_storage.getAttachment(
+      this._document_id,
+      getSubAttachmentIdFromParam(id),
+      {format: "json"}
+    );
+  };
+
+  DocumentStorage.prototype.allAttachments = function (id) {
+    return this._sub_storage.allAttachments(this._document_id)
+      .push(function (result) {
+        var attachments = {},
+          exec,
+          key;
+        for (key in result) {
+          if (result.hasOwnProperty(key)) {
+            if (ATTACHMENT_REGEXP.test(key)) {
+              exec = ATTACHMENT_REGEXP.exec(key);
+              try {
+                if (atob(exec[1]) === id) {
+                  attachments[atob(exec[2])] = {};
+                }
+              } catch (error) {
+                // Check if unable to decode base64 data
+                if (!error instanceof ReferenceError) {
+                  throw error;
+                }
+              }
+            }
+          }
+        }
+        return attachments;
+      });
+  };
+
+  DocumentStorage.prototype.put = function (doc_id, param) {
+    return this._sub_storage.putAttachment(
+      this._document_id,
+      getSubAttachmentIdFromParam(doc_id),
+      new Blob([JSON.stringify(param)], {type: "application/json"})
+    )
       .push(function () {
-        return new_id;
+        return doc_id;
       });
+
   };
 
-  ERP5Storage.prototype.put = function (id, data) {
+  DocumentStorage.prototype.remove = function (id) {
     var context = this;
-
-    return extractPropertyFromForm(context, id)
+    return this.allAttachments(id)
       .push(function (result) {
         var key,
-          json = result.form_data,
-          form_data = {};
-        form_data[json.form_id.key] = json.form_id["default"];
-
-        // XXX How to store datetime:!!!!!
-        for (key in data) {
-          if (data.hasOwnProperty(key)) {
-            if (key === "form_id") {
-              throw new jIO.util.jIOError(
-                "ERP5: forbidden property: " + key,
-                400
-              );
-            }
-            if ((key !== "portal_type") && (key !== "parent_relative_url")) {
-              if (!json.hasOwnProperty(key)) {
-                throw new jIO.util.jIOError(
-                  "ERP5: can not store property: " + key,
-                  400
-                );
-              }
-              form_data[json[key].key] = data[key];
-            }
+          promise_list = [];
+        for (key in result) {
+          if (result.hasOwnProperty(key)) {
+            promise_list.push(context.removeAttachment(id, key));
           }
         }
-        return context.putAttachment(
-          id,
-          result.action_href,
-          new Blob([JSON.stringify(form_data)], {type: "application/json"})
+        return RSVP.all(promise_list);
+      })
+      .push(function () {
+        return context._sub_storage.removeAttachment(
+          context._document_id,
+          getSubAttachmentIdFromParam(id)
         );
+      })
+      .push(function () {
+        return id;
       });
   };
 
-  ERP5Storage.prototype.allAttachments = function (id) {
+  DocumentStorage.prototype.repair = function () {
     var context = this;
-    return getDocumentAndHateoas(this, id)
-      .push(function () {
-        if (context._default_view_reference === undefined) {
-          return {
-            links: {}
-          };
+    return this._sub_storage.repair.apply(this._sub_storage, arguments)
+      .push(function (result) {
+        if (context._repair_attachment) {
+          return context._sub_storage.allAttachments(context._document_id)
+            .push(function (result_dict) {
+              var promise_list = [],
+                id_dict = {},
+                attachment_dict = {},
+                id,
+                attachment,
+                exec,
+                key;
+              for (key in result_dict) {
+                if (result_dict.hasOwnProperty(key)) {
+                  id = undefined;
+                  attachment = undefined;
+                  if (DOCUMENT_REGEXP.test(key)) {
+                    try {
+                      id = atob(DOCUMENT_REGEXP.exec(key)[1]);
+                    } catch (error) {
+                      // Check if unable to decode base64 data
+                      if (!error instanceof ReferenceError) {
+                        throw error;
+                      }
+                    }
+                    if (id !== undefined) {
+                      id_dict[id] = null;
+                    }
+                  } else if (ATTACHMENT_REGEXP.test(key)) {
+                    exec = ATTACHMENT_REGEXP.exec(key);
+                    try {
+                      id = atob(exec[1]);
+                      attachment = atob(exec[2]);
+                    } catch (error) {
+                      // Check if unable to decode base64 data
+                      if (!error instanceof ReferenceError) {
+                        throw error;
+                      }
+                    }
+                    if (attachment !== undefined) {
+                      if (!id_dict.hasOwnProperty(id)) {
+                        if (!attachment_dict.hasOwnProperty(id)) {
+                          attachment_dict[id] = {};
+                        }
+                        attachment_dict[id][attachment] = null;
+                      }
+                    }
+                  }
+                }
+              }
+              for (id in attachment_dict) {
+                if (attachment_dict.hasOwnProperty(id)) {
+                  if (!id_dict.hasOwnProperty(id)) {
+                    for (attachment in attachment_dict[id]) {
+                      if (attachment_dict[id].hasOwnProperty(attachment)) {
+                        promise_list.push(context.removeAttachment(
+                          id,
+                          attachment
+                        ));
+                      }
+                    }
+                  }
+                }
+              }
+              return RSVP.all(promise_list);
+            });
         }
-        return {
-          view: {},
-          links: {}
-        };
+        return result;
       });
   };
 
-  ERP5Storage.prototype.getAttachment = function (id, action) {
-
-    if (action === "view") {
-      if (this._default_view_reference === undefined) {
-        throw new jIO.util.jIOError(
-          "Cannot find attachment view for: " + id,
-          404
-        );
-      }
-      return getDocumentAndHateoas(this, id,
-                                   {"_view": this._default_view_reference})
-        .push(function (response) {
-          var result = JSON.parse(response.target.responseText);
-          result._id = id;
-          result.portal_type = result._links.type.name;
-          // Remove all ERP5 hateoas links / convert them into jIO ID
-
-          // XXX Change default action to an jio urn with attachment name inside
-          // if Base_edit, do put URN
-          // if others, do post URN (ie, unique new attachment name)
-          // XXX Except this attachment name should be generated when
-          return new Blob(
-            [JSON.stringify(result)],
-            {"type": 'application/hal+json'}
-          );
-        });
-    }
-    if (action === "links") {
-      return getDocumentAndHateoas(this, id)
-        .push(function (response) {
-          return new Blob(
-            [JSON.stringify(JSON.parse(response.target.responseText))],
-            {"type": 'application/hal+json'}
-          );
-        });
-    }
-    if (action.indexOf(this._url) === 0) {
-      return new RSVP.Queue()
-        .push(function () {
-          return jIO.util.ajax({
-            "type": "GET",
-            "url": action,
-            "xhrFields": {
-              withCredentials: true
-            }
-          });
-        })
-        .push(function (evt) {
-          var result = JSON.parse(evt.target.responseText);
-          result._id = id;
-          return new Blob(
-            [JSON.stringify(result)],
-            {"type": evt.target.getResponseHeader("Content-Type")}
-          );
-        });
-    }
-    throw new jIO.util.jIOError("ERP5: not support get attachment: " + action,
-                                400);
+  DocumentStorage.prototype.hasCapacity = function (capacity) {
+    return (capacity === "list");
   };
 
-  ERP5Storage.prototype.putAttachment = function (id, name, blob) {
-    // Assert we use a callable on a document from the ERP5 site
-    if (name.indexOf(this._url) !== 0) {
-      throw new jIO.util.jIOError("Can not store outside ERP5: " +
-                                  name, 400);
-    }
-
-    return new RSVP.Queue()
-      .push(function () {
-        return jIO.util.readBlobAsText(blob);
-      })
-      .push(function (evt) {
-        var form_data = JSON.parse(evt.target.result),
-          data = new FormData(),
-          i,
+  DocumentStorage.prototype.buildQuery = function () {
+    return this._sub_storage.allAttachments(this._document_id)
+      .push(function (attachment_dict) {
+        var result = [],
           key;
-        for (key in form_data) {
-          if (form_data.hasOwnProperty(key)) {
-            if (Array.isArray(form_data[key])) {
-              for (i = 0; i < form_data[key].length; i += 1) {
-                data.append(key, form_data[key][i]);
+        for (key in attachment_dict) {
+          if (attachment_dict.hasOwnProperty(key)) {
+            if (DOCUMENT_REGEXP.test(key)) {
+              try {
+                result.push({
+                  id: atob(DOCUMENT_REGEXP.exec(key)[1]),
+                  value: {}
+                });
+              } catch (error) {
+                // Check if unable to decode base64 data
+                if (!error instanceof ReferenceError) {
+                  throw error;
+                }
               }
-            } else {
-              data.append(key, form_data[key]);
             }
           }
         }
-        return jIO.util.ajax({
-          "type": "POST",
-          "url": name,
-          "data": data,
-          "xhrFields": {
-            withCredentials: true
-          }
-        });
+        return result;
       });
   };
 
-  ERP5Storage.prototype.hasCapacity = function (name) {
-    return ((name === "list") || (name === "query") ||
-            (name === "select") || (name === "limit") ||
-            (name === "sort"));
+  DocumentStorage.prototype.getAttachment = function (id, name) {
+    return this._sub_storage.getAttachment(
+      this._document_id,
+      getSubAttachmentIdFromParam(id, name)
+    );
+  };
+
+  DocumentStorage.prototype.putAttachment = function (id, name, blob) {
+    return this._sub_storage.putAttachment(
+      this._document_id,
+      getSubAttachmentIdFromParam(id, name),
+      blob
+    );
+  };
+
+  DocumentStorage.prototype.removeAttachment = function (id, name) {
+    return this._sub_storage.removeAttachment(
+      this._document_id,
+      getSubAttachmentIdFromParam(id, name)
+    );
   };
 
-  function isSingleLocalRoles(parsed_query) {
-    if ((parsed_query instanceof SimpleQuery) &&
-        (parsed_query.key === 'local_roles')) {
-      // local_roles:"Assignee"
-      return parsed_query.value;
+  jIO.addStorage('document', DocumentStorage);
+
+}(jIO, Blob, atob, btoa, RSVP));
+;/*
+ * Copyright 2013, Nexedi SA
+ * Released under the LGPL license.
+ * http://www.gnu.org/licenses/lgpl.html
+ */
+
+/*jslint nomen: true*/
+/*global jIO, sessionStorage, localStorage, RSVP */
+
+/**
+ * JIO Local Storage. Type = 'local'.
+ * Local browser "database" storage.
+ *
+ * Storage Description:
+ *
+ *     {
+ *       "type": "local",
+ *       "sessiononly": false
+ *     }
+ *
+ * @class LocalStorage
+ */
+
+(function (jIO, sessionStorage, localStorage, RSVP) {
+  "use strict";
+
+  function LocalStorage(spec) {
+    if (spec.sessiononly === true) {
+      this._storage = sessionStorage;
+    } else {
+      this._storage = localStorage;
     }
   }
 
-  function isMultipleLocalRoles(parsed_query) {
-    var i,
-      sub_query,
-      is_multiple = true,
-      local_role_list = [];
-    if ((parsed_query instanceof ComplexQuery) &&
-        (parsed_query.operator === 'OR')) {
-
-      for (i = 0; i < parsed_query.query_list.length; i += 1) {
-        sub_query = parsed_query.query_list[i];
-        if ((sub_query instanceof SimpleQuery) &&
-            (sub_query.key === 'local_roles')) {
-          local_role_list.push(sub_query.value);
-        } else {
-          is_multiple = false;
-        }
-      }
-      if (is_multiple) {
-        // local_roles:"Assignee" OR local_roles:"Assignor"
-        return local_role_list;
-      }
+  function restrictDocumentId(id) {
+    if (id !== "/") {
+      throw new jIO.util.jIOError("id " + id + " is forbidden (!== /)",
+                                  400);
     }
   }
 
-  ERP5Storage.prototype.buildQuery = function (options) {
-//     if (typeof options.query !== "string") {
-//       options.query = (options.query ?
-//                        jIO.Query.objectToSearchText(options.query) :
-//                        undefined);
-//     }
-    return getSiteDocument(this)
-      .push(function (site_hal) {
-        var query = options.query,
-          i,
-          parsed_query,
-          sub_query,
-          result_list,
-          local_roles;
-        if (options.query) {
-          parsed_query = jIO.QueryFactory.create(options.query);
+  LocalStorage.prototype.get = function (id) {
+    restrictDocumentId(id);
+    return {};
+  };
 
-          result_list = isSingleLocalRoles(parsed_query);
-          if (result_list) {
-            query = undefined;
-            local_roles = result_list;
-          } else {
+  LocalStorage.prototype.allAttachments = function (id) {
+    restrictDocumentId(id);
 
-            result_list = isMultipleLocalRoles(parsed_query);
-            if (result_list) {
-              query = undefined;
-              local_roles = result_list;
-            } else if ((parsed_query instanceof ComplexQuery) &&
-                       (parsed_query.operator === 'AND')) {
+    var attachments = {},
+      key;
 
-              // portal_type:"Person" AND local_roles:"Assignee"
-              for (i = 0; i < parsed_query.query_list.length; i += 1) {
-                sub_query = parsed_query.query_list[i];
+    for (key in this._storage) {
+      if (this._storage.hasOwnProperty(key)) {
+        attachments[key] = {};
+      }
+    }
+    return attachments;
+  };
 
-                result_list = isSingleLocalRoles(sub_query);
-                if (result_list) {
-                  local_roles = result_list;
-                  parsed_query.query_list.splice(i, 1);
-                  query = jIO.Query.objectToSearchText(parsed_query);
-                  i = parsed_query.query_list.length;
-                } else {
-                  result_list = isMultipleLocalRoles(sub_query);
-                  if (result_list) {
-                    local_roles = result_list;
-                    parsed_query.query_list.splice(i, 1);
-                    query = jIO.Query.objectToSearchText(parsed_query);
-                    i = parsed_query.query_list.length;
-                  }
-                }
-              }
-            }
+  LocalStorage.prototype.getAttachment = function (id, name) {
+    restrictDocumentId(id);
 
-          }
-        }
+    var textstring = this._storage.getItem(name);
 
-        return jIO.util.ajax({
-          "type": "GET",
-          "url": UriTemplate.parse(site_hal._links.raw_search.href)
-                            .expand({
-              query: query,
-              // XXX Force erp5 to return embedded document
-              select_list: options.select_list || ["title", "reference"],
-              limit: options.limit,
-              sort_on: options.sort_on,
-              local_roles: local_roles
-            }),
-          "xhrFields": {
-            withCredentials: true
-          }
-        });
-      })
-      .push(function (response) {
-        return JSON.parse(response.target.responseText);
+    if (textstring === null) {
+      throw new jIO.util.jIOError(
+        "Cannot find attachment " + name,
+        404
+      );
+    }
+    return jIO.util.dataURItoBlob(textstring);
+  };
+
+  LocalStorage.prototype.putAttachment = function (id, name, blob) {
+    var context = this;
+    restrictDocumentId(id);
+
+    // the document already exists
+    // download data
+    return new RSVP.Queue()
+      .push(function () {
+        return jIO.util.readBlobAsDataURL(blob);
       })
-      .push(function (catalog_json) {
-        var data = catalog_json._embedded.contents,
-          count = data.length,
-          i,
-          uri,
-          item,
-          result = [];
-        for (i = 0; i < count; i += 1) {
-          item = data[i];
-          uri = new URI(item._links.self.href);
-          delete item._links;
-          result.push({
-            id: uri.segment(2),
-            value: item
-          });
-        }
-        return result;
+      .push(function (e) {
+        context._storage.setItem(name, e.target.result);
       });
   };
 
-  jIO.addStorage("erp5", ERP5Storage);
+  LocalStorage.prototype.removeAttachment = function (id, name) {
+    restrictDocumentId(id);
+    return this._storage.removeItem(name);
+  };
 
-}(jIO, UriTemplate, FormData, RSVP, URI, Blob,
-  SimpleQuery, ComplexQuery));
-;/*jslint nomen: true*/
-/*global RSVP*/
-(function (jIO, RSVP) {
-  "use strict";
 
-  /**
-   * The jIO QueryStorage extension
-   *
-   * @class QueryStorage
-   * @constructor
-   */
-  function QueryStorage(spec) {
-    this._sub_storage = jIO.createJIO(spec.sub_storage);
-    this._key_schema = spec.key_schema;
-  }
+  LocalStorage.prototype.hasCapacity = function (name) {
+    return (name === "list");
+  };
+
+  LocalStorage.prototype.buildQuery = function () {
+    return [{
+      id: "/",
+      value: {}
+    }];
+  };
+
+  jIO.addStorage('local', LocalStorage);
+
+}(jIO, sessionStorage, localStorage, RSVP));
+;/*
+ * Copyright 2014, Nexedi SA
+ * Released under the LGPL license.
+ * http://www.gnu.org/licenses/lgpl.html
+ */
+
+/**
+ * JIO Indexed Database Storage.
+ *
+ * A local browser "database" storage greatly more powerful than localStorage.
+ *
+ * Description:
+ *
+ *    {
+ *      "type": "indexeddb",
+ *      "database": <string>
+ *    }
+ *
+ * The database name will be prefixed by "jio:", so if the database property is
+ * "hello", then you can manually reach this database with
+ * `indexedDB.open("jio:hello");`. (Or
+ * `indexedDB.deleteDatabase("jio:hello");`.)
+ *
+ * For more informations:
+ *
+ * - http://www.w3.org/TR/IndexedDB/
+ * - https://developer.mozilla.org/en-US/docs/IndexedDB/Using_IndexedDB
+ */
+
+/*jslint nomen: true */
+/*global indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange*/
+
+(function (indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange) {
+  "use strict";
 
-  QueryStorage.prototype.get = function () {
-    return this._sub_storage.get.apply(this._sub_storage, arguments);
-  };
-  QueryStorage.prototype.allAttachments = function () {
-    return this._sub_storage.allAttachments.apply(this._sub_storage, arguments);
-  };
-  QueryStorage.prototype.post = function () {
-    return this._sub_storage.post.apply(this._sub_storage, arguments);
-  };
-  QueryStorage.prototype.put = function () {
-    return this._sub_storage.put.apply(this._sub_storage, arguments);
-  };
-  QueryStorage.prototype.remove = function () {
-    return this._sub_storage.remove.apply(this._sub_storage, arguments);
-  };
-  QueryStorage.prototype.getAttachment = function () {
-    return this._sub_storage.getAttachment.apply(this._sub_storage, arguments);
-  };
-  QueryStorage.prototype.putAttachment = function () {
-    return this._sub_storage.putAttachment.apply(this._sub_storage, arguments);
-  };
-  QueryStorage.prototype.removeAttachment = function () {
-    return this._sub_storage.removeAttachment.apply(this._sub_storage,
-                                                    arguments);
-  };
-  QueryStorage.prototype.repair = function () {
-    return this._sub_storage.repair.apply(this._sub_storage, arguments);
-  };
+  // Read only as changing it can lead to data corruption
+  var UNITE = 2000000;
 
-  QueryStorage.prototype.hasCapacity = function (name) {
-    if (name === "list") {
-      return this._sub_storage.hasCapacity(name);
+  function IndexedDBStorage(description) {
+    if (typeof description.database !== "string" ||
+        description.database === "") {
+      throw new TypeError("IndexedDBStorage 'database' description property " +
+                          "must be a non-empty string");
     }
-    return true;
+    this._database_name = "jio:" + description.database;
+  }
+
+  IndexedDBStorage.prototype.hasCapacity = function (name) {
+    return ((name === "list") || (name === "include"));
   };
-  QueryStorage.prototype.buildQuery = function (options) {
-    var substorage = this._sub_storage,
-      context = this,
-      sub_options = {},
-      is_manual_query_needed = false,
-      is_manual_include_needed = false;
 
-    if (substorage.hasCapacity("list")) {
+  function buildKeyPath(key_list) {
+    return key_list.join("_");
+  }
 
-      // Can substorage handle the queries if needed?
-      try {
-        if (((options.query === undefined) ||
-             (substorage.hasCapacity("query"))) &&
-            ((options.sort_on === undefined) ||
-             (substorage.hasCapacity("sort"))) &&
-            ((options.select_list === undefined) ||
-             (substorage.hasCapacity("select"))) &&
-            ((options.limit === undefined) ||
-             (substorage.hasCapacity("limit")))) {
-          sub_options.query = options.query;
-          sub_options.sort_on = options.sort_on;
-          sub_options.select_list = options.select_list;
-          sub_options.limit = options.limit;
-        }
-      } catch (error) {
-        if ((error instanceof jIO.util.jIOError) &&
-            (error.status_code === 501)) {
-          is_manual_query_needed = true;
-        } else {
-          throw error;
-        }
-      }
+  function handleUpgradeNeeded(evt) {
+    var db = evt.target.result,
+      store;
 
-      // Can substorage include the docs if needed?
-      try {
-        if ((is_manual_query_needed ||
-            (options.include_docs === true)) &&
-            (substorage.hasCapacity("include"))) {
-          sub_options.include_docs = true;
-        }
-      } catch (error) {
-        if ((error instanceof jIO.util.jIOError) &&
-            (error.status_code === 501)) {
-          is_manual_include_needed = true;
-        } else {
-          throw error;
-        }
-      }
+    store = db.createObjectStore("metadata", {
+      keyPath: "_id",
+      autoIncrement: false
+    });
+    // It is not possible to use openKeyCursor on keypath directly
+    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=19955
+    store.createIndex("_id", "_id", {unique: true});
 
-      return substorage.buildQuery(sub_options)
+    store = db.createObjectStore("attachment", {
+      keyPath: "_key_path",
+      autoIncrement: false
+    });
+    store.createIndex("_id", "_id", {unique: false});
 
-        // Include docs if needed
-        .push(function (result) {
-          var include_query_list = [result],
-            len,
-            i;
+    store = db.createObjectStore("blob", {
+      keyPath: "_key_path",
+      autoIncrement: false
+    });
+    store.createIndex("_id_attachment",
+                      ["_id", "_attachment"], {unique: false});
+    store.createIndex("_id", "_id", {unique: false});
+  }
 
-          function safeGet(j) {
-            var id = result[j].id;
-            return substorage.get(id)
-              .push(function (doc) {
-                // XXX Can delete user data!
-                doc._id = id;
-                return doc;
-              }, function (error) {
-                // Document may have been dropped after listing
-                if ((error instanceof jIO.util.jIOError) &&
-                    (error.status_code === 404)) {
-                  return;
-                }
-                throw error;
-              });
-          }
+  function openIndexedDB(jio_storage) {
+    var db_name = jio_storage._database_name;
+    function resolver(resolve, reject) {
+      // Open DB //
+      var request = indexedDB.open(db_name);
+      request.onerror = function (error) {
+        if (request.result) {
+          request.result.close();
+        }
+        reject(error);
+      };
 
-          if (is_manual_include_needed) {
-            len = result.length;
-            for (i = 0; i < len; i += 1) {
-              include_query_list.push(safeGet(i));
-            }
-            result = RSVP.all(include_query_list);
-          }
-          return result;
-        })
-        .push(function (result) {
-          var original_result,
-            len,
-            i;
-          if (is_manual_include_needed) {
-            original_result = result[0];
-            len = original_result.length;
-            for (i = 0; i < len; i += 1) {
-              original_result[i].doc = result[i + 1];
-            }
-            result = original_result;
-          }
-          return result;
+      request.onabort = function () {
+        request.result.close();
+        reject("Aborting connection to: " + db_name);
+      };
 
-        })
+      request.ontimeout = function () {
+        request.result.close();
+        reject("Connection to: " + db_name + " timeout");
+      };
 
-        // Manual query if needed
-        .push(function (result) {
-          var data_rows = [],
-            len,
-            i;
-          if (is_manual_query_needed) {
-            len = result.length;
-            for (i = 0; i < len; i += 1) {
-              result[i].doc.__id = result[i].id;
-              data_rows.push(result[i].doc);
-            }
-            if (options.select_list) {
-              options.select_list.push("__id");
-            }
-            result = jIO.QueryFactory.create(options.query || "",
-                                             context._key_schema).
-              exec(data_rows, options);
-          }
-          return result;
-        })
+      request.onblocked = function () {
+        request.result.close();
+        reject("Connection to: " + db_name + " was blocked");
+      };
 
-        // reconstruct filtered rows, preserving the order from docs
-        .push(function (result) {
-          var new_result = [],
-            element,
-            len,
-            i;
-          if (is_manual_query_needed) {
-            len = result.length;
-            for (i = 0; i < len; i += 1) {
-              element = {
-                id: result[i].__id,
-                value: options.select_list ? result[i] : {},
-                doc: {}
-              };
-              if (options.select_list) {
-                // Does not work if user manually request __id
-                delete element.value.__id;
-              }
-              if (options.include_docs) {
-                // XXX To implement
-                throw new Error("QueryStorage does not support include docs");
-              }
-              new_result.push(element);
-            }
-            result = new_result;
-          }
-          return result;
-        });
+      // Create DB if necessary //
+      request.onupgradeneeded = handleUpgradeNeeded;
+
+      request.onversionchange = function () {
+        request.result.close();
+        reject(db_name + " was upgraded");
+      };
+
+      request.onsuccess = function () {
+        resolve(request.result);
+      };
+    }
+    // XXX Canceller???
+    return new RSVP.Queue()
+      .push(function () {
+        return new RSVP.Promise(resolver);
+      });
+  }
 
+  function openTransaction(db, stores, flag, autoclosedb) {
+    var tx = db.transaction(stores, flag);
+    if (autoclosedb !== false) {
+      tx.oncomplete = function () {
+        db.close();
+      };
     }
-  };
-
-  jIO.addStorage('query', QueryStorage);
+    tx.onabort = function () {
+      db.close();
+    };
+    return tx;
+  }
 
-}(jIO, RSVP));
-;/*jslint nomen: true*/
-/*global RSVP, Blob*/
-(function (jIO, RSVP, Blob) {
-  "use strict";
+  function handleCursor(request, callback) {
+    function resolver(resolve, reject) {
+      // Open DB //
+      request.onerror = function (error) {
+        if (request.transaction) {
+          request.transaction.abort();
+        }
+        reject(error);
+      };
 
-  /**
-   * The jIO FileSystemBridgeStorage extension
-   *
-   * @class FileSystemBridgeStorage
-   * @constructor
-   */
-  function FileSystemBridgeStorage(spec) {
-    this._sub_storage = jIO.createJIO(spec.sub_storage);
-  }
-  var DOCUMENT_EXTENSION = ".json",
-    DOCUMENT_KEY = "/.jio_documents/",
-    ROOT = "/";
+      request.onsuccess = function (evt) {
+        var cursor = evt.target.result;
+        if (cursor) {
+          // XXX Wait for result
+          try {
+            callback(cursor);
+          } catch (error) {
+            reject(error);
+          }
 
-  function endsWith(str, suffix) {
-    return str.indexOf(suffix, str.length - suffix.length) !== -1;
+          // continue to next iteration
+          cursor["continue"]();
+        } else {
+          resolve();
+        }
+      };
+    }
+    // XXX Canceller???
+    return new RSVP.Promise(resolver);
   }
 
-  FileSystemBridgeStorage.prototype.get = function (id) {
-    var context = this;
-    return new RSVP.Queue()
-
-      // First, try to get explicit reference to the document
+  IndexedDBStorage.prototype.buildQuery = function (options) {
+    var result_list = [];
 
-      .push(function () {
-        // First get the document itself if it exists
-        return context._sub_storage.getAttachment(
-          DOCUMENT_KEY,
-          id + DOCUMENT_EXTENSION,
-          {format: "json"}
-        );
-      })
-      .push(undefined, function (error) {
-        if ((error instanceof jIO.util.jIOError) &&
-            (error.status_code === 404)) {
+    function pushIncludedMetadata(cursor) {
+      result_list.push({
+        "id": cursor.key,
+        "value": {},
+        "doc": cursor.value.doc
+      });
+    }
 
-          // Second, try to get default attachment
-          return context._sub_storage.allAttachments(ROOT)
-            .push(function (attachment_dict) {
-              if (attachment_dict.hasOwnProperty(id)) {
-                return {};
-              }
-              throw new jIO.util.jIOError("Cannot find document " + id,
-                                          404);
-            });
+    function pushMetadata(cursor) {
+      result_list.push({
+        "id": cursor.key,
+        "value": {}
+      });
+    }
+    return openIndexedDB(this)
+      .push(function (db) {
+        var tx = openTransaction(db, ["metadata"], "readonly");
+        if (options.include_docs === true) {
+          return handleCursor(tx.objectStore("metadata").index("_id")
+                              .openCursor(), pushIncludedMetadata);
         }
-        throw error;
+        return handleCursor(tx.objectStore("metadata").index("_id")
+                            .openKeyCursor(), pushMetadata);
+      })
+      .push(function () {
+        return result_list;
       });
+
   };
 
-  FileSystemBridgeStorage.prototype.allAttachments = function (id) {
-    var context = this;
-    return context._sub_storage.allAttachments(ROOT)
-      .push(function (attachment_dict) {
-        if (attachment_dict.hasOwnProperty(id)) {
-          return {
-            enclosure: {}
-          };
+  function handleGet(request) {
+    function resolver(resolve, reject) {
+      request.onerror = reject;
+      request.onsuccess = function () {
+        if (request.result) {
+          resolve(request.result);
         }
-        // Second get the document itself if it exists
-        return context._sub_storage.getAttachment(
-          DOCUMENT_KEY,
-          id + DOCUMENT_EXTENSION
-        )
-          .push(function () {
-            return {};
-          }, function (error) {
-            if ((error instanceof jIO.util.jIOError) &&
-                (error.status_code === 404)) {
-              throw new jIO.util.jIOError("Cannot find document " + id,
-                                          404);
-            }
-            throw error;
-          });
-      });
+        // XXX How to get ID
+        reject(new jIO.util.jIOError("Cannot find document", 404));
+      };
+    }
+    return new RSVP.Promise(resolver);
+  }
 
+  IndexedDBStorage.prototype.get = function (id) {
+    return openIndexedDB(this)
+      .push(function (db) {
+        var transaction = openTransaction(db, ["metadata"],
+                                          "readonly");
+        return handleGet(transaction.objectStore("metadata").get(id));
+      })
+      .push(function (result) {
+        return result.doc;
+      });
   };
 
-  FileSystemBridgeStorage.prototype.put = function (doc_id, param) {
-    var context = this;
-    // XXX Handle conflict!
+  IndexedDBStorage.prototype.allAttachments = function (id) {
+    var attachment_dict = {};
 
-    return context._sub_storage.putAttachment(
-      DOCUMENT_KEY,
-      doc_id + DOCUMENT_EXTENSION,
-      new Blob([JSON.stringify(param)], {type: "application/json"})
-    )
-      .push(undefined, function (error) {
-        if ((error instanceof jIO.util.jIOError) &&
-            (error.status_code === 404)) {
-          return context._sub_storage.put(DOCUMENT_KEY, {})
-            .push(function () {
-              return context._sub_storage.putAttachment(
-                DOCUMENT_KEY,
-                doc_id + DOCUMENT_EXTENSION,
-                new Blob([JSON.stringify(param)],
-                         {type: "application/json"})
-              );
-            });
-        }
-        throw error;
+    function addEntry(cursor) {
+      attachment_dict[cursor.value._attachment] = {};
+    }
+
+    return openIndexedDB(this)
+      .push(function (db) {
+        var transaction = openTransaction(db, ["metadata", "attachment"],
+                                          "readonly");
+        return RSVP.all([
+          handleGet(transaction.objectStore("metadata").get(id)),
+          handleCursor(transaction.objectStore("attachment").index("_id")
+                       .openCursor(IDBKeyRange.only(id)), addEntry)
+        ]);
       })
       .push(function () {
-        return doc_id;
+        return attachment_dict;
       });
+  };
+
+  function handleRequest(request) {
+    function resolver(resolve, reject) {
+      request.onerror = reject;
+      request.onsuccess = function () {
+        resolve(request.result);
+      };
+    }
+    return new RSVP.Promise(resolver);
+  }
+
+  IndexedDBStorage.prototype.put = function (id, metadata) {
+    return openIndexedDB(this)
+      .push(function (db) {
+        var transaction = openTransaction(db, ["metadata"], "readwrite");
+        return handleRequest(transaction.objectStore("metadata").put({
+          "_id": id,
+          "doc": metadata
+        }));
+      });
+  };
+
+  function deleteEntry(cursor) {
+    cursor["delete"]();
+  }
 
+  IndexedDBStorage.prototype.remove = function (id) {
+    return openIndexedDB(this)
+      .push(function (db) {
+        var transaction = openTransaction(db, ["metadata", "attachment",
+                                          "blob"], "readwrite");
+        return RSVP.all([
+          handleRequest(transaction
+                        .objectStore("metadata")["delete"](id)),
+          // XXX Why not possible to delete with KeyCursor?
+          handleCursor(transaction.objectStore("attachment").index("_id")
+                       .openCursor(IDBKeyRange.only(id)), deleteEntry),
+          handleCursor(transaction.objectStore("blob").index("_id")
+                       .openCursor(IDBKeyRange.only(id)), deleteEntry)
+        ]);
+      });
   };
 
-  FileSystemBridgeStorage.prototype.remove = function (doc_id) {
-    var context = this,
-      got_error = false;
-    return new RSVP.Queue()
+  IndexedDBStorage.prototype.getAttachment = function (id, name, options) {
+    var transaction,
+      type,
+      start,
+      end;
+    if (options === undefined) {
+      options = {};
+    }
+    return openIndexedDB(this)
+      .push(function (db) {
+        transaction = openTransaction(db, ["attachment", "blob"], "readonly");
+        // XXX Should raise if key is not good
+        return handleGet(transaction.objectStore("attachment")
+                         .get(buildKeyPath([id, name])));
+      })
+      .push(function (attachment) {
+        var total_length = attachment.info.length,
+          i,
+          promise_list = [],
+          store = transaction.objectStore("blob"),
+          start_index,
+          end_index;
+
+        type = attachment.info.content_type;
+        start = options.start || 0;
+        end = options.end || total_length;
+        if (end > total_length) {
+          end = total_length;
+        }
 
-      // First, try to remove enclosure
-      .push(function () {
-        return context._sub_storage.removeAttachment(
-          ROOT,
-          doc_id
-        );
-      })
+        if (start < 0 || end < 0) {
+          throw new jIO.util.jIOError("_start and _end must be positive",
+                                      400);
+        }
+        if (start > end) {
+          throw new jIO.util.jIOError("_start is greater than _end",
+                                      400);
+        }
 
-      .push(undefined, function (error) {
-        if ((error instanceof jIO.util.jIOError) &&
-            (error.status_code === 404)) {
-          got_error = true;
-          return;
+        start_index = Math.floor(start / UNITE);
+        end_index =  Math.floor(end / UNITE);
+        if (end % UNITE === 0) {
+          end_index -= 1;
         }
-        throw error;
-      })
 
-      // Second, try to remove explicit doc
-      .push(function () {
-        return context._sub_storage.removeAttachment(
-          DOCUMENT_KEY,
-          doc_id + DOCUMENT_EXTENSION
-        );
+        for (i = start_index; i <= end_index; i += 1) {
+          promise_list.push(
+            handleGet(store.get(buildKeyPath([id,
+                                name, i])))
+          );
+        }
+        return RSVP.all(promise_list);
       })
-
-      .push(undefined, function (error) {
-        if ((!got_error) && (error instanceof jIO.util.jIOError) &&
-            (error.status_code === 404)) {
-          return doc_id;
+      .push(function (result_list) {
+        var array_buffer_list = [],
+          blob,
+          i,
+          index,
+          len = result_list.length;
+        for (i = 0; i < len; i += 1) {
+          array_buffer_list.push(result_list[i].blob);
         }
-        throw error;
+        if ((options.start === undefined) && (options.end === undefined)) {
+          return new Blob(array_buffer_list, {type: type});
+        }
+        index = Math.floor(start / UNITE) * UNITE;
+        blob = new Blob(array_buffer_list, {type: "application/octet-stream"});
+        return blob.slice(start - index, end - index,
+                          "application/octet-stream");
       });
-
   };
 
-  FileSystemBridgeStorage.prototype.hasCapacity = function (capacity) {
-    return (capacity === "list");
-  };
+  function removeAttachment(transaction, id, name) {
+    return RSVP.all([
+      // XXX How to get the right attachment
+      handleRequest(transaction.objectStore("attachment")["delete"](
+        buildKeyPath([id, name])
+      )),
+      handleCursor(transaction.objectStore("blob").index("_id_attachment")
+                   .openCursor(IDBKeyRange.only(
+          [id, name]
+        )),
+          deleteEntry
+        )
+    ]);
+  }
 
-  FileSystemBridgeStorage.prototype.buildQuery = function () {
-    var result_dict = {},
-      context = this;
-    return new RSVP.Queue()
+  IndexedDBStorage.prototype.putAttachment = function (id, name, blob) {
+    var blob_part = [],
+      transaction,
+      db;
 
-      // First, get list of explicit documents
+    return openIndexedDB(this)
+      .push(function (database) {
+        db = database;
 
-      .push(function () {
-        return context._sub_storage.allAttachments(DOCUMENT_KEY);
-      })
-      .push(function (result) {
-        var key;
-        for (key in result) {
-          if (result.hasOwnProperty(key)) {
-            if (endsWith(key, DOCUMENT_EXTENSION)) {
-              result_dict[key.substring(
-                0,
-                key.length - DOCUMENT_EXTENSION.length
-              )] = null;
-            }
-          }
-        }
-      }, function (error) {
-        if ((error instanceof jIO.util.jIOError) &&
-            (error.status_code === 404)) {
-          return;
-        }
-        throw error;
+        // Split the blob first
+        return jIO.util.readBlobAsArrayBuffer(blob);
       })
+      .push(function (event) {
+        var array_buffer = event.target.result,
+          total_size = blob.size,
+          handled_size = 0;
 
-      // Second, get list of enclosure
-
-      .push(function () {
-        return context._sub_storage.allAttachments(ROOT);
-      })
-      .push(function (result) {
-        var key;
-        for (key in result) {
-          if (result.hasOwnProperty(key)) {
-            result_dict[key] = null;
-          }
+        while (handled_size < total_size) {
+          blob_part.push(array_buffer.slice(handled_size,
+                                            handled_size + UNITE));
+          handled_size += UNITE;
         }
-      })
-
-      // Finally, build the result
 
+        // Remove previous attachment
+        transaction = openTransaction(db, ["attachment", "blob"], "readwrite");
+        return removeAttachment(transaction, id, name);
+      })
       .push(function () {
-        var result = [],
-          key;
-        for (key in result_dict) {
-          if (result_dict.hasOwnProperty(key)) {
-            result.push({
-              id: key,
-              value: {}
-            });
-          }
+
+        var promise_list = [
+            handleRequest(transaction.objectStore("attachment").put({
+              "_key_path": buildKeyPath([id, name]),
+              "_id": id,
+              "_attachment": name,
+              "info": {
+                "content_type": blob.type,
+                "length": blob.size
+              }
+            }))
+          ],
+          len = blob_part.length,
+          blob_store = transaction.objectStore("blob"),
+          i;
+        for (i = 0; i < len; i += 1) {
+          promise_list.push(
+            handleRequest(blob_store.put({
+              "_key_path": buildKeyPath([id, name,
+                                         i]),
+              "_id" : id,
+              "_attachment" : name,
+              "_part" : i,
+              "blob": blob_part[i]
+            }))
+          );
         }
-        return result;
+        // Store all new data
+        return RSVP.all(promise_list);
       });
-
   };
 
-  FileSystemBridgeStorage.prototype.getAttachment = function (id, name) {
-    if (name !== "enclosure") {
-      throw new jIO.util.jIOError("Only support 'enclosure' attachment",
-                                  400);
-    }
-
-    return this._sub_storage.getAttachment(ROOT, id);
+  IndexedDBStorage.prototype.removeAttachment = function (id, name) {
+    return openIndexedDB(this)
+      .push(function (db) {
+        var transaction = openTransaction(db, ["attachment", "blob"],
+                                          "readwrite");
+        return removeAttachment(transaction, id, name);
+      });
   };
 
-  FileSystemBridgeStorage.prototype.putAttachment = function (id, name, blob) {
-    if (name !== "enclosure") {
-      throw new jIO.util.jIOError("Only support 'enclosure' attachment",
-                                  400);
-    }
+  jIO.addStorage("indexeddb", IndexedDBStorage);
+}(indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange));
+;/*
+ * Copyright 2015, Nexedi SA
+ * Released under the LGPL license.
+ * http://www.gnu.org/licenses/lgpl.html
+ */
 
-    return this._sub_storage.putAttachment(
-      ROOT,
-      id,
-      blob
-    );
-  };
+/*jslint nomen: true*/
+/*global jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer*/
 
-  FileSystemBridgeStorage.prototype.removeAttachment = function (id, name) {
-    if (name !== "enclosure") {
-      throw new jIO.util.jIOError("Only support 'enclosure' attachment",
-                                  400);
-    }
+(function (jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer) {
+  "use strict";
 
-    return this._sub_storage.removeAttachment(ROOT, id);
-  };
 
-  FileSystemBridgeStorage.prototype.repair = function () {
-    return this._sub_storage.repair.apply(this._sub_storage, arguments);
-  };
+  // you the cryptography system used by this storage is AES-GCM.
+  // here is an example of how to generate a key to the json format.
 
-  jIO.addStorage('drivetojiomapping', FileSystemBridgeStorage);
+  // var key,
+  //     jsonKey;
+  // crypto.subtle.generateKey({name: "AES-GCM",length: 256},
+  //                           (true), ["encrypt", "decrypt"])
+  // .then(function(res){key = res;});
+  //
+  // window.crypto.subtle.exportKey("jwk", key)
+  // .then(function(res){jsonKey = val})
+  //
+  //var storage = jIO.createJIO({type: "crypt", key: jsonKey,
+  //                             sub_storage: {...}});
 
-}(jIO, RSVP, Blob));
-;/*jslint nomen: true*/
-/*global Blob, atob, btoa, RSVP*/
-(function (jIO, Blob, atob, btoa, RSVP) {
-  "use strict";
+  // find more informations about this cryptography system on
+  // https://github.com/diafygi/webcrypto-examples#aes-gcm
 
   /**
-   * The jIO DocumentStorage extension
+   * The JIO Cryptography Storage extension
    *
-   * @class DocumentStorage
+   * @class CryptStorage
    * @constructor
    */
-  function DocumentStorage(spec) {
+
+  var MIME_TYPE = "application/x-jio-aes-gcm-encryption";
+
+  function CryptStorage(spec) {
+    this._key = spec.key;
+    this._jsonKey = true;
     this._sub_storage = jIO.createJIO(spec.sub_storage);
-    this._document_id = spec.document_id;
-    this._repair_attachment = spec.repair_attachment || false;
   }
 
-  var DOCUMENT_EXTENSION = ".json",
-    DOCUMENT_REGEXP = new RegExp("^jio_document/([\\w=]+)" +
-                                 DOCUMENT_EXTENSION + "$"),
-    ATTACHMENT_REGEXP = new RegExp("^jio_attachment/([\\w=]+)/([\\w=]+)$");
-
-  function getSubAttachmentIdFromParam(id, name) {
-    if (name === undefined) {
-      return 'jio_document/' + btoa(id) + DOCUMENT_EXTENSION;
-    }
-    return 'jio_attachment/' + btoa(id) + "/" + btoa(name);
+  function convertKey(that) {
+    return new RSVP.Queue()
+      .push(function () {
+        return crypto.subtle.importKey("jwk", that._key,
+                                       "AES-GCM", false,
+                                       ["encrypt", "decrypt"]);
+      })
+      .push(function (res) {
+        that._key = res;
+        that._jsonKey = false;
+        return;
+      });
   }
 
-  DocumentStorage.prototype.get = function (id) {
-    return this._sub_storage.getAttachment(
-      this._document_id,
-      getSubAttachmentIdFromParam(id),
-      {format: "json"}
-    );
+  CryptStorage.prototype.get = function () {
+    return this._sub_storage.get.apply(this._sub_storage,
+                                       arguments);
   };
 
-  DocumentStorage.prototype.allAttachments = function (id) {
-    return this._sub_storage.allAttachments(this._document_id)
-      .push(function (result) {
-        var attachments = {},
-          exec,
-          key;
-        for (key in result) {
-          if (result.hasOwnProperty(key)) {
-            if (ATTACHMENT_REGEXP.test(key)) {
-              exec = ATTACHMENT_REGEXP.exec(key);
-              try {
-                if (atob(exec[1]) === id) {
-                  attachments[atob(exec[2])] = {};
-                }
-              } catch (error) {
-                // Check if unable to decode base64 data
-                if (!error instanceof ReferenceError) {
-                  throw error;
-                }
-              }
-            }
-          }
-        }
-        return attachments;
-      });
+  CryptStorage.prototype.post = function () {
+    return this._sub_storage.post.apply(this._sub_storage,
+                                        arguments);
   };
 
-  DocumentStorage.prototype.put = function (doc_id, param) {
-    return this._sub_storage.putAttachment(
-      this._document_id,
-      getSubAttachmentIdFromParam(doc_id),
-      new Blob([JSON.stringify(param)], {type: "application/json"})
-    )
-      .push(function () {
-        return doc_id;
-      });
+  CryptStorage.prototype.put = function () {
+    return this._sub_storage.put.apply(this._sub_storage,
+                                       arguments);
+  };
 
+  CryptStorage.prototype.remove = function () {
+    return this._sub_storage.remove.apply(this._sub_storage,
+                                          arguments);
   };
 
-  DocumentStorage.prototype.remove = function (id) {
-    var context = this;
-    return this.allAttachments(id)
-      .push(function (result) {
-        var key,
-          promise_list = [];
-        for (key in result) {
-          if (result.hasOwnProperty(key)) {
-            promise_list.push(context.removeAttachment(id, key));
-          }
+  CryptStorage.prototype.hasCapacity = function () {
+    return this._sub_storage.hasCapacity.apply(this._sub_storage,
+                                               arguments);
+  };
+
+  CryptStorage.prototype.buildQuery = function () {
+    return this._sub_storage.buildQuery.apply(this._sub_storage,
+                                              arguments);
+  };
+
+
+  CryptStorage.prototype.putAttachment = function (id, name, blob) {
+    var initializaton_vector = crypto.getRandomValues(new Uint8Array(12)),
+      that = this;
+
+    return new RSVP.Queue()
+      .push(function () {
+        if (that._jsonKey === true) {
+          return convertKey(that);
         }
-        return RSVP.all(promise_list);
+        return;
       })
       .push(function () {
-        return context._sub_storage.removeAttachment(
-          context._document_id,
-          getSubAttachmentIdFromParam(id)
-        );
+        return jIO.util.readBlobAsDataURL(blob);
       })
-      .push(function () {
-        return id;
-      });
-  };
+      .push(function (dataURL) {
+        //string->arraybuffer
+        var strLen = dataURL.currentTarget.result.length,
+          buf = new ArrayBuffer(strLen),
+          bufView = new Uint8Array(buf),
+          i;
 
-  DocumentStorage.prototype.repair = function () {
-    var context = this;
-    return this._sub_storage.repair.apply(this._sub_storage, arguments)
-      .push(function (result) {
-        if (context._repair_attachment) {
-          return context._sub_storage.allAttachments(context._document_id)
-            .push(function (result_dict) {
-              var promise_list = [],
-                id_dict = {},
-                attachment_dict = {},
-                id,
-                attachment,
-                exec,
-                key;
-              for (key in result_dict) {
-                if (result_dict.hasOwnProperty(key)) {
-                  id = undefined;
-                  attachment = undefined;
-                  if (DOCUMENT_REGEXP.test(key)) {
-                    try {
-                      id = atob(DOCUMENT_REGEXP.exec(key)[1]);
-                    } catch (error) {
-                      // Check if unable to decode base64 data
-                      if (!error instanceof ReferenceError) {
-                        throw error;
-                      }
-                    }
-                    if (id !== undefined) {
-                      id_dict[id] = null;
-                    }
-                  } else if (ATTACHMENT_REGEXP.test(key)) {
-                    exec = ATTACHMENT_REGEXP.exec(key);
-                    try {
-                      id = atob(exec[1]);
-                      attachment = atob(exec[2]);
-                    } catch (error) {
-                      // Check if unable to decode base64 data
-                      if (!error instanceof ReferenceError) {
-                        throw error;
-                      }
-                    }
-                    if (attachment !== undefined) {
-                      if (!id_dict.hasOwnProperty(id)) {
-                        if (!attachment_dict.hasOwnProperty(id)) {
-                          attachment_dict[id] = {};
-                        }
-                        attachment_dict[id][attachment] = null;
-                      }
-                    }
-                  }
-                }
-              }
-              for (id in attachment_dict) {
-                if (attachment_dict.hasOwnProperty(id)) {
-                  if (!id_dict.hasOwnProperty(id)) {
-                    for (attachment in attachment_dict[id]) {
-                      if (attachment_dict[id].hasOwnProperty(attachment)) {
-                        promise_list.push(context.removeAttachment(
-                          id,
-                          attachment
-                        ));
-                      }
-                    }
-                  }
-                }
-              }
-              return RSVP.all(promise_list);
-            });
+        dataURL = dataURL.currentTarget.result;
+        for (i = 0; i < strLen; i += 1) {
+          bufView[i] = dataURL.charCodeAt(i);
         }
-        return result;
+        return crypto.subtle.encrypt({
+          name : "AES-GCM",
+          iv : initializaton_vector
+        },
+                                     that._key, buf);
+      })
+      .push(function (coded) {
+        var blob = new Blob([initializaton_vector, coded], {type: MIME_TYPE});
+        return that._sub_storage.putAttachment(id, name, blob);
       });
   };
 
-  DocumentStorage.prototype.hasCapacity = function (capacity) {
-    return (capacity === "list");
-  };
+  CryptStorage.prototype.getAttachment = function (id, name) {
+    var that = this;
 
-  DocumentStorage.prototype.buildQuery = function () {
-    return this._sub_storage.allAttachments(this._document_id)
-      .push(function (attachment_dict) {
-        var result = [],
-          key;
-        for (key in attachment_dict) {
-          if (attachment_dict.hasOwnProperty(key)) {
-            if (DOCUMENT_REGEXP.test(key)) {
-              try {
-                result.push({
-                  id: atob(DOCUMENT_REGEXP.exec(key)[1]),
-                  value: {}
-                });
-              } catch (error) {
-                // Check if unable to decode base64 data
-                if (!error instanceof ReferenceError) {
-                  throw error;
+    return that._sub_storage.getAttachment(id, name)
+      .push(function (blob) {
+        if (blob.type !== MIME_TYPE) {
+          return blob;
+        }
+        return new RSVP.Queue()
+          .push(function () {
+            if (that._jsonKey === true) {
+              return convertKey(that);
+            }
+            return;
+          })
+          .push(function () {
+            return jIO.util.readBlobAsArrayBuffer(blob);
+          })
+          .push(function (coded) {
+            var initializaton_vector;
+
+            coded = coded.currentTarget.result;
+            initializaton_vector = new Uint8Array(coded.slice(0, 12));
+            return new RSVP.Queue()
+              .push(function () {
+                return crypto.subtle.decrypt({
+                  name : "AES-GCM",
+                  iv : initializaton_vector
+                },
+                                             that._key, coded.slice(12));
+              })
+              .push(function (arr) {
+                //arraybuffer->string
+                arr = String.fromCharCode.apply(null, new Uint8Array(arr));
+                return jIO.util.dataURItoBlob(arr);
+              })
+              .push(undefined, function (error) {
+                if (error instanceof DOMException) {
+                  return blob;
                 }
-              }
-            }
-          }
-        }
-        return result;
+                throw error;
+              });
+          });
       });
   };
 
-  DocumentStorage.prototype.getAttachment = function (id, name) {
-    return this._sub_storage.getAttachment(
-      this._document_id,
-      getSubAttachmentIdFromParam(id, name)
-    );
-  };
-
-  DocumentStorage.prototype.putAttachment = function (id, name, blob) {
-    return this._sub_storage.putAttachment(
-      this._document_id,
-      getSubAttachmentIdFromParam(id, name),
-      blob
-    );
+  CryptStorage.prototype.removeAttachment = function () {
+    return this._sub_storage.removeAttachment.apply(this._sub_storage,
+                                                    arguments);
   };
 
-  DocumentStorage.prototype.removeAttachment = function (id, name) {
-    return this._sub_storage.removeAttachment(
-      this._document_id,
-      getSubAttachmentIdFromParam(id, name)
-    );
+  CryptStorage.prototype.allAttachments = function () {
+    return this._sub_storage.allAttachments.apply(this._sub_storage,
+                                                  arguments);
   };
 
-  jIO.addStorage('document', DocumentStorage);
+  jIO.addStorage('crypt', CryptStorage);
 
-}(jIO, Blob, atob, btoa, RSVP));
+}(jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer));
 ;/*
- * Copyright 2014, Nexedi SA
+ * Copyright 2013, Nexedi SA
  * Released under the LGPL license.
  * http://www.gnu.org/licenses/lgpl.html
  */
-
 /**
- * JIO Indexed Database Storage.
- *
- * A local browser "database" storage greatly more powerful than localStorage.
- *
- * Description:
- *
- *    {
- *      "type": "indexeddb",
- *      "database": <string>
- *    }
- *
- * The database name will be prefixed by "jio:", so if the database property is
- * "hello", then you can manually reach this database with
- * `indexedDB.open("jio:hello");`. (Or
- * `indexedDB.deleteDatabase("jio:hello");`.)
- *
- * For more informations:
- *
- * - http://www.w3.org/TR/IndexedDB/
- * - https://developer.mozilla.org/en-US/docs/IndexedDB/Using_IndexedDB
+ * JIO Websql Storage. Type = "websql".
+ * websql "database" storage.
  */
+/*global Blob, jIO, RSVP, openDatabase*/
+/*jslint nomen: true*/
 
-/*jslint nomen: true */
-/*global indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange*/
+(function (jIO, RSVP, Blob, openDatabase) {
 
-(function (indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange) {
   "use strict";
 
-  // Read only as changing it can lead to data corruption
-  var UNITE = 2000000;
-
-  function IndexedDBStorage(description) {
-    if (typeof description.database !== "string" ||
-        description.database === "") {
-      throw new TypeError("IndexedDBStorage 'database' description property " +
-                          "must be a non-empty string");
-    }
-    this._database_name = "jio:" + description.database;
-  }
-
-  IndexedDBStorage.prototype.hasCapacity = function (name) {
-    return ((name === "list") || (name === "include"));
-  };
-
-  function buildKeyPath(key_list) {
-    return key_list.join("_");
-  }
-
-  function handleUpgradeNeeded(evt) {
-    var db = evt.target.result,
-      store;
-
-    store = db.createObjectStore("metadata", {
-      keyPath: "_id",
-      autoIncrement: false
-    });
-    // It is not possible to use openKeyCursor on keypath directly
-    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=19955
-    store.createIndex("_id", "_id", {unique: true});
-
-    store = db.createObjectStore("attachment", {
-      keyPath: "_key_path",
-      autoIncrement: false
-    });
-    store.createIndex("_id", "_id", {unique: false});
+  /**
+   * The JIO Websql Storage extension
+   *
+   * @class WebSQLStorage
+   * @constructor
+   */
 
-    store = db.createObjectStore("blob", {
-      keyPath: "_key_path",
-      autoIncrement: false
-    });
-    store.createIndex("_id_attachment",
-                      ["_id", "_attachment"], {unique: false});
-    store.createIndex("_id", "_id", {unique: false});
-  }
+  function queueSql(db, query_list, argument_list) {
+    return new RSVP.Promise(function (resolve, reject) {
+      /*jslint unparam: true*/
+      db.transaction(function (tx) {
+        var len = query_list.length,
+          result_list = [],
+          i;
 
-  function openIndexedDB(jio_storage) {
-    var db_name = jio_storage._database_name;
-    function resolver(resolve, reject) {
-      // Open DB //
-      var request = indexedDB.open(db_name);
-      request.onerror = function (error) {
-        if (request.result) {
-          request.result.close();
+        function resolveTransaction(tx, result) {
+          result_list.push(result);
+          if (result_list.length === len) {
+            resolve(result_list);
+          }
+        }
+        function rejectTransaction(tx, error) {
+          reject(error);
+          return true;
+        }
+        for (i = 0; i < len; i += 1) {
+          tx.executeSql(query_list[i], argument_list[i], resolveTransaction,
+                        rejectTransaction);
         }
+      }, function (tx, error) {
         reject(error);
-      };
-
-      request.onabort = function () {
-        request.result.close();
-        reject("Aborting connection to: " + db_name);
-      };
-
-      request.ontimeout = function () {
-        request.result.close();
-        reject("Connection to: " + db_name + " timeout");
-      };
-
-      request.onblocked = function () {
-        request.result.close();
-        reject("Connection to: " + db_name + " was blocked");
-      };
-
-      // Create DB if necessary //
-      request.onupgradeneeded = handleUpgradeNeeded;
-
-      request.onversionchange = function () {
-        request.result.close();
-        reject(db_name + " was upgraded");
-      };
+      });
+      /*jslint unparam: false*/
+    });
+  }
 
-      request.onsuccess = function () {
-        resolve(request.result);
-      };
-    }
-    // XXX Canceller???
+  function initDatabase(db) {
+    var query_list = [
+      "CREATE TABLE IF NOT EXISTS document" +
+        "(id VARCHAR PRIMARY KEY NOT NULL, data TEXT)",
+      "CREATE TABLE IF NOT EXISTS attachment" +
+        "(id VARCHAR, attachment VARCHAR, part INT, blob TEXT)",
+      "CREATE TRIGGER IF NOT EXISTS removeAttachment " +
+        "BEFORE DELETE ON document FOR EACH ROW " +
+        "BEGIN DELETE from attachment WHERE id = OLD.id;END;",
+      "CREATE INDEX IF NOT EXISTS index_document ON document (id);",
+      "CREATE INDEX IF NOT EXISTS index_attachment " +
+        "ON attachment (id, attachment);"
+    ];
     return new RSVP.Queue()
       .push(function () {
-        return new RSVP.Promise(resolver);
+        return queueSql(db, query_list, []);
       });
   }
 
-  function openTransaction(db, stores, flag, autoclosedb) {
-    var tx = db.transaction(stores, flag);
-    if (autoclosedb !== false) {
-      tx.oncomplete = function () {
-        db.close();
-      };
+  function WebSQLStorage(spec) {
+    if (typeof spec.database !== 'string' || !spec.database) {
+      throw new TypeError("database must be a string " +
+                          "which contains more than one character.");
     }
-    tx.onabort = function () {
-      db.close();
-    };
-    return tx;
-  }
-
-  function handleCursor(request, callback) {
-    function resolver(resolve, reject) {
-      // Open DB //
-      request.onerror = function (error) {
-        if (request.transaction) {
-          request.transaction.abort();
-        }
-        reject(error);
-      };
-
-      request.onsuccess = function (evt) {
-        var cursor = evt.target.result;
-        if (cursor) {
-          // XXX Wait for result
-          try {
-            callback(cursor);
-          } catch (error) {
-            reject(error);
-          }
-
-          // continue to next iteration
-          cursor["continue"]();
-        } else {
-          resolve();
-        }
-      };
+    this._database = openDatabase("jio:" + spec.database,
+                                  '1.0', '', 2 * 1024 * 1024);
+    if (spec.blob_length &&
+        (typeof spec.blob_length !== "number" ||
+         spec.blob_length < 20)) {
+      throw new TypeError("blob_len parameter must be a number >= 20");
     }
-    // XXX Canceller???
-    return new RSVP.Promise(resolver);
+    this._blob_length = spec.blob_length || 2000000;
+    this._init_db_promise = initDatabase(this._database);
   }
 
-  IndexedDBStorage.prototype.buildQuery = function (options) {
-    var result_list = [];
-
-    function pushIncludedMetadata(cursor) {
-      result_list.push({
-        "id": cursor.key,
-        "value": {},
-        "doc": cursor.value.doc
-      });
-    }
+  WebSQLStorage.prototype.put = function (id, param) {
+    var db = this._database,
+      that = this,
+      data_string = JSON.stringify(param);
 
-    function pushMetadata(cursor) {
-      result_list.push({
-        "id": cursor.key,
-        "value": {}
-      });
-    }
-    return openIndexedDB(this)
-      .push(function (db) {
-        var tx = openTransaction(db, ["metadata"], "readonly");
-        if (options.include_docs === true) {
-          return handleCursor(tx.objectStore("metadata").index("_id")
-                              .openCursor(), pushIncludedMetadata);
-        }
-        return handleCursor(tx.objectStore("metadata").index("_id")
-                            .openKeyCursor(), pushMetadata);
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
       })
       .push(function () {
-        return result_list;
+        return queueSql(db, ["INSERT OR REPLACE INTO " +
+                            "document(id, data) VALUES(?,?)"],
+                       [[id, data_string]]);
+      })
+      .push(function () {
+        return id;
       });
-
   };
 
-  function handleGet(request) {
-    function resolver(resolve, reject) {
-      request.onerror = reject;
-      request.onsuccess = function () {
-        if (request.result) {
-          resolve(request.result);
-        }
-        // XXX How to get ID
-        reject(new jIO.util.jIOError("Cannot find document", 404));
-      };
-    }
-    return new RSVP.Promise(resolver);
-  }
+  WebSQLStorage.prototype.remove = function (id) {
+    var db = this._database,
+      that = this;
 
-  IndexedDBStorage.prototype.get = function (id) {
-    return openIndexedDB(this)
-      .push(function (db) {
-        var transaction = openTransaction(db, ["metadata"],
-                                          "readonly");
-        return handleGet(transaction.objectStore("metadata").get(id));
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
       })
-      .push(function (result) {
-        return result.doc;
+      .push(function () {
+        return queueSql(db, ["DELETE FROM document WHERE id = ?"], [[id]]);
+      })
+      .push(function (result_list) {
+        if (result_list[0].rowsAffected === 0) {
+          throw new jIO.util.jIOError("Cannot find document", 404);
+        }
+        return id;
       });
-  };
 
-  IndexedDBStorage.prototype.allAttachments = function (id) {
-    var attachment_dict = {};
+  };
 
-    function addEntry(cursor) {
-      attachment_dict[cursor.value._attachment] = {};
-    }
+  WebSQLStorage.prototype.get = function (id) {
+    var db = this._database,
+      that = this;
 
-    return openIndexedDB(this)
-      .push(function (db) {
-        var transaction = openTransaction(db, ["metadata", "attachment"],
-                                          "readonly");
-        return RSVP.all([
-          handleGet(transaction.objectStore("metadata").get(id)),
-          handleCursor(transaction.objectStore("attachment").index("_id")
-                       .openCursor(IDBKeyRange.only(id)), addEntry)
-        ]);
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
       })
       .push(function () {
-        return attachment_dict;
+        return queueSql(db, ["SELECT data FROM document WHERE id = ?"],
+                        [[id]]);
+      })
+      .push(function (result_list) {
+        if (result_list[0].rows.length === 0) {
+          throw new jIO.util.jIOError("Cannot find document", 404);
+        }
+        return JSON.parse(result_list[0].rows[0].data);
       });
   };
 
-  function handleRequest(request) {
-    function resolver(resolve, reject) {
-      request.onerror = reject;
-      request.onsuccess = function () {
-        resolve(request.result);
-      };
-    }
-    return new RSVP.Promise(resolver);
-  }
+  WebSQLStorage.prototype.allAttachments = function (id) {
+    var db = this._database,
+      that = this;
 
-  IndexedDBStorage.prototype.put = function (id, metadata) {
-    return openIndexedDB(this)
-      .push(function (db) {
-        var transaction = openTransaction(db, ["metadata"], "readwrite");
-        return handleRequest(transaction.objectStore("metadata").put({
-          "_id": id,
-          "doc": metadata
-        }));
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
+      })
+      .push(function () {
+        return queueSql(db, [
+          "SELECT id FROM document WHERE id = ?",
+          "SELECT DISTINCT attachment FROM attachment WHERE id = ?"
+        ], [[id], [id]]);
+      })
+      .push(function (result_list) {
+        if (result_list[0].rows.length === 0) {
+          throw new jIO.util.jIOError("Cannot find document", 404);
+        }
+
+        var len = result_list[1].rows.length,
+          obj = {},
+          i;
+
+        for (i = 0; i < len; i += 1) {
+          obj[result_list[1].rows[i].attachment] = {};
+        }
+        return obj;
       });
   };
 
-  function deleteEntry(cursor) {
-    cursor["delete"]();
+  function sendBlobPart(blob, argument_list, index, queue) {
+    queue.push(function () {
+      return jIO.util.readBlobAsDataURL(blob);
+    })
+      .push(function (strBlob) {
+        argument_list[index + 2].push(strBlob.currentTarget.result);
+        return;
+      });
   }
 
-  IndexedDBStorage.prototype.remove = function (id) {
-    return openIndexedDB(this)
-      .push(function (db) {
-        var transaction = openTransaction(db, ["metadata", "attachment",
-                                          "blob"], "readwrite");
-        return RSVP.all([
-          handleRequest(transaction
-                        .objectStore("metadata")["delete"](id)),
-          // XXX Why not possible to delete with KeyCursor?
-          handleCursor(transaction.objectStore("attachment").index("_id")
-                       .openCursor(IDBKeyRange.only(id)), deleteEntry),
-          handleCursor(transaction.objectStore("blob").index("_id")
-                       .openCursor(IDBKeyRange.only(id)), deleteEntry)
-        ]);
-      });
-  };
+  WebSQLStorage.prototype.putAttachment = function (id, name, blob) {
+    var db = this._database,
+      that = this,
+      part_size = this._blob_length;
 
-  IndexedDBStorage.prototype.getAttachment = function (id, name, options) {
-    var transaction,
-      type,
-      start,
-      end;
-    if (options === undefined) {
-      options = {};
-    }
-    return openIndexedDB(this)
-      .push(function (db) {
-        transaction = openTransaction(db, ["attachment", "blob"], "readonly");
-        // XXX Should raise if key is not good
-        return handleGet(transaction.objectStore("attachment")
-                         .get(buildKeyPath([id, name])));
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
       })
-      .push(function (attachment) {
-        var total_length = attachment.info.length,
+      .push(function () {
+        return queueSql(db, ["SELECT id FROM document WHERE id = ?"], [[id]]);
+      })
+      .push(function (result) {
+        var query_list = [],
+          argument_list = [],
+          blob_size = blob.size,
+          queue = new RSVP.Queue(),
           i,
-          promise_list = [],
-          store = transaction.objectStore("blob"),
-          start_index,
-          end_index;
+          index;
 
-        type = attachment.info.content_type;
-        start = options.start || 0;
-        end = options.end || total_length;
-        if (end > total_length) {
-          end = total_length;
+        if (result[0].rows.length === 0) {
+          throw new jIO.util.jIOError("Cannot access subdocument", 404);
         }
+        query_list.push("DELETE FROM attachment WHERE id = ? " +
+                        "AND attachment = ?");
+        argument_list.push([id, name]);
+        query_list.push("INSERT INTO attachment(id, attachment, part, blob)" +
+                     "VALUES(?, ?, ?, ?)");
+        argument_list.push([id, name, -1,
+                            blob.type || "application/octet-stream"]);
+
+        for (i = 0, index = 0; i < blob_size; i += part_size, index += 1) {
+          query_list.push("INSERT INTO attachment(id, attachment, part, blob)" +
+                       "VALUES(?, ?, ?, ?)");
+          argument_list.push([id, name, index]);
+          sendBlobPart(blob.slice(i, i + part_size), argument_list, index,
+                       queue);
+        }
+        queue.push(function () {
+          return queueSql(db, query_list, argument_list);
+        });
+        return queue;
+      });
+  };
 
-        if (start < 0 || end < 0) {
-          throw new jIO.util.jIOError("_start and _end must be positive",
-                                      400);
-        }
-        if (start > end) {
-          throw new jIO.util.jIOError("_start is greater than _end",
-                                      400);
-        }
+  WebSQLStorage.prototype.getAttachment = function (id, name, options) {
+    var db = this._database,
+      that = this,
+      part_size = this._blob_length,
+      start,
+      end,
+      start_index,
+      end_index;
 
-        start_index = Math.floor(start / UNITE);
-        end_index =  Math.floor(end / UNITE);
-        if (end % UNITE === 0) {
-          end_index -= 1;
-        }
+    if (options === undefined) { options = {}; }
+    start = options.start || 0;
+    end = options.end || -1;
 
-        for (i = start_index; i <= end_index; i += 1) {
-          promise_list.push(
-            handleGet(store.get(buildKeyPath([id,
-                                name, i])))
-          );
+    if (start < 0 || (options.end !== undefined && options.end < 0)) {
+      throw new jIO.util.jIOError("_start and _end must be positive",
+                                  400);
+    }
+    if (start > end && end !== -1) {
+      throw new jIO.util.jIOError("_start is greater than _end",
+                                  400);
+    }
+
+    start_index = Math.floor(start / part_size);
+    if (start === 0) { start_index -= 1; }
+    end_index =  Math.floor(end / part_size);
+    if (end % part_size === 0) {
+      end_index -= 1;
+    }
+
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
+      })
+      .push(function () {
+        var command = "SELECT part, blob FROM attachment WHERE id = ? AND " +
+          "attachment = ? AND part >= ?",
+          argument_list = [id, name, start_index];
+
+        if (end !== -1) {
+          command += " AND part <= ?";
+          argument_list.push(end_index);
         }
-        return RSVP.all(promise_list);
+        return queueSql(db, [command], [argument_list]);
       })
-      .push(function (result_list) {
-        var array_buffer_list = [],
+      .push(function (response_list) {
+        var i,
+          response,
+          blob_array = [],
           blob,
-          i,
-          index,
-          len = result_list.length;
-        for (i = 0; i < len; i += 1) {
-          array_buffer_list.push(result_list[i].blob);
+          type;
+
+        response = response_list[0].rows;
+        if (response.length === 0) {
+          throw new jIO.util.jIOError("Cannot find document", 404);
         }
-        if ((options.start === undefined) && (options.end === undefined)) {
-          return new Blob(array_buffer_list, {type: type});
+        for (i = 0; i < response.length; i += 1) {
+          if (response[i].part === -1) {
+            type = response[i].blob;
+            start_index += 1;
+          } else {
+            blob_array.push(jIO.util.dataURItoBlob(response[i].blob));
+          }
         }
-        index = Math.floor(start / UNITE) * UNITE;
-        blob = new Blob(array_buffer_list, {type: "application/octet-stream"});
-        return blob.slice(start - index, end - index,
+        if ((start === 0) && (options.end === undefined)) {
+          return new Blob(blob_array, {type: type});
+        }
+        blob = new Blob(blob_array, {});
+        return blob.slice(start - (start_index * part_size),
+                          end === -1 ? blob.size :
+                          end - (start_index * part_size),
                           "application/octet-stream");
       });
   };
 
-  function removeAttachment(transaction, id, name) {
-    return RSVP.all([
-      // XXX How to get the right attachment
-      handleRequest(transaction.objectStore("attachment")["delete"](
-        buildKeyPath([id, name])
-      )),
-      handleCursor(transaction.objectStore("blob").index("_id_attachment")
-                   .openCursor(IDBKeyRange.only(
-          [id, name]
-        )),
-          deleteEntry
-        )
-    ]);
-  }
-
-  IndexedDBStorage.prototype.putAttachment = function (id, name, blob) {
-    var blob_part = [],
-      transaction,
-      db;
-
-    return openIndexedDB(this)
-      .push(function (database) {
-        db = database;
+  WebSQLStorage.prototype.removeAttachment = function (id, name) {
+    var db = this._database,
+      that = this;
 
-        // Split the blob first
-        return jIO.util.readBlobAsArrayBuffer(blob);
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
       })
-      .push(function (event) {
-        var array_buffer = event.target.result,
-          total_size = blob.size,
-          handled_size = 0;
-
-        while (handled_size < total_size) {
-          blob_part.push(array_buffer.slice(handled_size,
-                                            handled_size + UNITE));
-          handled_size += UNITE;
+      .push(function () {
+        return queueSql(db, ["DELETE FROM attachment WHERE " +
+                            "id = ? AND attachment = ?"], [[id, name]]);
+      })
+      .push(function (result) {
+        if (result[0].rowsAffected === 0) {
+          throw new jIO.util.jIOError("Cannot find document", 404);
         }
+        return name;
+      });
+  };
 
-        // Remove previous attachment
-        transaction = openTransaction(db, ["attachment", "blob"], "readwrite");
-        return removeAttachment(transaction, id, name);
+  WebSQLStorage.prototype.hasCapacity = function (name) {
+    return (name === "list" || (name === "include"));
+  };
+
+  WebSQLStorage.prototype.buildQuery = function (options) {
+    var db = this._database,
+      that = this,
+      query =  "SELECT id";
+
+    return new RSVP.Queue()
+      .push(function () {
+        return that._init_db_promise;
       })
       .push(function () {
-
-        var promise_list = [
-            handleRequest(transaction.objectStore("attachment").put({
-              "_key_path": buildKeyPath([id, name]),
-              "_id": id,
-              "_attachment": name,
-              "info": {
-                "content_type": blob.type,
-                "length": blob.size
-              }
-            }))
-          ],
-          len = blob_part.length,
-          blob_store = transaction.objectStore("blob"),
+        if (options === undefined) { options = {}; }
+        if (options.include_docs === true) {
+          query += ", data AS doc";
+        }
+        query += " FROM document";
+        return queueSql(db, [query], [[]]);
+      })
+      .push(function (result) {
+        var array = [],
+          len = result[0].rows.length,
           i;
+
         for (i = 0; i < len; i += 1) {
-          promise_list.push(
-            handleRequest(blob_store.put({
-              "_key_path": buildKeyPath([id, name,
-                                         i]),
-              "_id" : id,
-              "_attachment" : name,
-              "_part" : i,
-              "blob": blob_part[i]
-            }))
-          );
+          array.push(result[0].rows[i]);
+          array[i].value = {};
+          if (array[i].doc !== undefined) {
+            array[i].doc = JSON.parse(array[i].doc);
+          }
         }
-        // Store all new data
-        return RSVP.all(promise_list);
+        return array;
       });
   };
 
-  IndexedDBStorage.prototype.removeAttachment = function (id, name) {
-    return openIndexedDB(this)
-      .push(function (db) {
-        var transaction = openTransaction(db, ["attachment", "blob"],
-                                          "readwrite");
-        return removeAttachment(transaction, id, name);
-      });
-  };
+  jIO.addStorage('websql', WebSQLStorage);
 
-  jIO.addStorage("indexeddb", IndexedDBStorage);
-}(indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange));
\ No newline at end of file
+}(jIO, RSVP, Blob, openDatabase));
\ No newline at end of file
-- 
2.30.9