diff --git a/opentech/static_src/src/app/src/api/index.js b/opentech/static_src/src/app/src/api/index.js
index 6fcd16016bf03484a9777a09772ecd92b46f4507..cd360e714df61f83b2fe5f9eb35db4ead48b7650 100644
--- a/opentech/static_src/src/app/src/api/index.js
+++ b/opentech/static_src/src/app/src/api/index.js
@@ -1,6 +1,9 @@
 import { fetchSubmission, fetchSubmissionsByRound } from '@api/submissions';
+import { fetchNotesForSubmission } from '@api/notes';
 
 export default {
     fetchSubmissionsByRound,
     fetchSubmission,
+
+    fetchNotesForSubmission,
 };
diff --git a/opentech/static_src/src/app/src/api/notes.js b/opentech/static_src/src/app/src/api/notes.js
new file mode 100644
index 0000000000000000000000000000000000000000..a797ba32ffbdf65267bb3a35252face9fc7c4721
--- /dev/null
+++ b/opentech/static_src/src/app/src/api/notes.js
@@ -0,0 +1,8 @@
+import { apiFetch } from '@api/utils';
+
+export function fetchNotesForSubmission(submissionID, visibility = 'internal') {
+    return apiFetch(`/apply/api/submissions/${submissionID}/comments/`, 'GET', {
+        visibility,
+        page_size: 1000,
+    });
+}
diff --git a/opentech/static_src/src/app/src/components/GroupedListing.js b/opentech/static_src/src/app/src/components/GroupedListing.js
new file mode 100644
index 0000000000000000000000000000000000000000..ddac492a95a104ed200c5f376cfa4b8e3c1ec59c
--- /dev/null
+++ b/opentech/static_src/src/app/src/components/GroupedListing.js
@@ -0,0 +1,104 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Listing from '@components/Listing';
+import ListingGroup from '@components/ListingGroup';
+import ListingItem from '@components/ListingItem';
+
+export default class GroupedListing extends React.Component {
+    static propTypes = {
+        items: PropTypes.array,
+        activeItem: PropTypes.number,
+        isLoading: PropTypes.bool,
+        error: PropTypes.string,
+        groupBy: PropTypes.string,
+        order: PropTypes.arrayOf(PropTypes.string),
+        onItemSelection: PropTypes.func,
+        shouldSelectFirst: PropTypes.bool,
+    };
+
+    static defaultProps = {
+        shouldSelectFirst: true,
+    }
+
+
+    state = {
+        orderedItems: [],
+    };
+
+    componentDidMount() {
+        this.orderItems();
+    }
+
+    componentDidUpdate(prevProps, prevState) {
+        // Order items
+        if (this.props.items !== prevProps.items) {
+            this.orderItems();
+        }
+
+        if ( this.props.shouldSelectFirst ){
+            const oldItem = prevProps.activeItem
+            const newItem = this.props.activeItem
+
+            // If we have never activated a submission, get the first item
+            if ( !newItem && !oldItem ) {
+                const firstGroup = this.state.orderedItems[0]
+                if ( firstGroup && firstGroup.items[0] ) {
+                    this.setState({firstUpdate: false})
+                    this.props.onItemSelection(firstGroup.items[0].id)
+                }
+            }
+        }
+    }
+
+    getGroupedItems() {
+        const { groupBy, items } = this.props;
+
+        return items.reduce((tmpItems, v) => {
+            const groupByValue = v[groupBy];
+            if (!(groupByValue in tmpItems)) {
+                tmpItems[groupByValue] = [];
+            }
+            tmpItems[groupByValue].push({...v});
+            return tmpItems;
+        }, {});
+    }
+
+    orderItems() {
+        const groupedItems = this.getGroupedItems();
+        const { order = [] } = this.props;
+        const leftOverKeys = Object.keys(groupedItems).filter(v => !order.includes(v));
+        this.setState({
+            orderedItems: order.concat(leftOverKeys).filter(key => groupedItems[key] ).map(key => ({
+                name: key,
+                items: groupedItems[key] || []
+            })),
+        });
+    }
+
+    renderItem = group => {
+        const { activeItem, onItemSelection } = this.props;
+        return (
+            <ListingGroup key={`listing-group-${group.name}`} item={group}>
+                {group.items.map(item => {
+                    return <ListingItem
+                        selected={!!activeItem && activeItem===item.id}
+                        onClick={() => onItemSelection(item.id)}
+                        key={`listing-item-${item.id}`}
+                        item={item}/>;
+                })}
+            </ListingGroup>
+        );
+    }
+
+    render() {
+        const passProps = {
+            items: this.state.orderedItems,
+            renderItem: this.renderItem,
+            isLoading: this.props.isLoading,
+            isError: Boolean(this.error),
+            error: this.error,
+        };
+        return <Listing {...passProps} />;
+    }
+}
diff --git a/opentech/static_src/src/app/src/components/Listing/index.js b/opentech/static_src/src/app/src/components/Listing/index.js
index 0296b55c127cea075d1a1427d7c770af0601192c..6da541874683c12f5ae4e034e7e74fe34cf64b3e 100644
--- a/opentech/static_src/src/app/src/components/Listing/index.js
+++ b/opentech/static_src/src/app/src/components/Listing/index.js
@@ -1,123 +1,64 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import ListingGroup from '@components/ListingGroup';
-import ListingItem from '@components/ListingItem';
 import LoadingPanel from '@components/LoadingPanel';
 
 import './style.scss';
 
 export default class Listing extends React.Component {
     static propTypes = {
-        items: PropTypes.array,
-        activeItem: PropTypes.number,
+        items: PropTypes.array.isRequired,
         isLoading: PropTypes.bool,
+        isError: PropTypes.bool,
         error: PropTypes.string,
         groupBy: PropTypes.string,
         order: PropTypes.arrayOf(PropTypes.string),
         onItemSelection: PropTypes.func,
-        shouldSelectFirst: PropTypes.bool,
+        renderItem: PropTypes.func.isRequired,
+        handleRetry: PropTypes.func,
     };
 
-    static defaultProps = {
-        shouldSelectFirst: true,
-    }
-
-    state = {
-        orderedItems: [],
-    };
-
-    componentDidMount() {
-        this.orderItems();
-    }
-
-    componentDidUpdate(prevProps, prevState) {
-        // Order items
-        if (this.props.items !== prevProps.items) {
-            this.orderItems();
-        }
-
-        if ( this.props.shouldSelectFirst ){
-            const oldItem = prevProps.activeItem
-            const newItem = this.props.activeItem
-
-            // If we have never activated a submission, get the first item
-            if ( !newItem && !oldItem ) {
-                const firstGroup = this.state.orderedItems[0]
-                if ( firstGroup && firstGroup.items[0] ) {
-                    this.setState({firstUpdate: false})
-                    this.props.onItemSelection(firstGroup.items[0].id)
-                }
-            }
-        }
-    }
-
     renderListItems() {
-        const { isLoading, error, items, onItemSelection, activeItem } = this.props;
+        const {
+            isError,
+            isLoading,
+            items,
+            renderItem,
+        } = this.props;
 
         if (isLoading) {
             return (
                 <div className="listing__list is-loading">
                     <LoadingPanel />
                 </div>
-            )
-        } else if (error) {
-            return (
-                <div className="listing__list is-loading">
-                    <p>Something went wrong. Please try again later.</p>
-                    <p>{ error }</p>
-                </div>
-            )
+            );
+        } else if (isError) {
+            return this.renderError();
         } else if (items.length === 0) {
             return (
                 <div className="listing__list is-loading">
                     <p>No results found.</p>
                 </div>
-            )
+            );
         }
 
         return (
             <ul className="listing__list">
-                {this.state.orderedItems.map(group => {
-                    return (
-                        <ListingGroup key={`listing-group-${group.name}`} item={group}>
-                            {group.items.map(item => {
-                                return <ListingItem
-                                    selected={!!activeItem && activeItem===item.id}
-                                    onClick={() => onItemSelection(item.id)}
-                                    key={`listing-item-${item.id}`}
-                                    item={item}/>;
-                            })}
-                        </ListingGroup>
-                    );
-                })}
+                {items.map(v => renderItem(v))}
             </ul>
         );
     }
 
