├── .eslintignore
├── .gitignore
├── .npmignore
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── .babelrc
├── tests
├── ssr
│ ├── index.js
│ ├── index.html
│ ├── app.jsx
│ └── server.js
├── objects.js
└── index.jsx
├── .travis.yml
├── .editorconfig
├── src
├── objects.js
└── index.jsx
├── LICENSE
├── webpack.config.js
├── stories
└── index.js
├── dist
└── index.d.ts
├── package.json
├── .eslintrc
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/*
2 | **/webpack.config*.js
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .cache
3 | node_modules
4 | stories/build
5 | coverage
6 | *.log
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | .editorconfig
3 | .travis.yml
4 | .storybook
5 | stories
6 | webpack.config.js
7 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import 'storybook-addon-jsx/register'
2 | import '@storybook/addon-knobs/register';
3 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/env",
4 | "@babel/react"
5 | ],
6 | "plugins": [
7 | "@babel/plugin-proposal-class-properties"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/tests/ssr/index.js:
--------------------------------------------------------------------------------
1 | require('@babel/register')({
2 | ignore: [/(node_modules)/],
3 | presets: ['@babel/preset-env', '@babel/preset-react']
4 | });
5 |
6 | require('./server');
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6.4.0"
4 | - "8.0.0"
5 | before_install:
6 | - export TZ=America/New_York
7 | - date
8 | script:
9 | - npm run travis-test
10 |
--------------------------------------------------------------------------------
/tests/ssr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Moment
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 |
9 | [*.{js,jsx,css,scss,less,coffee,yml,json}]
10 | indent_style = space
11 | indent_size = 2
12 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure, setAddon } from '@storybook/react';
2 | import JSXAddon from 'storybook-addon-jsx'
3 |
4 | setAddon(JSXAddon);
5 |
6 | function loadStories() {
7 | require('../stories');
8 | }
9 |
10 | configure(loadStories, module);
11 |
--------------------------------------------------------------------------------
/tests/ssr/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Moment from '../../dist/index';
3 |
4 | /**
5 | *
6 | */
7 | class App extends React.Component {
8 | /**
9 | * @returns {*}
10 | */
11 | render() {
12 | return (
13 |
14 | The time is now:
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/tests/objects.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import * as objects from '../src/objects';
3 |
4 | test('objectKeyFilter', () => {
5 | const propTypes = {
6 | className: PropTypes.string
7 | };
8 | const props = {
9 | className: 'dp-item',
10 | title: 'foo'
11 | };
12 | const result = objects.objectKeyFilter(props, propTypes);
13 | expect(result.className).toBe(undefined);
14 | expect(result.title).not.toBe(undefined);
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
2 | // This is just the basic way to add additional webpack configurations.
3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config
4 |
5 | // IMPORTANT
6 | // When you add this file, we won't add the default configurations which is similar
7 | // to "React Create App". This only has babel loader to load JavaScript.
8 |
9 | module.exports = {
10 | plugins: [
11 | // your custom plugins
12 | ],
13 | module: {
14 | rules: [
15 | // add your custom loaders.
16 | ]
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/tests/ssr/server.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs';
3 | import express from 'express';
4 | import React from 'react';
5 | import ReactDOMServer from 'react-dom/server';
6 | import App from './app';
7 |
8 | const PORT = 8081;
9 | const app = express();
10 | const router = express.Router();
11 |
12 | const serverRenderer = (req, res) => {
13 | fs.readFile(path.resolve('./index.html'), 'utf8', (err, data) => {
14 | if (err) {
15 | console.error(err);
16 | return res.status(500).send('An error occurred');
17 | }
18 |
19 | return res.send(
20 | data.replace(
21 | '',
22 | `${ReactDOMServer.renderToString(
)}
`
23 | )
24 | );
25 | });
26 | };
27 |
28 | router.use('^/$', serverRenderer);
29 | app.use(router);
30 | app.listen(PORT, () => {
31 | console.log(`SSR running on port ${PORT}`);
32 | });
33 |
--------------------------------------------------------------------------------
/src/objects.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Performs a key comparison between two objects, deleting from the first where
3 | * the keys exist in the second
4 | *
5 | * Can be used to remove unwanted component prop values. For example:
6 | *
7 | * ```jsx
8 | * render() {
9 | * const { children, className, ...props } = this.props;
10 | *
11 | * return (
12 | *
16 | * {children}
17 | *
18 | * )
19 | * }
20 | * ```
21 | *
22 | * @param {Object} obj1
23 | * @param {Object} obj2
24 | * @returns {*}
25 | */
26 | export function objectKeyFilter(obj1, obj2) {
27 | const obj2Keys = Object.keys(obj2);
28 | const newProps = Object.assign({}, obj1);
29 | Object.keys(newProps)
30 | .filter(key => obj2Keys.indexOf(key) !== -1)
31 | .forEach(key => delete newProps[key]);
32 |
33 | return newProps;
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Sean Hickey
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src/index.jsx',
5 | output: {
6 | path: path.resolve(__dirname, 'dist'),
7 | filename: 'index.js',
8 | library: 'react-moment',
9 | libraryTarget: 'umd',
10 | globalObject: 'this'
11 | },
12 | externals: [
13 | {
14 | 'react': {
15 | root: 'react',
16 | commonjs2: 'react',
17 | commonjs: 'react',
18 | amd: 'react'
19 | },
20 | 'moment': {
21 | root: 'moment',
22 | commonjs2: 'moment',
23 | commonjs: 'moment',
24 | amd: 'moment'
25 | }
26 | }
27 | ],
28 | module: {
29 | rules: [
30 | {
31 | include: /\.json$/,
32 | loader: require.resolve('json-loader')
33 | },
34 | {
35 | test: /\.jsx?$/,
36 | exclude: /(node_modules)/,
37 | loader: require.resolve('babel-loader'),
38 | }
39 | ]
40 | },
41 | resolve: {
42 | extensions: ['.json', '.js', '.jsx']
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/stories/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-filename-extension */
2 | import React from 'react';
3 | import { storiesOf } from '@storybook/react';
4 | import { withKnobs, text, number } from '@storybook/addon-knobs';
5 | import Moment from '../src/index';
6 |
7 | Moment.globalLocale = 'fr';
8 |
9 | storiesOf('Moment', module)
10 | .addDecorator(withKnobs)
11 | .addWithJSX('with default props', () => {
12 | return (
13 |
14 | );
15 | })
16 | .addWithJSX('using the format prop', () => {
17 | return (
18 |
19 | );
20 | })
21 | .addWithJSX('using the fromNow prop', () => {
22 | return (
23 |
24 | );
25 | })
26 | .addWithJSX('using add and subtract props', () => {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | );
34 | })
35 | .addWithJSX('using the unix prop', () => {
36 | return (
37 |
38 | );
39 | })
40 | .addWithJSX('using filter prop', () => {
41 | return (
42 | {
45 | return d.toUpperCase();
46 | }}
47 | />
48 | );
49 | })
50 | ;
51 |
52 |
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 | import { Component, ComponentClass, SFC, CSSProperties } from 'react';
3 |
4 | type elementTypes = string | SFC | ComponentClass;
5 | type subtractOrAddTypes = {
6 | years?: number,
7 | y?: number,
8 | quarters?: number,
9 | Q?: number,
10 | months?: number,
11 | M?: number,
12 | weeks?: number,
13 | w?: number,
14 | days?: number,
15 | d?: number,
16 | hours?: number,
17 | h?: number,
18 | minutes?: number,
19 | m?: number,
20 | seconds?: number,
21 | s?: number,
22 | milliseconds?: number,
23 | ms?: number
24 | };
25 | type dateTypes = string|number|Array|object;
26 | type calendarTypes = boolean|object;
27 |
28 | export interface MomentProps {
29 | element?: elementTypes,
30 | date?: dateTypes,
31 | parse?: string | Array,
32 | format?: string,
33 | ago?: boolean,
34 | fromNow?: boolean,
35 | fromNowDuring?: number,
36 | from?: dateTypes,
37 | toNow?: boolean,
38 | to?: dateTypes,
39 | calendar?: calendarTypes,
40 | diff?: dateTypes,
41 | duration?: dateTypes,
42 | durationFromNow?: boolean,
43 | unit?: string,
44 | decimal?: boolean,
45 | unix?: boolean,
46 | utc?: boolean,
47 | local?: boolean,
48 | tz?: string,
49 | locale?: string,
50 | interval?: number,
51 | withTitle?: boolean,
52 | titleFormat?: string,
53 | subtract?: subtractOrAddTypes,
54 | add?: subtractOrAddTypes,
55 | children?: string | number | Date | moment.Moment,
56 | style?: CSSProperties,
57 | className?: string,
58 | filter?: (date: string) => string,
59 | onChange?: (content:any) => any
60 | }
61 |
62 | declare class Moment extends Component {
63 | constructor(props:MomentProps);
64 | public static globalMoment: Function;
65 | public static globalLocale: string;
66 | public static globalLocal: boolean;
67 | public static globalFormat: string;
68 | public static globalParse: string;
69 | public static globalTimezone: string;
70 | public static globalElement: any;
71 | public static globalFilter: Function;
72 | public static startPooledTimer(interval?: number): void;
73 | public static clearPooledTimer(): void;
74 | public static getDatetime(props: MomentProps): any;
75 | }
76 |
77 | export default Moment;
78 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-moment",
3 | "description": "React component for the moment date library.",
4 | "version": "0.9.7",
5 | "author": {
6 | "name": "Sean Hickey",
7 | "web": "http://headzoo.io"
8 | },
9 | "devDependencies": {
10 | "@babel/core": "^7.4.3",
11 | "@babel/plugin-proposal-class-properties": "^7.4.0",
12 | "@babel/preset-env": "^7.4.3",
13 | "@babel/preset-react": "^7.0.0",
14 | "@babel/preset-stage-2": "^7.0.0",
15 | "@babel/register": "^7.4.0",
16 | "@storybook/addon-actions": "^5.0.6",
17 | "@storybook/addon-knobs": "^5.0.6",
18 | "@storybook/addon-options": "^5.0.6",
19 | "@storybook/addons": "^5.0.6",
20 | "@storybook/react": "^5.0.6",
21 | "babel-eslint": "^10.0.1",
22 | "babel-jest": "^24.7.1",
23 | "babel-loader": "^8.0.5",
24 | "coveralls": "^3.0.3",
25 | "eslint": "^5.16.0",
26 | "eslint-config-airbnb": "^17.1.0",
27 | "eslint-import-resolver-babel-module": "^5.0.1",
28 | "eslint-plugin-import": "^2.16.0",
29 | "eslint-plugin-jsx-a11y": "^6.2.1",
30 | "eslint-plugin-react": "^7.12.4",
31 | "express": "^4.16.4",
32 | "istanbul": "^0.4.5",
33 | "istanbul-api": "^2.1.4",
34 | "istanbul-reports": "^2.2.2",
35 | "jest": "^24.7.1",
36 | "json-loader": "^0.5.4",
37 | "moment": "^2.24.0",
38 | "moment-duration-format": "^2.2.2",
39 | "moment-timezone": "^0.5.23",
40 | "prop-types": "^15.7.2",
41 | "react": "^16.0.0",
42 | "react-dom": "^16.0.0",
43 | "rimraf": "^2.6.3",
44 | "storybook-addon-jsx": "^7.1.0",
45 | "webpack": "^4.29.6",
46 | "webpack-cli": "^3.3.0"
47 | },
48 | "jest": {
49 | "testRegex": "/tests/.*",
50 | "moduleFileExtensions": [
51 | "js",
52 | "jsx"
53 | ],
54 | "modulePathIgnorePatterns": [
55 | "/tests/ssr"
56 | ]
57 | },
58 | "keywords": [
59 | "date",
60 | "moment",
61 | "react",
62 | "react-component",
63 | "time"
64 | ],
65 | "license": "MIT",
66 | "main": "dist/index.js",
67 | "peerDependencies": {
68 | "moment": "^2.24.0",
69 | "prop-types": "^15.7.2",
70 | "react": "^15.6.0 || ^16.0.0"
71 | },
72 | "repository": {
73 | "type": "git",
74 | "url": "git+https://github.com/headzoo/react-moment.git"
75 | },
76 | "scripts": {
77 | "build": "npm run clean && webpack -p",
78 | "clean": "rimraf ./dist/index.js",
79 | "test": "export TZ=America/New_York; jest --coverage",
80 | "travis-test": "npm run test && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls",
81 | "lint": "eslint ./src --cache --cache-location=.cache/eslint --ext .js,.jsx",
82 | "lint:fix": "npm run lint -- --fix",
83 | "storybook:dev": "start-storybook -p 6006",
84 | "storybook:build": "build-storybook -o stories/build"
85 | },
86 | "types": "dist/index.d.ts"
87 | }
88 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended", "plugin:react/recommended", "airbnb"],
3 | "env": {
4 | "browser": true,
5 | "mocha": true,
6 | "node": true,
7 | "jest": true,
8 | "jasmine": true
9 | },
10 | "parser": "babel-eslint",
11 | "rules": {
12 | "max-len": [
13 | 1,
14 | {
15 | "code": 120,
16 | "ignoreComments": true,
17 | "ignoreUrls": true,
18 | "ignorePattern": "^import\\s.+"
19 | }
20 | ],
21 | "no-unreachable": 0,
22 | "arrow-body-style": 0,
23 | "object-curly-newline": 0,
24 | "no-trailing-spaces": 0,
25 | "react/no-find-dom-node": 0,
26 | "react/no-multi-comp": 0,
27 | "react/forbid-prop-types": 0,
28 | "react/require-default-props": 0,
29 | "react/no-array-index-key": 0,
30 | "jsx-a11y/href-no-hash": 0,
31 | "jsx-quotes": [
32 | 2,
33 | "prefer-double"
34 | ],
35 | "key-spacing": [
36 | 2,
37 | {
38 | "singleLine": {
39 | "beforeColon": false,
40 | "afterColon": true
41 | },
42 | "multiLine": {
43 | "beforeColon": false,
44 | "afterColon": true,
45 | "align": "value"
46 | }
47 | }
48 | ],
49 | "no-multi-spaces": [
50 | 0,
51 | {
52 | "exceptions": {
53 | "VariableDeclarator": true
54 | }
55 | }
56 | ],
57 | "react/prefer-stateless-function": 0,
58 | "react/jsx-uses-react": 2,
59 | "react/jsx-uses-vars": 2,
60 | "react/react-in-jsx-scope": 2,
61 | "no-var": 2,
62 | "vars-on-top": 0,
63 | "comma-dangle": 0,
64 | "new-cap": 0,
65 | "no-console": 0,
66 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
67 | "indent": [
68 | 2,
69 | 2,
70 | {
71 | "SwitchCase": 1
72 | }
73 | ],
74 | "valid-jsdoc": 0,
75 | "import/no-extraneous-dependencies": ["off"],
76 | "jsx-a11y/no-static-element-interactions": 0,
77 | "import/prefer-default-export": "off",
78 | "import/extensions": ["warn", "always", {
79 | "js": "never", "jsx": "never"
80 | }],
81 | "react/no-unused-prop-types": [2, { "skipShapeProps": true }],
82 | "class-methods-use-this": 0,
83 | "jsx-a11y/tabindex-no-positive": 0,
84 | "strict": 0,
85 | "no-param-reassign": 0,
86 | "no-mixed-operators": "off"
87 | },
88 | "plugins": [
89 | "react",
90 | "jsx-a11y"
91 | ],
92 | "settings": {
93 | "import/resolver": {
94 | "babel-module": { }
95 | },
96 | "import/extensions": [".js", ".jsx"],
97 | "react": {
98 | "version": "15.3.2"
99 | // React version, default to the latest React stable release
100 | }
101 | },
102 | "parserOptions": {
103 | "ecmaFeatures": {
104 | "jsx": true
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import moment from 'moment';
4 | import 'moment-duration-format';
5 | import { objectKeyFilter } from './objects';
6 |
7 | const dateTypes = [
8 | PropTypes.string,
9 | PropTypes.number,
10 | PropTypes.array,
11 | PropTypes.object
12 | ];
13 |
14 | const parseTypes = [
15 | PropTypes.string,
16 | PropTypes.array
17 | ];
18 |
19 | const calendarTypes = [
20 | PropTypes.object,
21 | PropTypes.bool
22 | ];
23 |
24 | export default class Moment extends React.Component {
25 | static propTypes = {
26 | element: PropTypes.any,
27 | date: PropTypes.oneOfType(dateTypes),
28 | parse: PropTypes.oneOfType(parseTypes),
29 | format: PropTypes.string,
30 | add: PropTypes.object,
31 | subtract: PropTypes.object,
32 | ago: PropTypes.bool,
33 | fromNow: PropTypes.bool,
34 | fromNowDuring: PropTypes.number,
35 | from: PropTypes.oneOfType(dateTypes),
36 | toNow: PropTypes.bool,
37 | to: PropTypes.oneOfType(dateTypes),
38 | calendar: PropTypes.oneOfType(calendarTypes),
39 | unix: PropTypes.bool,
40 | utc: PropTypes.bool,
41 | local: PropTypes.bool,
42 | tz: PropTypes.string,
43 | withTitle: PropTypes.bool,
44 | titleFormat: PropTypes.string,
45 | locale: PropTypes.string,
46 | interval: PropTypes.number,
47 | diff: PropTypes.oneOfType(dateTypes),
48 | duration: PropTypes.oneOfType(dateTypes),
49 | durationFromNow: PropTypes.bool,
50 | unit: PropTypes.string,
51 | decimal: PropTypes.bool,
52 | filter: PropTypes.func,
53 | onChange: PropTypes.func
54 | };
55 |
56 | static defaultProps = {
57 | element: null,
58 | fromNow: false,
59 | toNow: false,
60 | calendar: false,
61 | ago: false,
62 | unix: false,
63 | utc: false,
64 | local: false,
65 | unit: null,
66 | withTitle: false,
67 | decimal: false,
68 | titleFormat: '',
69 | interval: 60000,
70 | filter: (d) => { return d; },
71 | onChange: () => {}
72 | };
73 |
74 | static globalMoment = null;
75 |
76 | static globalLocale = null;
77 |
78 | static globalLocal = null;
79 |
80 | static globalFormat = null;
81 |
82 | static globalParse = null;
83 |
84 | static globalFilter = null;
85 |
86 | static globalElement = 'time';
87 |
88 | static globalTimezone = null;
89 |
90 | static pooledElements = [];
91 |
92 | static pooledTimer = null;
93 |
94 | /**
95 | * Starts the pooled timer
96 | *
97 | * @param {number} interval
98 | */
99 | static startPooledTimer(interval = 60000) {
100 | Moment.clearPooledTimer();
101 | Moment.pooledTimer = setInterval(() => {
102 | Moment.pooledElements.forEach((element) => {
103 | if (element.props.interval !== 0) {
104 | element.update();
105 | }
106 | });
107 | }, interval);
108 | }
109 |
110 | /**
111 | * Stops the pooled timer
112 | */
113 | static clearPooledTimer() {
114 | if (Moment.pooledTimer) {
115 | clearInterval(Moment.pooledTimer);
116 | Moment.pooledTimer = null;
117 | Moment.pooledElements = [];
118 | }
119 | }
120 |
121 | /**
122 | * Adds a Moment instance to the pooled elements list
123 | *
124 | * @param {Moment|React.Component} element
125 | */
126 | static pushPooledElement(element) {
127 | if (!(element instanceof Moment)) {
128 | console.error('Element not an instance of Moment.');
129 | return;
130 | }
131 | if (Moment.pooledElements.indexOf(element) === -1) {
132 | Moment.pooledElements.push(element);
133 | }
134 | }
135 |
136 | /**
137 | * Removes a Moment instance from the pooled elements list
138 | *
139 | * @param {Moment|React.Component} element
140 | */
141 | static removePooledElement(element) {
142 | const index = Moment.pooledElements.indexOf(element);
143 | if (index !== -1) {
144 | Moment.pooledElements.splice(index, 1);
145 | }
146 | }
147 |
148 | /**
149 | * Returns a Date based on the set props
150 | *
151 | * @param {*} props
152 | * @returns {*}
153 | */
154 | static getDatetime(props) {
155 | const { utc, unix } = props;
156 | let { date, locale, parse, tz, local } = props;
157 |
158 | date = date || props.children;
159 | parse = parse || Moment.globalParse;
160 | local = local || Moment.globalLocal;
161 | tz = tz || Moment.globalTimezone;
162 | if (Moment.globalLocale) {
163 | locale = Moment.globalLocale;
164 | } else {
165 | locale = locale || Moment.globalMoment.locale();
166 | }
167 |
168 | let datetime = null;
169 | if (utc) {
170 | datetime = Moment.globalMoment.utc(date, parse, locale);
171 | } else if (unix) {
172 | // moment#unix fails because of a deprecation,
173 | // but since moment#unix(s) is implemented as moment(s * 1000),
174 | // this works equivalently
175 | datetime = Moment.globalMoment(date * 1000, parse, locale);
176 | } else {
177 | datetime = Moment.globalMoment(date, parse, locale);
178 | }
179 | if (tz) {
180 | datetime = datetime.tz(tz);
181 | } else if (local) {
182 | datetime = datetime.local();
183 | }
184 |
185 | return datetime;
186 | }
187 |
188 | /**
189 | * Returns computed content from sent props
190 | * @param {*} props
191 | * @returns {*}
192 | *
193 | */
194 | static getContent(props) {
195 | const {
196 | fromNow, fromNowDuring, from, add, subtract, toNow, to, ago,
197 | calendar, diff, duration, durationFromNow, unit, decimal,
198 | } = props;
199 |
200 | let { format } = props;
201 |
202 | format = format || Moment.globalFormat;
203 | const datetime = Moment.getDatetime(props);
204 | if (add) {
205 | datetime.add(add);
206 | }
207 | if (subtract) {
208 | datetime.subtract(subtract);
209 | }
210 |
211 | const fromNowPeriod = Boolean(fromNowDuring) && -datetime.diff(moment()) < fromNowDuring;
212 | let content = '';
213 | if (format && !fromNowPeriod) {
214 | content = datetime.format(format);
215 | } else if (from) {
216 | content = datetime.from(from, ago);
217 | } else if (fromNow || fromNowPeriod) {
218 | content = datetime.fromNow(ago);
219 | } else if (to) {
220 | content = datetime.to(to, ago);
221 | } else if (toNow) {
222 | content = datetime.toNow(ago);
223 | } else if (calendar) {
224 | content = datetime.calendar(null, calendar);
225 | } else if (diff) {
226 | content = datetime.diff(diff, unit, decimal);
227 | } else if (duration) {
228 | content = datetime.diff(duration);
229 | } else if (durationFromNow) {
230 | content = moment().diff(datetime);
231 | } else {
232 | content = datetime.toString();
233 | }
234 |
235 | if (duration || durationFromNow) {
236 | content = moment.duration(content);
237 | content = content.format(format);
238 | }
239 |
240 | const filter = Moment.globalFilter || props.filter;
241 | content = filter(content);
242 |
243 | return content;
244 | }
245 |
246 | /**
247 | * Constructor
248 | *
249 | * @param {*} props
250 | */
251 | constructor(props) {
252 | super(props);
253 |
254 | if (!Moment.globalMoment) {
255 | Moment.globalMoment = moment;
256 | }
257 | this.state = {
258 | content: ''
259 | };
260 | this.timer = null;
261 | }
262 |
263 | /**
264 | * Invoked immediately after a component is mounted
265 | */
266 | componentDidMount() {
267 | this.setTimer();
268 | if (Moment.pooledTimer) {
269 | Moment.pushPooledElement(this);
270 | }
271 | }
272 |
273 | /**
274 | * Invoked immediately after updating occurs
275 | *
276 | * @param {*} prevProps
277 | */
278 | componentDidUpdate(prevProps) {
279 | const { interval } = this.props;
280 |
281 | if (prevProps.interval !== interval) {
282 | this.setTimer();
283 | }
284 | }
285 |
286 | /**
287 | * Invoked immediately before a component is unmounted and destroyed
288 | */
289 | componentWillUnmount() {
290 | this.clearTimer();
291 | }
292 |
293 | /**
294 | * Invoked as a mounted component receives new props
295 | * What it returns will become state.
296 | *
297 | * @param {*} nextProps
298 | * @returns {*} (new state)
299 | */
300 | static getDerivedStateFromProps(nextProps) {
301 | const content = Moment.getContent(nextProps);
302 | return { content };
303 | }
304 |
305 | /**
306 | * Starts the interval timer.
307 | */
308 | setTimer = () => {
309 | const { interval } = this.props;
310 |
311 | this.clearTimer();
312 | if (!Moment.pooledTimer && interval !== 0) {
313 | this.timer = setInterval(() => {
314 | this.update(this.props);
315 | }, interval);
316 | }
317 | };
318 |
319 | /**
320 | * Returns the element title to use on hover
321 | */
322 | getTitle = () => {
323 | const { titleFormat } = this.props;
324 |
325 | const datetime = Moment.getDatetime(this.props);
326 | const format = titleFormat || Moment.globalFormat;
327 |
328 | return datetime.format(format);
329 | };
330 |
331 |
332 | /**
333 | * Clears the interval timer.
334 | */
335 | clearTimer = () => {
336 | if (!Moment.pooledTimer && this.timer) {
337 | clearInterval(this.timer);
338 | this.timer = null;
339 | }
340 | if (Moment.pooledTimer && !this.timer) {
341 | Moment.removePooledElement(this);
342 | }
343 | };
344 |
345 |
346 | /**
347 | * Updates this.state.content
348 | * @param {*} props
349 | */
350 | update(props) {
351 | const elementProps = (props || this.props);
352 | const { onChange } = elementProps;
353 |
354 | const content = Moment.getContent(elementProps);
355 | this.setState({ content }, () => {
356 | onChange(content);
357 | });
358 | }
359 |
360 | /**
361 | * @returns {*}
362 | */
363 | render() {
364 | const { withTitle, element, ...remaining } = this.props;
365 | const { content } = this.state;
366 |
367 | const props = objectKeyFilter(remaining, Moment.propTypes);
368 | if (withTitle) {
369 | props.title = this.getTitle();
370 | }
371 |
372 | return React.createElement(
373 | element || Moment.globalElement,
374 | {
375 | dateTime: Moment.getDatetime(this.props),
376 | ...props
377 | },
378 | content
379 | );
380 | }
381 | }
382 |
--------------------------------------------------------------------------------
/tests/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import TestUtils from 'react-dom/test-utils';
4 | import moment from 'moment';
5 | import 'moment-timezone';
6 | import Moment from '../src/index';
7 |
8 | const DATE_OUTPUT = 'Mon Apr 19 1976 12:59:00 GMT-0500';
9 | const DATE_STRING = '1976-04-19T12:59-0500';
10 | const DATE_DATE = new Date(DATE_STRING);
11 | const DATE_UNIX = DATE_DATE.getTime() / 1000;
12 |
13 | describe('react-moment', () => {
14 | beforeEach(() => {
15 | Moment.globalMoment = null;
16 | Moment.globalLocale = null;
17 | Moment.globalLocal = null;
18 | Moment.globalFormat = null;
19 | Moment.globalParse = null;
20 | Moment.globalFilter = null;
21 | Moment.globalElement = 'time';
22 | Moment.globalTimezone = null;
23 | Moment.pooledElements = [];
24 | Moment.pooledTimer = null;
25 | });
26 |
27 | it('children', () => {
28 | let date = TestUtils.renderIntoDocument(
29 |
30 | );
31 | const expected = moment().toString();
32 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
33 |
34 | date = TestUtils.renderIntoDocument(
35 | {DATE_STRING}
36 | );
37 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
38 |
39 | date = TestUtils.renderIntoDocument(
40 | {DATE_DATE}
41 | );
42 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
43 |
44 | date = TestUtils.renderIntoDocument(
45 | {DATE_UNIX}
46 | );
47 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
48 | });
49 |
50 | it('element', () => {
51 | const date = TestUtils.renderIntoDocument(
52 | 1976-04-19 12:59
53 | );
54 | expect(ReactDOM.findDOMNode(date).tagName).toEqual('SPAN');
55 | });
56 |
57 | it('date', () => {
58 | let date = TestUtils.renderIntoDocument(
59 |
60 | );
61 | const expected = moment().toString();
62 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
63 |
64 | date = TestUtils.renderIntoDocument(
65 |
66 | );
67 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
68 |
69 | date = TestUtils.renderIntoDocument(
70 |
71 | );
72 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
73 | });
74 |
75 | it('parse', () => {
76 | const date = TestUtils.renderIntoDocument(
77 | 1976-04-19 12:59
78 | );
79 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
80 | });
81 |
82 | it('filter', () => {
83 | const filter = (d) => { return d.toUpperCase(); };
84 | const date = TestUtils.renderIntoDocument(
85 | 1976-04-19 12:59
86 | );
87 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT.toUpperCase());
88 | });
89 |
90 | it('format', () => {
91 | const date = TestUtils.renderIntoDocument(
92 | {DATE_STRING}
93 | );
94 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual('1976-04-19');
95 | });
96 |
97 | it('add', () => {
98 | const date = TestUtils.renderIntoDocument(
99 | {new Date()}
100 | );
101 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual('in a day');
102 | });
103 |
104 | it('subtract', () => {
105 | const date = TestUtils.renderIntoDocument(
106 | {new Date()}
107 | );
108 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual('a day ago');
109 | });
110 |
111 | it('fromNow', () => {
112 | let date = TestUtils.renderIntoDocument(
113 | {DATE_STRING}
114 | );
115 | let expected = moment(DATE_STRING).fromNow();
116 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
117 |
118 | date = TestUtils.renderIntoDocument(
119 | {DATE_STRING}
120 | );
121 | expected = moment(DATE_STRING).fromNow(true);
122 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
123 | });
124 |
125 | it('fromNowDuring', () => {
126 | const twoHoursAgo = moment().add(-2, 'hours');
127 | const hour = 1e3 * 60 * 60;
128 | const TWO_HOURS_AGO_ISO = twoHoursAgo.format();
129 |
130 | let date = TestUtils.renderIntoDocument(
131 | {TWO_HOURS_AGO_ISO}
132 | );
133 | let expected = twoHoursAgo.fromNow();
134 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
135 |
136 | date = TestUtils.renderIntoDocument(
137 | {TWO_HOURS_AGO_ISO}
138 | );
139 | expected = twoHoursAgo.format('YYYY-MM-DD HH:mm');
140 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
141 | });
142 |
143 | it('from', () => {
144 | let date = TestUtils.renderIntoDocument(
145 | {DATE_STRING}
146 | );
147 | let expected = moment(DATE_STRING).from('2016-09-20T12:00');
148 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
149 |
150 | date = TestUtils.renderIntoDocument(
151 | {DATE_STRING}
152 | );
153 | expected = moment(DATE_STRING).from('2016-09-20T12:00', true);
154 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
155 | });
156 |
157 | it('diff', () => {
158 | let date = TestUtils.renderIntoDocument(
159 | {DATE_STRING}
160 | );
161 | let expected = moment(DATE_STRING).diff('2016-09-20T12:00').toString();
162 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
163 |
164 | date = TestUtils.renderIntoDocument(
165 | {DATE_STRING}
166 | );
167 | expected = moment(DATE_STRING).diff('2016-09-20T12:00', 'days');
168 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected.toString());
169 |
170 | date = TestUtils.renderIntoDocument(
171 |
172 | {DATE_STRING}
173 |
174 | );
175 | expected = moment(DATE_STRING).diff('2016-09-20T12:00', 'years', true);
176 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected.toString());
177 |
178 | date = TestUtils.renderIntoDocument(
179 |
180 | {DATE_STRING}
181 |
182 | );
183 | expected = moment(DATE_STRING).diff('2016-09-20T12:00', null, true);
184 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected.toString());
185 | });
186 |
187 | it('toNow', () => {
188 | let date = TestUtils.renderIntoDocument(
189 | {DATE_STRING}
190 | );
191 | let expected = moment(DATE_STRING).toNow();
192 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
193 |
194 | date = TestUtils.renderIntoDocument(
195 | {DATE_STRING}
196 | );
197 | expected = moment(DATE_STRING).toNow(true);
198 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
199 | });
200 |
201 | it('to', () => {
202 | let date = TestUtils.renderIntoDocument(
203 | {DATE_STRING}
204 | );
205 | let expected = moment(DATE_STRING).to('2016-09-20T12:00');
206 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
207 |
208 | date = TestUtils.renderIntoDocument(
209 | {DATE_STRING}
210 | );
211 | expected = moment(DATE_STRING).to('2016-09-20T12:00', true);
212 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
213 | });
214 |
215 | it('calendar', () => {
216 | let date = TestUtils.renderIntoDocument(
217 | {DATE_STRING}
218 | );
219 | let expected = moment(DATE_STRING).calendar();
220 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
221 |
222 | date = TestUtils.renderIntoDocument(
223 | {DATE_STRING}
224 | );
225 | expected = moment(DATE_STRING).calendar(null, { sameElse: 'YYYY-MM-DD HH:mm' });
226 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
227 | });
228 |
229 | it('unix', () => {
230 | const date = TestUtils.renderIntoDocument(
231 | {DATE_UNIX}
232 | );
233 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
234 | });
235 |
236 | it('utc', () => {
237 | const date = TestUtils.renderIntoDocument(
238 | 1976-04-19T12:59
239 | );
240 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual('Mon Apr 19 1976 12:59:00 GMT+0000');
241 | });
242 |
243 | it('local', () => {
244 | const date = TestUtils.renderIntoDocument(
245 | {DATE_STRING}
246 | );
247 | const expected = moment(DATE_STRING).local().toString();
248 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
249 | });
250 |
251 | it('utc and local', () => {
252 | const date = TestUtils.renderIntoDocument(
253 | {DATE_STRING}
254 | );
255 | const expected = moment.utc(DATE_STRING).local().toString();
256 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
257 | });
258 |
259 | it('tz', () => {
260 | const date = TestUtils.renderIntoDocument(
261 | {DATE_UNIX}
262 | );
263 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual('Mon Apr 19 1976 09:59:00 GMT-0800');
264 | });
265 |
266 | it('other', () => {
267 | const date = TestUtils.renderIntoDocument(
268 | {DATE_STRING}
269 | );
270 | expect(ReactDOM.findDOMNode(date).getAttribute('class')).toEqual('testing');
271 | expect(ReactDOM.findDOMNode(date).getAttribute('aria-hidden')).toEqual('true');
272 | });
273 |
274 | it('updates content when props are updated', () => {
275 | const renderInContainer = (component, componentProps = {}) => {
276 | class PropChangeContainer extends React.Component {
277 | constructor(props) {
278 | super(props);
279 | this.state = props;
280 | }
281 | render() {
282 | return React.createElement(component, this.state);
283 | }
284 | }
285 |
286 | const container = TestUtils.renderIntoDocument();
287 | const instance = TestUtils.findRenderedComponentWithType(container, component);
288 |
289 | return [container, instance];
290 | };
291 |
292 | const [container, date] = renderInContainer(Moment, { children: DATE_STRING });
293 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(DATE_OUTPUT);
294 |
295 | const NEW_DATE_STRING = '1976-04-20T12:59-0500';
296 | const NEW_DATE_OUTPUT = 'Tue Apr 20 1976 12:59:00 GMT-0500';
297 |
298 | container.setState({
299 | children: NEW_DATE_STRING
300 | });
301 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(NEW_DATE_OUTPUT);
302 | });
303 |
304 | it('pooled timer', () => {
305 | Moment.startPooledTimer(1);
306 | const date = TestUtils.renderIntoDocument(
307 |
308 | );
309 | const expected = moment().toString();
310 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
311 | date.componentWillUnmount();
312 | });
313 |
314 | it('globalLocale', () => {
315 | Moment.globalLocale = 'fr';
316 | const date = TestUtils.renderIntoDocument(
317 |
318 | );
319 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual('19 avr. 1976');
320 | });
321 |
322 | it('globalTimezone', () => {
323 | Moment.globalTimezone = 'America/Los_Angeles';
324 | const date = TestUtils.renderIntoDocument(
325 |
326 | );
327 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual('1976-04-19 09');
328 | });
329 |
330 | it('globalLocal', () => {
331 | Moment.globalLocal = true;
332 | const date = TestUtils.renderIntoDocument(
333 | {DATE_STRING}
334 | );
335 | const expected = moment(DATE_STRING).local().toString();
336 | expect(ReactDOM.findDOMNode(date).innerHTML).toEqual(expected);
337 | });
338 |
339 | it('globalElement', () => {
340 | Moment.globalElement = 'span';
341 | const date = TestUtils.renderIntoDocument(
342 | 1976-04-19 12:59
343 | );
344 | expect(ReactDOM.findDOMNode(date).tagName).toEqual('SPAN');
345 | });
346 |
347 | it('globalElement overwrite', () => {
348 | Moment.globalElement = 'span';
349 | const date = TestUtils.renderIntoDocument(
350 | 1976-04-19 12:59
351 | );
352 | expect(ReactDOM.findDOMNode(date).tagName).toEqual('DIV');
353 | });
354 | });
355 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | react-moment
2 | ============
3 | React component for the [moment](http://momentjs.com/) date library.
4 |
5 | [](https://travis-ci.org/headzoo/react-moment)
6 | [](https://coveralls.io/github/headzoo/react-moment?branch=master)
7 | [](https://www.npmjs.com/package/react-moment)
8 | [](https://raw.githubusercontent.com/headzoo/react-moment/master/LICENSE)
9 |
10 | - [Installing](#installing)
11 | - [Timezone Support](#timezone-support)
12 | - [Quick Start](#quick-start)
13 | - [Props](#props)
14 | - [Interval](#interval)
15 | - [Formatting](#formatting)
16 | - [Parsing Dates](#parsing-dates)
17 | - [Add and Subtract](#add-and-subtract)
18 | - [From Now](#from-now)
19 | - [From Now During](#from-now-during)
20 | - [From](#from)
21 | - [To Now](#to-now)
22 | - [To](#to)
23 | - [Filter](#filter)
24 | - [With Title](#with-title)
25 | - [Title Format](#title-format)
26 | - [Difference](#difference)
27 | - [Duration](#duration)
28 | - [Duration From Now](#duration-from-now)
29 | - [Unix Timestamps](#unix-timestamps)
30 | - [Local](#local)
31 | - [Timezone](#timezone)
32 | - [Calendar](#calendar)
33 | - [Locale](#locale)
34 | - [Element](#element)
35 | - [OnChange](#onchange)
36 | - [Other Props](#other-props)
37 | - [Pooled Timer](#pooled-timer)
38 | - [Global Config](#global-config)
39 | - [Usage with React Native](#usage-with-react-native)
40 | - [License](#license)
41 | - [Contributors](#contributors)
42 |
43 |
44 | ### Installing
45 | Node 6.4.0 or greater is required. Use npm to install `react-moment` along with its peer dependency, `moment`.
46 |
47 | ```sh
48 | npm install --save moment react-moment
49 | ```
50 |
51 |
52 | ### Timezone Support
53 | The `moment-timezone` package is required to use the timezone related functions.
54 |
55 | ```sh
56 | npm install --save moment-timezone
57 | ```
58 |
59 | Then import the package into your project.
60 |
61 | ```jsx
62 | import React from 'react';
63 | import Moment from 'react-moment';
64 | import 'moment-timezone';
65 |
66 | export default class App extends React.Component {
67 | ...
68 | }
69 | ```
70 |
71 |
72 | ### Quick Start
73 |
74 | ```jsx
75 | import React from 'react';
76 | import Moment from 'react-moment';
77 |
78 | export default class MyComponent extends React.Component {
79 | render() {
80 | return (
81 | const dateToFormat = '1976-04-19T12:59-0500';
82 | {dateToFormat}
83 | );
84 | }
85 | }
86 | ```
87 |
88 | Outputs:
89 |
90 | ```html
91 |
92 | ```
93 |
94 | The above example could also be written this way if you prefer to pass the date using an attribute rather than as a child to ``.
95 |
96 | ```jsx
97 | import React from 'react';
98 | import Moment from 'react-moment';
99 |
100 | export default class MyComponent extends React.Component {
101 | render() {
102 | return (
103 | const dateToFormat = '1976-04-19T12:59-0500';
104 |
105 | );
106 | }
107 | }
108 | ```
109 |
110 | The date value may be a string, object, array, or `Date` instance.
111 |
112 | ```jsx
113 | import React from 'react';
114 | import Moment from 'react-moment';
115 |
116 | export default class MyComponent extends React.Component {
117 | render() {
118 | return (
119 | const dateToFormat = new Date('1976-04-19T12:59-0500');
120 |
121 | );
122 | }
123 | }
124 | ```
125 |
126 |
127 | ### Props
128 | The component supports the following props. See the [Moment docs](https://momentjs.com/docs/) for more information.
129 |
130 | #### Interval
131 | _interval={number}_
132 |
133 | By default the time updates every 60 seconds (60000 milliseconds). Use the `interval` prop to change or disable updating.
134 |
135 | Updates the time every 30 seconds (30000 milliseconds).
136 | ```jsx
137 | import React from 'react';
138 | import Moment from 'react-moment';
139 |
140 | export default class MyComponent extends React.Component {
141 | render() {
142 | return (
143 |
144 | 1976-04-19T12:59-0500
145 |
146 | );
147 | }
148 | }
149 | ```
150 |
151 | Disables updating.
152 | ```jsx
153 | import React from 'react';
154 | import Moment from 'react-moment';
155 |
156 | export default class MyComponent extends React.Component {
157 | render() {
158 | return (
159 |
160 | 1976-04-19T12:59-0500
161 |
162 | );
163 | }
164 | }
165 | ```
166 |
167 |
168 | #### Formatting
169 | _format={string}_
170 |
171 | Formats the date according to the given format string. See the [Moment docs on formatting](https://momentjs.com/docs/#/parsing/string-format/) for more information.
172 |
173 | ```jsx
174 | import React from 'react';
175 | import Moment from 'react-moment';
176 |
177 | export default class MyComponent extends React.Component {
178 | render() {
179 | return (
180 |
181 | 1976-04-19T12:59-0500
182 |
183 | );
184 | }
185 | }
186 | ```
187 |
188 | Outputs:
189 |
190 | ```html
191 |
192 | ```
193 |
194 |
195 | #### Parsing Dates
196 | _parse={string}_
197 |
198 | Moment can parse most standard date formats. Use the `parse` attribute to tell moment how to parse the given date when non-standard. See the [Moment docs on parsing](https://momentjs.com/docs/#/parsing/string-format/) for more information.
199 |
200 | ```jsx
201 | import React from 'react';
202 | import Moment from 'react-moment';
203 |
204 | export default class MyComponent extends React.Component {
205 | render() {
206 | return (
207 |
208 | 1976-04-19 12:59
209 |
210 | );
211 | }
212 | }
213 | ```
214 |
215 | #### Add and Subtract
216 | _add={object}_
217 |
218 | _subtract={object}_
219 |
220 | Used to add and subtract periods of time from the given date, with the time periods expressed as object literals. See the [Moment docs on add and subtract](https://momentjs.com/docs/#/manipulating/add/) for more information.
221 |
222 | ```jsx
223 | import React from 'react';
224 | import Moment from 'react-moment';
225 |
226 | export default class MyComponent extends React.Component {
227 | render() {
228 | const date = new Date();
229 |
230 | return (
231 |
232 | {date}
233 | {date}
234 | {date}
235 | {date}
236 |
237 | );
238 | }
239 | }
240 | ```
241 |
242 | #### From Now
243 | _fromNow={bool}_
244 |
245 | Sometimes called timeago or relative time, displays the date as the time _from now_, e.g. "5 minutes ago".
246 |
247 | ```jsx
248 | import React from 'react';
249 | import Moment from 'react-moment';
250 |
251 | export default class MyComponent extends React.Component {
252 | render() {
253 | return (
254 | 1976-04-19T12:59-0500
255 | );
256 | }
257 | }
258 | ```
259 |
260 | Outputs:
261 |
262 | ```html
263 |
264 | ```
265 |
266 | Including `ago` with `fromNow` will omit the suffix from the relative time.
267 |
268 | ```jsx
269 | import React from 'react';
270 | import Moment from 'react-moment';
271 |
272 | export default class MyComponent extends React.Component {
273 | render() {
274 | return (
275 | 1976-04-19T12:59-0500
276 | );
277 | }
278 | }
279 | ```
280 |
281 | Outputs:
282 |
283 | ```html
284 |
285 | ```
286 |
287 | #### From Now During
288 |
289 | _fromNowDuring={number}_
290 |
291 | Setting _fromNowDuring_ will display the relative time as with _fromNow_ but just during its value in milliseconds, after that _format_ will be used instead.
292 |
293 | #### From
294 | _from={string}_
295 |
296 | ```jsx
297 | import React from 'react';
298 | import Moment from 'react-moment';
299 |
300 | export default class MyComponent extends React.Component {
301 | render() {
302 | return (
303 | 1976-04-19T12:59-0500
304 | );
305 | }
306 | }
307 | ```
308 |
309 | Outputs:
310 |
311 | ```html
312 |
313 | ```
314 |
315 |
316 | #### To Now
317 | _toNow={bool}_
318 |
319 | Similar to `fromNow`, but gives the opposite interval.
320 |
321 | ```jsx
322 | import React from 'react';
323 | import Moment from 'react-moment';
324 |
325 | export default class MyComponent extends React.Component {
326 | render() {
327 | return (
328 | 1976-04-19T12:59-0500
329 | );
330 | }
331 | }
332 | ```
333 |
334 | Outputs:
335 |
336 | ```html
337 |
338 | ```
339 |
340 |
341 | #### To
342 | _to={string}_
343 |
344 | ```jsx
345 | import React from 'react';
346 | import Moment from 'react-moment';
347 |
348 | export default class MyComponent extends React.Component {
349 | render() {
350 | return (
351 | 1976-04-19T12:59-0500
352 | );
353 | }
354 | }
355 | ```
356 |
357 | Outputs:
358 |
359 | ```html
360 |
361 | ```
362 |
363 | #### Filter
364 | _filter={function}_
365 |
366 | A function which modifies/transforms the date value prior to rendering.
367 |
368 | ```jsx
369 | import React from 'react';
370 | import Moment from 'react-moment';
371 |
372 | export default class MyComponent extends React.Component {
373 | render() {
374 | const toUpperCaseFilter = (d) => {
375 | return d.toUpperCase();
376 | };
377 |
378 | return (
379 | const dateToFormat = '1976-04-19T12:59-0500';
380 | {dateToFormat}
381 | );
382 | }
383 | }
384 | ```
385 |
386 | Outputs:
387 |
388 | ```html
389 |
390 | ```
391 |
392 |
393 | #### With Title
394 | _withTitle={bool}_
395 |
396 | Adds a `title` attribute to the element with the complete date.
397 |
398 | ```jsx
399 | import React from 'react';
400 | import Moment from 'react-moment';
401 |
402 | export default class MyComponent extends React.Component {
403 | render() {
404 | return (
405 |
406 | 1976-04-19T12:59-0500
407 |
408 | );
409 | }
410 | }
411 | ```
412 |
413 | Outputs:
414 |
415 | ```html
416 |
417 | ```
418 |
419 |
420 | #### Title Format
421 | _titleFormat={string}_
422 |
423 | How the `title` date is formatted when using the `withTitle` attribute.
424 |
425 | ```jsx
426 | import React from 'react';
427 | import Moment from 'react-moment';
428 |
429 | export default class MyComponent extends React.Component {
430 | render() {
431 | return (
432 |
433 | 1976-04-19T12:59-0500
434 |
435 | );
436 | }
437 | }
438 | ```
439 |
440 | Outputs:
441 |
442 | ```html
443 |
444 | ```
445 |
446 |
447 | #### Difference
448 | _diff={string}_
449 |
450 | _decimal={bool}_
451 |
452 | _unit={string}_
453 |
454 | ```jsx
455 | import React from 'react';
456 | import Moment from 'react-moment';
457 |
458 | export default class MyComponent extends React.Component {
459 | render() {
460 | return (
461 |
462 | 1976-04-19T12:59-0500
463 | 1976-04-19T12:59-0500
464 | 1976-04-19T12:59-0500
465 |
466 | );
467 | }
468 | }
469 | ```
470 |
471 | #### Duration
472 | _duration={string}_
473 |
474 | _date={string}_
475 |
476 | Shows the duration (elapsed time) between two dates. `duration` property should be behind `date` property time-wise.
477 |
478 | ```jsx
479 | import React from 'react';
480 | import Moment from 'react-moment';
481 |
482 | export default class MyComponent extends React.Component {
483 | render() {
484 | return (
485 |
488 | );
489 | }
490 | }
491 | ```
492 |
493 | #### Duration From Now
494 |
495 | _durationFromNow={bool}_
496 |
497 | Shows the duration (elapsed time) between now and the provided datetime.
498 |
499 | ```jsx
500 | import React from 'react';
501 | import Moment from 'react-moment';
502 |
503 | export default class MyComponent extends React.Component {
504 | render() {
505 | return (
506 |
509 | );
510 | }
511 | }
512 | ```
513 |
514 | #### Unix Timestamps
515 | _unix={bool}_
516 |
517 | Tells Moment to parse the given date value as a unix timestamp.
518 |
519 | ```jsx
520 | import React from 'react';
521 | import Moment from 'react-moment';
522 |
523 | export default class MyComponent extends React.Component {
524 | render() {
525 | const unixTimestamp = 198784740;
526 | return (
527 | {unixTimestamp}
528 | );
529 | }
530 | }
531 | ```
532 |
533 | Outputs:
534 |
535 | ```html
536 |
537 | ```
538 |
539 | #### Local
540 | _local={bool}_
541 |
542 | Outputs the result in local time.
543 |
544 | ```jsx
545 | import React from 'react';
546 | import Moment from 'react-moment';
547 |
548 | export default class MyComponent extends React.Component {
549 | render() {
550 | return (
551 |
552 | 2018-11-01T12:59-0500
553 |
554 | );
555 | }
556 | }
557 | ```
558 |
559 | Outputs:
560 |
561 | ```html
562 |
563 | ```
564 |
565 |
566 | #### Timezone
567 | _tz={string}_
568 |
569 | Sets the timezone. To enable server side rendering (SSR), client and server has to provide same datetime, based on common Timezone. The `tz` attribute will enable set the common timezone.
570 |
571 | ```jsx
572 | import React from 'react';
573 | import Moment from 'react-moment';
574 | import 'moment-timezone';
575 |
576 | export default class MyComponent extends React.Component {
577 | render() {
578 | const unixTimestamp = 198784740;
579 | return (
580 |
581 | {unixTimestamp}
582 |
583 | );
584 | }
585 | }
586 | ```
587 |
588 | Outputs:
589 |
590 | ```html
591 |
592 | ```
593 |
594 | #### Calendar
595 | _calendar={object|bool}_
596 |
597 | Customize the strings used for the calendar function.
598 |
599 | ```jsx
600 | import React from 'react';
601 | import Moment from 'react-moment';
602 |
603 | export default class MyComponent extends React.Component {
604 | render() {
605 | const calendarStrings = {
606 | lastDay : '[Yesterday at] LT',
607 | sameDay : '[Today at] LT',
608 | nextDay : '[Tomorrow at] LT',
609 | lastWeek : '[last] dddd [at] LT',
610 | nextWeek : 'dddd [at] LT',
611 | sameElse : 'L'
612 | };
613 |
614 | return (
615 |
616 | '1976-04-19T12:59-0500'
617 |
618 | );
619 | }
620 | }
621 | ```
622 |
623 |
624 | #### Locale
625 | _locale={string}_
626 |
627 | Sets the locale used to display the date.
628 |
629 | ```jsx
630 | import React from 'react';
631 | import Moment from 'react-moment';
632 |
633 | export default class MyComponent extends React.Component {
634 | render() {
635 | const dateToFormat = '1976-04-19T12:59-0500';
636 | return (
637 | {dateToFormat}
638 | );
639 | }
640 | }
641 | ```
642 |
643 | **Note**
644 | In some cases the language file is not automatically loaded by moment, and it must be manually loaded. For example, to use the French locale, add the following to your bootstrap (e.g. index.js) script.
645 |
646 | ```js
647 | import 'moment/locale/fr';
648 | ```
649 |
650 | #### Element
651 | _element={string|React.Component}_
652 |
653 | The element type to render as (string or function).
654 |
655 | ```jsx
656 | import React from 'react';
657 | import Moment from 'react-moment';
658 |
659 | export default class MyComponent extends React.Component {
660 | render() {
661 | return (
662 | 1976-04-19T12:59-0500
663 | );
664 | }
665 | }
666 | ```
667 |
668 | Outputs:
669 |
670 | ```html
671 | Mon Apr 19 1976 12:59:00 GMT-0500
672 | ```
673 |
674 |
675 | #### OnChange
676 | _onChange={func}_
677 |
678 | The `onChange` prop is called each time the date is updated, which by default is every 60 seconds. The function receives the new date value.
679 |
680 | ```jsx
681 | import React from 'react';
682 | import Moment from 'react-moment';
683 |
684 | export default class MyComponent extends React.Component {
685 | render() {
686 | return (
687 | { console.log(val); }}>
688 | 1976-04-19T12:59-0500
689 |
690 | );
691 | }
692 | }
693 | ```
694 |
695 |
696 | #### Other Props
697 | Any other properties are passed to the `