├── .gitignore ├── package.json ├── LICENSE ├── demo ├── public │ ├── index.html │ └── moment.js └── server.js ├── index.js ├── README.md └── test └── tests.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sse-pubsub", 3 | "version": "1.4.5", 4 | "description": "Server-sent events with pub/sub interface", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/tests.mjs --exit", 8 | "start": "node demo/server.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com/triblondon/node-sse-pubsub.git" 13 | }, 14 | "keywords": [ 15 | "sse", 16 | "http", 17 | "server", 18 | "pubsub" 19 | ], 20 | "author": "Andrew Betts ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/triblondon/node-sse-pubsub/issues" 24 | }, 25 | "homepage": "https://github.com/triblondon/node-sse-pubsub#readme", 26 | "devDependencies": { 27 | "chai": "^4.3.4", 28 | "express": "^4.17.1", 29 | "mocha": "^9.1.3", 30 | "node-fetch": "^3.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrew Betts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SSE demo 6 | 7 | 8 | 31 | 32 | 33 |
34 | 35 |

SSE pubsub for nodeJS

36 | 37 |

This page connects to two SSE streams, and outputs the events to the log containers below as they are received.

38 | 39 |
40 |
41 |

Channel 1

42 |
43 |
44 |
45 |

Channel 2

46 |
47 |
48 |
49 | 50 | 51 | 64 | 65 | -------------------------------------------------------------------------------- /demo/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const SSEChannel = require('../index'); 5 | 6 | const PORT = process.env.PORT || 3101; 7 | const randomIntInRange = (low, high) => Math.floor(Math.random() * high) + low; 8 | 9 | const app = express(); 10 | 11 | // Create SSE channels. Non-default options can be passed 12 | // as an object and only affect that instance 13 | const sseChannels = { 14 | 'ch1': new SSEChannel(), 15 | 'ch2': new SSEChannel({startId: 3456, pingInterval:5000}) 16 | }; 17 | 18 | // A function to generate some random data and publish it on 19 | // the SSE channels 20 | const publishStuff = () => { 21 | const randomChannel = sseChannels['ch'+randomIntInRange(1,2)]; 22 | const dataPayload = {newVal: randomIntInRange(0,1000) }; 23 | 24 | // Push the data into the channel as an event. This will generate 25 | // an SSE event payload and emit it on all the HTTP connections 26 | // which are subscribed to this SSE channel. 27 | randomChannel.publish(dataPayload, 'myEvent'); 28 | 29 | const randomDelay = randomIntInRange(150,400); 30 | setTimeout(publishStuff, randomDelay); 31 | } 32 | 33 | // Serve the static part of the demo 34 | app.use(express.static(__dirname+'/public')); 35 | 36 | // Serve the event streams 37 | app.get('/stream/:channel', (req, res, next) => { 38 | 39 | // If the requested channel doesn't exist, skip this route 40 | if (!(req.params.channel in sseChannels)) next(); 41 | 42 | // Subscribe this request to the requested channel. The 43 | // request is now attached to the SSE channel instance and 44 | // will receive any events published to it. You don't need 45 | // to retain any reference to the request yourself. 46 | sseChannels[req.params.channel].subscribe(req, res); 47 | }); 48 | 49 | // Return a 404 if no routes match 50 | app.use((req, res, next) => { 51 | res.set('Cache-Control', 'private, no-store'); 52 | res.status(404).end('Not found'); 53 | }); 54 | 55 | app.listen(PORT, () => { 56 | console.log('Listening on port ' + PORT); 57 | publishStuff(); 58 | }); 59 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | function hasEventMatch(subscriptionList, eventName) { 3 | return !subscriptionList || subscriptionList.some(pat => pat instanceof RegExp ? pat.test(eventName) : pat === eventName); 4 | } 5 | 6 | module.exports = class SSEChannel { 7 | 8 | constructor(options) { 9 | this.options = Object.assign({}, { 10 | pingInterval: 3000, 11 | maxStreamDuration: 30000, 12 | clientRetryInterval: 1000, 13 | startId: 1, 14 | historySize: 100, 15 | rewind: 0, 16 | cors: false 17 | }, options); 18 | 19 | this.nextID = this.options.startId; 20 | this.clients = new Set(); 21 | this.messages = []; 22 | this.active = true; 23 | 24 | if (this.options.pingInterval) { 25 | this.pingTimer = setInterval(() => this.publish(), this.options.pingInterval); 26 | } 27 | } 28 | 29 | publish(data, eventName) { 30 | if (!this.active) throw new Error('Channel closed'); 31 | let output; 32 | let id; 33 | if (!data && !eventName) { 34 | if (!this.clients.size) return; // No need to create a ping entry if there are no clients connected 35 | output = "data: \n\n"; 36 | } else { 37 | id = this.nextID++; 38 | if (typeof data === "object") data = JSON.stringify(data); 39 | data = data ? data.split(/[\r\n]+/).map(str => 'data: '+str).join('\n') : ''; 40 | output = ( 41 | "id: " + id + "\n" + 42 | (eventName ? "event: " + eventName + "\n" : "") + 43 | (data || "data: ") + '\n\n' 44 | ); 45 | this.messages.push({ id, eventName, output }); 46 | } 47 | 48 | [...this.clients].filter(c => !eventName || hasEventMatch(c.events, eventName)).forEach(c => c.res.write(output)); 49 | 50 | while (this.messages.length > this.options.historySize) { 51 | this.messages.shift(); 52 | } 53 | 54 | return id; 55 | } 56 | 57 | subscribe(req, res, events) { 58 | if (!this.active) throw new Error('Channel closed'); 59 | const c = {req, res, events}; 60 | const headers = { 61 | "Content-Type": "text/event-stream", 62 | "Cache-Control": "s-maxage="+(Math.floor(this.options.maxStreamDuration/1000)-1)+", max-age=0, stale-while-revalidate=0, stale-if-error=0, no-transform", 63 | "Connection": "keep-alive" 64 | } 65 | if (this.options.cors) { 66 | headers['access-control-allow-origin'] = "*"; 67 | } 68 | c.req.socket.setNoDelay(true); 69 | c.res.writeHead(200, headers); 70 | let body = "retry: " + this.options.clientRetryInterval + '\n\n'; 71 | 72 | const lastID = Number.parseInt(req.headers['last-event-id'], 10); 73 | const rewind = (!Number.isNaN(lastID)) ? ((this.nextID-1)-lastID) : this.options.rewind; 74 | if (rewind) { 75 | this.messages.filter(m => hasEventMatch(c.events, m.eventName)).slice(0-rewind).forEach(m => { 76 | body += m.output 77 | }); 78 | } 79 | 80 | c.res.write(body); 81 | this.clients.add(c); 82 | 83 | setTimeout(() => { 84 | if (!c.res.finished) { 85 | this.unsubscribe(c); 86 | } 87 | }, this.options.maxStreamDuration); 88 | c.res.on('close', () => this.unsubscribe(c)); 89 | return c; 90 | } 91 | 92 | unsubscribe(c) { 93 | c.res.end(); 94 | this.clients.delete(c); 95 | } 96 | 97 | close() { 98 | this.clients.forEach(c => c.res.end()); 99 | this.clients = new Set(); 100 | this.messages = []; 101 | if (this.pingTimer) clearInterval(this.pingTimer); 102 | this.active = false; 103 | } 104 | 105 | listClients() { 106 | const rollupByIP = {}; 107 | this.clients.forEach(c => { 108 | const ip = c.req.connection.remoteAddress; 109 | if (!(ip in rollupByIP)) { 110 | rollupByIP[ip] = 0; 111 | } 112 | rollupByIP[ip]++; 113 | }); 114 | return rollupByIP; 115 | } 116 | 117 | getSubscriberCount() { 118 | return this.clients.size; 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Server-sent events for NodeJS 2 | 3 | A simple NodeJS module to generate Server-Sent-Events streams with a publish/subscribe interface and simple integration with either Node's built in HTTP library or any framework that exposes it, eg. [ExpressJS](https://expressjs.com). 4 | 5 | ## Usage 6 | 7 | Install with `npm i sse-pubsub`, then, if you are using Express: 8 | 9 | ```javascript 10 | const express = require('express'); 11 | const SSEChannel = require('sse-pubsub'); 12 | 13 | const app = express(); 14 | const channel = new SSEChannel(); 15 | 16 | // Say hello every second 17 | setInterval(() => channel.publish("Hello everyone!", 'myEvent'), 1000); 18 | 19 | app.get('/stream', (req, res) => channel.subscribe(req, res)); 20 | 21 | app.listen(3000); 22 | ``` 23 | 24 | You should now be able to visit `http://localhost:3000/stream` and see a sequence of 'Hello everyone!' events appearing once per second. 25 | 26 | To subscribe to the stream from client-side JavaScript: 27 | 28 | ```javascript 29 | const es = new EventSource("/stream"); 30 | es.addEventListener('myEvent', ev => { 31 | alert(ev.data); 32 | }); 33 | ``` 34 | 35 | A more detailed demo lives in `/demo/` and can be run with `npm start` if the module is checked out for development (see [development](#development)). 36 | 37 | ## API 38 | 39 | ### `SSEChannel(options)` (constructor) 40 | 41 | Creates a new `SSEChannel`. Available options are: 42 | 43 | * `pingInterval` (integer): Interval at which to send pings (no-op events). Milliseconds, default is 3000, set to a falsy value to disable pings entirely. 44 | * `maxStreamDuration` (integer): Maximum amoiunt of time to allow a client to remain connected before closing the connection. SSE streams are expected to disconnect regularly to avoid zombie clients and problems with misbehaving middleboxes and proxies. Milliseconds, default is 30000. 45 | * `clientRetryInterval` (integer): Amount of time clients should wait before reconencting, if they become disconnected from the stream. Milliseconds, default is 1000. 46 | * `startId` (integer): ID to use for the first message on the channel. Default is 1. 47 | * `historySize` (integer): Number of messages to keep in memory, allowing for clients to use the `Last-Event-ID` header to request events that occured before they joined the stream. Default is 100. 48 | * `rewind` (integer): Number of messages to backtrack by default when serving a new subscriber that does not include a `Last-Event-ID` in their request. If request includes `Last-Event-ID`, the `rewind` option is ignored. 49 | * `cors` (boolean): Set to `true` to include an `Access-Control-Allow-Origin: *` header on the response. Default false. 50 | 51 | ```javascript 52 | const channel = new SSEChannel({ 53 | pingInterval: 10000, 54 | startId: 1330 55 | }); 56 | ``` 57 | 58 | ### `subscribe(req, res, [events])` 59 | 60 | Attaches an inbound HTTP request to an SSE channel. Usually used in conjuction with a framework like Express. Returns a reference for the client. 61 | 62 | * `req`: A NodeJS `IncomingMessage` object or anything that inherits from it 63 | * `res`: A NodeJS `ServerResponse` object or anything that inherits from it 64 | * `events`: Optional, an array of event names to deliver to this subscriber. If not provided, the subscriber will receive all events. 65 | 66 | ```javascript 67 | const channel = new SSEChannel(); 68 | app.get('/stream', (req, res) => channel.subscribe(req, res)); 69 | ``` 70 | 71 | ### `publish(data, [eventName])` 72 | 73 | Publishes a new event to all subscribers to the channel. 74 | 75 | * `data` (any): Message to send. If a primitive value, will be sent as-is, otherwise will be passed through `JSON.stringify`. 76 | * `eventName` (string): Event name to assign to the event. 77 | 78 | Since all events published to a channel will be sent to all subscribers to that channel, if you want to filter events that are of interest to only a subset of users, it makes sense to use multiple separate channel instances. However, `eventName` is useful if you are sending events that are all relevant to all subscribers, but might need to be processed by different client-side handlers. Event names can therefore be considered to be like method names of a method that you are invoking in the client-side code. 79 | 80 | Returns the ID of the new message. 81 | 82 | ### `unsubscribe(clientRef)` 83 | 84 | Detaches an active HTTP connection from the channel. 85 | 86 | * `clientRef` (object): A reference to an object obtained by calling the `subscribe` method. 87 | 88 | ### `close()` 89 | 90 | Closes all subscriber connections, deletes message history and stops ping timer. If you intend to create temporary channels, ensure you close them before dereferencing, otherwise timers and HTTP clients may cause memory leaks. 91 | 92 | ### `listClients()` 93 | 94 | Returns an object where the keys are the remote addresses of each connected client, and the values are the number of connections from that address. 95 | 96 | ```javascript 97 | console.log(channel.listClients()); 98 | // { 99 | // '::ffff:127.0.0.1': 2 100 | // } 101 | ``` 102 | 103 | ### `getSubscriberCount()` 104 | 105 | Returns the number of currently connected clients. 106 | 107 | ## Development 108 | 109 | To develop on the project, clone the repo and run `npm install`. Then if you want to run the demo server: 110 | 111 | 1. `npm start` 112 | 2. Open `http://127.0.0.1:3101` in your browser 113 | 114 | To run the tests, `npm test`. The project uses Express for the demo server and Mocha and Chai for the tests, however none of these are needed in production. The tests use Node's raw http library rather than express to ensure there is not any accidental dependence on request or response properties added by Express. 115 | 116 | ## Licence 117 | 118 | MIT. 119 | -------------------------------------------------------------------------------- /test/tests.mjs: -------------------------------------------------------------------------------- 1 | 2 | import { expect} from 'chai'; 3 | 4 | import fetch from 'node-fetch'; 5 | import http from 'http'; 6 | import SSEChannel from '../index.js'; 7 | 8 | const PORT = process.env.PORT || 3102; 9 | const URL = "http://localhost:"+PORT+"/stream"; 10 | 11 | const setupServer = (() => { 12 | let app, server, sockets; 13 | return async (sseOptions, subsOptions) => { 14 | return new Promise(resolve => { 15 | if (server) { 16 | sockets.forEach(s => s.destroy()); 17 | server.close(() => { 18 | server = null; 19 | resolve(setupServer(sseOptions, subsOptions)); 20 | }); 21 | } else { 22 | const sse = new SSEChannel(sseOptions); 23 | sockets = []; 24 | server = http.createServer((req, res) => { 25 | if (req.url == '/stream') sse.subscribe(req, res, subsOptions); 26 | }); 27 | server.listen(PORT, () => resolve(sse)); 28 | server.on('connection', s => sockets.push(s)); 29 | } 30 | }); 31 | }; 32 | })(); 33 | 34 | const nextChunk = async body => { 35 | return new Promise(resolve => { 36 | body.on('data', chunk => resolve(chunk.toString())); 37 | }); 38 | }; 39 | 40 | const measureTime = async pr => { 41 | const start = Date.now(); 42 | await pr; 43 | const end = Date.now(); 44 | return end - start; 45 | }; 46 | 47 | 48 | describe('SSEChannel', function () { 49 | 50 | it('should return 200 OK status', async function () { 51 | const sse = await setupServer(); 52 | let res = await fetch(URL); 53 | expect(res.status).to.equal(200); 54 | sse.close(); 55 | }); 56 | 57 | it('should have correct content-type', async function () { 58 | const sse = await setupServer(); 59 | let res = await fetch(URL); 60 | expect(res.headers.get('content-type')).to.equal('text/event-stream'); 61 | sse.close(); 62 | }); 63 | 64 | it('should be server-cachable for the duration of the stream', async function () { 65 | const sse = await setupServer({maxStreamDuration:20000}); 66 | let res = await fetch(URL); 67 | expect(res.headers.get('cache-control')).to.contain('s-maxage=19'); 68 | sse.close(); 69 | }); 70 | 71 | it('should not be client-cachable', async function () { 72 | const sse = await setupServer({maxStreamDuration:20000}); 73 | let res = await fetch(URL); 74 | expect(res.headers.get('cache-control')).to.contain('max-age=0'); 75 | sse.close(); 76 | }); 77 | 78 | it('should include retry in first response chunk', async function () { 79 | let sse, res, chunk; 80 | sse = await setupServer(); 81 | res = await fetch(URL); 82 | chunk = await nextChunk(res.body); 83 | expect(chunk).to.match(/retry:\s*\d{4,6}\n/); 84 | sse.close(); 85 | 86 | sse = await setupServer({clientRetryInterval:1234}); 87 | res = await fetch(URL); 88 | chunk = await nextChunk(res.body); 89 | expect(chunk).to.match(/retry:\s*1234\n/); 90 | sse.close(); 91 | }); 92 | 93 | it('should close the connection at maxStreamDuration', async function () { 94 | const sse = await setupServer({maxStreamDuration:1500}); 95 | let elapsed = await measureTime(fetch(URL).then(res => res.text())); 96 | expect(elapsed).to.be.approximately(1500, 30); 97 | sse.close(); 98 | }); 99 | 100 | it('should return an ID from publish', async function () { 101 | let sse, res, output, firstID, secondID, matches; 102 | sse = await setupServer(); 103 | res = await fetch(URL); 104 | await nextChunk(res.body); 105 | firstID = sse.publish('first'); 106 | output = await nextChunk(res.body); 107 | expect((new RegExp(`id:\\s*${firstID}\\b`)).test(output)).to.be.true; 108 | secondID = sse.publish('second'); 109 | output = await nextChunk(res.body); 110 | expect((new RegExp(`id:\\s*${secondID}\\b`)).test(output)).to.be.true; 111 | expect(secondID).to.equal(firstID+1); 112 | sse.close(); 113 | }); 114 | 115 | it('should start at startId', async function () { 116 | let sse, res, output; 117 | sse = await setupServer(); 118 | res = await fetch(URL); 119 | await nextChunk(res.body); 120 | sse.publish('something'); 121 | output = await nextChunk(res.body); 122 | expect(output).to.match(/id:\s*1\n/); 123 | expect(output).to.match(/data:\s*something\n/); 124 | sse.publish('something else'); 125 | output = await nextChunk(res.body); 126 | expect(output).to.match(/id:\s*2\n/); 127 | expect(output).to.match(/data:\s*something else\n/); 128 | sse.close(); 129 | 130 | sse = await setupServer({startId: 8777}); 131 | res = await fetch(URL); 132 | await nextChunk(res.body); 133 | sse.publish('something'); 134 | output = await nextChunk(res.body); 135 | expect(output).to.match(/id:\s*8777\n/); 136 | expect(output).to.match(/data:\s*something\n/); 137 | sse.close(); 138 | }); 139 | 140 | it('should ping at pingInterval', async function () { 141 | const sse = await setupServer({pingInterval:500}); 142 | let res = await fetch(URL); 143 | await nextChunk(res.body) 144 | let chunkPromise = nextChunk(res.body); 145 | let elapsed = await measureTime(chunkPromise); 146 | let output = await chunkPromise; 147 | expect(elapsed).to.be.approximately(500, 30); 148 | expect(output).to.match(/data:\s*\n\n/); 149 | expect(output).to.not.contain('event:'); 150 | expect(output).to.not.contain('id:'); 151 | sse.close(); 152 | }); 153 | 154 | it('should output a retry number', async function () { 155 | const sse = await setupServer({clientRetryInterval:4321}); 156 | let res = await fetch(URL); 157 | let chunk = await nextChunk(res.body); 158 | expect(chunk).to.match(/retry:\s*4321\n\n/); 159 | sse.close(); 160 | }); 161 | 162 | it('should include event name if specified', async function () { 163 | let chunk; 164 | let sse = await setupServer(); 165 | let res = await fetch(URL); 166 | await nextChunk(res.body) 167 | sse.publish('no-event-name'); 168 | chunk = await nextChunk(res.body); 169 | expect(chunk).to.not.contain('event:'); 170 | sse.publish('with-event-name', 'myEvent'); 171 | chunk = await nextChunk(res.body); 172 | expect(chunk).to.match(/event:\s*myEvent\n/); 173 | sse.close(); 174 | }); 175 | 176 | it('should include only events the subscriber has specified', async function () { 177 | let chunk; 178 | let sse = await setupServer({ pingInterval: 500 }, ['event-1']); 179 | let res = await fetch(URL); 180 | await nextChunk(res.body) 181 | sse.publish('foo', 'event-1'); 182 | sse.publish('bar', 'event-2'); 183 | chunk = await nextChunk(res.body); 184 | expect(chunk).to.contain('event-1'); 185 | expect(chunk).to.not.contain('event-2'); 186 | sse.publish('baz', 'event-3'); 187 | chunk = await nextChunk(res.body); 188 | expect(chunk).to.not.contain('event-3'); 189 | sse.close(); 190 | }); 191 | 192 | it('should include previous events if last-event-id is specified', async function () { 193 | let sse = await setupServer(); 194 | for (let i=1; i<=10; i++) { 195 | sse.publish('event-'+i); 196 | } 197 | let res = await fetch(URL, {headers: {'Last-Event-ID': 4}}); 198 | let output = await nextChunk(res.body); 199 | let events = output.split('\n\n').filter(str => str.includes('id:')); 200 | expect(events.length).to.equal(6); 201 | expect(events[0]).to.match(/id:\s*5\n/); 202 | sse.close(); 203 | }); 204 | 205 | it('should limit history length to historySize', async function () { 206 | let sse = await setupServer({historySize:5}); 207 | for (let i=1; i<=10; i++) { 208 | sse.publish('event-'+i); 209 | } 210 | let res = await fetch(URL, {headers: {'Last-Event-ID': 2}}); 211 | let output = await nextChunk(res.body); 212 | let events = output.split('\n\n').filter(str => str.includes('id:')); 213 | expect(events.length).to.equal(5); 214 | expect(events[0]).to.match(/id:\s*6\n/); 215 | sse.close(); 216 | }); 217 | 218 | it('should serialise non scalar values as JSON', async function () { 219 | let testdata = {foo:42}; 220 | let sse = await setupServer(); 221 | let res = await fetch(URL); 222 | await nextChunk(res.body); 223 | sse.publish(testdata); 224 | let chunk = await nextChunk(res.body); 225 | expect(chunk).to.contain(JSON.stringify(testdata)); 226 | sse.close(); 227 | }); 228 | 229 | it('should list connected clients by IP address', async function () { 230 | let sse = await setupServer(); 231 | let res1 = await fetch(URL); 232 | let res2 = await fetch(URL); 233 | expect(sse.listClients()).to.eql({ 234 | '::ffff:127.0.0.1': 2 235 | }); 236 | sse.close(); 237 | }); 238 | 239 | it('should count connected clients', async function () { 240 | let sse = await setupServer(); 241 | let res1 = await fetch(URL); 242 | let res2 = await fetch(URL); 243 | expect(sse.getSubscriberCount()).to.equal(2); 244 | sse.close(); 245 | }); 246 | 247 | it('should understand the rewind option', async function () { 248 | let sse, res, output; 249 | sse = await setupServer({rewind: 2}); 250 | sse.publish('message-1'); 251 | sse.publish('message-2'); 252 | sse.publish('message-3'); 253 | res = await fetch(URL); 254 | output = await nextChunk(res.body); 255 | expect(output).to.not.include('message-1'); 256 | expect(output).to.include('message-2'); 257 | expect(output).to.include('message-3'); 258 | sse.close(); 259 | 260 | sse = await setupServer({rewind: 6}); 261 | sse.publish('message-1'); 262 | sse.publish('message-2'); 263 | sse.publish('message-3'); 264 | res = await fetch(URL); 265 | output = await nextChunk(res.body); 266 | expect(output).to.include('message-1'); 267 | expect(output).to.include('message-2'); 268 | expect(output).to.include('message-3'); 269 | sse.close(); 270 | }); 271 | 272 | it('should stop pinging and hang up when the stream is closed', async function () { 273 | let sse, res; 274 | let hasEnded = false; 275 | sse = await setupServer(); 276 | res = await fetch(URL); 277 | sse.close(); 278 | let respEndPromise = res.text(); 279 | let elapsed = await measureTime(respEndPromise); 280 | expect(elapsed).to.be.lessThan(100, 30); 281 | }); 282 | 283 | }); 284 | -------------------------------------------------------------------------------- /demo/public/moment.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.18.1 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | 7 | ;(function (global, factory) { 8 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 9 | typeof define === 'function' && define.amd ? define(factory) : 10 | global.moment = factory() 11 | }(this, (function () { 'use strict'; 12 | 13 | var hookCallback; 14 | 15 | function hooks () { 16 | return hookCallback.apply(null, arguments); 17 | } 18 | 19 | // This is done to register the method called with moment() 20 | // without creating circular dependencies. 21 | function setHookCallback (callback) { 22 | hookCallback = callback; 23 | } 24 | 25 | function isArray(input) { 26 | return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; 27 | } 28 | 29 | function isObject(input) { 30 | // IE8 will treat undefined and null as object if it wasn't for 31 | // input != null 32 | return input != null && Object.prototype.toString.call(input) === '[object Object]'; 33 | } 34 | 35 | function isObjectEmpty(obj) { 36 | var k; 37 | for (k in obj) { 38 | // even if its not own property I'd still call it non-empty 39 | return false; 40 | } 41 | return true; 42 | } 43 | 44 | function isUndefined(input) { 45 | return input === void 0; 46 | } 47 | 48 | function isNumber(input) { 49 | return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; 50 | } 51 | 52 | function isDate(input) { 53 | return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; 54 | } 55 | 56 | function map(arr, fn) { 57 | var res = [], i; 58 | for (i = 0; i < arr.length; ++i) { 59 | res.push(fn(arr[i], i)); 60 | } 61 | return res; 62 | } 63 | 64 | function hasOwnProp(a, b) { 65 | return Object.prototype.hasOwnProperty.call(a, b); 66 | } 67 | 68 | function extend(a, b) { 69 | for (var i in b) { 70 | if (hasOwnProp(b, i)) { 71 | a[i] = b[i]; 72 | } 73 | } 74 | 75 | if (hasOwnProp(b, 'toString')) { 76 | a.toString = b.toString; 77 | } 78 | 79 | if (hasOwnProp(b, 'valueOf')) { 80 | a.valueOf = b.valueOf; 81 | } 82 | 83 | return a; 84 | } 85 | 86 | function createUTC (input, format, locale, strict) { 87 | return createLocalOrUTC(input, format, locale, strict, true).utc(); 88 | } 89 | 90 | function defaultParsingFlags() { 91 | // We need to deep clone this object. 92 | return { 93 | empty : false, 94 | unusedTokens : [], 95 | unusedInput : [], 96 | overflow : -2, 97 | charsLeftOver : 0, 98 | nullInput : false, 99 | invalidMonth : null, 100 | invalidFormat : false, 101 | userInvalidated : false, 102 | iso : false, 103 | parsedDateParts : [], 104 | meridiem : null, 105 | rfc2822 : false, 106 | weekdayMismatch : false 107 | }; 108 | } 109 | 110 | function getParsingFlags(m) { 111 | if (m._pf == null) { 112 | m._pf = defaultParsingFlags(); 113 | } 114 | return m._pf; 115 | } 116 | 117 | var some; 118 | if (Array.prototype.some) { 119 | some = Array.prototype.some; 120 | } else { 121 | some = function (fun) { 122 | var t = Object(this); 123 | var len = t.length >>> 0; 124 | 125 | for (var i = 0; i < len; i++) { 126 | if (i in t && fun.call(this, t[i], i, t)) { 127 | return true; 128 | } 129 | } 130 | 131 | return false; 132 | }; 133 | } 134 | 135 | var some$1 = some; 136 | 137 | function isValid(m) { 138 | if (m._isValid == null) { 139 | var flags = getParsingFlags(m); 140 | var parsedParts = some$1.call(flags.parsedDateParts, function (i) { 141 | return i != null; 142 | }); 143 | var isNowValid = !isNaN(m._d.getTime()) && 144 | flags.overflow < 0 && 145 | !flags.empty && 146 | !flags.invalidMonth && 147 | !flags.invalidWeekday && 148 | !flags.nullInput && 149 | !flags.invalidFormat && 150 | !flags.userInvalidated && 151 | (!flags.meridiem || (flags.meridiem && parsedParts)); 152 | 153 | if (m._strict) { 154 | isNowValid = isNowValid && 155 | flags.charsLeftOver === 0 && 156 | flags.unusedTokens.length === 0 && 157 | flags.bigHour === undefined; 158 | } 159 | 160 | if (Object.isFrozen == null || !Object.isFrozen(m)) { 161 | m._isValid = isNowValid; 162 | } 163 | else { 164 | return isNowValid; 165 | } 166 | } 167 | return m._isValid; 168 | } 169 | 170 | function createInvalid (flags) { 171 | var m = createUTC(NaN); 172 | if (flags != null) { 173 | extend(getParsingFlags(m), flags); 174 | } 175 | else { 176 | getParsingFlags(m).userInvalidated = true; 177 | } 178 | 179 | return m; 180 | } 181 | 182 | // Plugins that add properties should also add the key here (null value), 183 | // so we can properly clone ourselves. 184 | var momentProperties = hooks.momentProperties = []; 185 | 186 | function copyConfig(to, from) { 187 | var i, prop, val; 188 | 189 | if (!isUndefined(from._isAMomentObject)) { 190 | to._isAMomentObject = from._isAMomentObject; 191 | } 192 | if (!isUndefined(from._i)) { 193 | to._i = from._i; 194 | } 195 | if (!isUndefined(from._f)) { 196 | to._f = from._f; 197 | } 198 | if (!isUndefined(from._l)) { 199 | to._l = from._l; 200 | } 201 | if (!isUndefined(from._strict)) { 202 | to._strict = from._strict; 203 | } 204 | if (!isUndefined(from._tzm)) { 205 | to._tzm = from._tzm; 206 | } 207 | if (!isUndefined(from._isUTC)) { 208 | to._isUTC = from._isUTC; 209 | } 210 | if (!isUndefined(from._offset)) { 211 | to._offset = from._offset; 212 | } 213 | if (!isUndefined(from._pf)) { 214 | to._pf = getParsingFlags(from); 215 | } 216 | if (!isUndefined(from._locale)) { 217 | to._locale = from._locale; 218 | } 219 | 220 | if (momentProperties.length > 0) { 221 | for (i = 0; i < momentProperties.length; i++) { 222 | prop = momentProperties[i]; 223 | val = from[prop]; 224 | if (!isUndefined(val)) { 225 | to[prop] = val; 226 | } 227 | } 228 | } 229 | 230 | return to; 231 | } 232 | 233 | var updateInProgress = false; 234 | 235 | // Moment prototype object 236 | function Moment(config) { 237 | copyConfig(this, config); 238 | this._d = new Date(config._d != null ? config._d.getTime() : NaN); 239 | if (!this.isValid()) { 240 | this._d = new Date(NaN); 241 | } 242 | // Prevent infinite loop in case updateOffset creates new moment 243 | // objects. 244 | if (updateInProgress === false) { 245 | updateInProgress = true; 246 | hooks.updateOffset(this); 247 | updateInProgress = false; 248 | } 249 | } 250 | 251 | function isMoment (obj) { 252 | return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); 253 | } 254 | 255 | function absFloor (number) { 256 | if (number < 0) { 257 | // -0 -> 0 258 | return Math.ceil(number) || 0; 259 | } else { 260 | return Math.floor(number); 261 | } 262 | } 263 | 264 | function toInt(argumentForCoercion) { 265 | var coercedNumber = +argumentForCoercion, 266 | value = 0; 267 | 268 | if (coercedNumber !== 0 && isFinite(coercedNumber)) { 269 | value = absFloor(coercedNumber); 270 | } 271 | 272 | return value; 273 | } 274 | 275 | // compare two arrays, return the number of differences 276 | function compareArrays(array1, array2, dontConvert) { 277 | var len = Math.min(array1.length, array2.length), 278 | lengthDiff = Math.abs(array1.length - array2.length), 279 | diffs = 0, 280 | i; 281 | for (i = 0; i < len; i++) { 282 | if ((dontConvert && array1[i] !== array2[i]) || 283 | (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { 284 | diffs++; 285 | } 286 | } 287 | return diffs + lengthDiff; 288 | } 289 | 290 | function warn(msg) { 291 | if (hooks.suppressDeprecationWarnings === false && 292 | (typeof console !== 'undefined') && console.warn) { 293 | console.warn('Deprecation warning: ' + msg); 294 | } 295 | } 296 | 297 | function deprecate(msg, fn) { 298 | var firstTime = true; 299 | 300 | return extend(function () { 301 | if (hooks.deprecationHandler != null) { 302 | hooks.deprecationHandler(null, msg); 303 | } 304 | if (firstTime) { 305 | var args = []; 306 | var arg; 307 | for (var i = 0; i < arguments.length; i++) { 308 | arg = ''; 309 | if (typeof arguments[i] === 'object') { 310 | arg += '\n[' + i + '] '; 311 | for (var key in arguments[0]) { 312 | arg += key + ': ' + arguments[0][key] + ', '; 313 | } 314 | arg = arg.slice(0, -2); // Remove trailing comma and space 315 | } else { 316 | arg = arguments[i]; 317 | } 318 | args.push(arg); 319 | } 320 | warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); 321 | firstTime = false; 322 | } 323 | return fn.apply(this, arguments); 324 | }, fn); 325 | } 326 | 327 | var deprecations = {}; 328 | 329 | function deprecateSimple(name, msg) { 330 | if (hooks.deprecationHandler != null) { 331 | hooks.deprecationHandler(name, msg); 332 | } 333 | if (!deprecations[name]) { 334 | warn(msg); 335 | deprecations[name] = true; 336 | } 337 | } 338 | 339 | hooks.suppressDeprecationWarnings = false; 340 | hooks.deprecationHandler = null; 341 | 342 | function isFunction(input) { 343 | return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; 344 | } 345 | 346 | function set (config) { 347 | var prop, i; 348 | for (i in config) { 349 | prop = config[i]; 350 | if (isFunction(prop)) { 351 | this[i] = prop; 352 | } else { 353 | this['_' + i] = prop; 354 | } 355 | } 356 | this._config = config; 357 | // Lenient ordinal parsing accepts just a number in addition to 358 | // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. 359 | // TODO: Remove "ordinalParse" fallback in next major release. 360 | this._dayOfMonthOrdinalParseLenient = new RegExp( 361 | (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + 362 | '|' + (/\d{1,2}/).source); 363 | } 364 | 365 | function mergeConfigs(parentConfig, childConfig) { 366 | var res = extend({}, parentConfig), prop; 367 | for (prop in childConfig) { 368 | if (hasOwnProp(childConfig, prop)) { 369 | if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { 370 | res[prop] = {}; 371 | extend(res[prop], parentConfig[prop]); 372 | extend(res[prop], childConfig[prop]); 373 | } else if (childConfig[prop] != null) { 374 | res[prop] = childConfig[prop]; 375 | } else { 376 | delete res[prop]; 377 | } 378 | } 379 | } 380 | for (prop in parentConfig) { 381 | if (hasOwnProp(parentConfig, prop) && 382 | !hasOwnProp(childConfig, prop) && 383 | isObject(parentConfig[prop])) { 384 | // make sure changes to properties don't modify parent config 385 | res[prop] = extend({}, res[prop]); 386 | } 387 | } 388 | return res; 389 | } 390 | 391 | function Locale(config) { 392 | if (config != null) { 393 | this.set(config); 394 | } 395 | } 396 | 397 | var keys; 398 | 399 | if (Object.keys) { 400 | keys = Object.keys; 401 | } else { 402 | keys = function (obj) { 403 | var i, res = []; 404 | for (i in obj) { 405 | if (hasOwnProp(obj, i)) { 406 | res.push(i); 407 | } 408 | } 409 | return res; 410 | }; 411 | } 412 | 413 | var keys$1 = keys; 414 | 415 | var defaultCalendar = { 416 | sameDay : '[Today at] LT', 417 | nextDay : '[Tomorrow at] LT', 418 | nextWeek : 'dddd [at] LT', 419 | lastDay : '[Yesterday at] LT', 420 | lastWeek : '[Last] dddd [at] LT', 421 | sameElse : 'L' 422 | }; 423 | 424 | function calendar (key, mom, now) { 425 | var output = this._calendar[key] || this._calendar['sameElse']; 426 | return isFunction(output) ? output.call(mom, now) : output; 427 | } 428 | 429 | var defaultLongDateFormat = { 430 | LTS : 'h:mm:ss A', 431 | LT : 'h:mm A', 432 | L : 'MM/DD/YYYY', 433 | LL : 'MMMM D, YYYY', 434 | LLL : 'MMMM D, YYYY h:mm A', 435 | LLLL : 'dddd, MMMM D, YYYY h:mm A' 436 | }; 437 | 438 | function longDateFormat (key) { 439 | var format = this._longDateFormat[key], 440 | formatUpper = this._longDateFormat[key.toUpperCase()]; 441 | 442 | if (format || !formatUpper) { 443 | return format; 444 | } 445 | 446 | this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { 447 | return val.slice(1); 448 | }); 449 | 450 | return this._longDateFormat[key]; 451 | } 452 | 453 | var defaultInvalidDate = 'Invalid date'; 454 | 455 | function invalidDate () { 456 | return this._invalidDate; 457 | } 458 | 459 | var defaultOrdinal = '%d'; 460 | var defaultDayOfMonthOrdinalParse = /\d{1,2}/; 461 | 462 | function ordinal (number) { 463 | return this._ordinal.replace('%d', number); 464 | } 465 | 466 | var defaultRelativeTime = { 467 | future : 'in %s', 468 | past : '%s ago', 469 | s : 'a few seconds', 470 | ss : '%d seconds', 471 | m : 'a minute', 472 | mm : '%d minutes', 473 | h : 'an hour', 474 | hh : '%d hours', 475 | d : 'a day', 476 | dd : '%d days', 477 | M : 'a month', 478 | MM : '%d months', 479 | y : 'a year', 480 | yy : '%d years' 481 | }; 482 | 483 | function relativeTime (number, withoutSuffix, string, isFuture) { 484 | var output = this._relativeTime[string]; 485 | return (isFunction(output)) ? 486 | output(number, withoutSuffix, string, isFuture) : 487 | output.replace(/%d/i, number); 488 | } 489 | 490 | function pastFuture (diff, output) { 491 | var format = this._relativeTime[diff > 0 ? 'future' : 'past']; 492 | return isFunction(format) ? format(output) : format.replace(/%s/i, output); 493 | } 494 | 495 | var aliases = {}; 496 | 497 | function addUnitAlias (unit, shorthand) { 498 | var lowerCase = unit.toLowerCase(); 499 | aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; 500 | } 501 | 502 | function normalizeUnits(units) { 503 | return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; 504 | } 505 | 506 | function normalizeObjectUnits(inputObject) { 507 | var normalizedInput = {}, 508 | normalizedProp, 509 | prop; 510 | 511 | for (prop in inputObject) { 512 | if (hasOwnProp(inputObject, prop)) { 513 | normalizedProp = normalizeUnits(prop); 514 | if (normalizedProp) { 515 | normalizedInput[normalizedProp] = inputObject[prop]; 516 | } 517 | } 518 | } 519 | 520 | return normalizedInput; 521 | } 522 | 523 | var priorities = {}; 524 | 525 | function addUnitPriority(unit, priority) { 526 | priorities[unit] = priority; 527 | } 528 | 529 | function getPrioritizedUnits(unitsObj) { 530 | var units = []; 531 | for (var u in unitsObj) { 532 | units.push({unit: u, priority: priorities[u]}); 533 | } 534 | units.sort(function (a, b) { 535 | return a.priority - b.priority; 536 | }); 537 | return units; 538 | } 539 | 540 | function makeGetSet (unit, keepTime) { 541 | return function (value) { 542 | if (value != null) { 543 | set$1(this, unit, value); 544 | hooks.updateOffset(this, keepTime); 545 | return this; 546 | } else { 547 | return get(this, unit); 548 | } 549 | }; 550 | } 551 | 552 | function get (mom, unit) { 553 | return mom.isValid() ? 554 | mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; 555 | } 556 | 557 | function set$1 (mom, unit, value) { 558 | if (mom.isValid()) { 559 | mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); 560 | } 561 | } 562 | 563 | // MOMENTS 564 | 565 | function stringGet (units) { 566 | units = normalizeUnits(units); 567 | if (isFunction(this[units])) { 568 | return this[units](); 569 | } 570 | return this; 571 | } 572 | 573 | 574 | function stringSet (units, value) { 575 | if (typeof units === 'object') { 576 | units = normalizeObjectUnits(units); 577 | var prioritized = getPrioritizedUnits(units); 578 | for (var i = 0; i < prioritized.length; i++) { 579 | this[prioritized[i].unit](units[prioritized[i].unit]); 580 | } 581 | } else { 582 | units = normalizeUnits(units); 583 | if (isFunction(this[units])) { 584 | return this[units](value); 585 | } 586 | } 587 | return this; 588 | } 589 | 590 | function zeroFill(number, targetLength, forceSign) { 591 | var absNumber = '' + Math.abs(number), 592 | zerosToFill = targetLength - absNumber.length, 593 | sign = number >= 0; 594 | return (sign ? (forceSign ? '+' : '') : '-') + 595 | Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; 596 | } 597 | 598 | var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; 599 | 600 | var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; 601 | 602 | var formatFunctions = {}; 603 | 604 | var formatTokenFunctions = {}; 605 | 606 | // token: 'M' 607 | // padded: ['MM', 2] 608 | // ordinal: 'Mo' 609 | // callback: function () { this.month() + 1 } 610 | function addFormatToken (token, padded, ordinal, callback) { 611 | var func = callback; 612 | if (typeof callback === 'string') { 613 | func = function () { 614 | return this[callback](); 615 | }; 616 | } 617 | if (token) { 618 | formatTokenFunctions[token] = func; 619 | } 620 | if (padded) { 621 | formatTokenFunctions[padded[0]] = function () { 622 | return zeroFill(func.apply(this, arguments), padded[1], padded[2]); 623 | }; 624 | } 625 | if (ordinal) { 626 | formatTokenFunctions[ordinal] = function () { 627 | return this.localeData().ordinal(func.apply(this, arguments), token); 628 | }; 629 | } 630 | } 631 | 632 | function removeFormattingTokens(input) { 633 | if (input.match(/\[[\s\S]/)) { 634 | return input.replace(/^\[|\]$/g, ''); 635 | } 636 | return input.replace(/\\/g, ''); 637 | } 638 | 639 | function makeFormatFunction(format) { 640 | var array = format.match(formattingTokens), i, length; 641 | 642 | for (i = 0, length = array.length; i < length; i++) { 643 | if (formatTokenFunctions[array[i]]) { 644 | array[i] = formatTokenFunctions[array[i]]; 645 | } else { 646 | array[i] = removeFormattingTokens(array[i]); 647 | } 648 | } 649 | 650 | return function (mom) { 651 | var output = '', i; 652 | for (i = 0; i < length; i++) { 653 | output += isFunction(array[i]) ? array[i].call(mom, format) : array[i]; 654 | } 655 | return output; 656 | }; 657 | } 658 | 659 | // format date using native date object 660 | function formatMoment(m, format) { 661 | if (!m.isValid()) { 662 | return m.localeData().invalidDate(); 663 | } 664 | 665 | format = expandFormat(format, m.localeData()); 666 | formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); 667 | 668 | return formatFunctions[format](m); 669 | } 670 | 671 | function expandFormat(format, locale) { 672 | var i = 5; 673 | 674 | function replaceLongDateFormatTokens(input) { 675 | return locale.longDateFormat(input) || input; 676 | } 677 | 678 | localFormattingTokens.lastIndex = 0; 679 | while (i >= 0 && localFormattingTokens.test(format)) { 680 | format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); 681 | localFormattingTokens.lastIndex = 0; 682 | i -= 1; 683 | } 684 | 685 | return format; 686 | } 687 | 688 | var match1 = /\d/; // 0 - 9 689 | var match2 = /\d\d/; // 00 - 99 690 | var match3 = /\d{3}/; // 000 - 999 691 | var match4 = /\d{4}/; // 0000 - 9999 692 | var match6 = /[+-]?\d{6}/; // -999999 - 999999 693 | var match1to2 = /\d\d?/; // 0 - 99 694 | var match3to4 = /\d\d\d\d?/; // 999 - 9999 695 | var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 696 | var match1to3 = /\d{1,3}/; // 0 - 999 697 | var match1to4 = /\d{1,4}/; // 0 - 9999 698 | var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 699 | 700 | var matchUnsigned = /\d+/; // 0 - inf 701 | var matchSigned = /[+-]?\d+/; // -inf - inf 702 | 703 | var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z 704 | var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z 705 | 706 | var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 707 | 708 | // any word (or two) characters or numbers including two/three word month in arabic. 709 | // includes scottish gaelic two word and hyphenated months 710 | var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; 711 | 712 | 713 | var regexes = {}; 714 | 715 | function addRegexToken (token, regex, strictRegex) { 716 | regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { 717 | return (isStrict && strictRegex) ? strictRegex : regex; 718 | }; 719 | } 720 | 721 | function getParseRegexForToken (token, config) { 722 | if (!hasOwnProp(regexes, token)) { 723 | return new RegExp(unescapeFormat(token)); 724 | } 725 | 726 | return regexes[token](config._strict, config._locale); 727 | } 728 | 729 | // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript 730 | function unescapeFormat(s) { 731 | return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { 732 | return p1 || p2 || p3 || p4; 733 | })); 734 | } 735 | 736 | function regexEscape(s) { 737 | return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); 738 | } 739 | 740 | var tokens = {}; 741 | 742 | function addParseToken (token, callback) { 743 | var i, func = callback; 744 | if (typeof token === 'string') { 745 | token = [token]; 746 | } 747 | if (isNumber(callback)) { 748 | func = function (input, array) { 749 | array[callback] = toInt(input); 750 | }; 751 | } 752 | for (i = 0; i < token.length; i++) { 753 | tokens[token[i]] = func; 754 | } 755 | } 756 | 757 | function addWeekParseToken (token, callback) { 758 | addParseToken(token, function (input, array, config, token) { 759 | config._w = config._w || {}; 760 | callback(input, config._w, config, token); 761 | }); 762 | } 763 | 764 | function addTimeToArrayFromToken(token, input, config) { 765 | if (input != null && hasOwnProp(tokens, token)) { 766 | tokens[token](input, config._a, config, token); 767 | } 768 | } 769 | 770 | var YEAR = 0; 771 | var MONTH = 1; 772 | var DATE = 2; 773 | var HOUR = 3; 774 | var MINUTE = 4; 775 | var SECOND = 5; 776 | var MILLISECOND = 6; 777 | var WEEK = 7; 778 | var WEEKDAY = 8; 779 | 780 | var indexOf; 781 | 782 | if (Array.prototype.indexOf) { 783 | indexOf = Array.prototype.indexOf; 784 | } else { 785 | indexOf = function (o) { 786 | // I know 787 | var i; 788 | for (i = 0; i < this.length; ++i) { 789 | if (this[i] === o) { 790 | return i; 791 | } 792 | } 793 | return -1; 794 | }; 795 | } 796 | 797 | var indexOf$1 = indexOf; 798 | 799 | function daysInMonth(year, month) { 800 | return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); 801 | } 802 | 803 | // FORMATTING 804 | 805 | addFormatToken('M', ['MM', 2], 'Mo', function () { 806 | return this.month() + 1; 807 | }); 808 | 809 | addFormatToken('MMM', 0, 0, function (format) { 810 | return this.localeData().monthsShort(this, format); 811 | }); 812 | 813 | addFormatToken('MMMM', 0, 0, function (format) { 814 | return this.localeData().months(this, format); 815 | }); 816 | 817 | // ALIASES 818 | 819 | addUnitAlias('month', 'M'); 820 | 821 | // PRIORITY 822 | 823 | addUnitPriority('month', 8); 824 | 825 | // PARSING 826 | 827 | addRegexToken('M', match1to2); 828 | addRegexToken('MM', match1to2, match2); 829 | addRegexToken('MMM', function (isStrict, locale) { 830 | return locale.monthsShortRegex(isStrict); 831 | }); 832 | addRegexToken('MMMM', function (isStrict, locale) { 833 | return locale.monthsRegex(isStrict); 834 | }); 835 | 836 | addParseToken(['M', 'MM'], function (input, array) { 837 | array[MONTH] = toInt(input) - 1; 838 | }); 839 | 840 | addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { 841 | var month = config._locale.monthsParse(input, token, config._strict); 842 | // if we didn't find a month name, mark the date as invalid. 843 | if (month != null) { 844 | array[MONTH] = month; 845 | } else { 846 | getParsingFlags(config).invalidMonth = input; 847 | } 848 | }); 849 | 850 | // LOCALES 851 | 852 | var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; 853 | var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); 854 | function localeMonths (m, format) { 855 | if (!m) { 856 | return isArray(this._months) ? this._months : 857 | this._months['standalone']; 858 | } 859 | return isArray(this._months) ? this._months[m.month()] : 860 | this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; 861 | } 862 | 863 | var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); 864 | function localeMonthsShort (m, format) { 865 | if (!m) { 866 | return isArray(this._monthsShort) ? this._monthsShort : 867 | this._monthsShort['standalone']; 868 | } 869 | return isArray(this._monthsShort) ? this._monthsShort[m.month()] : 870 | this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; 871 | } 872 | 873 | function handleStrictParse(monthName, format, strict) { 874 | var i, ii, mom, llc = monthName.toLocaleLowerCase(); 875 | if (!this._monthsParse) { 876 | // this is not used 877 | this._monthsParse = []; 878 | this._longMonthsParse = []; 879 | this._shortMonthsParse = []; 880 | for (i = 0; i < 12; ++i) { 881 | mom = createUTC([2000, i]); 882 | this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); 883 | this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); 884 | } 885 | } 886 | 887 | if (strict) { 888 | if (format === 'MMM') { 889 | ii = indexOf$1.call(this._shortMonthsParse, llc); 890 | return ii !== -1 ? ii : null; 891 | } else { 892 | ii = indexOf$1.call(this._longMonthsParse, llc); 893 | return ii !== -1 ? ii : null; 894 | } 895 | } else { 896 | if (format === 'MMM') { 897 | ii = indexOf$1.call(this._shortMonthsParse, llc); 898 | if (ii !== -1) { 899 | return ii; 900 | } 901 | ii = indexOf$1.call(this._longMonthsParse, llc); 902 | return ii !== -1 ? ii : null; 903 | } else { 904 | ii = indexOf$1.call(this._longMonthsParse, llc); 905 | if (ii !== -1) { 906 | return ii; 907 | } 908 | ii = indexOf$1.call(this._shortMonthsParse, llc); 909 | return ii !== -1 ? ii : null; 910 | } 911 | } 912 | } 913 | 914 | function localeMonthsParse (monthName, format, strict) { 915 | var i, mom, regex; 916 | 917 | if (this._monthsParseExact) { 918 | return handleStrictParse.call(this, monthName, format, strict); 919 | } 920 | 921 | if (!this._monthsParse) { 922 | this._monthsParse = []; 923 | this._longMonthsParse = []; 924 | this._shortMonthsParse = []; 925 | } 926 | 927 | // TODO: add sorting 928 | // Sorting makes sure if one month (or abbr) is a prefix of another 929 | // see sorting in computeMonthsParse 930 | for (i = 0; i < 12; i++) { 931 | // make the regex if we don't have it already 932 | mom = createUTC([2000, i]); 933 | if (strict && !this._longMonthsParse[i]) { 934 | this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); 935 | this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); 936 | } 937 | if (!strict && !this._monthsParse[i]) { 938 | regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); 939 | this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); 940 | } 941 | // test the regex 942 | if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { 943 | return i; 944 | } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { 945 | return i; 946 | } else if (!strict && this._monthsParse[i].test(monthName)) { 947 | return i; 948 | } 949 | } 950 | } 951 | 952 | // MOMENTS 953 | 954 | function setMonth (mom, value) { 955 | var dayOfMonth; 956 | 957 | if (!mom.isValid()) { 958 | // No op 959 | return mom; 960 | } 961 | 962 | if (typeof value === 'string') { 963 | if (/^\d+$/.test(value)) { 964 | value = toInt(value); 965 | } else { 966 | value = mom.localeData().monthsParse(value); 967 | // TODO: Another silent failure? 968 | if (!isNumber(value)) { 969 | return mom; 970 | } 971 | } 972 | } 973 | 974 | dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); 975 | mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); 976 | return mom; 977 | } 978 | 979 | function getSetMonth (value) { 980 | if (value != null) { 981 | setMonth(this, value); 982 | hooks.updateOffset(this, true); 983 | return this; 984 | } else { 985 | return get(this, 'Month'); 986 | } 987 | } 988 | 989 | function getDaysInMonth () { 990 | return daysInMonth(this.year(), this.month()); 991 | } 992 | 993 | var defaultMonthsShortRegex = matchWord; 994 | function monthsShortRegex (isStrict) { 995 | if (this._monthsParseExact) { 996 | if (!hasOwnProp(this, '_monthsRegex')) { 997 | computeMonthsParse.call(this); 998 | } 999 | if (isStrict) { 1000 | return this._monthsShortStrictRegex; 1001 | } else { 1002 | return this._monthsShortRegex; 1003 | } 1004 | } else { 1005 | if (!hasOwnProp(this, '_monthsShortRegex')) { 1006 | this._monthsShortRegex = defaultMonthsShortRegex; 1007 | } 1008 | return this._monthsShortStrictRegex && isStrict ? 1009 | this._monthsShortStrictRegex : this._monthsShortRegex; 1010 | } 1011 | } 1012 | 1013 | var defaultMonthsRegex = matchWord; 1014 | function monthsRegex (isStrict) { 1015 | if (this._monthsParseExact) { 1016 | if (!hasOwnProp(this, '_monthsRegex')) { 1017 | computeMonthsParse.call(this); 1018 | } 1019 | if (isStrict) { 1020 | return this._monthsStrictRegex; 1021 | } else { 1022 | return this._monthsRegex; 1023 | } 1024 | } else { 1025 | if (!hasOwnProp(this, '_monthsRegex')) { 1026 | this._monthsRegex = defaultMonthsRegex; 1027 | } 1028 | return this._monthsStrictRegex && isStrict ? 1029 | this._monthsStrictRegex : this._monthsRegex; 1030 | } 1031 | } 1032 | 1033 | function computeMonthsParse () { 1034 | function cmpLenRev(a, b) { 1035 | return b.length - a.length; 1036 | } 1037 | 1038 | var shortPieces = [], longPieces = [], mixedPieces = [], 1039 | i, mom; 1040 | for (i = 0; i < 12; i++) { 1041 | // make the regex if we don't have it already 1042 | mom = createUTC([2000, i]); 1043 | shortPieces.push(this.monthsShort(mom, '')); 1044 | longPieces.push(this.months(mom, '')); 1045 | mixedPieces.push(this.months(mom, '')); 1046 | mixedPieces.push(this.monthsShort(mom, '')); 1047 | } 1048 | // Sorting makes sure if one month (or abbr) is a prefix of another it 1049 | // will match the longer piece. 1050 | shortPieces.sort(cmpLenRev); 1051 | longPieces.sort(cmpLenRev); 1052 | mixedPieces.sort(cmpLenRev); 1053 | for (i = 0; i < 12; i++) { 1054 | shortPieces[i] = regexEscape(shortPieces[i]); 1055 | longPieces[i] = regexEscape(longPieces[i]); 1056 | } 1057 | for (i = 0; i < 24; i++) { 1058 | mixedPieces[i] = regexEscape(mixedPieces[i]); 1059 | } 1060 | 1061 | this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); 1062 | this._monthsShortRegex = this._monthsRegex; 1063 | this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); 1064 | this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); 1065 | } 1066 | 1067 | // FORMATTING 1068 | 1069 | addFormatToken('Y', 0, 0, function () { 1070 | var y = this.year(); 1071 | return y <= 9999 ? '' + y : '+' + y; 1072 | }); 1073 | 1074 | addFormatToken(0, ['YY', 2], 0, function () { 1075 | return this.year() % 100; 1076 | }); 1077 | 1078 | addFormatToken(0, ['YYYY', 4], 0, 'year'); 1079 | addFormatToken(0, ['YYYYY', 5], 0, 'year'); 1080 | addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); 1081 | 1082 | // ALIASES 1083 | 1084 | addUnitAlias('year', 'y'); 1085 | 1086 | // PRIORITIES 1087 | 1088 | addUnitPriority('year', 1); 1089 | 1090 | // PARSING 1091 | 1092 | addRegexToken('Y', matchSigned); 1093 | addRegexToken('YY', match1to2, match2); 1094 | addRegexToken('YYYY', match1to4, match4); 1095 | addRegexToken('YYYYY', match1to6, match6); 1096 | addRegexToken('YYYYYY', match1to6, match6); 1097 | 1098 | addParseToken(['YYYYY', 'YYYYYY'], YEAR); 1099 | addParseToken('YYYY', function (input, array) { 1100 | array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); 1101 | }); 1102 | addParseToken('YY', function (input, array) { 1103 | array[YEAR] = hooks.parseTwoDigitYear(input); 1104 | }); 1105 | addParseToken('Y', function (input, array) { 1106 | array[YEAR] = parseInt(input, 10); 1107 | }); 1108 | 1109 | // HELPERS 1110 | 1111 | function daysInYear(year) { 1112 | return isLeapYear(year) ? 366 : 365; 1113 | } 1114 | 1115 | function isLeapYear(year) { 1116 | return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; 1117 | } 1118 | 1119 | // HOOKS 1120 | 1121 | hooks.parseTwoDigitYear = function (input) { 1122 | return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); 1123 | }; 1124 | 1125 | // MOMENTS 1126 | 1127 | var getSetYear = makeGetSet('FullYear', true); 1128 | 1129 | function getIsLeapYear () { 1130 | return isLeapYear(this.year()); 1131 | } 1132 | 1133 | function createDate (y, m, d, h, M, s, ms) { 1134 | // can't just apply() to create a date: 1135 | // https://stackoverflow.com/q/181348 1136 | var date = new Date(y, m, d, h, M, s, ms); 1137 | 1138 | // the date constructor remaps years 0-99 to 1900-1999 1139 | if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { 1140 | date.setFullYear(y); 1141 | } 1142 | return date; 1143 | } 1144 | 1145 | function createUTCDate (y) { 1146 | var date = new Date(Date.UTC.apply(null, arguments)); 1147 | 1148 | // the Date.UTC function remaps years 0-99 to 1900-1999 1149 | if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { 1150 | date.setUTCFullYear(y); 1151 | } 1152 | return date; 1153 | } 1154 | 1155 | // start-of-first-week - start-of-year 1156 | function firstWeekOffset(year, dow, doy) { 1157 | var // first-week day -- which january is always in the first week (4 for iso, 1 for other) 1158 | fwd = 7 + dow - doy, 1159 | // first-week day local weekday -- which local weekday is fwd 1160 | fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; 1161 | 1162 | return -fwdlw + fwd - 1; 1163 | } 1164 | 1165 | // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday 1166 | function dayOfYearFromWeeks(year, week, weekday, dow, doy) { 1167 | var localWeekday = (7 + weekday - dow) % 7, 1168 | weekOffset = firstWeekOffset(year, dow, doy), 1169 | dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, 1170 | resYear, resDayOfYear; 1171 | 1172 | if (dayOfYear <= 0) { 1173 | resYear = year - 1; 1174 | resDayOfYear = daysInYear(resYear) + dayOfYear; 1175 | } else if (dayOfYear > daysInYear(year)) { 1176 | resYear = year + 1; 1177 | resDayOfYear = dayOfYear - daysInYear(year); 1178 | } else { 1179 | resYear = year; 1180 | resDayOfYear = dayOfYear; 1181 | } 1182 | 1183 | return { 1184 | year: resYear, 1185 | dayOfYear: resDayOfYear 1186 | }; 1187 | } 1188 | 1189 | function weekOfYear(mom, dow, doy) { 1190 | var weekOffset = firstWeekOffset(mom.year(), dow, doy), 1191 | week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, 1192 | resWeek, resYear; 1193 | 1194 | if (week < 1) { 1195 | resYear = mom.year() - 1; 1196 | resWeek = week + weeksInYear(resYear, dow, doy); 1197 | } else if (week > weeksInYear(mom.year(), dow, doy)) { 1198 | resWeek = week - weeksInYear(mom.year(), dow, doy); 1199 | resYear = mom.year() + 1; 1200 | } else { 1201 | resYear = mom.year(); 1202 | resWeek = week; 1203 | } 1204 | 1205 | return { 1206 | week: resWeek, 1207 | year: resYear 1208 | }; 1209 | } 1210 | 1211 | function weeksInYear(year, dow, doy) { 1212 | var weekOffset = firstWeekOffset(year, dow, doy), 1213 | weekOffsetNext = firstWeekOffset(year + 1, dow, doy); 1214 | return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; 1215 | } 1216 | 1217 | // FORMATTING 1218 | 1219 | addFormatToken('w', ['ww', 2], 'wo', 'week'); 1220 | addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); 1221 | 1222 | // ALIASES 1223 | 1224 | addUnitAlias('week', 'w'); 1225 | addUnitAlias('isoWeek', 'W'); 1226 | 1227 | // PRIORITIES 1228 | 1229 | addUnitPriority('week', 5); 1230 | addUnitPriority('isoWeek', 5); 1231 | 1232 | // PARSING 1233 | 1234 | addRegexToken('w', match1to2); 1235 | addRegexToken('ww', match1to2, match2); 1236 | addRegexToken('W', match1to2); 1237 | addRegexToken('WW', match1to2, match2); 1238 | 1239 | addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { 1240 | week[token.substr(0, 1)] = toInt(input); 1241 | }); 1242 | 1243 | // HELPERS 1244 | 1245 | // LOCALES 1246 | 1247 | function localeWeek (mom) { 1248 | return weekOfYear(mom, this._week.dow, this._week.doy).week; 1249 | } 1250 | 1251 | var defaultLocaleWeek = { 1252 | dow : 0, // Sunday is the first day of the week. 1253 | doy : 6 // The week that contains Jan 1st is the first week of the year. 1254 | }; 1255 | 1256 | function localeFirstDayOfWeek () { 1257 | return this._week.dow; 1258 | } 1259 | 1260 | function localeFirstDayOfYear () { 1261 | return this._week.doy; 1262 | } 1263 | 1264 | // MOMENTS 1265 | 1266 | function getSetWeek (input) { 1267 | var week = this.localeData().week(this); 1268 | return input == null ? week : this.add((input - week) * 7, 'd'); 1269 | } 1270 | 1271 | function getSetISOWeek (input) { 1272 | var week = weekOfYear(this, 1, 4).week; 1273 | return input == null ? week : this.add((input - week) * 7, 'd'); 1274 | } 1275 | 1276 | // FORMATTING 1277 | 1278 | addFormatToken('d', 0, 'do', 'day'); 1279 | 1280 | addFormatToken('dd', 0, 0, function (format) { 1281 | return this.localeData().weekdaysMin(this, format); 1282 | }); 1283 | 1284 | addFormatToken('ddd', 0, 0, function (format) { 1285 | return this.localeData().weekdaysShort(this, format); 1286 | }); 1287 | 1288 | addFormatToken('dddd', 0, 0, function (format) { 1289 | return this.localeData().weekdays(this, format); 1290 | }); 1291 | 1292 | addFormatToken('e', 0, 0, 'weekday'); 1293 | addFormatToken('E', 0, 0, 'isoWeekday'); 1294 | 1295 | // ALIASES 1296 | 1297 | addUnitAlias('day', 'd'); 1298 | addUnitAlias('weekday', 'e'); 1299 | addUnitAlias('isoWeekday', 'E'); 1300 | 1301 | // PRIORITY 1302 | addUnitPriority('day', 11); 1303 | addUnitPriority('weekday', 11); 1304 | addUnitPriority('isoWeekday', 11); 1305 | 1306 | // PARSING 1307 | 1308 | addRegexToken('d', match1to2); 1309 | addRegexToken('e', match1to2); 1310 | addRegexToken('E', match1to2); 1311 | addRegexToken('dd', function (isStrict, locale) { 1312 | return locale.weekdaysMinRegex(isStrict); 1313 | }); 1314 | addRegexToken('ddd', function (isStrict, locale) { 1315 | return locale.weekdaysShortRegex(isStrict); 1316 | }); 1317 | addRegexToken('dddd', function (isStrict, locale) { 1318 | return locale.weekdaysRegex(isStrict); 1319 | }); 1320 | 1321 | addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { 1322 | var weekday = config._locale.weekdaysParse(input, token, config._strict); 1323 | // if we didn't get a weekday name, mark the date as invalid 1324 | if (weekday != null) { 1325 | week.d = weekday; 1326 | } else { 1327 | getParsingFlags(config).invalidWeekday = input; 1328 | } 1329 | }); 1330 | 1331 | addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { 1332 | week[token] = toInt(input); 1333 | }); 1334 | 1335 | // HELPERS 1336 | 1337 | function parseWeekday(input, locale) { 1338 | if (typeof input !== 'string') { 1339 | return input; 1340 | } 1341 | 1342 | if (!isNaN(input)) { 1343 | return parseInt(input, 10); 1344 | } 1345 | 1346 | input = locale.weekdaysParse(input); 1347 | if (typeof input === 'number') { 1348 | return input; 1349 | } 1350 | 1351 | return null; 1352 | } 1353 | 1354 | function parseIsoWeekday(input, locale) { 1355 | if (typeof input === 'string') { 1356 | return locale.weekdaysParse(input) % 7 || 7; 1357 | } 1358 | return isNaN(input) ? null : input; 1359 | } 1360 | 1361 | // LOCALES 1362 | 1363 | var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); 1364 | function localeWeekdays (m, format) { 1365 | if (!m) { 1366 | return isArray(this._weekdays) ? this._weekdays : 1367 | this._weekdays['standalone']; 1368 | } 1369 | return isArray(this._weekdays) ? this._weekdays[m.day()] : 1370 | this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; 1371 | } 1372 | 1373 | var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); 1374 | function localeWeekdaysShort (m) { 1375 | return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; 1376 | } 1377 | 1378 | var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); 1379 | function localeWeekdaysMin (m) { 1380 | return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; 1381 | } 1382 | 1383 | function handleStrictParse$1(weekdayName, format, strict) { 1384 | var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); 1385 | if (!this._weekdaysParse) { 1386 | this._weekdaysParse = []; 1387 | this._shortWeekdaysParse = []; 1388 | this._minWeekdaysParse = []; 1389 | 1390 | for (i = 0; i < 7; ++i) { 1391 | mom = createUTC([2000, 1]).day(i); 1392 | this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); 1393 | this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); 1394 | this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); 1395 | } 1396 | } 1397 | 1398 | if (strict) { 1399 | if (format === 'dddd') { 1400 | ii = indexOf$1.call(this._weekdaysParse, llc); 1401 | return ii !== -1 ? ii : null; 1402 | } else if (format === 'ddd') { 1403 | ii = indexOf$1.call(this._shortWeekdaysParse, llc); 1404 | return ii !== -1 ? ii : null; 1405 | } else { 1406 | ii = indexOf$1.call(this._minWeekdaysParse, llc); 1407 | return ii !== -1 ? ii : null; 1408 | } 1409 | } else { 1410 | if (format === 'dddd') { 1411 | ii = indexOf$1.call(this._weekdaysParse, llc); 1412 | if (ii !== -1) { 1413 | return ii; 1414 | } 1415 | ii = indexOf$1.call(this._shortWeekdaysParse, llc); 1416 | if (ii !== -1) { 1417 | return ii; 1418 | } 1419 | ii = indexOf$1.call(this._minWeekdaysParse, llc); 1420 | return ii !== -1 ? ii : null; 1421 | } else if (format === 'ddd') { 1422 | ii = indexOf$1.call(this._shortWeekdaysParse, llc); 1423 | if (ii !== -1) { 1424 | return ii; 1425 | } 1426 | ii = indexOf$1.call(this._weekdaysParse, llc); 1427 | if (ii !== -1) { 1428 | return ii; 1429 | } 1430 | ii = indexOf$1.call(this._minWeekdaysParse, llc); 1431 | return ii !== -1 ? ii : null; 1432 | } else { 1433 | ii = indexOf$1.call(this._minWeekdaysParse, llc); 1434 | if (ii !== -1) { 1435 | return ii; 1436 | } 1437 | ii = indexOf$1.call(this._weekdaysParse, llc); 1438 | if (ii !== -1) { 1439 | return ii; 1440 | } 1441 | ii = indexOf$1.call(this._shortWeekdaysParse, llc); 1442 | return ii !== -1 ? ii : null; 1443 | } 1444 | } 1445 | } 1446 | 1447 | function localeWeekdaysParse (weekdayName, format, strict) { 1448 | var i, mom, regex; 1449 | 1450 | if (this._weekdaysParseExact) { 1451 | return handleStrictParse$1.call(this, weekdayName, format, strict); 1452 | } 1453 | 1454 | if (!this._weekdaysParse) { 1455 | this._weekdaysParse = []; 1456 | this._minWeekdaysParse = []; 1457 | this._shortWeekdaysParse = []; 1458 | this._fullWeekdaysParse = []; 1459 | } 1460 | 1461 | for (i = 0; i < 7; i++) { 1462 | // make the regex if we don't have it already 1463 | 1464 | mom = createUTC([2000, 1]).day(i); 1465 | if (strict && !this._fullWeekdaysParse[i]) { 1466 | this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); 1467 | this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); 1468 | this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); 1469 | } 1470 | if (!this._weekdaysParse[i]) { 1471 | regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); 1472 | this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); 1473 | } 1474 | // test the regex 1475 | if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { 1476 | return i; 1477 | } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { 1478 | return i; 1479 | } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { 1480 | return i; 1481 | } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { 1482 | return i; 1483 | } 1484 | } 1485 | } 1486 | 1487 | // MOMENTS 1488 | 1489 | function getSetDayOfWeek (input) { 1490 | if (!this.isValid()) { 1491 | return input != null ? this : NaN; 1492 | } 1493 | var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); 1494 | if (input != null) { 1495 | input = parseWeekday(input, this.localeData()); 1496 | return this.add(input - day, 'd'); 1497 | } else { 1498 | return day; 1499 | } 1500 | } 1501 | 1502 | function getSetLocaleDayOfWeek (input) { 1503 | if (!this.isValid()) { 1504 | return input != null ? this : NaN; 1505 | } 1506 | var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; 1507 | return input == null ? weekday : this.add(input - weekday, 'd'); 1508 | } 1509 | 1510 | function getSetISODayOfWeek (input) { 1511 | if (!this.isValid()) { 1512 | return input != null ? this : NaN; 1513 | } 1514 | 1515 | // behaves the same as moment#day except 1516 | // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) 1517 | // as a setter, sunday should belong to the previous week. 1518 | 1519 | if (input != null) { 1520 | var weekday = parseIsoWeekday(input, this.localeData()); 1521 | return this.day(this.day() % 7 ? weekday : weekday - 7); 1522 | } else { 1523 | return this.day() || 7; 1524 | } 1525 | } 1526 | 1527 | var defaultWeekdaysRegex = matchWord; 1528 | function weekdaysRegex (isStrict) { 1529 | if (this._weekdaysParseExact) { 1530 | if (!hasOwnProp(this, '_weekdaysRegex')) { 1531 | computeWeekdaysParse.call(this); 1532 | } 1533 | if (isStrict) { 1534 | return this._weekdaysStrictRegex; 1535 | } else { 1536 | return this._weekdaysRegex; 1537 | } 1538 | } else { 1539 | if (!hasOwnProp(this, '_weekdaysRegex')) { 1540 | this._weekdaysRegex = defaultWeekdaysRegex; 1541 | } 1542 | return this._weekdaysStrictRegex && isStrict ? 1543 | this._weekdaysStrictRegex : this._weekdaysRegex; 1544 | } 1545 | } 1546 | 1547 | var defaultWeekdaysShortRegex = matchWord; 1548 | function weekdaysShortRegex (isStrict) { 1549 | if (this._weekdaysParseExact) { 1550 | if (!hasOwnProp(this, '_weekdaysRegex')) { 1551 | computeWeekdaysParse.call(this); 1552 | } 1553 | if (isStrict) { 1554 | return this._weekdaysShortStrictRegex; 1555 | } else { 1556 | return this._weekdaysShortRegex; 1557 | } 1558 | } else { 1559 | if (!hasOwnProp(this, '_weekdaysShortRegex')) { 1560 | this._weekdaysShortRegex = defaultWeekdaysShortRegex; 1561 | } 1562 | return this._weekdaysShortStrictRegex && isStrict ? 1563 | this._weekdaysShortStrictRegex : this._weekdaysShortRegex; 1564 | } 1565 | } 1566 | 1567 | var defaultWeekdaysMinRegex = matchWord; 1568 | function weekdaysMinRegex (isStrict) { 1569 | if (this._weekdaysParseExact) { 1570 | if (!hasOwnProp(this, '_weekdaysRegex')) { 1571 | computeWeekdaysParse.call(this); 1572 | } 1573 | if (isStrict) { 1574 | return this._weekdaysMinStrictRegex; 1575 | } else { 1576 | return this._weekdaysMinRegex; 1577 | } 1578 | } else { 1579 | if (!hasOwnProp(this, '_weekdaysMinRegex')) { 1580 | this._weekdaysMinRegex = defaultWeekdaysMinRegex; 1581 | } 1582 | return this._weekdaysMinStrictRegex && isStrict ? 1583 | this._weekdaysMinStrictRegex : this._weekdaysMinRegex; 1584 | } 1585 | } 1586 | 1587 | 1588 | function computeWeekdaysParse () { 1589 | function cmpLenRev(a, b) { 1590 | return b.length - a.length; 1591 | } 1592 | 1593 | var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], 1594 | i, mom, minp, shortp, longp; 1595 | for (i = 0; i < 7; i++) { 1596 | // make the regex if we don't have it already 1597 | mom = createUTC([2000, 1]).day(i); 1598 | minp = this.weekdaysMin(mom, ''); 1599 | shortp = this.weekdaysShort(mom, ''); 1600 | longp = this.weekdays(mom, ''); 1601 | minPieces.push(minp); 1602 | shortPieces.push(shortp); 1603 | longPieces.push(longp); 1604 | mixedPieces.push(minp); 1605 | mixedPieces.push(shortp); 1606 | mixedPieces.push(longp); 1607 | } 1608 | // Sorting makes sure if one weekday (or abbr) is a prefix of another it 1609 | // will match the longer piece. 1610 | minPieces.sort(cmpLenRev); 1611 | shortPieces.sort(cmpLenRev); 1612 | longPieces.sort(cmpLenRev); 1613 | mixedPieces.sort(cmpLenRev); 1614 | for (i = 0; i < 7; i++) { 1615 | shortPieces[i] = regexEscape(shortPieces[i]); 1616 | longPieces[i] = regexEscape(longPieces[i]); 1617 | mixedPieces[i] = regexEscape(mixedPieces[i]); 1618 | } 1619 | 1620 | this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); 1621 | this._weekdaysShortRegex = this._weekdaysRegex; 1622 | this._weekdaysMinRegex = this._weekdaysRegex; 1623 | 1624 | this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); 1625 | this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); 1626 | this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); 1627 | } 1628 | 1629 | // FORMATTING 1630 | 1631 | function hFormat() { 1632 | return this.hours() % 12 || 12; 1633 | } 1634 | 1635 | function kFormat() { 1636 | return this.hours() || 24; 1637 | } 1638 | 1639 | addFormatToken('H', ['HH', 2], 0, 'hour'); 1640 | addFormatToken('h', ['hh', 2], 0, hFormat); 1641 | addFormatToken('k', ['kk', 2], 0, kFormat); 1642 | 1643 | addFormatToken('hmm', 0, 0, function () { 1644 | return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); 1645 | }); 1646 | 1647 | addFormatToken('hmmss', 0, 0, function () { 1648 | return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + 1649 | zeroFill(this.seconds(), 2); 1650 | }); 1651 | 1652 | addFormatToken('Hmm', 0, 0, function () { 1653 | return '' + this.hours() + zeroFill(this.minutes(), 2); 1654 | }); 1655 | 1656 | addFormatToken('Hmmss', 0, 0, function () { 1657 | return '' + this.hours() + zeroFill(this.minutes(), 2) + 1658 | zeroFill(this.seconds(), 2); 1659 | }); 1660 | 1661 | function meridiem (token, lowercase) { 1662 | addFormatToken(token, 0, 0, function () { 1663 | return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); 1664 | }); 1665 | } 1666 | 1667 | meridiem('a', true); 1668 | meridiem('A', false); 1669 | 1670 | // ALIASES 1671 | 1672 | addUnitAlias('hour', 'h'); 1673 | 1674 | // PRIORITY 1675 | addUnitPriority('hour', 13); 1676 | 1677 | // PARSING 1678 | 1679 | function matchMeridiem (isStrict, locale) { 1680 | return locale._meridiemParse; 1681 | } 1682 | 1683 | addRegexToken('a', matchMeridiem); 1684 | addRegexToken('A', matchMeridiem); 1685 | addRegexToken('H', match1to2); 1686 | addRegexToken('h', match1to2); 1687 | addRegexToken('k', match1to2); 1688 | addRegexToken('HH', match1to2, match2); 1689 | addRegexToken('hh', match1to2, match2); 1690 | addRegexToken('kk', match1to2, match2); 1691 | 1692 | addRegexToken('hmm', match3to4); 1693 | addRegexToken('hmmss', match5to6); 1694 | addRegexToken('Hmm', match3to4); 1695 | addRegexToken('Hmmss', match5to6); 1696 | 1697 | addParseToken(['H', 'HH'], HOUR); 1698 | addParseToken(['k', 'kk'], function (input, array, config) { 1699 | var kInput = toInt(input); 1700 | array[HOUR] = kInput === 24 ? 0 : kInput; 1701 | }); 1702 | addParseToken(['a', 'A'], function (input, array, config) { 1703 | config._isPm = config._locale.isPM(input); 1704 | config._meridiem = input; 1705 | }); 1706 | addParseToken(['h', 'hh'], function (input, array, config) { 1707 | array[HOUR] = toInt(input); 1708 | getParsingFlags(config).bigHour = true; 1709 | }); 1710 | addParseToken('hmm', function (input, array, config) { 1711 | var pos = input.length - 2; 1712 | array[HOUR] = toInt(input.substr(0, pos)); 1713 | array[MINUTE] = toInt(input.substr(pos)); 1714 | getParsingFlags(config).bigHour = true; 1715 | }); 1716 | addParseToken('hmmss', function (input, array, config) { 1717 | var pos1 = input.length - 4; 1718 | var pos2 = input.length - 2; 1719 | array[HOUR] = toInt(input.substr(0, pos1)); 1720 | array[MINUTE] = toInt(input.substr(pos1, 2)); 1721 | array[SECOND] = toInt(input.substr(pos2)); 1722 | getParsingFlags(config).bigHour = true; 1723 | }); 1724 | addParseToken('Hmm', function (input, array, config) { 1725 | var pos = input.length - 2; 1726 | array[HOUR] = toInt(input.substr(0, pos)); 1727 | array[MINUTE] = toInt(input.substr(pos)); 1728 | }); 1729 | addParseToken('Hmmss', function (input, array, config) { 1730 | var pos1 = input.length - 4; 1731 | var pos2 = input.length - 2; 1732 | array[HOUR] = toInt(input.substr(0, pos1)); 1733 | array[MINUTE] = toInt(input.substr(pos1, 2)); 1734 | array[SECOND] = toInt(input.substr(pos2)); 1735 | }); 1736 | 1737 | // LOCALES 1738 | 1739 | function localeIsPM (input) { 1740 | // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays 1741 | // Using charAt should be more compatible. 1742 | return ((input + '').toLowerCase().charAt(0) === 'p'); 1743 | } 1744 | 1745 | var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; 1746 | function localeMeridiem (hours, minutes, isLower) { 1747 | if (hours > 11) { 1748 | return isLower ? 'pm' : 'PM'; 1749 | } else { 1750 | return isLower ? 'am' : 'AM'; 1751 | } 1752 | } 1753 | 1754 | 1755 | // MOMENTS 1756 | 1757 | // Setting the hour should keep the time, because the user explicitly 1758 | // specified which hour he wants. So trying to maintain the same hour (in 1759 | // a new timezone) makes sense. Adding/subtracting hours does not follow 1760 | // this rule. 1761 | var getSetHour = makeGetSet('Hours', true); 1762 | 1763 | // months 1764 | // week 1765 | // weekdays 1766 | // meridiem 1767 | var baseConfig = { 1768 | calendar: defaultCalendar, 1769 | longDateFormat: defaultLongDateFormat, 1770 | invalidDate: defaultInvalidDate, 1771 | ordinal: defaultOrdinal, 1772 | dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, 1773 | relativeTime: defaultRelativeTime, 1774 | 1775 | months: defaultLocaleMonths, 1776 | monthsShort: defaultLocaleMonthsShort, 1777 | 1778 | week: defaultLocaleWeek, 1779 | 1780 | weekdays: defaultLocaleWeekdays, 1781 | weekdaysMin: defaultLocaleWeekdaysMin, 1782 | weekdaysShort: defaultLocaleWeekdaysShort, 1783 | 1784 | meridiemParse: defaultLocaleMeridiemParse 1785 | }; 1786 | 1787 | // internal storage for locale config files 1788 | var locales = {}; 1789 | var localeFamilies = {}; 1790 | var globalLocale; 1791 | 1792 | function normalizeLocale(key) { 1793 | return key ? key.toLowerCase().replace('_', '-') : key; 1794 | } 1795 | 1796 | // pick the locale from the array 1797 | // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each 1798 | // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root 1799 | function chooseLocale(names) { 1800 | var i = 0, j, next, locale, split; 1801 | 1802 | while (i < names.length) { 1803 | split = normalizeLocale(names[i]).split('-'); 1804 | j = split.length; 1805 | next = normalizeLocale(names[i + 1]); 1806 | next = next ? next.split('-') : null; 1807 | while (j > 0) { 1808 | locale = loadLocale(split.slice(0, j).join('-')); 1809 | if (locale) { 1810 | return locale; 1811 | } 1812 | if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { 1813 | //the next array item is better than a shallower substring of this one 1814 | break; 1815 | } 1816 | j--; 1817 | } 1818 | i++; 1819 | } 1820 | return null; 1821 | } 1822 | 1823 | function loadLocale(name) { 1824 | var oldLocale = null; 1825 | // TODO: Find a better way to register and load all the locales in Node 1826 | if (!locales[name] && (typeof module !== 'undefined') && 1827 | module && module.exports) { 1828 | try { 1829 | oldLocale = globalLocale._abbr; 1830 | require('./locale/' + name); 1831 | // because defineLocale currently also sets the global locale, we 1832 | // want to undo that for lazy loaded locales 1833 | getSetGlobalLocale(oldLocale); 1834 | } catch (e) { } 1835 | } 1836 | return locales[name]; 1837 | } 1838 | 1839 | // This function will load locale and then set the global locale. If 1840 | // no arguments are passed in, it will simply return the current global 1841 | // locale key. 1842 | function getSetGlobalLocale (key, values) { 1843 | var data; 1844 | if (key) { 1845 | if (isUndefined(values)) { 1846 | data = getLocale(key); 1847 | } 1848 | else { 1849 | data = defineLocale(key, values); 1850 | } 1851 | 1852 | if (data) { 1853 | // moment.duration._locale = moment._locale = data; 1854 | globalLocale = data; 1855 | } 1856 | } 1857 | 1858 | return globalLocale._abbr; 1859 | } 1860 | 1861 | function defineLocale (name, config) { 1862 | if (config !== null) { 1863 | var parentConfig = baseConfig; 1864 | config.abbr = name; 1865 | if (locales[name] != null) { 1866 | deprecateSimple('defineLocaleOverride', 1867 | 'use moment.updateLocale(localeName, config) to change ' + 1868 | 'an existing locale. moment.defineLocale(localeName, ' + 1869 | 'config) should only be used for creating a new locale ' + 1870 | 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); 1871 | parentConfig = locales[name]._config; 1872 | } else if (config.parentLocale != null) { 1873 | if (locales[config.parentLocale] != null) { 1874 | parentConfig = locales[config.parentLocale]._config; 1875 | } else { 1876 | if (!localeFamilies[config.parentLocale]) { 1877 | localeFamilies[config.parentLocale] = []; 1878 | } 1879 | localeFamilies[config.parentLocale].push({ 1880 | name: name, 1881 | config: config 1882 | }); 1883 | return null; 1884 | } 1885 | } 1886 | locales[name] = new Locale(mergeConfigs(parentConfig, config)); 1887 | 1888 | if (localeFamilies[name]) { 1889 | localeFamilies[name].forEach(function (x) { 1890 | defineLocale(x.name, x.config); 1891 | }); 1892 | } 1893 | 1894 | // backwards compat for now: also set the locale 1895 | // make sure we set the locale AFTER all child locales have been 1896 | // created, so we won't end up with the child locale set. 1897 | getSetGlobalLocale(name); 1898 | 1899 | 1900 | return locales[name]; 1901 | } else { 1902 | // useful for testing 1903 | delete locales[name]; 1904 | return null; 1905 | } 1906 | } 1907 | 1908 | function updateLocale(name, config) { 1909 | if (config != null) { 1910 | var locale, parentConfig = baseConfig; 1911 | // MERGE 1912 | if (locales[name] != null) { 1913 | parentConfig = locales[name]._config; 1914 | } 1915 | config = mergeConfigs(parentConfig, config); 1916 | locale = new Locale(config); 1917 | locale.parentLocale = locales[name]; 1918 | locales[name] = locale; 1919 | 1920 | // backwards compat for now: also set the locale 1921 | getSetGlobalLocale(name); 1922 | } else { 1923 | // pass null for config to unupdate, useful for tests 1924 | if (locales[name] != null) { 1925 | if (locales[name].parentLocale != null) { 1926 | locales[name] = locales[name].parentLocale; 1927 | } else if (locales[name] != null) { 1928 | delete locales[name]; 1929 | } 1930 | } 1931 | } 1932 | return locales[name]; 1933 | } 1934 | 1935 | // returns locale data 1936 | function getLocale (key) { 1937 | var locale; 1938 | 1939 | if (key && key._locale && key._locale._abbr) { 1940 | key = key._locale._abbr; 1941 | } 1942 | 1943 | if (!key) { 1944 | return globalLocale; 1945 | } 1946 | 1947 | if (!isArray(key)) { 1948 | //short-circuit everything else 1949 | locale = loadLocale(key); 1950 | if (locale) { 1951 | return locale; 1952 | } 1953 | key = [key]; 1954 | } 1955 | 1956 | return chooseLocale(key); 1957 | } 1958 | 1959 | function listLocales() { 1960 | return keys$1(locales); 1961 | } 1962 | 1963 | function checkOverflow (m) { 1964 | var overflow; 1965 | var a = m._a; 1966 | 1967 | if (a && getParsingFlags(m).overflow === -2) { 1968 | overflow = 1969 | a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : 1970 | a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : 1971 | a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : 1972 | a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : 1973 | a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : 1974 | a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : 1975 | -1; 1976 | 1977 | if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { 1978 | overflow = DATE; 1979 | } 1980 | if (getParsingFlags(m)._overflowWeeks && overflow === -1) { 1981 | overflow = WEEK; 1982 | } 1983 | if (getParsingFlags(m)._overflowWeekday && overflow === -1) { 1984 | overflow = WEEKDAY; 1985 | } 1986 | 1987 | getParsingFlags(m).overflow = overflow; 1988 | } 1989 | 1990 | return m; 1991 | } 1992 | 1993 | // iso 8601 regex 1994 | // 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) 1995 | var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; 1996 | var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; 1997 | 1998 | var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; 1999 | 2000 | var isoDates = [ 2001 | ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], 2002 | ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], 2003 | ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], 2004 | ['GGGG-[W]WW', /\d{4}-W\d\d/, false], 2005 | ['YYYY-DDD', /\d{4}-\d{3}/], 2006 | ['YYYY-MM', /\d{4}-\d\d/, false], 2007 | ['YYYYYYMMDD', /[+-]\d{10}/], 2008 | ['YYYYMMDD', /\d{8}/], 2009 | // YYYYMM is NOT allowed by the standard 2010 | ['GGGG[W]WWE', /\d{4}W\d{3}/], 2011 | ['GGGG[W]WW', /\d{4}W\d{2}/, false], 2012 | ['YYYYDDD', /\d{7}/] 2013 | ]; 2014 | 2015 | // iso time formats and regexes 2016 | var isoTimes = [ 2017 | ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], 2018 | ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], 2019 | ['HH:mm:ss', /\d\d:\d\d:\d\d/], 2020 | ['HH:mm', /\d\d:\d\d/], 2021 | ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], 2022 | ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], 2023 | ['HHmmss', /\d\d\d\d\d\d/], 2024 | ['HHmm', /\d\d\d\d/], 2025 | ['HH', /\d\d/] 2026 | ]; 2027 | 2028 | var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; 2029 | 2030 | // date from iso format 2031 | function configFromISO(config) { 2032 | var i, l, 2033 | string = config._i, 2034 | match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), 2035 | allowTime, dateFormat, timeFormat, tzFormat; 2036 | 2037 | if (match) { 2038 | getParsingFlags(config).iso = true; 2039 | 2040 | for (i = 0, l = isoDates.length; i < l; i++) { 2041 | if (isoDates[i][1].exec(match[1])) { 2042 | dateFormat = isoDates[i][0]; 2043 | allowTime = isoDates[i][2] !== false; 2044 | break; 2045 | } 2046 | } 2047 | if (dateFormat == null) { 2048 | config._isValid = false; 2049 | return; 2050 | } 2051 | if (match[3]) { 2052 | for (i = 0, l = isoTimes.length; i < l; i++) { 2053 | if (isoTimes[i][1].exec(match[3])) { 2054 | // match[2] should be 'T' or space 2055 | timeFormat = (match[2] || ' ') + isoTimes[i][0]; 2056 | break; 2057 | } 2058 | } 2059 | if (timeFormat == null) { 2060 | config._isValid = false; 2061 | return; 2062 | } 2063 | } 2064 | if (!allowTime && timeFormat != null) { 2065 | config._isValid = false; 2066 | return; 2067 | } 2068 | if (match[4]) { 2069 | if (tzRegex.exec(match[4])) { 2070 | tzFormat = 'Z'; 2071 | } else { 2072 | config._isValid = false; 2073 | return; 2074 | } 2075 | } 2076 | config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); 2077 | configFromStringAndFormat(config); 2078 | } else { 2079 | config._isValid = false; 2080 | } 2081 | } 2082 | 2083 | // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 2084 | var basicRfcRegex = /^((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d?\d\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(?:\d\d)?\d\d\s)(\d\d:\d\d)(\:\d\d)?(\s(?:UT|GMT|[ECMP][SD]T|[A-IK-Za-ik-z]|[+-]\d{4}))$/; 2085 | 2086 | // date and time from ref 2822 format 2087 | function configFromRFC2822(config) { 2088 | var string, match, dayFormat, 2089 | dateFormat, timeFormat, tzFormat; 2090 | var timezones = { 2091 | ' GMT': ' +0000', 2092 | ' EDT': ' -0400', 2093 | ' EST': ' -0500', 2094 | ' CDT': ' -0500', 2095 | ' CST': ' -0600', 2096 | ' MDT': ' -0600', 2097 | ' MST': ' -0700', 2098 | ' PDT': ' -0700', 2099 | ' PST': ' -0800' 2100 | }; 2101 | var military = 'YXWVUTSRQPONZABCDEFGHIKLM'; 2102 | var timezone, timezoneIndex; 2103 | 2104 | string = config._i 2105 | .replace(/\([^\)]*\)|[\n\t]/g, ' ') // Remove comments and folding whitespace 2106 | .replace(/(\s\s+)/g, ' ') // Replace multiple-spaces with a single space 2107 | .replace(/^\s|\s$/g, ''); // Remove leading and trailing spaces 2108 | match = basicRfcRegex.exec(string); 2109 | 2110 | if (match) { 2111 | dayFormat = match[1] ? 'ddd' + ((match[1].length === 5) ? ', ' : ' ') : ''; 2112 | dateFormat = 'D MMM ' + ((match[2].length > 10) ? 'YYYY ' : 'YY '); 2113 | timeFormat = 'HH:mm' + (match[4] ? ':ss' : ''); 2114 | 2115 | // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. 2116 | if (match[1]) { // day of week given 2117 | var momentDate = new Date(match[2]); 2118 | var momentDay = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][momentDate.getDay()]; 2119 | 2120 | if (match[1].substr(0,3) !== momentDay) { 2121 | getParsingFlags(config).weekdayMismatch = true; 2122 | config._isValid = false; 2123 | return; 2124 | } 2125 | } 2126 | 2127 | switch (match[5].length) { 2128 | case 2: // military 2129 | if (timezoneIndex === 0) { 2130 | timezone = ' +0000'; 2131 | } else { 2132 | timezoneIndex = military.indexOf(match[5][1].toUpperCase()) - 12; 2133 | timezone = ((timezoneIndex < 0) ? ' -' : ' +') + 2134 | (('' + timezoneIndex).replace(/^-?/, '0')).match(/..$/)[0] + '00'; 2135 | } 2136 | break; 2137 | case 4: // Zone 2138 | timezone = timezones[match[5]]; 2139 | break; 2140 | default: // UT or +/-9999 2141 | timezone = timezones[' GMT']; 2142 | } 2143 | match[5] = timezone; 2144 | config._i = match.splice(1).join(''); 2145 | tzFormat = ' ZZ'; 2146 | config._f = dayFormat + dateFormat + timeFormat + tzFormat; 2147 | configFromStringAndFormat(config); 2148 | getParsingFlags(config).rfc2822 = true; 2149 | } else { 2150 | config._isValid = false; 2151 | } 2152 | } 2153 | 2154 | // date from iso format or fallback 2155 | function configFromString(config) { 2156 | var matched = aspNetJsonRegex.exec(config._i); 2157 | 2158 | if (matched !== null) { 2159 | config._d = new Date(+matched[1]); 2160 | return; 2161 | } 2162 | 2163 | configFromISO(config); 2164 | if (config._isValid === false) { 2165 | delete config._isValid; 2166 | } else { 2167 | return; 2168 | } 2169 | 2170 | configFromRFC2822(config); 2171 | if (config._isValid === false) { 2172 | delete config._isValid; 2173 | } else { 2174 | return; 2175 | } 2176 | 2177 | // Final attempt, use Input Fallback 2178 | hooks.createFromInputFallback(config); 2179 | } 2180 | 2181 | hooks.createFromInputFallback = deprecate( 2182 | 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + 2183 | 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + 2184 | 'discouraged and will be removed in an upcoming major release. Please refer to ' + 2185 | 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', 2186 | function (config) { 2187 | config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); 2188 | } 2189 | ); 2190 | 2191 | // Pick the first defined of two or three arguments. 2192 | function defaults(a, b, c) { 2193 | if (a != null) { 2194 | return a; 2195 | } 2196 | if (b != null) { 2197 | return b; 2198 | } 2199 | return c; 2200 | } 2201 | 2202 | function currentDateArray(config) { 2203 | // hooks is actually the exported moment object 2204 | var nowValue = new Date(hooks.now()); 2205 | if (config._useUTC) { 2206 | return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; 2207 | } 2208 | return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; 2209 | } 2210 | 2211 | // convert an array to a date. 2212 | // the array should mirror the parameters below 2213 | // note: all values past the year are optional and will default to the lowest possible value. 2214 | // [year, month, day , hour, minute, second, millisecond] 2215 | function configFromArray (config) { 2216 | var i, date, input = [], currentDate, yearToUse; 2217 | 2218 | if (config._d) { 2219 | return; 2220 | } 2221 | 2222 | currentDate = currentDateArray(config); 2223 | 2224 | //compute day of the year from weeks and weekdays 2225 | if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { 2226 | dayOfYearFromWeekInfo(config); 2227 | } 2228 | 2229 | //if the day of the year is set, figure out what it is 2230 | if (config._dayOfYear != null) { 2231 | yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); 2232 | 2233 | if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) { 2234 | getParsingFlags(config)._overflowDayOfYear = true; 2235 | } 2236 | 2237 | date = createUTCDate(yearToUse, 0, config._dayOfYear); 2238 | config._a[MONTH] = date.getUTCMonth(); 2239 | config._a[DATE] = date.getUTCDate(); 2240 | } 2241 | 2242 | // Default to current date. 2243 | // * if no year, month, day of month are given, default to today 2244 | // * if day of month is given, default month and year 2245 | // * if month is given, default only year 2246 | // * if year is given, don't default anything 2247 | for (i = 0; i < 3 && config._a[i] == null; ++i) { 2248 | config._a[i] = input[i] = currentDate[i]; 2249 | } 2250 | 2251 | // Zero out whatever was not defaulted, including time 2252 | for (; i < 7; i++) { 2253 | config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; 2254 | } 2255 | 2256 | // Check for 24:00:00.000 2257 | if (config._a[HOUR] === 24 && 2258 | config._a[MINUTE] === 0 && 2259 | config._a[SECOND] === 0 && 2260 | config._a[MILLISECOND] === 0) { 2261 | config._nextDay = true; 2262 | config._a[HOUR] = 0; 2263 | } 2264 | 2265 | config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); 2266 | // Apply timezone offset from input. The actual utcOffset can be changed 2267 | // with parseZone. 2268 | if (config._tzm != null) { 2269 | config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); 2270 | } 2271 | 2272 | if (config._nextDay) { 2273 | config._a[HOUR] = 24; 2274 | } 2275 | } 2276 | 2277 | function dayOfYearFromWeekInfo(config) { 2278 | var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; 2279 | 2280 | w = config._w; 2281 | if (w.GG != null || w.W != null || w.E != null) { 2282 | dow = 1; 2283 | doy = 4; 2284 | 2285 | // TODO: We need to take the current isoWeekYear, but that depends on 2286 | // how we interpret now (local, utc, fixed offset). So create 2287 | // a now version of current config (take local/utc/offset flags, and 2288 | // create now). 2289 | weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); 2290 | week = defaults(w.W, 1); 2291 | weekday = defaults(w.E, 1); 2292 | if (weekday < 1 || weekday > 7) { 2293 | weekdayOverflow = true; 2294 | } 2295 | } else { 2296 | dow = config._locale._week.dow; 2297 | doy = config._locale._week.doy; 2298 | 2299 | var curWeek = weekOfYear(createLocal(), dow, doy); 2300 | 2301 | weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); 2302 | 2303 | // Default to current week. 2304 | week = defaults(w.w, curWeek.week); 2305 | 2306 | if (w.d != null) { 2307 | // weekday -- low day numbers are considered next week 2308 | weekday = w.d; 2309 | if (weekday < 0 || weekday > 6) { 2310 | weekdayOverflow = true; 2311 | } 2312 | } else if (w.e != null) { 2313 | // local weekday -- counting starts from begining of week 2314 | weekday = w.e + dow; 2315 | if (w.e < 0 || w.e > 6) { 2316 | weekdayOverflow = true; 2317 | } 2318 | } else { 2319 | // default to begining of week 2320 | weekday = dow; 2321 | } 2322 | } 2323 | if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { 2324 | getParsingFlags(config)._overflowWeeks = true; 2325 | } else if (weekdayOverflow != null) { 2326 | getParsingFlags(config)._overflowWeekday = true; 2327 | } else { 2328 | temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); 2329 | config._a[YEAR] = temp.year; 2330 | config._dayOfYear = temp.dayOfYear; 2331 | } 2332 | } 2333 | 2334 | // constant that refers to the ISO standard 2335 | hooks.ISO_8601 = function () {}; 2336 | 2337 | // constant that refers to the RFC 2822 form 2338 | hooks.RFC_2822 = function () {}; 2339 | 2340 | // date from string and format string 2341 | function configFromStringAndFormat(config) { 2342 | // TODO: Move this to another part of the creation flow to prevent circular deps 2343 | if (config._f === hooks.ISO_8601) { 2344 | configFromISO(config); 2345 | return; 2346 | } 2347 | if (config._f === hooks.RFC_2822) { 2348 | configFromRFC2822(config); 2349 | return; 2350 | } 2351 | config._a = []; 2352 | getParsingFlags(config).empty = true; 2353 | 2354 | // This array is used to make a Date, either with `new Date` or `Date.UTC` 2355 | var string = '' + config._i, 2356 | i, parsedInput, tokens, token, skipped, 2357 | stringLength = string.length, 2358 | totalParsedInputLength = 0; 2359 | 2360 | tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; 2361 | 2362 | for (i = 0; i < tokens.length; i++) { 2363 | token = tokens[i]; 2364 | parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; 2365 | // console.log('token', token, 'parsedInput', parsedInput, 2366 | // 'regex', getParseRegexForToken(token, config)); 2367 | if (parsedInput) { 2368 | skipped = string.substr(0, string.indexOf(parsedInput)); 2369 | if (skipped.length > 0) { 2370 | getParsingFlags(config).unusedInput.push(skipped); 2371 | } 2372 | string = string.slice(string.indexOf(parsedInput) + parsedInput.length); 2373 | totalParsedInputLength += parsedInput.length; 2374 | } 2375 | // don't parse if it's not a known token 2376 | if (formatTokenFunctions[token]) { 2377 | if (parsedInput) { 2378 | getParsingFlags(config).empty = false; 2379 | } 2380 | else { 2381 | getParsingFlags(config).unusedTokens.push(token); 2382 | } 2383 | addTimeToArrayFromToken(token, parsedInput, config); 2384 | } 2385 | else if (config._strict && !parsedInput) { 2386 | getParsingFlags(config).unusedTokens.push(token); 2387 | } 2388 | } 2389 | 2390 | // add remaining unparsed input length to the string 2391 | getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; 2392 | if (string.length > 0) { 2393 | getParsingFlags(config).unusedInput.push(string); 2394 | } 2395 | 2396 | // clear _12h flag if hour is <= 12 2397 | if (config._a[HOUR] <= 12 && 2398 | getParsingFlags(config).bigHour === true && 2399 | config._a[HOUR] > 0) { 2400 | getParsingFlags(config).bigHour = undefined; 2401 | } 2402 | 2403 | getParsingFlags(config).parsedDateParts = config._a.slice(0); 2404 | getParsingFlags(config).meridiem = config._meridiem; 2405 | // handle meridiem 2406 | config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); 2407 | 2408 | configFromArray(config); 2409 | checkOverflow(config); 2410 | } 2411 | 2412 | 2413 | function meridiemFixWrap (locale, hour, meridiem) { 2414 | var isPm; 2415 | 2416 | if (meridiem == null) { 2417 | // nothing to do 2418 | return hour; 2419 | } 2420 | if (locale.meridiemHour != null) { 2421 | return locale.meridiemHour(hour, meridiem); 2422 | } else if (locale.isPM != null) { 2423 | // Fallback 2424 | isPm = locale.isPM(meridiem); 2425 | if (isPm && hour < 12) { 2426 | hour += 12; 2427 | } 2428 | if (!isPm && hour === 12) { 2429 | hour = 0; 2430 | } 2431 | return hour; 2432 | } else { 2433 | // this is not supposed to happen 2434 | return hour; 2435 | } 2436 | } 2437 | 2438 | // date from string and array of format strings 2439 | function configFromStringAndArray(config) { 2440 | var tempConfig, 2441 | bestMoment, 2442 | 2443 | scoreToBeat, 2444 | i, 2445 | currentScore; 2446 | 2447 | if (config._f.length === 0) { 2448 | getParsingFlags(config).invalidFormat = true; 2449 | config._d = new Date(NaN); 2450 | return; 2451 | } 2452 | 2453 | for (i = 0; i < config._f.length; i++) { 2454 | currentScore = 0; 2455 | tempConfig = copyConfig({}, config); 2456 | if (config._useUTC != null) { 2457 | tempConfig._useUTC = config._useUTC; 2458 | } 2459 | tempConfig._f = config._f[i]; 2460 | configFromStringAndFormat(tempConfig); 2461 | 2462 | if (!isValid(tempConfig)) { 2463 | continue; 2464 | } 2465 | 2466 | // if there is any input that was not parsed add a penalty for that format 2467 | currentScore += getParsingFlags(tempConfig).charsLeftOver; 2468 | 2469 | //or tokens 2470 | currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; 2471 | 2472 | getParsingFlags(tempConfig).score = currentScore; 2473 | 2474 | if (scoreToBeat == null || currentScore < scoreToBeat) { 2475 | scoreToBeat = currentScore; 2476 | bestMoment = tempConfig; 2477 | } 2478 | } 2479 | 2480 | extend(config, bestMoment || tempConfig); 2481 | } 2482 | 2483 | function configFromObject(config) { 2484 | if (config._d) { 2485 | return; 2486 | } 2487 | 2488 | var i = normalizeObjectUnits(config._i); 2489 | config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { 2490 | return obj && parseInt(obj, 10); 2491 | }); 2492 | 2493 | configFromArray(config); 2494 | } 2495 | 2496 | function createFromConfig (config) { 2497 | var res = new Moment(checkOverflow(prepareConfig(config))); 2498 | if (res._nextDay) { 2499 | // Adding is smart enough around DST 2500 | res.add(1, 'd'); 2501 | res._nextDay = undefined; 2502 | } 2503 | 2504 | return res; 2505 | } 2506 | 2507 | function prepareConfig (config) { 2508 | var input = config._i, 2509 | format = config._f; 2510 | 2511 | config._locale = config._locale || getLocale(config._l); 2512 | 2513 | if (input === null || (format === undefined && input === '')) { 2514 | return createInvalid({nullInput: true}); 2515 | } 2516 | 2517 | if (typeof input === 'string') { 2518 | config._i = input = config._locale.preparse(input); 2519 | } 2520 | 2521 | if (isMoment(input)) { 2522 | return new Moment(checkOverflow(input)); 2523 | } else if (isDate(input)) { 2524 | config._d = input; 2525 | } else if (isArray(format)) { 2526 | configFromStringAndArray(config); 2527 | } else if (format) { 2528 | configFromStringAndFormat(config); 2529 | } else { 2530 | configFromInput(config); 2531 | } 2532 | 2533 | if (!isValid(config)) { 2534 | config._d = null; 2535 | } 2536 | 2537 | return config; 2538 | } 2539 | 2540 | function configFromInput(config) { 2541 | var input = config._i; 2542 | if (isUndefined(input)) { 2543 | config._d = new Date(hooks.now()); 2544 | } else if (isDate(input)) { 2545 | config._d = new Date(input.valueOf()); 2546 | } else if (typeof input === 'string') { 2547 | configFromString(config); 2548 | } else if (isArray(input)) { 2549 | config._a = map(input.slice(0), function (obj) { 2550 | return parseInt(obj, 10); 2551 | }); 2552 | configFromArray(config); 2553 | } else if (isObject(input)) { 2554 | configFromObject(config); 2555 | } else if (isNumber(input)) { 2556 | // from milliseconds 2557 | config._d = new Date(input); 2558 | } else { 2559 | hooks.createFromInputFallback(config); 2560 | } 2561 | } 2562 | 2563 | function createLocalOrUTC (input, format, locale, strict, isUTC) { 2564 | var c = {}; 2565 | 2566 | if (locale === true || locale === false) { 2567 | strict = locale; 2568 | locale = undefined; 2569 | } 2570 | 2571 | if ((isObject(input) && isObjectEmpty(input)) || 2572 | (isArray(input) && input.length === 0)) { 2573 | input = undefined; 2574 | } 2575 | // object construction must be done this way. 2576 | // https://github.com/moment/moment/issues/1423 2577 | c._isAMomentObject = true; 2578 | c._useUTC = c._isUTC = isUTC; 2579 | c._l = locale; 2580 | c._i = input; 2581 | c._f = format; 2582 | c._strict = strict; 2583 | 2584 | return createFromConfig(c); 2585 | } 2586 | 2587 | function createLocal (input, format, locale, strict) { 2588 | return createLocalOrUTC(input, format, locale, strict, false); 2589 | } 2590 | 2591 | var prototypeMin = deprecate( 2592 | 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', 2593 | function () { 2594 | var other = createLocal.apply(null, arguments); 2595 | if (this.isValid() && other.isValid()) { 2596 | return other < this ? this : other; 2597 | } else { 2598 | return createInvalid(); 2599 | } 2600 | } 2601 | ); 2602 | 2603 | var prototypeMax = deprecate( 2604 | 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', 2605 | function () { 2606 | var other = createLocal.apply(null, arguments); 2607 | if (this.isValid() && other.isValid()) { 2608 | return other > this ? this : other; 2609 | } else { 2610 | return createInvalid(); 2611 | } 2612 | } 2613 | ); 2614 | 2615 | // Pick a moment m from moments so that m[fn](other) is true for all 2616 | // other. This relies on the function fn to be transitive. 2617 | // 2618 | // moments should either be an array of moment objects or an array, whose 2619 | // first element is an array of moment objects. 2620 | function pickBy(fn, moments) { 2621 | var res, i; 2622 | if (moments.length === 1 && isArray(moments[0])) { 2623 | moments = moments[0]; 2624 | } 2625 | if (!moments.length) { 2626 | return createLocal(); 2627 | } 2628 | res = moments[0]; 2629 | for (i = 1; i < moments.length; ++i) { 2630 | if (!moments[i].isValid() || moments[i][fn](res)) { 2631 | res = moments[i]; 2632 | } 2633 | } 2634 | return res; 2635 | } 2636 | 2637 | // TODO: Use [].sort instead? 2638 | function min () { 2639 | var args = [].slice.call(arguments, 0); 2640 | 2641 | return pickBy('isBefore', args); 2642 | } 2643 | 2644 | function max () { 2645 | var args = [].slice.call(arguments, 0); 2646 | 2647 | return pickBy('isAfter', args); 2648 | } 2649 | 2650 | var now = function () { 2651 | return Date.now ? Date.now() : +(new Date()); 2652 | }; 2653 | 2654 | var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond']; 2655 | 2656 | function isDurationValid(m) { 2657 | for (var key in m) { 2658 | if (!(ordering.indexOf(key) !== -1 && (m[key] == null || !isNaN(m[key])))) { 2659 | return false; 2660 | } 2661 | } 2662 | 2663 | var unitHasDecimal = false; 2664 | for (var i = 0; i < ordering.length; ++i) { 2665 | if (m[ordering[i]]) { 2666 | if (unitHasDecimal) { 2667 | return false; // only allow non-integers for smallest unit 2668 | } 2669 | if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { 2670 | unitHasDecimal = true; 2671 | } 2672 | } 2673 | } 2674 | 2675 | return true; 2676 | } 2677 | 2678 | function isValid$1() { 2679 | return this._isValid; 2680 | } 2681 | 2682 | function createInvalid$1() { 2683 | return createDuration(NaN); 2684 | } 2685 | 2686 | function Duration (duration) { 2687 | var normalizedInput = normalizeObjectUnits(duration), 2688 | years = normalizedInput.year || 0, 2689 | quarters = normalizedInput.quarter || 0, 2690 | months = normalizedInput.month || 0, 2691 | weeks = normalizedInput.week || 0, 2692 | days = normalizedInput.day || 0, 2693 | hours = normalizedInput.hour || 0, 2694 | minutes = normalizedInput.minute || 0, 2695 | seconds = normalizedInput.second || 0, 2696 | milliseconds = normalizedInput.millisecond || 0; 2697 | 2698 | this._isValid = isDurationValid(normalizedInput); 2699 | 2700 | // representation for dateAddRemove 2701 | this._milliseconds = +milliseconds + 2702 | seconds * 1e3 + // 1000 2703 | minutes * 6e4 + // 1000 * 60 2704 | hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 2705 | // Because of dateAddRemove treats 24 hours as different from a 2706 | // day when working around DST, we need to store them separately 2707 | this._days = +days + 2708 | weeks * 7; 2709 | // It is impossible translate months into days without knowing 2710 | // which months you are are talking about, so we have to store 2711 | // it separately. 2712 | this._months = +months + 2713 | quarters * 3 + 2714 | years * 12; 2715 | 2716 | this._data = {}; 2717 | 2718 | this._locale = getLocale(); 2719 | 2720 | this._bubble(); 2721 | } 2722 | 2723 | function isDuration (obj) { 2724 | return obj instanceof Duration; 2725 | } 2726 | 2727 | function absRound (number) { 2728 | if (number < 0) { 2729 | return Math.round(-1 * number) * -1; 2730 | } else { 2731 | return Math.round(number); 2732 | } 2733 | } 2734 | 2735 | // FORMATTING 2736 | 2737 | function offset (token, separator) { 2738 | addFormatToken(token, 0, 0, function () { 2739 | var offset = this.utcOffset(); 2740 | var sign = '+'; 2741 | if (offset < 0) { 2742 | offset = -offset; 2743 | sign = '-'; 2744 | } 2745 | return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); 2746 | }); 2747 | } 2748 | 2749 | offset('Z', ':'); 2750 | offset('ZZ', ''); 2751 | 2752 | // PARSING 2753 | 2754 | addRegexToken('Z', matchShortOffset); 2755 | addRegexToken('ZZ', matchShortOffset); 2756 | addParseToken(['Z', 'ZZ'], function (input, array, config) { 2757 | config._useUTC = true; 2758 | config._tzm = offsetFromString(matchShortOffset, input); 2759 | }); 2760 | 2761 | // HELPERS 2762 | 2763 | // timezone chunker 2764 | // '+10:00' > ['10', '00'] 2765 | // '-1530' > ['-15', '30'] 2766 | var chunkOffset = /([\+\-]|\d\d)/gi; 2767 | 2768 | function offsetFromString(matcher, string) { 2769 | var matches = (string || '').match(matcher); 2770 | 2771 | if (matches === null) { 2772 | return null; 2773 | } 2774 | 2775 | var chunk = matches[matches.length - 1] || []; 2776 | var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; 2777 | var minutes = +(parts[1] * 60) + toInt(parts[2]); 2778 | 2779 | return minutes === 0 ? 2780 | 0 : 2781 | parts[0] === '+' ? minutes : -minutes; 2782 | } 2783 | 2784 | // Return a moment from input, that is local/utc/zone equivalent to model. 2785 | function cloneWithOffset(input, model) { 2786 | var res, diff; 2787 | if (model._isUTC) { 2788 | res = model.clone(); 2789 | diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); 2790 | // Use low-level api, because this fn is low-level api. 2791 | res._d.setTime(res._d.valueOf() + diff); 2792 | hooks.updateOffset(res, false); 2793 | return res; 2794 | } else { 2795 | return createLocal(input).local(); 2796 | } 2797 | } 2798 | 2799 | function getDateOffset (m) { 2800 | // On Firefox.24 Date#getTimezoneOffset returns a floating point. 2801 | // https://github.com/moment/moment/pull/1871 2802 | return -Math.round(m._d.getTimezoneOffset() / 15) * 15; 2803 | } 2804 | 2805 | // HOOKS 2806 | 2807 | // This function will be called whenever a moment is mutated. 2808 | // It is intended to keep the offset in sync with the timezone. 2809 | hooks.updateOffset = function () {}; 2810 | 2811 | // MOMENTS 2812 | 2813 | // keepLocalTime = true means only change the timezone, without 2814 | // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> 2815 | // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset 2816 | // +0200, so we adjust the time as needed, to be valid. 2817 | // 2818 | // Keeping the time actually adds/subtracts (one hour) 2819 | // from the actual represented time. That is why we call updateOffset 2820 | // a second time. In case it wants us to change the offset again 2821 | // _changeInProgress == true case, then we have to adjust, because 2822 | // there is no such time in the given timezone. 2823 | function getSetOffset (input, keepLocalTime, keepMinutes) { 2824 | var offset = this._offset || 0, 2825 | localAdjust; 2826 | if (!this.isValid()) { 2827 | return input != null ? this : NaN; 2828 | } 2829 | if (input != null) { 2830 | if (typeof input === 'string') { 2831 | input = offsetFromString(matchShortOffset, input); 2832 | if (input === null) { 2833 | return this; 2834 | } 2835 | } else if (Math.abs(input) < 16 && !keepMinutes) { 2836 | input = input * 60; 2837 | } 2838 | if (!this._isUTC && keepLocalTime) { 2839 | localAdjust = getDateOffset(this); 2840 | } 2841 | this._offset = input; 2842 | this._isUTC = true; 2843 | if (localAdjust != null) { 2844 | this.add(localAdjust, 'm'); 2845 | } 2846 | if (offset !== input) { 2847 | if (!keepLocalTime || this._changeInProgress) { 2848 | addSubtract(this, createDuration(input - offset, 'm'), 1, false); 2849 | } else if (!this._changeInProgress) { 2850 | this._changeInProgress = true; 2851 | hooks.updateOffset(this, true); 2852 | this._changeInProgress = null; 2853 | } 2854 | } 2855 | return this; 2856 | } else { 2857 | return this._isUTC ? offset : getDateOffset(this); 2858 | } 2859 | } 2860 | 2861 | function getSetZone (input, keepLocalTime) { 2862 | if (input != null) { 2863 | if (typeof input !== 'string') { 2864 | input = -input; 2865 | } 2866 | 2867 | this.utcOffset(input, keepLocalTime); 2868 | 2869 | return this; 2870 | } else { 2871 | return -this.utcOffset(); 2872 | } 2873 | } 2874 | 2875 | function setOffsetToUTC (keepLocalTime) { 2876 | return this.utcOffset(0, keepLocalTime); 2877 | } 2878 | 2879 | function setOffsetToLocal (keepLocalTime) { 2880 | if (this._isUTC) { 2881 | this.utcOffset(0, keepLocalTime); 2882 | this._isUTC = false; 2883 | 2884 | if (keepLocalTime) { 2885 | this.subtract(getDateOffset(this), 'm'); 2886 | } 2887 | } 2888 | return this; 2889 | } 2890 | 2891 | function setOffsetToParsedOffset () { 2892 | if (this._tzm != null) { 2893 | this.utcOffset(this._tzm, false, true); 2894 | } else if (typeof this._i === 'string') { 2895 | var tZone = offsetFromString(matchOffset, this._i); 2896 | if (tZone != null) { 2897 | this.utcOffset(tZone); 2898 | } 2899 | else { 2900 | this.utcOffset(0, true); 2901 | } 2902 | } 2903 | return this; 2904 | } 2905 | 2906 | function hasAlignedHourOffset (input) { 2907 | if (!this.isValid()) { 2908 | return false; 2909 | } 2910 | input = input ? createLocal(input).utcOffset() : 0; 2911 | 2912 | return (this.utcOffset() - input) % 60 === 0; 2913 | } 2914 | 2915 | function isDaylightSavingTime () { 2916 | return ( 2917 | this.utcOffset() > this.clone().month(0).utcOffset() || 2918 | this.utcOffset() > this.clone().month(5).utcOffset() 2919 | ); 2920 | } 2921 | 2922 | function isDaylightSavingTimeShifted () { 2923 | if (!isUndefined(this._isDSTShifted)) { 2924 | return this._isDSTShifted; 2925 | } 2926 | 2927 | var c = {}; 2928 | 2929 | copyConfig(c, this); 2930 | c = prepareConfig(c); 2931 | 2932 | if (c._a) { 2933 | var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); 2934 | this._isDSTShifted = this.isValid() && 2935 | compareArrays(c._a, other.toArray()) > 0; 2936 | } else { 2937 | this._isDSTShifted = false; 2938 | } 2939 | 2940 | return this._isDSTShifted; 2941 | } 2942 | 2943 | function isLocal () { 2944 | return this.isValid() ? !this._isUTC : false; 2945 | } 2946 | 2947 | function isUtcOffset () { 2948 | return this.isValid() ? this._isUTC : false; 2949 | } 2950 | 2951 | function isUtc () { 2952 | return this.isValid() ? this._isUTC && this._offset === 0 : false; 2953 | } 2954 | 2955 | // ASP.NET json date format regex 2956 | var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; 2957 | 2958 | // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html 2959 | // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere 2960 | // and further modified to allow for strings containing both week and day 2961 | var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; 2962 | 2963 | function createDuration (input, key) { 2964 | var duration = input, 2965 | // matching against regexp is expensive, do it on demand 2966 | match = null, 2967 | sign, 2968 | ret, 2969 | diffRes; 2970 | 2971 | if (isDuration(input)) { 2972 | duration = { 2973 | ms : input._milliseconds, 2974 | d : input._days, 2975 | M : input._months 2976 | }; 2977 | } else if (isNumber(input)) { 2978 | duration = {}; 2979 | if (key) { 2980 | duration[key] = input; 2981 | } else { 2982 | duration.milliseconds = input; 2983 | } 2984 | } else if (!!(match = aspNetRegex.exec(input))) { 2985 | sign = (match[1] === '-') ? -1 : 1; 2986 | duration = { 2987 | y : 0, 2988 | d : toInt(match[DATE]) * sign, 2989 | h : toInt(match[HOUR]) * sign, 2990 | m : toInt(match[MINUTE]) * sign, 2991 | s : toInt(match[SECOND]) * sign, 2992 | ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match 2993 | }; 2994 | } else if (!!(match = isoRegex.exec(input))) { 2995 | sign = (match[1] === '-') ? -1 : 1; 2996 | duration = { 2997 | y : parseIso(match[2], sign), 2998 | M : parseIso(match[3], sign), 2999 | w : parseIso(match[4], sign), 3000 | d : parseIso(match[5], sign), 3001 | h : parseIso(match[6], sign), 3002 | m : parseIso(match[7], sign), 3003 | s : parseIso(match[8], sign) 3004 | }; 3005 | } else if (duration == null) {// checks for null or undefined 3006 | duration = {}; 3007 | } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { 3008 | diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); 3009 | 3010 | duration = {}; 3011 | duration.ms = diffRes.milliseconds; 3012 | duration.M = diffRes.months; 3013 | } 3014 | 3015 | ret = new Duration(duration); 3016 | 3017 | if (isDuration(input) && hasOwnProp(input, '_locale')) { 3018 | ret._locale = input._locale; 3019 | } 3020 | 3021 | return ret; 3022 | } 3023 | 3024 | createDuration.fn = Duration.prototype; 3025 | createDuration.invalid = createInvalid$1; 3026 | 3027 | function parseIso (inp, sign) { 3028 | // We'd normally use ~~inp for this, but unfortunately it also 3029 | // converts floats to ints. 3030 | // inp may be undefined, so careful calling replace on it. 3031 | var res = inp && parseFloat(inp.replace(',', '.')); 3032 | // apply sign while we're at it 3033 | return (isNaN(res) ? 0 : res) * sign; 3034 | } 3035 | 3036 | function positiveMomentsDifference(base, other) { 3037 | var res = {milliseconds: 0, months: 0}; 3038 | 3039 | res.months = other.month() - base.month() + 3040 | (other.year() - base.year()) * 12; 3041 | if (base.clone().add(res.months, 'M').isAfter(other)) { 3042 | --res.months; 3043 | } 3044 | 3045 | res.milliseconds = +other - +(base.clone().add(res.months, 'M')); 3046 | 3047 | return res; 3048 | } 3049 | 3050 | function momentsDifference(base, other) { 3051 | var res; 3052 | if (!(base.isValid() && other.isValid())) { 3053 | return {milliseconds: 0, months: 0}; 3054 | } 3055 | 3056 | other = cloneWithOffset(other, base); 3057 | if (base.isBefore(other)) { 3058 | res = positiveMomentsDifference(base, other); 3059 | } else { 3060 | res = positiveMomentsDifference(other, base); 3061 | res.milliseconds = -res.milliseconds; 3062 | res.months = -res.months; 3063 | } 3064 | 3065 | return res; 3066 | } 3067 | 3068 | // TODO: remove 'name' arg after deprecation is removed 3069 | function createAdder(direction, name) { 3070 | return function (val, period) { 3071 | var dur, tmp; 3072 | //invert the arguments, but complain about it 3073 | if (period !== null && !isNaN(+period)) { 3074 | deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + 3075 | 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); 3076 | tmp = val; val = period; period = tmp; 3077 | } 3078 | 3079 | val = typeof val === 'string' ? +val : val; 3080 | dur = createDuration(val, period); 3081 | addSubtract(this, dur, direction); 3082 | return this; 3083 | }; 3084 | } 3085 | 3086 | function addSubtract (mom, duration, isAdding, updateOffset) { 3087 | var milliseconds = duration._milliseconds, 3088 | days = absRound(duration._days), 3089 | months = absRound(duration._months); 3090 | 3091 | if (!mom.isValid()) { 3092 | // No op 3093 | return; 3094 | } 3095 | 3096 | updateOffset = updateOffset == null ? true : updateOffset; 3097 | 3098 | if (milliseconds) { 3099 | mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); 3100 | } 3101 | if (days) { 3102 | set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); 3103 | } 3104 | if (months) { 3105 | setMonth(mom, get(mom, 'Month') + months * isAdding); 3106 | } 3107 | if (updateOffset) { 3108 | hooks.updateOffset(mom, days || months); 3109 | } 3110 | } 3111 | 3112 | var add = createAdder(1, 'add'); 3113 | var subtract = createAdder(-1, 'subtract'); 3114 | 3115 | function getCalendarFormat(myMoment, now) { 3116 | var diff = myMoment.diff(now, 'days', true); 3117 | return diff < -6 ? 'sameElse' : 3118 | diff < -1 ? 'lastWeek' : 3119 | diff < 0 ? 'lastDay' : 3120 | diff < 1 ? 'sameDay' : 3121 | diff < 2 ? 'nextDay' : 3122 | diff < 7 ? 'nextWeek' : 'sameElse'; 3123 | } 3124 | 3125 | function calendar$1 (time, formats) { 3126 | // We want to compare the start of today, vs this. 3127 | // Getting start-of-today depends on whether we're local/utc/offset or not. 3128 | var now = time || createLocal(), 3129 | sod = cloneWithOffset(now, this).startOf('day'), 3130 | format = hooks.calendarFormat(this, sod) || 'sameElse'; 3131 | 3132 | var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); 3133 | 3134 | return this.format(output || this.localeData().calendar(format, this, createLocal(now))); 3135 | } 3136 | 3137 | function clone () { 3138 | return new Moment(this); 3139 | } 3140 | 3141 | function isAfter (input, units) { 3142 | var localInput = isMoment(input) ? input : createLocal(input); 3143 | if (!(this.isValid() && localInput.isValid())) { 3144 | return false; 3145 | } 3146 | units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); 3147 | if (units === 'millisecond') { 3148 | return this.valueOf() > localInput.valueOf(); 3149 | } else { 3150 | return localInput.valueOf() < this.clone().startOf(units).valueOf(); 3151 | } 3152 | } 3153 | 3154 | function isBefore (input, units) { 3155 | var localInput = isMoment(input) ? input : createLocal(input); 3156 | if (!(this.isValid() && localInput.isValid())) { 3157 | return false; 3158 | } 3159 | units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); 3160 | if (units === 'millisecond') { 3161 | return this.valueOf() < localInput.valueOf(); 3162 | } else { 3163 | return this.clone().endOf(units).valueOf() < localInput.valueOf(); 3164 | } 3165 | } 3166 | 3167 | function isBetween (from, to, units, inclusivity) { 3168 | inclusivity = inclusivity || '()'; 3169 | return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && 3170 | (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); 3171 | } 3172 | 3173 | function isSame (input, units) { 3174 | var localInput = isMoment(input) ? input : createLocal(input), 3175 | inputMs; 3176 | if (!(this.isValid() && localInput.isValid())) { 3177 | return false; 3178 | } 3179 | units = normalizeUnits(units || 'millisecond'); 3180 | if (units === 'millisecond') { 3181 | return this.valueOf() === localInput.valueOf(); 3182 | } else { 3183 | inputMs = localInput.valueOf(); 3184 | return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); 3185 | } 3186 | } 3187 | 3188 | function isSameOrAfter (input, units) { 3189 | return this.isSame(input, units) || this.isAfter(input,units); 3190 | } 3191 | 3192 | function isSameOrBefore (input, units) { 3193 | return this.isSame(input, units) || this.isBefore(input,units); 3194 | } 3195 | 3196 | function diff (input, units, asFloat) { 3197 | var that, 3198 | zoneDelta, 3199 | delta, output; 3200 | 3201 | if (!this.isValid()) { 3202 | return NaN; 3203 | } 3204 | 3205 | that = cloneWithOffset(input, this); 3206 | 3207 | if (!that.isValid()) { 3208 | return NaN; 3209 | } 3210 | 3211 | zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; 3212 | 3213 | units = normalizeUnits(units); 3214 | 3215 | if (units === 'year' || units === 'month' || units === 'quarter') { 3216 | output = monthDiff(this, that); 3217 | if (units === 'quarter') { 3218 | output = output / 3; 3219 | } else if (units === 'year') { 3220 | output = output / 12; 3221 | } 3222 | } else { 3223 | delta = this - that; 3224 | output = units === 'second' ? delta / 1e3 : // 1000 3225 | units === 'minute' ? delta / 6e4 : // 1000 * 60 3226 | units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 3227 | units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst 3228 | units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst 3229 | delta; 3230 | } 3231 | return asFloat ? output : absFloor(output); 3232 | } 3233 | 3234 | function monthDiff (a, b) { 3235 | // difference in months 3236 | var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), 3237 | // b is in (anchor - 1 month, anchor + 1 month) 3238 | anchor = a.clone().add(wholeMonthDiff, 'months'), 3239 | anchor2, adjust; 3240 | 3241 | if (b - anchor < 0) { 3242 | anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); 3243 | // linear across the month 3244 | adjust = (b - anchor) / (anchor - anchor2); 3245 | } else { 3246 | anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); 3247 | // linear across the month 3248 | adjust = (b - anchor) / (anchor2 - anchor); 3249 | } 3250 | 3251 | //check for negative zero, return zero if negative zero 3252 | return -(wholeMonthDiff + adjust) || 0; 3253 | } 3254 | 3255 | hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; 3256 | hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; 3257 | 3258 | function toString () { 3259 | return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); 3260 | } 3261 | 3262 | function toISOString() { 3263 | if (!this.isValid()) { 3264 | return null; 3265 | } 3266 | var m = this.clone().utc(); 3267 | if (m.year() < 0 || m.year() > 9999) { 3268 | return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); 3269 | } 3270 | if (isFunction(Date.prototype.toISOString)) { 3271 | // native implementation is ~50x faster, use it when we can 3272 | return this.toDate().toISOString(); 3273 | } 3274 | return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); 3275 | } 3276 | 3277 | /** 3278 | * Return a human readable representation of a moment that can 3279 | * also be evaluated to get a new moment which is the same 3280 | * 3281 | * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects 3282 | */ 3283 | function inspect () { 3284 | if (!this.isValid()) { 3285 | return 'moment.invalid(/* ' + this._i + ' */)'; 3286 | } 3287 | var func = 'moment'; 3288 | var zone = ''; 3289 | if (!this.isLocal()) { 3290 | func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; 3291 | zone = 'Z'; 3292 | } 3293 | var prefix = '[' + func + '("]'; 3294 | var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; 3295 | var datetime = '-MM-DD[T]HH:mm:ss.SSS'; 3296 | var suffix = zone + '[")]'; 3297 | 3298 | return this.format(prefix + year + datetime + suffix); 3299 | } 3300 | 3301 | function format (inputString) { 3302 | if (!inputString) { 3303 | inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; 3304 | } 3305 | var output = formatMoment(this, inputString); 3306 | return this.localeData().postformat(output); 3307 | } 3308 | 3309 | function from (time, withoutSuffix) { 3310 | if (this.isValid() && 3311 | ((isMoment(time) && time.isValid()) || 3312 | createLocal(time).isValid())) { 3313 | return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); 3314 | } else { 3315 | return this.localeData().invalidDate(); 3316 | } 3317 | } 3318 | 3319 | function fromNow (withoutSuffix) { 3320 | return this.from(createLocal(), withoutSuffix); 3321 | } 3322 | 3323 | function to (time, withoutSuffix) { 3324 | if (this.isValid() && 3325 | ((isMoment(time) && time.isValid()) || 3326 | createLocal(time).isValid())) { 3327 | return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); 3328 | } else { 3329 | return this.localeData().invalidDate(); 3330 | } 3331 | } 3332 | 3333 | function toNow (withoutSuffix) { 3334 | return this.to(createLocal(), withoutSuffix); 3335 | } 3336 | 3337 | // If passed a locale key, it will set the locale for this 3338 | // instance. Otherwise, it will return the locale configuration 3339 | // variables for this instance. 3340 | function locale (key) { 3341 | var newLocaleData; 3342 | 3343 | if (key === undefined) { 3344 | return this._locale._abbr; 3345 | } else { 3346 | newLocaleData = getLocale(key); 3347 | if (newLocaleData != null) { 3348 | this._locale = newLocaleData; 3349 | } 3350 | return this; 3351 | } 3352 | } 3353 | 3354 | var lang = deprecate( 3355 | 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', 3356 | function (key) { 3357 | if (key === undefined) { 3358 | return this.localeData(); 3359 | } else { 3360 | return this.locale(key); 3361 | } 3362 | } 3363 | ); 3364 | 3365 | function localeData () { 3366 | return this._locale; 3367 | } 3368 | 3369 | function startOf (units) { 3370 | units = normalizeUnits(units); 3371 | // the following switch intentionally omits break keywords 3372 | // to utilize falling through the cases. 3373 | switch (units) { 3374 | case 'year': 3375 | this.month(0); 3376 | /* falls through */ 3377 | case 'quarter': 3378 | case 'month': 3379 | this.date(1); 3380 | /* falls through */ 3381 | case 'week': 3382 | case 'isoWeek': 3383 | case 'day': 3384 | case 'date': 3385 | this.hours(0); 3386 | /* falls through */ 3387 | case 'hour': 3388 | this.minutes(0); 3389 | /* falls through */ 3390 | case 'minute': 3391 | this.seconds(0); 3392 | /* falls through */ 3393 | case 'second': 3394 | this.milliseconds(0); 3395 | } 3396 | 3397 | // weeks are a special case 3398 | if (units === 'week') { 3399 | this.weekday(0); 3400 | } 3401 | if (units === 'isoWeek') { 3402 | this.isoWeekday(1); 3403 | } 3404 | 3405 | // quarters are also special 3406 | if (units === 'quarter') { 3407 | this.month(Math.floor(this.month() / 3) * 3); 3408 | } 3409 | 3410 | return this; 3411 | } 3412 | 3413 | function endOf (units) { 3414 | units = normalizeUnits(units); 3415 | if (units === undefined || units === 'millisecond') { 3416 | return this; 3417 | } 3418 | 3419 | // 'date' is an alias for 'day', so it should be considered as such. 3420 | if (units === 'date') { 3421 | units = 'day'; 3422 | } 3423 | 3424 | return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); 3425 | } 3426 | 3427 | function valueOf () { 3428 | return this._d.valueOf() - ((this._offset || 0) * 60000); 3429 | } 3430 | 3431 | function unix () { 3432 | return Math.floor(this.valueOf() / 1000); 3433 | } 3434 | 3435 | function toDate () { 3436 | return new Date(this.valueOf()); 3437 | } 3438 | 3439 | function toArray () { 3440 | var m = this; 3441 | return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; 3442 | } 3443 | 3444 | function toObject () { 3445 | var m = this; 3446 | return { 3447 | years: m.year(), 3448 | months: m.month(), 3449 | date: m.date(), 3450 | hours: m.hours(), 3451 | minutes: m.minutes(), 3452 | seconds: m.seconds(), 3453 | milliseconds: m.milliseconds() 3454 | }; 3455 | } 3456 | 3457 | function toJSON () { 3458 | // new Date(NaN).toJSON() === null 3459 | return this.isValid() ? this.toISOString() : null; 3460 | } 3461 | 3462 | function isValid$2 () { 3463 | return isValid(this); 3464 | } 3465 | 3466 | function parsingFlags () { 3467 | return extend({}, getParsingFlags(this)); 3468 | } 3469 | 3470 | function invalidAt () { 3471 | return getParsingFlags(this).overflow; 3472 | } 3473 | 3474 | function creationData() { 3475 | return { 3476 | input: this._i, 3477 | format: this._f, 3478 | locale: this._locale, 3479 | isUTC: this._isUTC, 3480 | strict: this._strict 3481 | }; 3482 | } 3483 | 3484 | // FORMATTING 3485 | 3486 | addFormatToken(0, ['gg', 2], 0, function () { 3487 | return this.weekYear() % 100; 3488 | }); 3489 | 3490 | addFormatToken(0, ['GG', 2], 0, function () { 3491 | return this.isoWeekYear() % 100; 3492 | }); 3493 | 3494 | function addWeekYearFormatToken (token, getter) { 3495 | addFormatToken(0, [token, token.length], 0, getter); 3496 | } 3497 | 3498 | addWeekYearFormatToken('gggg', 'weekYear'); 3499 | addWeekYearFormatToken('ggggg', 'weekYear'); 3500 | addWeekYearFormatToken('GGGG', 'isoWeekYear'); 3501 | addWeekYearFormatToken('GGGGG', 'isoWeekYear'); 3502 | 3503 | // ALIASES 3504 | 3505 | addUnitAlias('weekYear', 'gg'); 3506 | addUnitAlias('isoWeekYear', 'GG'); 3507 | 3508 | // PRIORITY 3509 | 3510 | addUnitPriority('weekYear', 1); 3511 | addUnitPriority('isoWeekYear', 1); 3512 | 3513 | 3514 | // PARSING 3515 | 3516 | addRegexToken('G', matchSigned); 3517 | addRegexToken('g', matchSigned); 3518 | addRegexToken('GG', match1to2, match2); 3519 | addRegexToken('gg', match1to2, match2); 3520 | addRegexToken('GGGG', match1to4, match4); 3521 | addRegexToken('gggg', match1to4, match4); 3522 | addRegexToken('GGGGG', match1to6, match6); 3523 | addRegexToken('ggggg', match1to6, match6); 3524 | 3525 | addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { 3526 | week[token.substr(0, 2)] = toInt(input); 3527 | }); 3528 | 3529 | addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { 3530 | week[token] = hooks.parseTwoDigitYear(input); 3531 | }); 3532 | 3533 | // MOMENTS 3534 | 3535 | function getSetWeekYear (input) { 3536 | return getSetWeekYearHelper.call(this, 3537 | input, 3538 | this.week(), 3539 | this.weekday(), 3540 | this.localeData()._week.dow, 3541 | this.localeData()._week.doy); 3542 | } 3543 | 3544 | function getSetISOWeekYear (input) { 3545 | return getSetWeekYearHelper.call(this, 3546 | input, this.isoWeek(), this.isoWeekday(), 1, 4); 3547 | } 3548 | 3549 | function getISOWeeksInYear () { 3550 | return weeksInYear(this.year(), 1, 4); 3551 | } 3552 | 3553 | function getWeeksInYear () { 3554 | var weekInfo = this.localeData()._week; 3555 | return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); 3556 | } 3557 | 3558 | function getSetWeekYearHelper(input, week, weekday, dow, doy) { 3559 | var weeksTarget; 3560 | if (input == null) { 3561 | return weekOfYear(this, dow, doy).year; 3562 | } else { 3563 | weeksTarget = weeksInYear(input, dow, doy); 3564 | if (week > weeksTarget) { 3565 | week = weeksTarget; 3566 | } 3567 | return setWeekAll.call(this, input, week, weekday, dow, doy); 3568 | } 3569 | } 3570 | 3571 | function setWeekAll(weekYear, week, weekday, dow, doy) { 3572 | var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), 3573 | date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); 3574 | 3575 | this.year(date.getUTCFullYear()); 3576 | this.month(date.getUTCMonth()); 3577 | this.date(date.getUTCDate()); 3578 | return this; 3579 | } 3580 | 3581 | // FORMATTING 3582 | 3583 | addFormatToken('Q', 0, 'Qo', 'quarter'); 3584 | 3585 | // ALIASES 3586 | 3587 | addUnitAlias('quarter', 'Q'); 3588 | 3589 | // PRIORITY 3590 | 3591 | addUnitPriority('quarter', 7); 3592 | 3593 | // PARSING 3594 | 3595 | addRegexToken('Q', match1); 3596 | addParseToken('Q', function (input, array) { 3597 | array[MONTH] = (toInt(input) - 1) * 3; 3598 | }); 3599 | 3600 | // MOMENTS 3601 | 3602 | function getSetQuarter (input) { 3603 | return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); 3604 | } 3605 | 3606 | // FORMATTING 3607 | 3608 | addFormatToken('D', ['DD', 2], 'Do', 'date'); 3609 | 3610 | // ALIASES 3611 | 3612 | addUnitAlias('date', 'D'); 3613 | 3614 | // PRIOROITY 3615 | addUnitPriority('date', 9); 3616 | 3617 | // PARSING 3618 | 3619 | addRegexToken('D', match1to2); 3620 | addRegexToken('DD', match1to2, match2); 3621 | addRegexToken('Do', function (isStrict, locale) { 3622 | // TODO: Remove "ordinalParse" fallback in next major release. 3623 | return isStrict ? 3624 | (locale._dayOfMonthOrdinalParse || locale._ordinalParse) : 3625 | locale._dayOfMonthOrdinalParseLenient; 3626 | }); 3627 | 3628 | addParseToken(['D', 'DD'], DATE); 3629 | addParseToken('Do', function (input, array) { 3630 | array[DATE] = toInt(input.match(match1to2)[0], 10); 3631 | }); 3632 | 3633 | // MOMENTS 3634 | 3635 | var getSetDayOfMonth = makeGetSet('Date', true); 3636 | 3637 | // FORMATTING 3638 | 3639 | addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); 3640 | 3641 | // ALIASES 3642 | 3643 | addUnitAlias('dayOfYear', 'DDD'); 3644 | 3645 | // PRIORITY 3646 | addUnitPriority('dayOfYear', 4); 3647 | 3648 | // PARSING 3649 | 3650 | addRegexToken('DDD', match1to3); 3651 | addRegexToken('DDDD', match3); 3652 | addParseToken(['DDD', 'DDDD'], function (input, array, config) { 3653 | config._dayOfYear = toInt(input); 3654 | }); 3655 | 3656 | // HELPERS 3657 | 3658 | // MOMENTS 3659 | 3660 | function getSetDayOfYear (input) { 3661 | var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; 3662 | return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); 3663 | } 3664 | 3665 | // FORMATTING 3666 | 3667 | addFormatToken('m', ['mm', 2], 0, 'minute'); 3668 | 3669 | // ALIASES 3670 | 3671 | addUnitAlias('minute', 'm'); 3672 | 3673 | // PRIORITY 3674 | 3675 | addUnitPriority('minute', 14); 3676 | 3677 | // PARSING 3678 | 3679 | addRegexToken('m', match1to2); 3680 | addRegexToken('mm', match1to2, match2); 3681 | addParseToken(['m', 'mm'], MINUTE); 3682 | 3683 | // MOMENTS 3684 | 3685 | var getSetMinute = makeGetSet('Minutes', false); 3686 | 3687 | // FORMATTING 3688 | 3689 | addFormatToken('s', ['ss', 2], 0, 'second'); 3690 | 3691 | // ALIASES 3692 | 3693 | addUnitAlias('second', 's'); 3694 | 3695 | // PRIORITY 3696 | 3697 | addUnitPriority('second', 15); 3698 | 3699 | // PARSING 3700 | 3701 | addRegexToken('s', match1to2); 3702 | addRegexToken('ss', match1to2, match2); 3703 | addParseToken(['s', 'ss'], SECOND); 3704 | 3705 | // MOMENTS 3706 | 3707 | var getSetSecond = makeGetSet('Seconds', false); 3708 | 3709 | // FORMATTING 3710 | 3711 | addFormatToken('S', 0, 0, function () { 3712 | return ~~(this.millisecond() / 100); 3713 | }); 3714 | 3715 | addFormatToken(0, ['SS', 2], 0, function () { 3716 | return ~~(this.millisecond() / 10); 3717 | }); 3718 | 3719 | addFormatToken(0, ['SSS', 3], 0, 'millisecond'); 3720 | addFormatToken(0, ['SSSS', 4], 0, function () { 3721 | return this.millisecond() * 10; 3722 | }); 3723 | addFormatToken(0, ['SSSSS', 5], 0, function () { 3724 | return this.millisecond() * 100; 3725 | }); 3726 | addFormatToken(0, ['SSSSSS', 6], 0, function () { 3727 | return this.millisecond() * 1000; 3728 | }); 3729 | addFormatToken(0, ['SSSSSSS', 7], 0, function () { 3730 | return this.millisecond() * 10000; 3731 | }); 3732 | addFormatToken(0, ['SSSSSSSS', 8], 0, function () { 3733 | return this.millisecond() * 100000; 3734 | }); 3735 | addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { 3736 | return this.millisecond() * 1000000; 3737 | }); 3738 | 3739 | 3740 | // ALIASES 3741 | 3742 | addUnitAlias('millisecond', 'ms'); 3743 | 3744 | // PRIORITY 3745 | 3746 | addUnitPriority('millisecond', 16); 3747 | 3748 | // PARSING 3749 | 3750 | addRegexToken('S', match1to3, match1); 3751 | addRegexToken('SS', match1to3, match2); 3752 | addRegexToken('SSS', match1to3, match3); 3753 | 3754 | var token; 3755 | for (token = 'SSSS'; token.length <= 9; token += 'S') { 3756 | addRegexToken(token, matchUnsigned); 3757 | } 3758 | 3759 | function parseMs(input, array) { 3760 | array[MILLISECOND] = toInt(('0.' + input) * 1000); 3761 | } 3762 | 3763 | for (token = 'S'; token.length <= 9; token += 'S') { 3764 | addParseToken(token, parseMs); 3765 | } 3766 | // MOMENTS 3767 | 3768 | var getSetMillisecond = makeGetSet('Milliseconds', false); 3769 | 3770 | // FORMATTING 3771 | 3772 | addFormatToken('z', 0, 0, 'zoneAbbr'); 3773 | addFormatToken('zz', 0, 0, 'zoneName'); 3774 | 3775 | // MOMENTS 3776 | 3777 | function getZoneAbbr () { 3778 | return this._isUTC ? 'UTC' : ''; 3779 | } 3780 | 3781 | function getZoneName () { 3782 | return this._isUTC ? 'Coordinated Universal Time' : ''; 3783 | } 3784 | 3785 | var proto = Moment.prototype; 3786 | 3787 | proto.add = add; 3788 | proto.calendar = calendar$1; 3789 | proto.clone = clone; 3790 | proto.diff = diff; 3791 | proto.endOf = endOf; 3792 | proto.format = format; 3793 | proto.from = from; 3794 | proto.fromNow = fromNow; 3795 | proto.to = to; 3796 | proto.toNow = toNow; 3797 | proto.get = stringGet; 3798 | proto.invalidAt = invalidAt; 3799 | proto.isAfter = isAfter; 3800 | proto.isBefore = isBefore; 3801 | proto.isBetween = isBetween; 3802 | proto.isSame = isSame; 3803 | proto.isSameOrAfter = isSameOrAfter; 3804 | proto.isSameOrBefore = isSameOrBefore; 3805 | proto.isValid = isValid$2; 3806 | proto.lang = lang; 3807 | proto.locale = locale; 3808 | proto.localeData = localeData; 3809 | proto.max = prototypeMax; 3810 | proto.min = prototypeMin; 3811 | proto.parsingFlags = parsingFlags; 3812 | proto.set = stringSet; 3813 | proto.startOf = startOf; 3814 | proto.subtract = subtract; 3815 | proto.toArray = toArray; 3816 | proto.toObject = toObject; 3817 | proto.toDate = toDate; 3818 | proto.toISOString = toISOString; 3819 | proto.inspect = inspect; 3820 | proto.toJSON = toJSON; 3821 | proto.toString = toString; 3822 | proto.unix = unix; 3823 | proto.valueOf = valueOf; 3824 | proto.creationData = creationData; 3825 | 3826 | // Year 3827 | proto.year = getSetYear; 3828 | proto.isLeapYear = getIsLeapYear; 3829 | 3830 | // Week Year 3831 | proto.weekYear = getSetWeekYear; 3832 | proto.isoWeekYear = getSetISOWeekYear; 3833 | 3834 | // Quarter 3835 | proto.quarter = proto.quarters = getSetQuarter; 3836 | 3837 | // Month 3838 | proto.month = getSetMonth; 3839 | proto.daysInMonth = getDaysInMonth; 3840 | 3841 | // Week 3842 | proto.week = proto.weeks = getSetWeek; 3843 | proto.isoWeek = proto.isoWeeks = getSetISOWeek; 3844 | proto.weeksInYear = getWeeksInYear; 3845 | proto.isoWeeksInYear = getISOWeeksInYear; 3846 | 3847 | // Day 3848 | proto.date = getSetDayOfMonth; 3849 | proto.day = proto.days = getSetDayOfWeek; 3850 | proto.weekday = getSetLocaleDayOfWeek; 3851 | proto.isoWeekday = getSetISODayOfWeek; 3852 | proto.dayOfYear = getSetDayOfYear; 3853 | 3854 | // Hour 3855 | proto.hour = proto.hours = getSetHour; 3856 | 3857 | // Minute 3858 | proto.minute = proto.minutes = getSetMinute; 3859 | 3860 | // Second 3861 | proto.second = proto.seconds = getSetSecond; 3862 | 3863 | // Millisecond 3864 | proto.millisecond = proto.milliseconds = getSetMillisecond; 3865 | 3866 | // Offset 3867 | proto.utcOffset = getSetOffset; 3868 | proto.utc = setOffsetToUTC; 3869 | proto.local = setOffsetToLocal; 3870 | proto.parseZone = setOffsetToParsedOffset; 3871 | proto.hasAlignedHourOffset = hasAlignedHourOffset; 3872 | proto.isDST = isDaylightSavingTime; 3873 | proto.isLocal = isLocal; 3874 | proto.isUtcOffset = isUtcOffset; 3875 | proto.isUtc = isUtc; 3876 | proto.isUTC = isUtc; 3877 | 3878 | // Timezone 3879 | proto.zoneAbbr = getZoneAbbr; 3880 | proto.zoneName = getZoneName; 3881 | 3882 | // Deprecations 3883 | proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); 3884 | proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); 3885 | proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); 3886 | proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); 3887 | proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); 3888 | 3889 | function createUnix (input) { 3890 | return createLocal(input * 1000); 3891 | } 3892 | 3893 | function createInZone () { 3894 | return createLocal.apply(null, arguments).parseZone(); 3895 | } 3896 | 3897 | function preParsePostFormat (string) { 3898 | return string; 3899 | } 3900 | 3901 | var proto$1 = Locale.prototype; 3902 | 3903 | proto$1.calendar = calendar; 3904 | proto$1.longDateFormat = longDateFormat; 3905 | proto$1.invalidDate = invalidDate; 3906 | proto$1.ordinal = ordinal; 3907 | proto$1.preparse = preParsePostFormat; 3908 | proto$1.postformat = preParsePostFormat; 3909 | proto$1.relativeTime = relativeTime; 3910 | proto$1.pastFuture = pastFuture; 3911 | proto$1.set = set; 3912 | 3913 | // Month 3914 | proto$1.months = localeMonths; 3915 | proto$1.monthsShort = localeMonthsShort; 3916 | proto$1.monthsParse = localeMonthsParse; 3917 | proto$1.monthsRegex = monthsRegex; 3918 | proto$1.monthsShortRegex = monthsShortRegex; 3919 | 3920 | // Week 3921 | proto$1.week = localeWeek; 3922 | proto$1.firstDayOfYear = localeFirstDayOfYear; 3923 | proto$1.firstDayOfWeek = localeFirstDayOfWeek; 3924 | 3925 | // Day of Week 3926 | proto$1.weekdays = localeWeekdays; 3927 | proto$1.weekdaysMin = localeWeekdaysMin; 3928 | proto$1.weekdaysShort = localeWeekdaysShort; 3929 | proto$1.weekdaysParse = localeWeekdaysParse; 3930 | 3931 | proto$1.weekdaysRegex = weekdaysRegex; 3932 | proto$1.weekdaysShortRegex = weekdaysShortRegex; 3933 | proto$1.weekdaysMinRegex = weekdaysMinRegex; 3934 | 3935 | // Hours 3936 | proto$1.isPM = localeIsPM; 3937 | proto$1.meridiem = localeMeridiem; 3938 | 3939 | function get$1 (format, index, field, setter) { 3940 | var locale = getLocale(); 3941 | var utc = createUTC().set(setter, index); 3942 | return locale[field](utc, format); 3943 | } 3944 | 3945 | function listMonthsImpl (format, index, field) { 3946 | if (isNumber(format)) { 3947 | index = format; 3948 | format = undefined; 3949 | } 3950 | 3951 | format = format || ''; 3952 | 3953 | if (index != null) { 3954 | return get$1(format, index, field, 'month'); 3955 | } 3956 | 3957 | var i; 3958 | var out = []; 3959 | for (i = 0; i < 12; i++) { 3960 | out[i] = get$1(format, i, field, 'month'); 3961 | } 3962 | return out; 3963 | } 3964 | 3965 | // () 3966 | // (5) 3967 | // (fmt, 5) 3968 | // (fmt) 3969 | // (true) 3970 | // (true, 5) 3971 | // (true, fmt, 5) 3972 | // (true, fmt) 3973 | function listWeekdaysImpl (localeSorted, format, index, field) { 3974 | if (typeof localeSorted === 'boolean') { 3975 | if (isNumber(format)) { 3976 | index = format; 3977 | format = undefined; 3978 | } 3979 | 3980 | format = format || ''; 3981 | } else { 3982 | format = localeSorted; 3983 | index = format; 3984 | localeSorted = false; 3985 | 3986 | if (isNumber(format)) { 3987 | index = format; 3988 | format = undefined; 3989 | } 3990 | 3991 | format = format || ''; 3992 | } 3993 | 3994 | var locale = getLocale(), 3995 | shift = localeSorted ? locale._week.dow : 0; 3996 | 3997 | if (index != null) { 3998 | return get$1(format, (index + shift) % 7, field, 'day'); 3999 | } 4000 | 4001 | var i; 4002 | var out = []; 4003 | for (i = 0; i < 7; i++) { 4004 | out[i] = get$1(format, (i + shift) % 7, field, 'day'); 4005 | } 4006 | return out; 4007 | } 4008 | 4009 | function listMonths (format, index) { 4010 | return listMonthsImpl(format, index, 'months'); 4011 | } 4012 | 4013 | function listMonthsShort (format, index) { 4014 | return listMonthsImpl(format, index, 'monthsShort'); 4015 | } 4016 | 4017 | function listWeekdays (localeSorted, format, index) { 4018 | return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); 4019 | } 4020 | 4021 | function listWeekdaysShort (localeSorted, format, index) { 4022 | return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); 4023 | } 4024 | 4025 | function listWeekdaysMin (localeSorted, format, index) { 4026 | return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); 4027 | } 4028 | 4029 | getSetGlobalLocale('en', { 4030 | dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, 4031 | ordinal : function (number) { 4032 | var b = number % 10, 4033 | output = (toInt(number % 100 / 10) === 1) ? 'th' : 4034 | (b === 1) ? 'st' : 4035 | (b === 2) ? 'nd' : 4036 | (b === 3) ? 'rd' : 'th'; 4037 | return number + output; 4038 | } 4039 | }); 4040 | 4041 | // Side effect imports 4042 | hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); 4043 | hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); 4044 | 4045 | var mathAbs = Math.abs; 4046 | 4047 | function abs () { 4048 | var data = this._data; 4049 | 4050 | this._milliseconds = mathAbs(this._milliseconds); 4051 | this._days = mathAbs(this._days); 4052 | this._months = mathAbs(this._months); 4053 | 4054 | data.milliseconds = mathAbs(data.milliseconds); 4055 | data.seconds = mathAbs(data.seconds); 4056 | data.minutes = mathAbs(data.minutes); 4057 | data.hours = mathAbs(data.hours); 4058 | data.months = mathAbs(data.months); 4059 | data.years = mathAbs(data.years); 4060 | 4061 | return this; 4062 | } 4063 | 4064 | function addSubtract$1 (duration, input, value, direction) { 4065 | var other = createDuration(input, value); 4066 | 4067 | duration._milliseconds += direction * other._milliseconds; 4068 | duration._days += direction * other._days; 4069 | duration._months += direction * other._months; 4070 | 4071 | return duration._bubble(); 4072 | } 4073 | 4074 | // supports only 2.0-style add(1, 's') or add(duration) 4075 | function add$1 (input, value) { 4076 | return addSubtract$1(this, input, value, 1); 4077 | } 4078 | 4079 | // supports only 2.0-style subtract(1, 's') or subtract(duration) 4080 | function subtract$1 (input, value) { 4081 | return addSubtract$1(this, input, value, -1); 4082 | } 4083 | 4084 | function absCeil (number) { 4085 | if (number < 0) { 4086 | return Math.floor(number); 4087 | } else { 4088 | return Math.ceil(number); 4089 | } 4090 | } 4091 | 4092 | function bubble () { 4093 | var milliseconds = this._milliseconds; 4094 | var days = this._days; 4095 | var months = this._months; 4096 | var data = this._data; 4097 | var seconds, minutes, hours, years, monthsFromDays; 4098 | 4099 | // if we have a mix of positive and negative values, bubble down first 4100 | // check: https://github.com/moment/moment/issues/2166 4101 | if (!((milliseconds >= 0 && days >= 0 && months >= 0) || 4102 | (milliseconds <= 0 && days <= 0 && months <= 0))) { 4103 | milliseconds += absCeil(monthsToDays(months) + days) * 864e5; 4104 | days = 0; 4105 | months = 0; 4106 | } 4107 | 4108 | // The following code bubbles up values, see the tests for 4109 | // examples of what that means. 4110 | data.milliseconds = milliseconds % 1000; 4111 | 4112 | seconds = absFloor(milliseconds / 1000); 4113 | data.seconds = seconds % 60; 4114 | 4115 | minutes = absFloor(seconds / 60); 4116 | data.minutes = minutes % 60; 4117 | 4118 | hours = absFloor(minutes / 60); 4119 | data.hours = hours % 24; 4120 | 4121 | days += absFloor(hours / 24); 4122 | 4123 | // convert days to months 4124 | monthsFromDays = absFloor(daysToMonths(days)); 4125 | months += monthsFromDays; 4126 | days -= absCeil(monthsToDays(monthsFromDays)); 4127 | 4128 | // 12 months -> 1 year 4129 | years = absFloor(months / 12); 4130 | months %= 12; 4131 | 4132 | data.days = days; 4133 | data.months = months; 4134 | data.years = years; 4135 | 4136 | return this; 4137 | } 4138 | 4139 | function daysToMonths (days) { 4140 | // 400 years have 146097 days (taking into account leap year rules) 4141 | // 400 years have 12 months === 4800 4142 | return days * 4800 / 146097; 4143 | } 4144 | 4145 | function monthsToDays (months) { 4146 | // the reverse of daysToMonths 4147 | return months * 146097 / 4800; 4148 | } 4149 | 4150 | function as (units) { 4151 | if (!this.isValid()) { 4152 | return NaN; 4153 | } 4154 | var days; 4155 | var months; 4156 | var milliseconds = this._milliseconds; 4157 | 4158 | units = normalizeUnits(units); 4159 | 4160 | if (units === 'month' || units === 'year') { 4161 | days = this._days + milliseconds / 864e5; 4162 | months = this._months + daysToMonths(days); 4163 | return units === 'month' ? months : months / 12; 4164 | } else { 4165 | // handle milliseconds separately because of floating point math errors (issue #1867) 4166 | days = this._days + Math.round(monthsToDays(this._months)); 4167 | switch (units) { 4168 | case 'week' : return days / 7 + milliseconds / 6048e5; 4169 | case 'day' : return days + milliseconds / 864e5; 4170 | case 'hour' : return days * 24 + milliseconds / 36e5; 4171 | case 'minute' : return days * 1440 + milliseconds / 6e4; 4172 | case 'second' : return days * 86400 + milliseconds / 1000; 4173 | // Math.floor prevents floating point math errors here 4174 | case 'millisecond': return Math.floor(days * 864e5) + milliseconds; 4175 | default: throw new Error('Unknown unit ' + units); 4176 | } 4177 | } 4178 | } 4179 | 4180 | // TODO: Use this.as('ms')? 4181 | function valueOf$1 () { 4182 | if (!this.isValid()) { 4183 | return NaN; 4184 | } 4185 | return ( 4186 | this._milliseconds + 4187 | this._days * 864e5 + 4188 | (this._months % 12) * 2592e6 + 4189 | toInt(this._months / 12) * 31536e6 4190 | ); 4191 | } 4192 | 4193 | function makeAs (alias) { 4194 | return function () { 4195 | return this.as(alias); 4196 | }; 4197 | } 4198 | 4199 | var asMilliseconds = makeAs('ms'); 4200 | var asSeconds = makeAs('s'); 4201 | var asMinutes = makeAs('m'); 4202 | var asHours = makeAs('h'); 4203 | var asDays = makeAs('d'); 4204 | var asWeeks = makeAs('w'); 4205 | var asMonths = makeAs('M'); 4206 | var asYears = makeAs('y'); 4207 | 4208 | function get$2 (units) { 4209 | units = normalizeUnits(units); 4210 | return this.isValid() ? this[units + 's']() : NaN; 4211 | } 4212 | 4213 | function makeGetter(name) { 4214 | return function () { 4215 | return this.isValid() ? this._data[name] : NaN; 4216 | }; 4217 | } 4218 | 4219 | var milliseconds = makeGetter('milliseconds'); 4220 | var seconds = makeGetter('seconds'); 4221 | var minutes = makeGetter('minutes'); 4222 | var hours = makeGetter('hours'); 4223 | var days = makeGetter('days'); 4224 | var months = makeGetter('months'); 4225 | var years = makeGetter('years'); 4226 | 4227 | function weeks () { 4228 | return absFloor(this.days() / 7); 4229 | } 4230 | 4231 | var round = Math.round; 4232 | var thresholds = { 4233 | ss: 44, // a few seconds to seconds 4234 | s : 45, // seconds to minute 4235 | m : 45, // minutes to hour 4236 | h : 22, // hours to day 4237 | d : 26, // days to month 4238 | M : 11 // months to year 4239 | }; 4240 | 4241 | // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize 4242 | function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { 4243 | return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); 4244 | } 4245 | 4246 | function relativeTime$1 (posNegDuration, withoutSuffix, locale) { 4247 | var duration = createDuration(posNegDuration).abs(); 4248 | var seconds = round(duration.as('s')); 4249 | var minutes = round(duration.as('m')); 4250 | var hours = round(duration.as('h')); 4251 | var days = round(duration.as('d')); 4252 | var months = round(duration.as('M')); 4253 | var years = round(duration.as('y')); 4254 | 4255 | var a = seconds <= thresholds.ss && ['s', seconds] || 4256 | seconds < thresholds.s && ['ss', seconds] || 4257 | minutes <= 1 && ['m'] || 4258 | minutes < thresholds.m && ['mm', minutes] || 4259 | hours <= 1 && ['h'] || 4260 | hours < thresholds.h && ['hh', hours] || 4261 | days <= 1 && ['d'] || 4262 | days < thresholds.d && ['dd', days] || 4263 | months <= 1 && ['M'] || 4264 | months < thresholds.M && ['MM', months] || 4265 | years <= 1 && ['y'] || ['yy', years]; 4266 | 4267 | a[2] = withoutSuffix; 4268 | a[3] = +posNegDuration > 0; 4269 | a[4] = locale; 4270 | return substituteTimeAgo.apply(null, a); 4271 | } 4272 | 4273 | // This function allows you to set the rounding function for relative time strings 4274 | function getSetRelativeTimeRounding (roundingFunction) { 4275 | if (roundingFunction === undefined) { 4276 | return round; 4277 | } 4278 | if (typeof(roundingFunction) === 'function') { 4279 | round = roundingFunction; 4280 | return true; 4281 | } 4282 | return false; 4283 | } 4284 | 4285 | // This function allows you to set a threshold for relative time strings 4286 | function getSetRelativeTimeThreshold (threshold, limit) { 4287 | if (thresholds[threshold] === undefined) { 4288 | return false; 4289 | } 4290 | if (limit === undefined) { 4291 | return thresholds[threshold]; 4292 | } 4293 | thresholds[threshold] = limit; 4294 | if (threshold === 's') { 4295 | thresholds.ss = limit - 1; 4296 | } 4297 | return true; 4298 | } 4299 | 4300 | function humanize (withSuffix) { 4301 | if (!this.isValid()) { 4302 | return this.localeData().invalidDate(); 4303 | } 4304 | 4305 | var locale = this.localeData(); 4306 | var output = relativeTime$1(this, !withSuffix, locale); 4307 | 4308 | if (withSuffix) { 4309 | output = locale.pastFuture(+this, output); 4310 | } 4311 | 4312 | return locale.postformat(output); 4313 | } 4314 | 4315 | var abs$1 = Math.abs; 4316 | 4317 | function toISOString$1() { 4318 | // for ISO strings we do not use the normal bubbling rules: 4319 | // * milliseconds bubble up until they become hours 4320 | // * days do not bubble at all 4321 | // * months bubble up until they become years 4322 | // This is because there is no context-free conversion between hours and days 4323 | // (think of clock changes) 4324 | // and also not between days and months (28-31 days per month) 4325 | if (!this.isValid()) { 4326 | return this.localeData().invalidDate(); 4327 | } 4328 | 4329 | var seconds = abs$1(this._milliseconds) / 1000; 4330 | var days = abs$1(this._days); 4331 | var months = abs$1(this._months); 4332 | var minutes, hours, years; 4333 | 4334 | // 3600 seconds -> 60 minutes -> 1 hour 4335 | minutes = absFloor(seconds / 60); 4336 | hours = absFloor(minutes / 60); 4337 | seconds %= 60; 4338 | minutes %= 60; 4339 | 4340 | // 12 months -> 1 year 4341 | years = absFloor(months / 12); 4342 | months %= 12; 4343 | 4344 | 4345 | // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js 4346 | var Y = years; 4347 | var M = months; 4348 | var D = days; 4349 | var h = hours; 4350 | var m = minutes; 4351 | var s = seconds; 4352 | var total = this.asSeconds(); 4353 | 4354 | if (!total) { 4355 | // this is the same as C#'s (Noda) and python (isodate)... 4356 | // but not other JS (goog.date) 4357 | return 'P0D'; 4358 | } 4359 | 4360 | return (total < 0 ? '-' : '') + 4361 | 'P' + 4362 | (Y ? Y + 'Y' : '') + 4363 | (M ? M + 'M' : '') + 4364 | (D ? D + 'D' : '') + 4365 | ((h || m || s) ? 'T' : '') + 4366 | (h ? h + 'H' : '') + 4367 | (m ? m + 'M' : '') + 4368 | (s ? s + 'S' : ''); 4369 | } 4370 | 4371 | var proto$2 = Duration.prototype; 4372 | 4373 | proto$2.isValid = isValid$1; 4374 | proto$2.abs = abs; 4375 | proto$2.add = add$1; 4376 | proto$2.subtract = subtract$1; 4377 | proto$2.as = as; 4378 | proto$2.asMilliseconds = asMilliseconds; 4379 | proto$2.asSeconds = asSeconds; 4380 | proto$2.asMinutes = asMinutes; 4381 | proto$2.asHours = asHours; 4382 | proto$2.asDays = asDays; 4383 | proto$2.asWeeks = asWeeks; 4384 | proto$2.asMonths = asMonths; 4385 | proto$2.asYears = asYears; 4386 | proto$2.valueOf = valueOf$1; 4387 | proto$2._bubble = bubble; 4388 | proto$2.get = get$2; 4389 | proto$2.milliseconds = milliseconds; 4390 | proto$2.seconds = seconds; 4391 | proto$2.minutes = minutes; 4392 | proto$2.hours = hours; 4393 | proto$2.days = days; 4394 | proto$2.weeks = weeks; 4395 | proto$2.months = months; 4396 | proto$2.years = years; 4397 | proto$2.humanize = humanize; 4398 | proto$2.toISOString = toISOString$1; 4399 | proto$2.toString = toISOString$1; 4400 | proto$2.toJSON = toISOString$1; 4401 | proto$2.locale = locale; 4402 | proto$2.localeData = localeData; 4403 | 4404 | // Deprecations 4405 | proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); 4406 | proto$2.lang = lang; 4407 | 4408 | // Side effect imports 4409 | 4410 | // FORMATTING 4411 | 4412 | addFormatToken('X', 0, 0, 'unix'); 4413 | addFormatToken('x', 0, 0, 'valueOf'); 4414 | 4415 | // PARSING 4416 | 4417 | addRegexToken('x', matchSigned); 4418 | addRegexToken('X', matchTimestamp); 4419 | addParseToken('X', function (input, array, config) { 4420 | config._d = new Date(parseFloat(input, 10) * 1000); 4421 | }); 4422 | addParseToken('x', function (input, array, config) { 4423 | config._d = new Date(toInt(input)); 4424 | }); 4425 | 4426 | // Side effect imports 4427 | 4428 | 4429 | hooks.version = '2.18.1'; 4430 | 4431 | setHookCallback(createLocal); 4432 | 4433 | hooks.fn = proto; 4434 | hooks.min = min; 4435 | hooks.max = max; 4436 | hooks.now = now; 4437 | hooks.utc = createUTC; 4438 | hooks.unix = createUnix; 4439 | hooks.months = listMonths; 4440 | hooks.isDate = isDate; 4441 | hooks.locale = getSetGlobalLocale; 4442 | hooks.invalid = createInvalid; 4443 | hooks.duration = createDuration; 4444 | hooks.isMoment = isMoment; 4445 | hooks.weekdays = listWeekdays; 4446 | hooks.parseZone = createInZone; 4447 | hooks.localeData = getLocale; 4448 | hooks.isDuration = isDuration; 4449 | hooks.monthsShort = listMonthsShort; 4450 | hooks.weekdaysMin = listWeekdaysMin; 4451 | hooks.defineLocale = defineLocale; 4452 | hooks.updateLocale = updateLocale; 4453 | hooks.locales = listLocales; 4454 | hooks.weekdaysShort = listWeekdaysShort; 4455 | hooks.normalizeUnits = normalizeUnits; 4456 | hooks.relativeTimeRounding = getSetRelativeTimeRounding; 4457 | hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; 4458 | hooks.calendarFormat = getCalendarFormat; 4459 | hooks.prototype = proto; 4460 | 4461 | return hooks; 4462 | 4463 | }))); 4464 | --------------------------------------------------------------------------------