-    getGroupedItems() {
-        const { groupBy, items } = this.props;
-
-        return items.reduce((tmpItems, v) => {
-            const groupByValue = v[groupBy];
-            if (!(groupByValue in tmpItems)) {
-                tmpItems[groupByValue] = [];
-            }
-            tmpItems[groupByValue].push({...v});
-            return tmpItems;
-        }, {});
-    }
-
-    orderItems() {
-        const groupedItems = this.getGroupedItems();
-        const { order = [] } = this.props;
-        const leftOverKeys = Object.keys(groupedItems).filter(v => !order.includes(v));
-        this.setState({
-            orderedItems: order.concat(leftOverKeys).filter(key => groupedItems[key] ).map(key => ({
-                name: key,
-                items: groupedItems[key] || []
-            })),
-        });
+    renderError = () => {
+        const { handleRetry, error } = this.props;
+        const retryButton = <a onClick={handleRetry}>Refresh</a>;
+        return (
+            <div className="listing__list is-loading">
+                <p>Something went wrong. Please try again later.</p>
+                {error && <p>{error}</p>}
+                {handleRetry && retryButton}
+            </div>
+        );
     }
 
     render() {
diff --git a/opentech/static_src/src/app/src/components/NoteListingItem.js b/opentech/static_src/src/app/src/components/NoteListingItem.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7f88178d3821b33706d9739b0e9108ec94a71b1
--- /dev/null
+++ b/opentech/static_src/src/app/src/components/NoteListingItem.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+
+export default class NoteListingItem extends React.Component {
+    static propTypes = {
+        user: PropTypes.string.isRequired,
+        message: PropTypes.string.isRequired,
+        timestamp: PropTypes.instanceOf(moment).isRequired,
+    };
+
+    render() {
+        const { user, timestamp, message } = this.props;
+        return (
+            <div>
+                <div style={{fontWeight: 'bold'}}>{user} - {timestamp.format('ll')}</div>
+                <div>{message}</div>
+            </div>
+        );
+    }
+}
diff --git a/opentech/static_src/src/app/src/containers/ByStatusListing.js b/opentech/static_src/src/app/src/containers/ByStatusListing.js
index f33b78e512a3130228d31acfb2353c32b5603c12..c6c2d705227ec77f1d9409a379b3aaf979a8ad2b 100644
--- a/opentech/static_src/src/app/src/containers/ByStatusListing.js
+++ b/opentech/static_src/src/app/src/containers/ByStatusListing.js
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { connect } from 'react-redux'
 
-import Listing from '@components/Listing';
+import GroupedListing from '@components/GroupedListing';
 import {
     loadCurrentRound,
     setCurrentSubmission,
@@ -50,7 +50,7 @@ class ByStatusListing extends React.Component {
     render() {
         const { error, submissions, round, setCurrentItem, activeSubmission, shouldSelectFirst} = this.props;
         const isLoading = round && round.isFetching
-        return <Listing
+        return <GroupedListing
                     isLoading={isLoading}
                     error={error}
                     items={submissions}
diff --git a/opentech/static_src/src/app/src/containers/DisplayPanel/index.js b/opentech/static_src/src/app/src/containers/DisplayPanel/index.js
index e09e83f962d083bafa4acb743007b7443aef068b..0da90bb5bb0c4b23687ad2d7302be2320ee5b645 100644
--- a/opentech/static_src/src/app/src/containers/DisplayPanel/index.js
+++ b/opentech/static_src/src/app/src/containers/DisplayPanel/index.js
@@ -13,10 +13,10 @@ import {
 } from '@selectors/submissions';
 
 import CurrentSubmissionDisplay from '@containers/CurrentSubmissionDisplay'
+import NoteListing from '@containers/NoteListing';
 import Tabber, {Tab} from '@components/Tabber'
 import './style.scss';
 
-
 class DisplayPanel extends React.Component  {
     static propTypes = {
         submissionID: PropTypes.number,
@@ -28,7 +28,7 @@ class DisplayPanel extends React.Component  {
     };
 
     render() {
-        const { windowSize: {windowWidth: width} } = this.props;
+        const { windowSize: {windowWidth: width}, submissionID } = this.props;
         const { clearSubmission } = this.props;
 
         const isMobile = width < 1024;
@@ -37,7 +37,7 @@ class DisplayPanel extends React.Component  {
 
         let tabs = [
             <Tab button="Notes" key="note">
-                <p>Notes</p>
+                <NoteListing submissionID={submissionID} />
             </Tab>,
             <Tab button="Status" key="status">
                 <p>Status</p>
@@ -88,5 +88,4 @@ const mapDispatchToProps = {
     clearSubmission: clearCurrentSubmission
 }
 
-
 export default connect(mapStateToProps, mapDispatchToProps)(withWindowSizeListener(DisplayPanel));
diff --git a/opentech/static_src/src/app/src/containers/Note.js b/opentech/static_src/src/app/src/containers/Note.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f297145bc34b0939cef8354166084d8db41435d
--- /dev/null
+++ b/opentech/static_src/src/app/src/containers/Note.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+
+import { getNoteOfID } from '@selectors/notes';
+import NoteListingItem from '@components/NoteListingItem';
+
+class Note extends React.Component {
+    static propTypes = {
+        note: PropTypes.shape({
+            user: PropTypes.string,
+            timestamp: PropTypes.string,
+            message: PropTypes.string,
+        }),
+    };
+
+    render() {
+        const { note } = this.props;
+
+        return <NoteListingItem
+                user={note.user}
+                message={note.message}
+                timestamp={moment(note.timestamp)}
+        />;
+    }
+
+}
+
+const mapStateToProps = (state, ownProps) => ({
+    note: getNoteOfID(ownProps.noteID)(state),
+});
+
+export default connect(mapStateToProps)(Note);
diff --git a/opentech/static_src/src/app/src/containers/NoteListing.js b/opentech/static_src/src/app/src/containers/NoteListing.js
new file mode 100644
index 0000000000000000000000000000000000000000..7f16d6c648ca597a8856d8eec3d77c2530c6d97f
--- /dev/null
+++ b/opentech/static_src/src/app/src/containers/NoteListing.js
@@ -0,0 +1,71 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+
+import { fetchNotesForSubmission } from '@actions/notes';
+import Listing from '@components/Listing';
+import Note from '@containers/Note';
+import {
+    getNotesErrorState,
+    getNoteIDsForSubmissionOfID,
+    getNotesFetchState,
+} from '@selectors/notes';
+
+class NoteListing extends React.Component {
+    static propTypes = {
+        loadNotes: PropTypes.func,
+        submissionID: PropTypes.number,
+        noteIDs: PropTypes.array,
+        isErrored: PropTypes.bool,
+        isLoading: PropTypes.bool,
+    };
+
+    componentDidUpdate(prevProps) {
+        const { submissionID } = this.props;
+        const prevSubmissionID = prevProps.submissionID;
+
+        if(
+            submissionID !== null && submissionID !== undefined &&
+            prevSubmissionID !== submissionID && !this.props.isLoading
+        ) {
+            this.props.loadNotes(submissionID);
+        }
+    }
+
+    handleRetry = () => {
+        if (this.props.isLoading || !this.props.isErrored) {
+            return;
+        }
+        this.props.loadNotes(this.props.submissionID);
+    }
+
+    renderItem = noteID => {
+        return <Note key={`note-${noteID}`} noteID={noteID} />;
+    }
+
+    render() {
+        const { noteIDs } = this.props;
+        const passProps = {
+            isLoading: this.props.isLoading,
+            isError: this.props.isErrored,
+            handleRetry: this.handleRetry,
+            renderItem: this.renderItem,
+            items: noteIDs,
+        };
+        return (
+            <Listing {...passProps} />
+        );
+    }
+}
+
+const mapDispatchToProps = dispatch => ({
+    loadNotes: submissionID => dispatch(fetchNotesForSubmission(submissionID)),
+});
+
+const mapStateToProps = (state, ownProps) => ({
+    noteIDs: getNoteIDsForSubmissionOfID(ownProps.submissionID)(state),
+    isLoading: getNotesFetchState(state),
+    isErrored: getNotesErrorState(state),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(NoteListing);
diff --git a/opentech/static_src/src/app/src/datetime.js b/opentech/static_src/src/app/src/datetime.js
new file mode 100644
index 0000000000000000000000000000000000000000..f906f3acee590e92f3df0b5c7a9bb66a8ca79e24
--- /dev/null
+++ b/opentech/static_src/src/app/src/datetime.js
@@ -0,0 +1,8 @@
+import moment from 'moment';
+import 'moment-timezone';
+
+// Use GMT globally for all the dates.
+moment.tz.setDefault('GMT');
+
+// Use en-US locale for all the dates.
+moment.locale('en');
diff --git a/opentech/static_src/src/app/src/redux/actions/notes.js b/opentech/static_src/src/app/src/redux/actions/notes.js
new file mode 100644
index 0000000000000000000000000000000000000000..ecac7ad5fc3bac9f8cc82fc7db1a6fe8af6b96d6
--- /dev/null
+++ b/opentech/static_src/src/app/src/redux/actions/notes.js
@@ -0,0 +1,46 @@
+import { updateSubmission } from '@actions/submissions';
+import api from '@api';
+
+export const FAIL_FETCHING_NOTES = 'FAIL_FETCHING_NOTES';
+export const START_FETCHING_NOTES = 'START_FETCHING_NOTES';
+export const UPDATE_NOTES = 'UPDATE_NOTES';
+export const UPDATE_NOTE = 'UPDATE_NOTE';
+
+const startFetchingNotes = () => ({
+    type: START_FETCHING_NOTES,
+});
+
+const failFetchingNotes = message => ({
+    type: FAIL_FETCHING_NOTES,
+    message,
+});
+
+export const fetchNotesForSubmission = submissionID => {
+    return async function(dispatch) {
+        dispatch(startFetchingNotes());
+        try {
+            const response = await api.fetchNotesForSubmission(submissionID);
+            const json = await response.json();
+            if (!response.ok) {
+                return dispatch(failFetchingNotes(json.detail));
+            }
+            return dispatch(updateNotesForSubmission(submissionID, json));
+        } catch(e) {
+            return dispatch(failFetchingNotes(e.message));
+        }
+    };
+};
+
+const updateNotesForSubmission = (submissionID, data) => {
+    return async function(dispatch) {
+        dispatch(updateNotes(data));
+        dispatch(updateSubmission(submissionID, {
+            comments: data.results.map(v => v.id),
+        }));
+    };
+};
+
+export const updateNotes = data => ({
+    type: UPDATE_NOTES,
+    data,
+});
diff --git a/opentech/static_src/src/app/src/redux/actions/submissions.js b/opentech/static_src/src/app/src/redux/actions/submissions.js
index db35aec0678e3239cec174980b3de9ff6a8d6459..181f755f4fb588a3338a82c948b5ecd6a9b9a8ec 100644
--- a/opentech/static_src/src/app/src/redux/actions/submissions.js
+++ b/opentech/static_src/src/app/src/redux/actions/submissions.js
@@ -123,7 +123,7 @@ const failLoadingSubmission = submissionID => ({
 });
 
 
-const updateSubmission = (submissionID, data) => ({
+export const updateSubmission = (submissionID, data) => ({
     type: UPDATE_SUBMISSION,
     submissionID,
     data,
diff --git a/opentech/static_src/src/app/src/redux/reducers/index.js b/opentech/static_src/src/app/src/redux/reducers/index.js
index d1e5e237467ee34a6e305045c469b99560e3e92b..f7e4cfa44051f3fffa375327372c6fb087a8d119 100644
--- a/opentech/static_src/src/app/src/redux/reducers/index.js
+++ b/opentech/static_src/src/app/src/redux/reducers/index.js
@@ -2,8 +2,10 @@ import { combineReducers } from 'redux'
 
 import submissions from '@reducers/submissions';
 import rounds from '@reducers/rounds';
+import notes from '@reducers/notes';
 
 export default combineReducers({
+    notes,
     submissions,
     rounds,
 });
diff --git a/opentech/static_src/src/app/src/redux/reducers/notes.js b/opentech/static_src/src/app/src/redux/reducers/notes.js
new file mode 100644
index 0000000000000000000000000000000000000000..4137c6103999494d66ad54784cbb75397b974a0e
--- /dev/null
+++ b/opentech/static_src/src/app/src/redux/reducers/notes.js
@@ -0,0 +1,68 @@
+import { combineReducers } from 'redux';
+
+import {
+    UPDATE_NOTE,
+    UPDATE_NOTES,
+    START_FETCHING_NOTES,
+    FAIL_FETCHING_NOTES,
+} from '@actions/notes';
+
+function notesFetching(state = false, action) {
+    switch (action.type) {
+        case START_FETCHING_NOTES:
+            return true;
+        case UPDATE_NOTES:
+        case FAIL_FETCHING_NOTES:
+            return false;
+        default:
+            return state;
+    }
+}
+
+function notesErrored(state = false, action) {
+    switch (action.type) {
+        case UPDATE_NOTES:
+        case START_FETCHING_NOTES:
+            return false;
+        case FAIL_FETCHING_NOTES:
+            return true;
+        default:
+            return state;
+    }
+}
+
+function note(state, action) {
+    switch (action.type) {
+        case UPDATE_NOTE:
+            return {
+                ...state,
+                ...action.data,
+            };
+        default:
+            return state;
+    }
+}
+
+function notesByID(state = {}, action) {
+    switch(action.type) {
+        case UPDATE_NOTES:
+            return {
+                ...state,
+                ...action.data.results.reduce((newNotesAccumulator, newNote) => {
+                    newNotesAccumulator[newNote.id] = note(state[newNote.id], {
+                        type: UPDATE_NOTE,
+                        data: newNote,
+                    });
+                    return newNotesAccumulator;
+                }, {}),
+            };
+        default:
+            return state;
+    }
+}
+
+export default combineReducers({
+    byID: notesByID,
+    isFetching: notesFetching,
+    isErrored: notesErrored,
+});
diff --git a/opentech/static_src/src/app/src/redux/selectors/notes.js b/opentech/static_src/src/app/src/redux/selectors/notes.js
new file mode 100644
index 0000000000000000000000000000000000000000..cf5ccc2fd82a54c81ba08884242409ac6b565117
--- /dev/null
+++ b/opentech/static_src/src/app/src/redux/selectors/notes.js
@@ -0,0 +1,18 @@
+import { createSelector } from 'reselect';
+
+import { getSubmissionOfID } from '@selectors/submissions';
+
+const getNotes = state => state.notes.byID;
+
+export const getNoteOfID = (noteID) => createSelector(
+    [getNotes], notes => notes[noteID]
+);
+
+export const getNotesFetchState = state => state.notes.isFetching === true;
+
+export const getNotesErrorState = state => state.notes.isErrored === true;
+
+export const getNoteIDsForSubmissionOfID = submissionID => createSelector(
+    [getSubmissionOfID(submissionID)],
+    submission => (submission || {}).comments || []
+);
diff --git a/opentech/static_src/src/app/src/redux/selectors/submissions.js b/opentech/static_src/src/app/src/redux/selectors/submissions.js
index 09124b6896da42c2c177f2e706a8b2528deaca54..d012fcb6ae2f7dcd593cf75fe381fc98a6d6ced1 100644
--- a/opentech/static_src/src/app/src/redux/selectors/submissions.js
+++ b/opentech/static_src/src/app/src/redux/selectors/submissions.js
@@ -32,6 +32,10 @@ const getCurrentSubmission = createSelector(
     }
 );
 
+const getSubmissionOfID = (submissionID) => createSelector(
+    [getSubmissions], submissions => submissions[submissionID]
+);
+
 const getSubmissionLoadingState = state => state.submissions.itemLoading === true;
 
 const getSubmissionErrorState = state => state.submissions.itemLoadingError === true;
@@ -50,4 +54,5 @@ export {
     getSubmissionsByRoundLoadingState,
     getSubmissionLoadingState,
     getSubmissionErrorState,
+    getSubmissionOfID,
 };
diff --git a/opentech/static_src/src/app/webpack.base.config.js b/opentech/static_src/src/app/webpack.base.config.js
index b6017a03190fc4870e014265d069f79bdd48d734..b30d026d86c698c658126352f7bc9fd8c41fcca8 100644
--- a/opentech/static_src/src/app/webpack.base.config.js
+++ b/opentech/static_src/src/app/webpack.base.config.js
@@ -3,7 +3,7 @@ var path = require('path');
 module.exports = {
     context: __dirname,
 
-    entry: ['@babel/polyfill', './src/index'],
+    entry: ['@babel/polyfill', './src/datetime', './src/index'],
 
     output: {
         filename: '[name]-[hash].js'
diff --git a/package-lock.json b/package-lock.json
index c03d14147002a5f8545753a762f00216e4b07807..72ffbc0221f795b987f45498096eaad4293faa0e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4272,7 +4272,8 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -4290,11 +4291,13 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -4307,15 +4310,18 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -4418,7 +4424,8 @@
                 },
                 "inherits": {
                     "version": "2.0.3",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -4428,6 +4435,7 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -4440,17 +4448,20 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "minipass": {
                     "version": "2.3.5",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -4467,6 +4478,7 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -4539,7 +4551,8 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -4549,6 +4562,7 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -4624,7 +4638,8 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -4654,6 +4669,7 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -4671,6 +4687,7 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -4709,11 +4726,13 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "yallist": {
                     "version": "3.0.3",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 }
             }
         },
@@ -7133,6 +7152,19 @@
                 }
             }
         },
+        "moment": {
+            "version": "2.24.0",
+            "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+            "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+        },
+        "moment-timezone": {
+            "version": "0.5.23",
+            "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz",
+            "integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==",
+            "requires": {
+                "moment": ">= 2.9.0"
+            }
+        },
         "move-concurrently": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
diff --git a/package.json b/package.json
index 7ad0fcfa4597981cdb4b18bee2f73c21963125a2..cbac85369d9acd98c168bde41defadb953004125 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,8 @@
         "gulp-size": "^3.0.0",
         "gulp-touch-cmd": "0.0.1",
         "gulp-uglify": "^3.0.1",
+        "moment": "^2.24.0",
+        "moment-timezone": "^0.5.23",
         "node-sass-import-once": "^1.2.0",
         "prop-types": "^15.6.2",
         "react": "^16.7.0",