From: Tim Wood Date: Sun, 11 Jan 2015 19:15:03 +0000 (-0800) Subject: Convert moment.js to ES6 modules. X-Git-Tag: 2.10.2~15^2~42 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d5262a01beec4f6ed1b0ad73ba2da66782b2f60b;p=thirdparty%2Fmoment.git Convert moment.js to ES6 modules. --- diff --git a/lib/create/check-overflow.js b/lib/create/check-overflow.js new file mode 100644 index 000000000..92c96593f --- /dev/null +++ b/lib/create/check-overflow.js @@ -0,0 +1,27 @@ +import { daysInMonth } from "../units/month"; +import { YEAR, MONTH, DATE, HOUR, MINUTE, SECOND, MILLISECOND } from "../units/constants"; + +export default function checkOverflow (m) { + var overflow; + var a = m._a; + + if (a && m._pf.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 (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + + m._pf.overflow = overflow; + } + + return m; +} + diff --git a/lib/create/date-from-array.js b/lib/create/date-from-array.js new file mode 100644 index 000000000..349eebcc4 --- /dev/null +++ b/lib/create/date-from-array.js @@ -0,0 +1,19 @@ +export 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) { + date.setFullYear(y); + } + return date; +} + +export function createUTCDate (y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; +} diff --git a/lib/create/default-parsing-flags.js b/lib/create/default-parsing-flags.js new file mode 100644 index 000000000..0732d5bf2 --- /dev/null +++ b/lib/create/default-parsing-flags.js @@ -0,0 +1,15 @@ +export default 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 + }; +} diff --git a/lib/create/from-anything.js b/lib/create/from-anything.js new file mode 100644 index 000000000..44b0103ec --- /dev/null +++ b/lib/create/from-anything.js @@ -0,0 +1,93 @@ +import defaultParsingFlags from "./default-parsing-flags"; +import isArray from "../utils/is-array"; +import isDate from "../utils/is-date"; +import map from "../utils/map"; +import { createInvalid } from "./valid"; +import { Moment, isMoment } from "../moment/constructor"; +import { getLocale } from "../locale/locales"; +import { hooks } from "../utils/hooks"; +import checkOverflow from "./check-overflow"; + +import { configFromStringAndArray } from "./from-string-and-array"; +import { configFromStringAndFormat } from "./from-string-and-format"; +import { configFromString } from "./from-string"; +import { configFromArray } from "./from-array"; +import { configFromObject } from "./from-object"; + +function createFromConfig (config) { + var input = config._i, + format = config._f, + res; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return 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 { + configFromInput(config); + } + + res = new Moment(checkOverflow(config)); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; +} + +function configFromInput(config) { + var input = config._i; + if (input === undefined) { + config._d = new Date(); + } else if (isDate(input)) { + config._d = new Date(+input); + } 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 { + hooks.createFromInputFallback(config); + } +} + +export 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; + c._pf = defaultParsingFlags(); + + return createFromConfig(c); +} diff --git a/lib/create/from-array.js b/lib/create/from-array.js new file mode 100644 index 000000000..63b810aa9 --- /dev/null +++ b/lib/create/from-array.js @@ -0,0 +1,123 @@ +import { createDate, createUTCDate } from "./date-from-array"; +import { daysInYear } from "../units/year"; +import { weekOfYear } from "../units/week"; +import { dayOfYearFromWeeks } from "../units/day-of-year"; +import { YEAR, MONTH, DATE, HOUR, MINUTE, SECOND, MILLISECOND } from "../units/constants"; +import { createLocal } from "./local"; +import defaults from "../utils/defaults"; + +function currentDateArray(config) { + var now = new Date(); + if (config._useUTC) { + return [now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()]; + } + return [now.getFullYear(), now.getMonth(), now.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] +export function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; + + if (config._d) { + return; + } + + 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 the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse)) { + config._pf._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // 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]; + } + + // 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]; + } + + // 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; + } + + 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); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } +} + +function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // 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(createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(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 < dow) { + ++week; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + } else { + // default to begining of week + weekday = dow; + } + } + temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; +} diff --git a/lib/create/from-object.js b/lib/create/from-object.js new file mode 100644 index 000000000..56189c344 --- /dev/null +++ b/lib/create/from-object.js @@ -0,0 +1,13 @@ +import { normalizeObjectUnits } from "../units/aliases"; +import { configFromArray } from "./from-array"; + +export function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i); + config._a = [i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond]; + + configFromArray(config); +} diff --git a/lib/create/from-string-and-array.js b/lib/create/from-string-and-array.js new file mode 100644 index 000000000..8576b41a5 --- /dev/null +++ b/lib/create/from-string-and-array.js @@ -0,0 +1,51 @@ +import { copyConfig } from "../moment/constructor"; +import { configFromStringAndFormat } from "./from-string-and-format"; +import defaultParsingFlags from "./default-parsing-flags"; +import { isValid } from "./valid"; +import extend from "../utils/extend"; + +// date from string and array of format strings +export function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + config._pf.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._pf = defaultParsingFlags(); + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += tempConfig._pf.charsLeftOver; + + //or tokens + currentScore += tempConfig._pf.unusedTokens.length * 10; + + tempConfig._pf.score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); +} diff --git a/lib/create/from-string-and-format.js b/lib/create/from-string-and-format.js new file mode 100644 index 000000000..fccf9d68f --- /dev/null +++ b/lib/create/from-string-and-format.js @@ -0,0 +1,99 @@ +import { configFromISO } from "./from-string"; +import { configFromArray } from "./from-array"; +import { getParseRegexForToken } from "../parse/regex"; +import { addTimeToArrayFromToken } from "../parse/token"; +import { expandFormat, formatTokenFunctions, formattingTokens } from "../format/format"; +import checkOverflow from "./check-overflow"; +import { HOUR } from "../units/constants"; +import { hooks } from "../utils/hooks"; + +// constant that refers to the ISO standard +hooks.ISO_8601 = function () {}; + +// date from string and format string +export function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + + config._a = []; + config._pf.empty = true; + + // 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; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + 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; + } + // 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); + } + else if (config._strict && !parsedInput) { + config._pf.unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + config._pf.charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + config._pf.unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if (config._pf.bigHour === true && config._a[HOUR] <= 12) { + config._pf.bigHour = undefined; + } + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); + + 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; + } +} diff --git a/lib/create/from-string.js b/lib/create/from-string.js new file mode 100644 index 000000000..8353100cc --- /dev/null +++ b/lib/create/from-string.js @@ -0,0 +1,82 @@ +import { matchOffset } from "../parse/regex"; +import { configFromStringAndFormat } from "./from-string-and-format"; +import { hooks } from "../utils/hooks"; +import { deprecate } from "../utils/deprecate"; + +// 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 isoRegex = /^\s*(?:[+-]\d{6}|\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)?)?$/; + +var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], + ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], + ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], + ['GGGG-[W]WW', /\d{4}-W\d{2}/], + ['YYYY-DDD', /\d{4}-\d{3}/] +]; + +// iso time formats and regexes +var isoTimes = [ + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] +]; + +var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + +// date from iso format +export function configFromISO(config) { + var i, l, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + config._pf.iso = true; + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(string)) { + // match[5] should be 'T' or undefined + config._f = isoDates[i][0] + (match[6] || ' '); + break; + } + } + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (string.match(matchOffset)) { + config._f += 'Z'; + } + configFromStringAndFormat(config); + } else { + config._isValid = false; + } +} + +// date from iso format or fallback +export function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + hooks.createFromInputFallback(config); + } +} + +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' : '')); + } +); diff --git a/lib/create/local.js b/lib/create/local.js new file mode 100644 index 000000000..4d0da5498 --- /dev/null +++ b/lib/create/local.js @@ -0,0 +1,5 @@ +import { createLocalOrUTC } from "./from-anything"; + +export function createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); +} diff --git a/lib/create/utc.js b/lib/create/utc.js new file mode 100644 index 000000000..f35e3a069 --- /dev/null +++ b/lib/create/utc.js @@ -0,0 +1,5 @@ +import { createLocalOrUTC } from "./from-anything"; + +export function createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); +} diff --git a/lib/create/valid.js b/lib/create/valid.js new file mode 100644 index 000000000..292b00f39 --- /dev/null +++ b/lib/create/valid.js @@ -0,0 +1,34 @@ +import extend from "../utils/extend"; +import { createUTC } from "./utc"; + +export 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; + + if (m._strict) { + m._isValid = m._isValid && + m._pf.charsLeftOver === 0 && + m._pf.unusedTokens.length === 0 && + m._pf.bigHour === undefined; + } + } + return m._isValid; +} + +export function createInvalid (flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(m._pf, flags); + } + else { + m._pf.userInvalidated = true; + } + + return m; +} diff --git a/lib/duration/abs.js b/lib/duration/abs.js new file mode 100644 index 000000000..103a4cf9b --- /dev/null +++ b/lib/duration/abs.js @@ -0,0 +1,18 @@ +var mathAbs = Math.abs; + +export function 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; +} diff --git a/lib/duration/add-subtract.js b/lib/duration/add-subtract.js new file mode 100644 index 000000000..20c68f239 --- /dev/null +++ b/lib/duration/add-subtract.js @@ -0,0 +1,21 @@ +import { createDuration } from "./create"; + +function addSubtract (duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); +} + +// supports only 2.0-style add(1, 's') or add(duration) +export function add (input, value) { + return addSubtract(this, input, value, 1); +} + +// supports only 2.0-style subtract(1, 's') or subtract(duration) +export function subtract (input, value) { + return addSubtract(this, input, value, -1); +} diff --git a/lib/duration/as.js b/lib/duration/as.js new file mode 100644 index 000000000..7fb5f62f5 --- /dev/null +++ b/lib/duration/as.js @@ -0,0 +1,55 @@ +import { daysToYears, yearsToDays } from "./bubble"; +import { normalizeUnits } from "../units/aliases"; +import toInt from "../utils/to-int"; + +export 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 + daysToYears(days) * 12; + return units === "month" ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(yearsToDays(this._months / 12)); + 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 * 24 * 60 + milliseconds / 6e4; + case "second" : return days * 24 * 60 * 60 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case "millisecond": return Math.floor(days * 24 * 60 * 60 * 1000) + milliseconds; + default: throw new Error("Unknown unit " + units); + } + } +} + +// TODO: Use this.as('ms')? +export function 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); + }; +} + +export var asMilliseconds = makeAs("ms"); +export var asSeconds = makeAs("s"); +export var asMinutes = makeAs("m"); +export var asHours = makeAs("h"); +export var asDays = makeAs("d"); +export var asWeeks = makeAs("w"); +export var asMonths = makeAs("M"); +export var asYears = makeAs("y"); diff --git a/lib/duration/bubble.js b/lib/duration/bubble.js new file mode 100644 index 000000000..d85ee25b8 --- /dev/null +++ b/lib/duration/bubble.js @@ -0,0 +1,54 @@ +import absFloor from "../utils/abs-floor"; + +export function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years = 0; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // Accurately convert days to years, assume start from year 0. + years = absFloor(daysToYears(days)); + days -= absFloor(yearsToDays(years)); + + // 30 days to a month + // TODO (iskren): Use anchor date (like 1st Jan) to compute this. + months += absFloor(days / 30); + days %= 30; + + // 12 months -> 1 year + years += absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; +} + +export function daysToYears (days) { + // 400 years have 146097 days (taking into account leap year rules) + return days * 400 / 146097; +} + +export function yearsToDays (years) { + // years * 365 + absFloor(years / 4) - + // absFloor(years / 100) + absFloor(years / 400); + return years * 146097 / 400; +} diff --git a/lib/duration/constructor.js b/lib/duration/constructor.js new file mode 100644 index 000000000..ef605d916 --- /dev/null +++ b/lib/duration/constructor.js @@ -0,0 +1,41 @@ +import { normalizeObjectUnits } from "../units/aliases"; +import { getLocale } from "../locale/locales"; + +export 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 * 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 + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); +} + +export function isDuration (obj) { + return obj instanceof Duration; +} diff --git a/lib/duration/create.js b/lib/duration/create.js new file mode 100644 index 000000000..1050d3553 --- /dev/null +++ b/lib/duration/create.js @@ -0,0 +1,111 @@ +import { Duration, isDuration } from "./constructor"; +import toInt from "../utils/to-int"; +import hasOwnProp from "../utils/has-own-prop"; +import { DATE, HOUR, MINUTE, SECOND, MILLISECOND } from "../units/constants"; +import { cloneWithOffset } from "../units/offset"; +import { createLocal } from "../create/local"; + +// ASP.NET json date format regex +var aspNetRegex = /(\-)?(?:(\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 +var isoRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/; + +export function 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), + d : parseIso(match[4], sign), + h : parseIso(match[5], sign), + m : parseIso(match[6], sign), + s : parseIso(match[7], sign), + w : 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(createLocal(duration.from), 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; +} + +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; + 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; +} diff --git a/lib/duration/duration.js b/lib/duration/duration.js new file mode 100644 index 000000000..50324872e --- /dev/null +++ b/lib/duration/duration.js @@ -0,0 +1,12 @@ +// Side effect imports +import "./prototype"; + +import { createDuration } from "./create"; +import { isDuration } from "./constructor"; +import { getSetRelativeTimeThreshold } from "./humanize"; + +export { + createDuration, + isDuration, + getSetRelativeTimeThreshold +}; diff --git a/lib/duration/get.js b/lib/duration/get.js new file mode 100644 index 000000000..68d4d2edf --- /dev/null +++ b/lib/duration/get.js @@ -0,0 +1,25 @@ +import { normalizeUnits } from "../units/aliases"; +import absFloor from "../utils/abs-floor"; + +export function get (units) { + units = normalizeUnits(units); + return this[units + 's'](); +} + +function makeGetter(name) { + return function () { + return this._data[name]; + }; +} + +export var milliseconds = makeGetter('milliseconds'); +export var seconds = makeGetter('seconds'); +export var minutes = makeGetter('minutes'); +export var hours = makeGetter('hours'); +export var days = makeGetter('days'); +export var months = makeGetter('months'); +export var years = makeGetter('years'); + +export function weeks () { + return absFloor(this.days() / 7); +} diff --git a/lib/duration/humanize.js b/lib/duration/humanize.js new file mode 100644 index 000000000..c7903f145 --- /dev/null +++ b/lib/duration/humanize.js @@ -0,0 +1,64 @@ +import { createDuration } from "./create"; + +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 relativeTime (posNegDuration, withoutSuffix, locale) { + var duration = 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 +export function getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + return true; +} + +export function humanize (withSuffix) { + var locale = this.localeData(); + var output = relativeTime(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); +} diff --git a/lib/duration/iso-string.js b/lib/duration/iso-string.js new file mode 100644 index 000000000..2670a9d33 --- /dev/null +++ b/lib/duration/iso-string.js @@ -0,0 +1,28 @@ +var abs = Math.abs; + +export function toISOString() { + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = abs(this.years()); + var M = abs(this.months()); + var D = abs(this.days()); + var h = abs(this.hours()); + var m = abs(this.minutes()); + var s = abs(this.seconds() + this.milliseconds() / 1000); + 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' : ''); +} diff --git a/lib/duration/prototype.js b/lib/duration/prototype.js new file mode 100644 index 000000000..f07105eaf --- /dev/null +++ b/lib/duration/prototype.js @@ -0,0 +1,48 @@ +import { Duration } from "./constructor"; + +var proto = Duration.prototype; + +import { abs } from "./abs"; +import { add, subtract } from "./add-subtract"; +import { as, asMilliseconds, asSeconds, asMinutes, asHours, asDays, asWeeks, asMonths, asYears, valueOf } from "./as"; +import { bubble } from "./bubble"; +import { get, milliseconds, seconds, minutes, hours, days, months, years, weeks } from "./get"; +import { humanize } from "./humanize"; +import { toISOString } from "./iso-string"; +import { lang, locale, localeData } from "../moment/locale"; + +proto.abs = abs; +proto.add = add; +proto.subtract = subtract; +proto.as = as; +proto.asMilliseconds = asMilliseconds; +proto.asSeconds = asSeconds; +proto.asMinutes = asMinutes; +proto.asHours = asHours; +proto.asDays = asDays; +proto.asWeeks = asWeeks; +proto.asMonths = asMonths; +proto.asYears = asYears; +proto.valueOf = valueOf; +proto._bubble = bubble; +proto.get = get; +proto.milliseconds = milliseconds; +proto.seconds = seconds; +proto.minutes = minutes; +proto.hours = hours; +proto.days = days; +proto.weeks = weeks; +proto.months = months; +proto.years = years; +proto.humanize = humanize; +proto.toISOString = toISOString; +proto.toString = toISOString; +proto.toJSON = toISOString; +proto.locale = locale; +proto.localeData = localeData; + +// Deprecations +import { deprecate } from "../utils/deprecate"; + +proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString); +proto.lang = lang; diff --git a/lib/format/format.js b/lib/format/format.js new file mode 100644 index 000000000..402c971f8 --- /dev/null +++ b/lib/format/format.js @@ -0,0 +1,95 @@ +import zeroFill from "../utils/zero-fill"; + +export var formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g; + +var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + +var formatFunctions = {}; + +export var formatTokenFunctions = {}; + +// token: "M" +// padded: ["MM", 2] +// ordinal: "Mo" +// callback: function () { this.month() + 1 } +export 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); + }; + } +} + +function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); +} + +function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ''; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; +} + +// format date using native date object +export function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); +} + +export function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; +} + diff --git a/lib/locale/calendar.js b/lib/locale/calendar.js new file mode 100644 index 000000000..22545b964 --- /dev/null +++ b/lib/locale/calendar.js @@ -0,0 +1,13 @@ +export 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' +}; + +export function calendar (key, mom, now) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.call(mom, now) : output; +} diff --git a/lib/locale/constructor.js b/lib/locale/constructor.js new file mode 100644 index 000000000..a22c2cff1 --- /dev/null +++ b/lib/locale/constructor.js @@ -0,0 +1,3 @@ +export function Locale() { + +} diff --git a/lib/locale/en.js b/lib/locale/en.js new file mode 100644 index 000000000..bbd76228d --- /dev/null +++ b/lib/locale/en.js @@ -0,0 +1,15 @@ +import "./prototype"; +import { getSetGlobalLocale } from "./locales"; +import toInt from "../utils/to-int"; + +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; + } +}); diff --git a/lib/locale/formats.js b/lib/locale/formats.js new file mode 100644 index 000000000..22b75ad34 --- /dev/null +++ b/lib/locale/formats.js @@ -0,0 +1,19 @@ +export var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY LT', + LLLL : 'dddd, MMMM D, YYYY LT' +}; + +export function longDateFormat (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; +} diff --git a/lib/locale/invalid.js b/lib/locale/invalid.js new file mode 100644 index 000000000..e9096339b --- /dev/null +++ b/lib/locale/invalid.js @@ -0,0 +1,5 @@ +export var defaultInvalidDate = 'Invalid date'; + +export function invalidDate () { + return this._invalidDate; +} diff --git a/lib/locale/lists.js b/lib/locale/lists.js new file mode 100644 index 000000000..c45901d49 --- /dev/null +++ b/lib/locale/lists.js @@ -0,0 +1,48 @@ +import { getLocale } from "./locales"; +import { createUTC } from "../create/utc"; + +function get (format, index, field, setter) { + var locale = getLocale(); + var utc = createUTC().set(setter, index); + return locale[field](utc, format); +} + +function list (format, index, field, count, setter) { + if (typeof format === 'number') { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get(format, index, field, setter); + } + + var i; + var out = []; + for (i = 0; i < count; i++) { + out[i] = get(format, i, field, setter); + } + return out; +} + +export function listMonths (format, index) { + return list(format, index, "months", 12, "month"); +} + +export function listMonthsShort (format, index) { + return list(format, index, "monthsShort", 12, "month"); +} + +export function listWeekdays (format, index) { + return list(format, index, "weekdays", 7, "day"); +} + +export function listWeekdaysShort (format, index) { + return list(format, index, "weekdaysShort", 7, "day"); +} + +export function listWeekdaysMin (format, index) { + return list(format, index, "weekdaysMin", 7, "day"); +} diff --git a/lib/locale/locale.js b/lib/locale/locale.js new file mode 100644 index 000000000..170c86750 --- /dev/null +++ b/lib/locale/locale.js @@ -0,0 +1,35 @@ +// Side effect imports +import "./prototype"; + +import { + getSetGlobalLocale, + defineLocale, + getLocale +} from "./locales"; + +import { + listMonths, + listMonthsShort, + listWeekdays, + listWeekdaysShort, + listWeekdaysMin +} from "./lists"; + +export { + getSetGlobalLocale, + defineLocale, + getLocale, + listMonths, + listMonthsShort, + listWeekdays, + listWeekdaysShort, + listWeekdaysMin +}; + +import { deprecate } from "../utils/deprecate"; +import { hooks } from "../utils/hooks"; + +hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); +hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); + +import "./en"; diff --git a/lib/locale/locales.js b/lib/locale/locales.js new file mode 100644 index 000000000..12034457e --- /dev/null +++ b/lib/locale/locales.js @@ -0,0 +1,117 @@ +import isArray from "../utils/is-array"; +import compareArrays from "../utils/compare-arrays"; +import { Locale } from "./constructor"; + +// internal storage for locale config files +var locales = {}; +var globalLocale; + +function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; +} + +// 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; + + 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 null; +} + +function loadLocale(name) { + // var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + // if (!locales[name] && hasModule) { + // try { + // oldLocale = moment.locale(); + // require('./locale/' + name); + // // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales + // moment.locale(oldLocale); + // } catch (e) { } + // } + return locales[name]; +} + +// 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. +export function getSetGlobalLocale (key, values) { + var data; + if (key) { + if (typeof values === 'undefined') { + data = getLocale(key); + } + else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } + + return globalLocale._abbr; +} + +export function defineLocale (name, values) { + if (values !== null) { + values.abbr = name; + if (!locales[name]) { + locales[name] = new Locale(); + } + locales[name].set(values); + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } +} + +// returns locale data +export function getLocale (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); +} diff --git a/lib/locale/ordinal.js b/lib/locale/ordinal.js new file mode 100644 index 000000000..0028aca02 --- /dev/null +++ b/lib/locale/ordinal.js @@ -0,0 +1,7 @@ +export var defaultOrdinal = '%d'; +export var defaultOrdinalParse = /\d{1,2}/; + +export function ordinal (number) { + return this._ordinal.replace('%d', number); +} + diff --git a/lib/locale/pre-post-format.js b/lib/locale/pre-post-format.js new file mode 100644 index 000000000..10ed20584 --- /dev/null +++ b/lib/locale/pre-post-format.js @@ -0,0 +1,3 @@ +export function preParsePostFormat (string) { + return string; +} diff --git a/lib/locale/prototype.js b/lib/locale/prototype.js new file mode 100644 index 000000000..ddc8241cb --- /dev/null +++ b/lib/locale/prototype.js @@ -0,0 +1,70 @@ +import { Locale } from "./constructor"; + +var proto = Locale.prototype; + +import { defaultCalendar, calendar } from "./calendar"; +import { defaultLongDateFormat, longDateFormat } from "./formats"; +import { defaultInvalidDate, invalidDate } from "./invalid"; +import { defaultOrdinal, ordinal, defaultOrdinalParse } from "./ordinal"; +import { preParsePostFormat } from "./pre-post-format"; +import { defaultRelativeTime, relativeTime, pastFuture } from "./relative"; +import { set } from "./set"; + +proto._calendar = defaultCalendar; +proto.calendar = calendar; +proto._longDateFormat = defaultLongDateFormat; +proto.longDateFormat = longDateFormat; +proto._invalidDate = defaultInvalidDate; +proto.invalidDate = invalidDate; +proto._ordinal = defaultOrdinal; +proto.ordinal = ordinal; +proto._ordinalParse = defaultOrdinalParse; +proto.preparse = preParsePostFormat; +proto.postformat = preParsePostFormat; +proto._relativeTime = defaultRelativeTime; +proto.relativeTime = relativeTime; +proto.pastFuture = pastFuture; +proto.set = set; + +// Month +import { + localeMonthsParse, + defaultLocaleMonths, localeMonths, + defaultLocaleMonthsShort, localeMonthsShort +} from "../units/month"; + +proto.months = localeMonths; +proto._months = defaultLocaleMonths; +proto.monthsShort = localeMonthsShort; +proto._monthsShort = defaultLocaleMonthsShort; +proto.monthsParse = localeMonthsParse; + +// Week +import { localeWeek, defaultLocaleWeek, localeFirstDayOfYear, localeFirstDayOfWeek } from "../units/week"; +proto.week = localeWeek; +proto._week = defaultLocaleWeek; +proto.firstDayOfYear = localeFirstDayOfYear; +proto.firstDayOfWeek = localeFirstDayOfWeek; + +// Day of Week +import { + localeWeekdaysParse, + defaultLocaleWeekdays, localeWeekdays, + defaultLocaleWeekdaysMin, localeWeekdaysMin, + defaultLocaleWeekdaysShort, localeWeekdaysShort +} from "../units/day-of-week"; + +proto.weekdays = localeWeekdays; +proto._weekdays = defaultLocaleWeekdays; +proto.weekdaysMin = localeWeekdaysMin; +proto._weekdaysMin = defaultLocaleWeekdaysMin; +proto.weekdaysShort = localeWeekdaysShort; +proto._weekdaysShort = defaultLocaleWeekdaysShort; +proto.weekdaysParse = localeWeekdaysParse; + +// Hours +import { localeIsPM, defaultLocaleMeridiemParse, localeMeridiem } from "../units/hour"; + +proto.isPM = localeIsPM; +proto._meridiemParse = defaultLocaleMeridiemParse; +proto.meridiem = localeMeridiem; diff --git a/lib/locale/relative.js b/lib/locale/relative.js new file mode 100644 index 000000000..5986a6754 --- /dev/null +++ b/lib/locale/relative.js @@ -0,0 +1,27 @@ +export 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' +}; + +export function relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); +} + +export function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); +} diff --git a/lib/locale/set.js b/lib/locale/set.js new file mode 100644 index 000000000..b9eb5ed6e --- /dev/null +++ b/lib/locale/set.js @@ -0,0 +1,14 @@ +export function set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + // 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); +} diff --git a/lib/moment.js b/lib/moment.js new file mode 100644 index 000000000..b01423a2d --- /dev/null +++ b/lib/moment.js @@ -0,0 +1,68 @@ +//! moment.js +//! version : 2.8.3 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +import { hooks as moment, setHookCallback } from "./utils/hooks"; + +moment.version = '2.8.3'; + +import { + min, + max, + isMoment, + momentPrototype as fn, + createUTC as utc, + createUnix as unix, + createLocal as local, + createInvalid as invalid, + createInZone as parseZone +} from "./moment/moment"; + +import { + defineLocale, + getSetGlobalLocale as locale, + getLocale as localeData, + listMonths as months, + listMonthsShort as monthsShort, + listWeekdays as weekdays, + listWeekdaysMin as weekdaysMin, + listWeekdaysShort as weekdaysShort +} from "./locale/locale"; + +import { + isDuration, + createDuration as duration, + getSetRelativeTimeThreshold as relativeTimeThreshold +} from "./duration/duration"; + +import { normalizeUnits } from "./units/units"; + +import isDate from "./utils/is-date"; + +setHookCallback(local); + +moment.fn = fn; +moment.min = min; +moment.max = max; +moment.utc = utc; +moment.unix = unix; +moment.months = months; +moment.isDate = isDate; +moment.locale = locale; +moment.invalid = invalid; +moment.duration = duration; +moment.isMoment = isMoment; +moment.weekdays = weekdays; +moment.parseZone = parseZone; +moment.localeData = localeData; +moment.isDuration = isDuration; +moment.monthsShort = monthsShort; +moment.weekdaysMin = weekdaysMin; +moment.defineLocale = defineLocale; +moment.weekdaysShort = weekdaysShort; +moment.normalizeUnits = normalizeUnits; +moment.relativeTimeThreshold = relativeTimeThreshold; + +export default moment; diff --git a/lib/moment/add-subtract.js b/lib/moment/add-subtract.js new file mode 100644 index 000000000..50adf8a3b --- /dev/null +++ b/lib/moment/add-subtract.js @@ -0,0 +1,46 @@ +import { get, set } from "./get-set"; +import { setMonth } from "../units/month"; +import { createDuration } from "../duration/create"; +import { deprecateSimple } from "../utils/deprecate"; +import { hooks } from "../utils/hooks"; + +// 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 = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; +} + +export function addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months; + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + if (days) { + set(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } +} + +export var add = createAdder( 1, 'add'); +export var subtract = createAdder(-1, 'subtract'); + diff --git a/lib/moment/calendar.js b/lib/moment/calendar.js new file mode 100644 index 000000000..50c82b5a6 --- /dev/null +++ b/lib/moment/calendar.js @@ -0,0 +1,17 @@ +import { createLocal } from "../create/local"; +import { cloneWithOffset } from "../units/offset"; + +export function calendar (time) { + // 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 || 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'; + return this.format(this.localeData().calendar(format, this, createLocal(now))); +} diff --git a/lib/moment/clone.js b/lib/moment/clone.js new file mode 100644 index 000000000..fb3141e8c --- /dev/null +++ b/lib/moment/clone.js @@ -0,0 +1,5 @@ +import { Moment } from "./constructor"; + +export function clone () { + return new Moment(this); +} diff --git a/lib/moment/compare.js b/lib/moment/compare.js new file mode 100644 index 000000000..75d164be4 --- /dev/null +++ b/lib/moment/compare.js @@ -0,0 +1,43 @@ +import { isMoment } from "./constructor"; +import { normalizeUnits } from "../units/aliases"; +import { createLocal } from "../create/local"; + +export function isAfter (input, units) { + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = isMoment(input) ? input : createLocal(input); + return +this > +input; + } else { + inputMs = isMoment(input) ? +input : +createLocal(input); + return inputMs < +this.clone().startOf(units); + } +} + +export function isBefore (input, units) { + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = isMoment(input) ? input : createLocal(input); + return +this < +input; + } else { + inputMs = isMoment(input) ? +input : +createLocal(input); + return +this.clone().endOf(units) < inputMs; + } +} + +export function isBetween (from, to, units) { + return this.isAfter(from, units) && this.isBefore(to, units); +} + +export function isSame (input, units) { + var inputMs; + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + input = isMoment(input) ? input : createLocal(input); + return +this === +input; + } else { + inputMs = +createLocal(input); + return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); + } +} diff --git a/lib/moment/constructor.js b/lib/moment/constructor.js new file mode 100644 index 000000000..61686edc6 --- /dev/null +++ b/lib/moment/constructor.js @@ -0,0 +1,72 @@ +import { hooks } from "../utils/hooks"; +import hasOwnProp from "../utils/has-own-prop"; + +// Plugins that add properties should also add the key here (null value), +// so we can properly clone ourselves. +var momentProperties = hooks.momentProperties = []; + +export function copyConfig(to, from) { + var i, prop, val; + + if (typeof from._isAMomentObject !== 'undefined') { + to._isAMomentObject = from._isAMomentObject; + } + if (typeof from._i !== 'undefined') { + to._i = from._i; + } + if (typeof from._f !== 'undefined') { + to._f = from._f; + } + if (typeof from._l !== 'undefined') { + to._l = from._l; + } + if (typeof from._strict !== 'undefined') { + to._strict = from._strict; + } + if (typeof from._tzm !== 'undefined') { + to._tzm = from._tzm; + } + if (typeof from._isUTC !== 'undefined') { + to._isUTC = from._isUTC; + } + if (typeof from._offset !== 'undefined') { + to._offset = from._offset; + } + if (typeof from._pf !== 'undefined') { + to._pf = from._pf; + } + if (typeof from._locale !== 'undefined') { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (typeof val !== 'undefined') { + to[prop] = val; + } + } + } + + return to; +} + +var updateInProgress = false; + +// Moment prototype object +export function Moment(config) { + copyConfig(this, config); + this._d = new Date(+config._d); + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } +} + +export function isMoment (obj) { + return obj instanceof Moment || (obj != null && hasOwnProp(obj, '_isAMomentObject')); +} diff --git a/lib/moment/diff.js b/lib/moment/diff.js new file mode 100644 index 000000000..9177b315c --- /dev/null +++ b/lib/moment/diff.js @@ -0,0 +1,49 @@ +import absFloor from "../utils/abs-floor"; +import { cloneWithOffset } from "../units/offset"; +import { normalizeUnits } from "../units/aliases"; + +export function diff (input, units, asFloat) { + var that = cloneWithOffset(input, this), + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4, + delta, output; + + 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); + } + + return -(wholeMonthDiff + adjust); +} diff --git a/lib/moment/format.js b/lib/moment/format.js new file mode 100644 index 000000000..0855a6a43 --- /dev/null +++ b/lib/moment/format.js @@ -0,0 +1,27 @@ +import { formatMoment } from "../format/format"; +import { hooks } from "../utils/hooks"; + +hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + +export function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); +} + +export function toISOString () { + var m = this.clone().utc(); + if (0 < m.year() && m.year() <= 9999) { + if ('function' === typeof 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]'); + } +} + +export function format (inputString) { + var output = formatMoment(this, inputString || hooks.defaultFormat); + return this.localeData().postformat(output); +} diff --git a/lib/moment/from.js b/lib/moment/from.js new file mode 100644 index 000000000..116fe2046 --- /dev/null +++ b/lib/moment/from.js @@ -0,0 +1,10 @@ +import { createDuration } from "../duration/create"; +import { createLocal } from "../create/local"; + +export function from (time, withoutSuffix) { + return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); +} + +export function fromNow (withoutSuffix) { + return this.from(createLocal(), withoutSuffix); +} diff --git a/lib/moment/get-set.js b/lib/moment/get-set.js new file mode 100644 index 000000000..b91b13bc4 --- /dev/null +++ b/lib/moment/get-set.js @@ -0,0 +1,39 @@ +import { normalizeUnits } from "../units/aliases"; +import { hooks } from "../utils/hooks"; + +export function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + set(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; +} + +export function get (mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); +} + +export function set (mom, unit, value) { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); +} + +// MOMENTS + +export function getSet (units, value) { + var unit; + if (typeof units === 'object') { + for (unit in units) { + this.set(unit, units[unit]); + } + } else { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + return this[units](value); + } + } + return this; +} diff --git a/lib/moment/locale.js b/lib/moment/locale.js new file mode 100644 index 000000000..aa8e58846 --- /dev/null +++ b/lib/moment/locale.js @@ -0,0 +1,34 @@ +import { getLocale } from "../locale/locales"; +import { deprecate } from "../utils/deprecate"; + +// If passed a locale key, it will set the locale for this +// instance. Otherwise, it will return the locale configuration +// variables for this instance. +export function locale (key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } +} + +export 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); + } + } +); + +export function localeData () { + return this._locale; +} diff --git a/lib/moment/min-max.js b/lib/moment/min-max.js new file mode 100644 index 000000000..7e0d658c3 --- /dev/null +++ b/lib/moment/min-max.js @@ -0,0 +1,54 @@ +import { deprecate } from "../utils/deprecate"; +import isArray from "../utils/is-array"; +import { createLocal } from "../create/local"; + +export var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', + function () { + var other = createLocal.apply(null, arguments); + return other < this ? this : other; + } + ); + +export var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', + function () { + var other = createLocal.apply(null, arguments); + return other > this ? this : other; + } +); + +// 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 createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (moments[i][fn](res)) { + res = moments[i]; + } + } + return res; +} + +// TODO: Use [].sort instead? +export function min () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); +} + +export function max () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); +} diff --git a/lib/moment/moment.js b/lib/moment/moment.js new file mode 100644 index 000000000..c692569c6 --- /dev/null +++ b/lib/moment/moment.js @@ -0,0 +1,26 @@ +import { createLocal } from "../create/local"; +import { createUTC } from "../create/utc"; +import { createInvalid } from "../create/valid"; +import { isMoment } from "./constructor"; +import { min, max } from "./min-max"; +import momentPrototype from "./prototype"; + +function createUnix (input) { + return createLocal(input * 1000); +} + +function createInZone () { + return createLocal.apply(null, arguments).parseZone(); +} + +export { + min, + max, + isMoment, + createUTC, + createUnix, + createLocal, + createInZone, + createInvalid, + momentPrototype +}; diff --git a/lib/moment/prototype.js b/lib/moment/prototype.js new file mode 100644 index 000000000..0c656377a --- /dev/null +++ b/lib/moment/prototype.js @@ -0,0 +1,141 @@ +import { Moment } from "./constructor"; + +var proto = Moment.prototype; + +import { add, subtract } from "./add-subtract"; +import { calendar } from "./calendar"; +import { clone } from "./clone"; +import { isBefore, isBetween, isSame, isAfter } from "./compare"; +import { diff } from "./diff"; +import { format, toString, toISOString } from "./format"; +import { from, fromNow } from "./from"; +import { getSet } from "./get-set"; +import { locale, localeData, lang } from "./locale"; +import { prototypeMin, prototypeMax } from "./min-max"; +import { startOf, endOf } from "./start-end-of"; +import { valueOf, toDate, toArray, unix } from "./to-type"; +import { isValid, parsingFlags, invalidAt } from "./valid"; + +proto.add = add; +proto.calendar = calendar; +proto.clone = clone; +proto.diff = diff; +proto.endOf = endOf; +proto.format = format; +proto.from = from; +proto.fromNow = fromNow; +proto.get = getSet; +proto.invalidAt = invalidAt; +proto.isAfter = isAfter; +proto.isBefore = isBefore; +proto.isBetween = isBetween; +proto.isSame = isSame; +proto.isValid = isValid; +proto.lang = lang; +proto.locale = locale; +proto.localeData = localeData; +proto.max = prototypeMax; +proto.min = prototypeMin; +proto.parsingFlags = parsingFlags; +proto.set = getSet; +proto.startOf = startOf; +proto.subtract = subtract; +proto.toArray = toArray; +proto.toDate = toDate; +proto.toISOString = toISOString; +proto.toJSON = toISOString; +proto.toString = toString; +proto.unix = unix; +proto.valueOf = valueOf; + +// Year +import { getSetYear, getIsLeapYear } from "../units/year"; +proto.year = getSetYear; +proto.isLeapYear = getIsLeapYear; + +// Week Year +import { getSetWeekYear, getSetISOWeekYear, getWeeksInYear, getISOWeeksInYear } from "../units/week-year"; +proto.weekYear = getSetWeekYear; +proto.isoWeekYear = getSetISOWeekYear; + +// Quarter +import { getSetQuarter } from "../units/quarter"; +proto.quarter = proto.quarters = getSetQuarter; + +// Month +import { getSetMonth, getDaysInMonth } from "../units/month"; +proto.month = getSetMonth; +proto.daysInMonth = getDaysInMonth; + +// Week +import { getSetWeek, getSetISOWeek } from "../units/week"; +proto.week = proto.weeks = getSetWeek; +proto.isoWeek = proto.isoWeeks = getSetISOWeek; +proto.weeksInYear = getWeeksInYear; +proto.isoWeeksInYear = getISOWeeksInYear; + +// Day +import { getSetDayOfMonth } from "../units/day-of-month"; +import { getSetDayOfWeek, getSetISODayOfWeek, getSetLocaleDayOfWeek } from "../units/day-of-week"; +import { getSetDayOfYear } from "../units/day-of-year"; +proto.date = getSetDayOfMonth; +proto.day = proto.days = getSetDayOfWeek; +proto.weekday = getSetLocaleDayOfWeek; +proto.isoWeekday = getSetISODayOfWeek; +proto.dayOfYear = getSetDayOfYear; + +// Hour +import { getSetHour } from "../units/hour"; +proto.hour = proto.hours = getSetHour; + +// Minute +import { getSetMinute } from "../units/minute"; +proto.minute = proto.minutes = getSetMinute; + +// Second +import { getSetSecond } from "../units/second"; +proto.second = proto.seconds = getSetSecond; + +// Millisecond +import { getSetMillisecond } from "../units/millisecond"; +proto.millisecond = proto.milliseconds = getSetMillisecond; + +// Offset +import { + getSetOffset, + setOffsetToUTC, + setOffsetToLocal, + setOffsetToParsedOffset, + hasAlignedHourOffset, + isDaylightSavingTime, + isDaylightSavingTimeShifted, + getSetZone, + isLocal, + isUtcOffset, + isUtc +} from "../units/offset"; +proto.utcOffset = getSetOffset; +proto.utc = setOffsetToUTC; +proto.local = setOffsetToLocal; +proto.parseZone = setOffsetToParsedOffset; +proto.hasAlignedHourOffset = hasAlignedHourOffset; +proto.isDST = isDaylightSavingTime; +proto.isDSTShifted = isDaylightSavingTimeShifted; +proto.isLocal = isLocal; +proto.isUtcOffset = isUtcOffset; +proto.isUtc = isUtc; +proto.isUTC = isUtc; + +// Timezone +import { getZoneAbbr, getZoneName } from "../units/timezone"; +proto.zoneAbbr = getZoneAbbr; +proto.zoneName = getZoneName; + +// Deprecations +import { deprecate } from "../utils/deprecate"; +proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); +proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); +proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); +proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone); + +export default proto; diff --git a/lib/moment/start-end-of.js b/lib/moment/start-end-of.js new file mode 100644 index 000000000..4434d6bda --- /dev/null +++ b/lib/moment/start-end-of.js @@ -0,0 +1,53 @@ +import { normalizeUnits } from "../units/aliases"; + +export 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': + 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); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; +} + +export function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); +} diff --git a/lib/moment/to-type.js b/lib/moment/to-type.js new file mode 100644 index 000000000..edc1338d1 --- /dev/null +++ b/lib/moment/to-type.js @@ -0,0 +1,16 @@ +export function valueOf () { + return +this._d - ((this._offset || 0) * 60000); +} + +export function unix () { + return Math.floor(+this / 1000); +} + +export function toDate () { + return this._offset ? new Date(+this) : this._d; +} + +export function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; +} diff --git a/lib/moment/valid.js b/lib/moment/valid.js new file mode 100644 index 000000000..afbf54be1 --- /dev/null +++ b/lib/moment/valid.js @@ -0,0 +1,14 @@ +import { isValid as _isValid } from "../create/valid"; +import extend from "../utils/extend"; + +export function isValid () { + return _isValid(this); +} + +export function parsingFlags () { + return extend({}, this._pf); +} + +export function invalidAt () { + return this._pf.overflow; +} diff --git a/lib/parse/regex.js b/lib/parse/regex.js new file mode 100644 index 000000000..ade9c99e0 --- /dev/null +++ b/lib/parse/regex.js @@ -0,0 +1,46 @@ +export var match1 = /\d/; // 0 - 9 +export var match2 = /\d\d/; // 00 - 99 +export var match3 = /\d{3}/; // 000 - 999 +export var match4 = /\d{4}/; // 0000 - 9999 +export var match6 = /[+-]?\d{6}/; // -999999 - 999999 +export var match1to2 = /\d\d?/; // 0 - 99 +export var match1to3 = /\d{1,3}/; // 0 - 999 +export var match1to4 = /\d{1,4}/; // 0 - 9999 +export var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + +export var matchUnsigned = /\d+/; // 0 - inf +export var matchSigned = /[+-]?\d+/; // -inf - inf + +export var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + +export var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + +// any word (or two) characters or numbers including two/three word month in arabic. +export var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + +import hasOwnProp from "../utils/has-own-prop"; + +var regexes = {}; + +export function addRegexToken (token, regex, strictRegex) { + regexes[token] = typeof regex === "function" ? regex : function (isStrict) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; +} + +export function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); +} + +// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript +function unescapeFormat(s) { + return s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + }).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +} + +addRegexToken("T", /T/i); // TODO: Remove this? diff --git a/lib/parse/token.js b/lib/parse/token.js new file mode 100644 index 000000000..b476cc8a2 --- /dev/null +++ b/lib/parse/token.js @@ -0,0 +1,32 @@ +import hasOwnProp from "../utils/has-own-prop"; +import toInt from "../utils/to-int"; + +var tokens = {}; + +export 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; + } +} + +export function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); +} + +export function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } +} diff --git a/lib/units/aliases.js b/lib/units/aliases.js new file mode 100644 index 000000000..b73332a03 --- /dev/null +++ b/lib/units/aliases.js @@ -0,0 +1,29 @@ +import hasOwnProp from "../utils/has-own-prop"; + +var aliases = {}; + +export function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; +} + +export function normalizeUnits(units) { + return typeof units === "string" ? aliases[units] || aliases[units.toLowerCase()] : undefined; +} + +export function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; +} diff --git a/lib/units/constants.js b/lib/units/constants.js new file mode 100644 index 000000000..588e70db5 --- /dev/null +++ b/lib/units/constants.js @@ -0,0 +1,7 @@ +export var YEAR = 0; +export var MONTH = 1; +export var DATE = 2; +export var HOUR = 3; +export var MINUTE = 4; +export var SECOND = 5; +export var MILLISECOND = 6; diff --git a/lib/units/day-of-month.js b/lib/units/day-of-month.js new file mode 100644 index 000000000..bd58d5cdc --- /dev/null +++ b/lib/units/day-of-month.js @@ -0,0 +1,31 @@ +import { makeGetSet } from "../moment/get-set"; +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match2 } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; + +// 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"], 2); // TODO: use a constant for DATE +addParseToken("Do", function (input, array) { + array[2] = toInt(input.match(match1to2)[0], 10); // TODO: use a constant for DATE +}); + +// MOMENTS + +export var getSetDayOfMonth = makeGetSet('Date', true); diff --git a/lib/units/day-of-week.js b/lib/units/day-of-week.js new file mode 100644 index 000000000..7cef2f86e --- /dev/null +++ b/lib/units/day-of-week.js @@ -0,0 +1,133 @@ +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, matchWord } from "../parse/regex"; +import { addWeekParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; +import { createLocal } from "../create/local"; + +// 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", matchWord); +addRegexToken("ddd", matchWord); +addRegexToken("dddd", matchWord); + +addWeekParseToken(["dd", "ddd", "dddd"], function (input, week, config) { + var weekday = config._locale.weekdaysParse(input); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + config._pf.invalidWeekday = input; + } +}); + +addWeekParseToken(["d", "e", "E"], function (input, week, config, token) { + week[token] = toInt(input); +}); + +// HELPERS + +function parseWeekday(input, locale) { + if (typeof input === 'string') { + if (!isNaN(input)) { + input = parseInt(input, 10); + } + else { + input = locale.weekdaysParse(input); + if (typeof input !== 'number') { + return null; + } + } + } + return input; +} + +// LOCALES + +export var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); +export function localeWeekdays (m) { + return this._weekdays[m.day()]; +} + +export var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); +export function localeWeekdaysShort (m) { + return this._weekdaysShort[m.day()]; +} + +export var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); +export function localeWeekdaysMin (m) { + return this._weekdaysMin[m.day()]; +} + +export function localeWeekdaysParse (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = createLocal([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; + } + } +} + +// MOMENTS + +export function getSetDayOfWeek (input) { + 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; + } +} + +export function getSetLocaleDayOfWeek (input) { + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); +} + +export function getSetISODayOfWeek (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); +} diff --git a/lib/units/day-of-year.js b/lib/units/day-of-year.js new file mode 100644 index 000000000..eef2d68b0 --- /dev/null +++ b/lib/units/day-of-year.js @@ -0,0 +1,49 @@ +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match3, match1to3 } from "../parse/regex"; +import { daysInYear } from "./year"; +import { createUTCDate } from "../create/date-from-array"; +import { addParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; + +// 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 + +//http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday +export function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { + var d = createUTCDate(year, 0, 1).getUTCDay(); + var daysToAdd; + var dayOfYear; + + d = d === 0 ? 7 : d; + weekday = weekday != null ? weekday : firstDayOfWeek; + daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); + dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + + return { + year : dayOfYear > 0 ? year : year - 1, + dayOfYear : dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + }; +} + +// MOMENTS + +export 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'); +} diff --git a/lib/units/hour.js b/lib/units/hour.js new file mode 100644 index 000000000..d8d7c6015 --- /dev/null +++ b/lib/units/hour.js @@ -0,0 +1,75 @@ +import { makeGetSet } from "../moment/get-set"; +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match2 } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; + +// FORMATTING + +addFormatToken("H", ["HH", 2], 0, "hour"); +addFormatToken("h", ["hh", 2], 0, function () { + return this.hours() % 12 || 12; +}); + +function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); +} + +meridiem('a', true); +meridiem('A', false); + +// ALIASES + +addUnitAlias("hour", "h"); + +// PARSING + +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); + +addParseToken(["H", "HH"], 3); // TODO: use a constant for 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[3] = toInt(input); // TODO: use a constant for HOUR + config._pf.bigHour = true; +}); + +// LOCALES + +export 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'); +} + +export var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; +export function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } +} + + +// MOMENTS + +// 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. +export var getSetHour = makeGetSet('Hours', true); diff --git a/lib/units/millisecond.js b/lib/units/millisecond.js new file mode 100644 index 000000000..2dac76916 --- /dev/null +++ b/lib/units/millisecond.js @@ -0,0 +1,41 @@ +import { makeGetSet } from "../moment/get-set"; +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1, match2, match3, match1to3, matchUnsigned } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; + +// FORMATTING + +addFormatToken("S", 0, 0, function () { + return ~~(this.millisecond() / 100); +}); + +addFormatToken(0, ["SS", 2], 0, function () { + return ~~(this.millisecond() / 10); +}); + +function milliseconds (token) { + addFormatToken(0, [token, 3], 0, "millisecond"); +} + +milliseconds("SSS"); +milliseconds("SSSS"); + +// ALIASES + +addUnitAlias("millisecond", "ms"); + +// PARSING + +addRegexToken("S", match1to3, match1); +addRegexToken("SS", match1to3, match2); +addRegexToken("SSS", match1to3, match3); +addRegexToken("SSSS", matchUnsigned); +addParseToken(["S", "SS", "SSS", "SSSS"], function (input, array) { + array[6] = toInt(('0.' + input) * 1000); // TODO: use a constant for MILLISECOND +}); + +// MOMENTS + +export var getSetMillisecond = makeGetSet('Milliseconds', false); diff --git a/lib/units/minute.js b/lib/units/minute.js new file mode 100644 index 000000000..c3b681f79 --- /dev/null +++ b/lib/units/minute.js @@ -0,0 +1,23 @@ +import { makeGetSet } from "../moment/get-set"; +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match2 } from "../parse/regex"; +import { addParseToken } from "../parse/token"; + +// FORMATTING + +addFormatToken("m", ["mm", 2], 0, "minute"); + +// ALIASES + +addUnitAlias("minute", "m"); + +// PARSING + +addRegexToken("m", match1to2); +addRegexToken("mm", match1to2, match2); +addParseToken(["m", "mm"], 4); // TODO: use a constant for MINUTE + +// MOMENTS + +export var getSetMinute = makeGetSet('Minutes', false); diff --git a/lib/units/month.js b/lib/units/month.js new file mode 100644 index 000000000..b183e875b --- /dev/null +++ b/lib/units/month.js @@ -0,0 +1,127 @@ +import { get } from "../moment/get-set"; +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match2, matchWord } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import { hooks } from "../utils/hooks"; +import toInt from "../utils/to-int"; +import { createUTC } from "../create/utc"; + +export function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); +} + +// FORMATTING + +addFormatToken("M", ["MM", 2], "Mo", function () { + return this.month() + 1; +}); + +addFormatToken("MMM", 0, 0, function (format) { + return this.localeData().monthsShort(this, format); +}); + +addFormatToken("MMMM", 0, 0, function (format) { + return this.localeData().months(this, format); +}); + +// ALIASES + +addUnitAlias("month", "M"); + +// PARSING + +addRegexToken("M", match1to2); +addRegexToken("MM", match1to2, match2); +addRegexToken("MMM", matchWord); +addRegexToken("MMMM", matchWord); + +addParseToken(["M", "MM"], function (input, array) { + array[1] = toInt(input) - 1; // TODO: use a constant for MONTH +}); + +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[1] = month; // TODO: use a constant for MONTH + } else { + config._pf.invalidMonth = input; + } +}); + +// LOCALES + +export var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); +export function localeMonths (m) { + return this._months[m.month()]; +} + +export var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); +export function localeMonthsShort (m) { + return this._monthsShort[m.month()]; +} + +export function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = 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'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // 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; + } + } +} + +// MOMENTS + +export function setMonth (mom, value) { + var dayOfMonth; + + // TODO: Move this out of here! + if (typeof value === 'string') { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; +} + +export function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, "Month"); + } +} + +export function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); +} diff --git a/lib/units/offset.js b/lib/units/offset.js new file mode 100644 index 000000000..4b720d9b6 --- /dev/null +++ b/lib/units/offset.js @@ -0,0 +1,206 @@ +import zeroFill from "../utils/zero-fill"; +import { createDuration } from "../duration/create"; +import { addSubtract } from "../moment/add-subtract"; +import { isMoment } from "../moment/constructor"; +import { addFormatToken } from "../format/format"; +import { addRegexToken, matchOffset } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import { createLocal } from "../create/local"; +import { createUTC } from "../create/utc"; +import isDate from "../utils/is-date"; +import toInt from "../utils/to-int"; +import compareArrays from "../utils/compare-arrays"; +import { hooks } from "../utils/hooks"; + +// 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", matchOffset); +addRegexToken("ZZ", matchOffset); +addParseToken(["Z", "ZZ"], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(input); +}); + +// HELPERS + +// timezone chunker +// '+10:00' > ['10', '00'] +// '-1530' > ['-15', '30'] +var chunkOffset = /([\+\-]|\d\d)/gi; + +function offsetFromString(string) { + var matches = ((string || "").match(matchOffset) || []); + 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. +export function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? +input : +createLocal(input)) - (+res); + // Use low-level api, because this fn is low-level api. + res._d.setTime(+res._d + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } + return model._isUTC ? createLocal(input).zone(model._offset || 0) : 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. +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. +export function getSetOffset (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(input); + } + 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) { + addSubtract(this, createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } +} + +export function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } +} + +export function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); +} + +export function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; +} + +export function setOffsetToParsedOffset () { + if (this._tzm) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + this.utcOffset(offsetFromString(this._i)); + } + return this; +} + +export function hasAlignedHourOffset (input) { + if (!input) { + input = 0; + } + else { + input = createLocal(input).utcOffset(); + } + + return (this.utcOffset() - input) % 60 === 0; +} + +export function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); +} + +export function isDaylightSavingTimeShifted () { + if (this._a) { + var other = this._isUTC ? createUTC(this._a) : createLocal(this._a); + return this.isValid() && compareArrays(this._a, other.toArray()) > 0; + } + + return false; +} + +export function isLocal () { + return !this._isUTC; +} + +export function isUtcOffset () { + return this._isUTC; +} + +export function isUtc () { + return this._isUTC && this._offset === 0; +} diff --git a/lib/units/quarter.js b/lib/units/quarter.js new file mode 100644 index 000000000..d7df2582c --- /dev/null +++ b/lib/units/quarter.js @@ -0,0 +1,26 @@ +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1 } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; + +// FORMATTING + +addFormatToken("Q", 0, 0, "quarter"); + +// ALIASES + +addUnitAlias("quarter", "Q"); + +// PARSING + +addRegexToken("Q", match1); +addParseToken("Q", function (input, array) { + array[1] = (toInt(input) - 1) * 3; // TODO: use a constant for MONTH +}); + +// MOMENTS + +export function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); +} diff --git a/lib/units/second.js b/lib/units/second.js new file mode 100644 index 000000000..8cab5af6d --- /dev/null +++ b/lib/units/second.js @@ -0,0 +1,23 @@ +import { makeGetSet } from "../moment/get-set"; +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match2 } from "../parse/regex"; +import { addParseToken } from "../parse/token"; + +// FORMATTING + +addFormatToken("s", ["ss", 2], 0, "second"); + +// ALIASES + +addUnitAlias("second", "s"); + +// PARSING + +addRegexToken("s", match1to2); +addRegexToken("ss", match1to2, match2); +addParseToken(["s", "ss"], 5); // TODO: use a constant for SECOND + +// MOMENTS + +export var getSetSecond = makeGetSet('Seconds', false); diff --git a/lib/units/timestamp.js b/lib/units/timestamp.js new file mode 100644 index 000000000..bbe54b115 --- /dev/null +++ b/lib/units/timestamp.js @@ -0,0 +1,20 @@ +import { addFormatToken } from "../format/format"; +import { addRegexToken, matchTimestamp, matchSigned } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; + +// 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)); +}); diff --git a/lib/units/timezone.js b/lib/units/timezone.js new file mode 100644 index 000000000..b8b3c82f9 --- /dev/null +++ b/lib/units/timezone.js @@ -0,0 +1,16 @@ +import { addFormatToken } from "../format/format"; + +// FORMATTING + +addFormatToken("z", 0, 0, "zoneAbbr"); +addFormatToken("zz", 0, 0, "zoneName"); + +// MOMENTS + +export function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; +} + +export function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; +} diff --git a/lib/units/units.js b/lib/units/units.js new file mode 100644 index 000000000..ded65f70d --- /dev/null +++ b/lib/units/units.js @@ -0,0 +1,20 @@ +// Side effect imports +import "./day-of-month"; +import "./day-of-week"; +import "./day-of-year"; +import "./hour"; +import "./millisecond"; +import "./minute"; +import "./month"; +import "./offset"; +import "./quarter"; +import "./second"; +import "./timestamp"; +import "./timezone"; +import "./week-year"; +import "./week"; +import "./year"; + +import { normalizeUnits } from "./aliases"; + +export { normalizeUnits }; diff --git a/lib/units/week-year.js b/lib/units/week-year.js new file mode 100644 index 000000000..78735210b --- /dev/null +++ b/lib/units/week-year.js @@ -0,0 +1,78 @@ +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match1to4, match1to6, match2, match4, match6, matchSigned } from "../parse/regex"; +import { addWeekParseToken } from "../parse/token"; +import { weekOfYear } from "./week"; +import toInt from "../utils/to-int"; +import { hooks } from "../utils/hooks"; +import { createLocal } from "../create/local"; + +// 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] = hooks.parseTwoDigitYear(input); +}); + +// HELPERS + +function weeksInYear(year, dow, doy) { + return weekOfYear(createLocal([year, 11, 31 + dow - doy]), dow, doy).week; +} + +// MOMENTS + +export function getSetWeekYear (input) { + var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; + return input == null ? year : this.add((input - year), 'y'); +} + +export function getSetISOWeekYear (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add((input - year), 'y'); +} + +export function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); +} + +export function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); +} diff --git a/lib/units/week.js b/lib/units/week.js new file mode 100644 index 000000000..0e4cf2543 --- /dev/null +++ b/lib/units/week.js @@ -0,0 +1,88 @@ +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match2 } from "../parse/regex"; +import { addWeekParseToken } from "../parse/token"; +import toInt from "../utils/to-int"; +import { createLocal } from "../create/local"; + +// 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 + +// 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)) +export function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = createLocal(mom).add(daysToDayOfWeek, 'd'); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; +} + +// LOCALES + +export function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; +} + +export 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. +}; + +export function localeFirstDayOfWeek () { + return this._week.dow; +} + +export function localeFirstDayOfYear () { + return this._week.doy; +} + +// MOMENTS + +export function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); +} + +export function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); +} diff --git a/lib/units/year.js b/lib/units/year.js new file mode 100644 index 000000000..8b85b86a9 --- /dev/null +++ b/lib/units/year.js @@ -0,0 +1,59 @@ +import { makeGetSet } from "../moment/get-set"; +import { addFormatToken } from "../format/format"; +import { addUnitAlias } from "./aliases"; +import { addRegexToken, match1to2, match1to4, match1to6, match2, match4, match6, matchSigned } from "../parse/regex"; +import { addParseToken } from "../parse/token"; +import { hooks } from "../utils/hooks"; +import toInt from "../utils/to-int"; + +// FORMATTING + +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"); + +// 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(["YYYY", "YYYYY", "YYYYYY"], 0); +addParseToken("YY", function (input, array) { + array[0] = hooks.parseTwoDigitYear(input); +}); + +// HELPERS + +export function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; +} + +function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; +} + +// HOOKS + +hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); +}; + +// MOMENTS + +export var getSetYear = makeGetSet('FullYear', false); + +export function getIsLeapYear () { + return isLeapYear(this.year()); +} + diff --git a/lib/utils/abs-floor.js b/lib/utils/abs-floor.js new file mode 100644 index 000000000..8ba131831 --- /dev/null +++ b/lib/utils/abs-floor.js @@ -0,0 +1,7 @@ +export default function absFloor (number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } +} diff --git a/lib/utils/compare-arrays.js b/lib/utils/compare-arrays.js new file mode 100644 index 000000000..78a60f14e --- /dev/null +++ b/lib/utils/compare-arrays.js @@ -0,0 +1,16 @@ +import toInt from "./to-int"; + +// compare two arrays, return the number of differences +export default function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; +} diff --git a/lib/utils/defaults.js b/lib/utils/defaults.js new file mode 100644 index 000000000..d69197c16 --- /dev/null +++ b/lib/utils/defaults.js @@ -0,0 +1,8 @@ +// Pick the first defined of two or three arguments. dfl comes from default. +export default function defaults(a, b, c) { + switch (arguments.length) { + case 2: return a != null ? a : b; + case 3: return a != null ? a : b != null ? b : c; + default: throw new Error('Implement me'); // TODO: Fix + } +} diff --git a/lib/utils/deprecate.js b/lib/utils/deprecate.js new file mode 100644 index 000000000..23557b0c8 --- /dev/null +++ b/lib/utils/deprecate.js @@ -0,0 +1,31 @@ +import extend from "./extend"; +import { hooks } from "./hooks"; + +function warn(msg) { + if (hooks.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) { + console.warn('Deprecation warning: ' + msg); + } +} + +export function deprecate(msg, fn) { + var firstTime = true; + return extend(function () { + if (firstTime) { + warn(msg); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); +} + +var deprecations = {}; + +export function deprecateSimple(name, msg) { + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } +} + +hooks.suppressDeprecationWarnings = false; + diff --git a/lib/utils/extend.js b/lib/utils/extend.js new file mode 100644 index 000000000..9822bef6d --- /dev/null +++ b/lib/utils/extend.js @@ -0,0 +1,19 @@ +import hasOwnProp from "./has-own-prop"; + +export default function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; +} diff --git a/lib/utils/has-own-prop.js b/lib/utils/has-own-prop.js new file mode 100644 index 000000000..4d2403ccb --- /dev/null +++ b/lib/utils/has-own-prop.js @@ -0,0 +1,3 @@ +export default function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); +} diff --git a/lib/utils/hooks.js b/lib/utils/hooks.js new file mode 100644 index 000000000..02a5bd3da --- /dev/null +++ b/lib/utils/hooks.js @@ -0,0 +1,13 @@ +export { hooks, setHookCallback }; + +var hookCallback; + +function hooks () { + return hookCallback.apply(null, arguments); +} + +// This is done to register the method called with moment() +// without creating circular dependencies. +function setHookCallback (callback) { + hookCallback = callback; +} diff --git a/lib/utils/is-array.js b/lib/utils/is-array.js new file mode 100644 index 000000000..46bd6c68b --- /dev/null +++ b/lib/utils/is-array.js @@ -0,0 +1,3 @@ +export default function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; +} diff --git a/lib/utils/is-date.js b/lib/utils/is-date.js new file mode 100644 index 000000000..561fd715f --- /dev/null +++ b/lib/utils/is-date.js @@ -0,0 +1,3 @@ +export default function isDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; +} diff --git a/lib/utils/map.js b/lib/utils/map.js new file mode 100644 index 000000000..1cbc5639f --- /dev/null +++ b/lib/utils/map.js @@ -0,0 +1,7 @@ +export default function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; +} diff --git a/lib/utils/to-int.js b/lib/utils/to-int.js new file mode 100644 index 000000000..945e0198f --- /dev/null +++ b/lib/utils/to-int.js @@ -0,0 +1,14 @@ +export default function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } + + return value; +} diff --git a/lib/utils/zero-fill.js b/lib/utils/zero-fill.js new file mode 100644 index 000000000..af45dd90e --- /dev/null +++ b/lib/utils/zero-fill.js @@ -0,0 +1,9 @@ +export default function zeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; + + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; +} diff --git a/templates/amd-named.js b/templates/amd-named.js new file mode 100644 index 000000000..761045644 --- /dev/null +++ b/templates/amd-named.js @@ -0,0 +1,7 @@ +/*global define:false*/ + +import moment from "./moment"; + +define("moment", [], function () { + return moment; +}); diff --git a/templates/amd.js b/templates/amd.js new file mode 100644 index 000000000..847bcefa8 --- /dev/null +++ b/templates/amd.js @@ -0,0 +1,7 @@ +/*global define:false*/ + +import moment from "./moment"; + +define([], function () { + return moment; +}); diff --git a/templates/globals.js b/templates/globals.js new file mode 100644 index 000000000..5ac160f12 --- /dev/null +++ b/templates/globals.js @@ -0,0 +1,5 @@ +/*global window:false*/ + +import moment from "./moment"; + +window.moment = moment;