superdesk/superdesk-client-core

View on GitHub
scripts/core/datetime/datetime.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import _ from 'lodash';
import {gettext} from 'core/utils';
import moment from 'moment-timezone';
import {appConfig} from 'appConfig';
import {IArticle} from 'superdesk-api';

const ISO_DATE_FORMAT = 'YYYY-MM-DD';
const ISO_WEEK_FORMAT = 'YYYY-W';
const ISO_YEAR_FORMAT = 'YYYY';

const LONG_FORMAT = appConfig.longDateFormat || 'LLL';
const TIME_FORMAT = appConfig.shortTimeFormat || 'hh:mm';
const DATE_FORMAT = appConfig.shortDateFormat || 'MM/DD';
const WEEK_FORMAT = appConfig.shortWeekFormat || 'dddd, ' + TIME_FORMAT;
const ARCHIVE_FORMAT = appConfig.ArchivedDateFormat || DATE_FORMAT;
const SERVER_FORMAT = 'YYYY-MM-DDTHH:mm:ssZZ';

/**
* Get long representation of given datetime
*
* @param {String} d iso format datetime
*/
export function longFormat(d: string | moment.Moment): string {
    return moment(d).format(LONG_FORMAT);
}

export function serverFormat(d: string | moment.Moment): string {
    return moment(d).utc().format(SERVER_FORMAT);
}

DateTimeDirective.$inject = ['datetime'];
function DateTimeDirective(datetime) {
    return {
        scope: {date: '=', fromNow: '='},
        link: function datetimeLink(scope, elem) {
            scope.$watch('date', renderDate);

            /**
             * Render relative date within given directive
             *
             * @param {string} date iso date
             */
            function renderDate(date) {
                var momentDate = moment(date);
                var txt = scope.fromNow ? momentDate.fromNow() : datetime.shortFormat(momentDate);

                elem.text(txt);
                elem.attr('title', momentDate.format('LLLL'));
            }
        },
    };
}

ShortDateDirective.$inject = [];
function ShortDateDirective() {
    var CONFIG_DATE_FORMAT = appConfig.view.dateformat || appConfig.model.dateformat;

    return {
        scope: {date: '='},
        link: function dateLink(scope, elem) {
            scope.$watch('date', renderDate);

            /**
             * Render short date within given directive
             *
             * @param {string} date iso date
             */
            function renderDate(date) {
                var momentDate = moment(date);
                var text = momentDate.format(CONFIG_DATE_FORMAT);

                if (momentDate) {
                    elem.text(text);
                    elem.attr('title', text);
                }
            }
        },
    };
}

function isSameDay(a, b) {
    return a.format(ISO_DATE_FORMAT) === b.format(ISO_DATE_FORMAT);
}

function isSameWeek(a, b) {
    return a.format(ISO_WEEK_FORMAT) === b.format(ISO_WEEK_FORMAT);
}

function isArchiveYear(a, b) {
    return (appConfig.ArchivedDateOnCalendarYear === 1) ?
        a.format(ISO_YEAR_FORMAT) !== b.format(ISO_YEAR_FORMAT) : b.diff(a, 'years') >= 1;
}

export function isScheduled(__item: IArticle) {
    const item = __item.archive_item ?? __item;

    return item.publish_schedule != null;
}

/**
 * Get date and time format for scheduled datetime
 * Returns time for current day, date and time otherwise
 */
export function scheduledFormat(__item: IArticle): {short: string, long: string} {
    const browserTimezone = moment.tz.guess();

    const item = __item.archive_item ?? __item;

    const datetime = item?.schedule_settings?.time_zone == null
        ? moment(item.publish_schedule).tz(browserTimezone)
        : moment.tz(
            item.publish_schedule.replace('+0000', ''),
            item.schedule_settings.time_zone,
        ).tz(browserTimezone);

    var now = moment();

    const _date = datetime.format(appConfig.view.dateformat || 'MM/DD'),
        _time = datetime.format(appConfig.view.timeformat || 'hh:mm');

    let short = isSameDay(datetime, now) ? '@ '.concat(_time) : _date.concat(' @ ', _time);

    return {
        short: short,
        long: longFormat(datetime),
    };
}

DateTimeService.$inject = [];
function DateTimeService() {
    /**
     * Get short representation of given datetime
     *
     * It returns time for current day, day + time for current week, date otherwise.
     *
     * @param {String} d iso format datetime
     * @return {String}
     */
    this.shortFormat = function(d) {
        var m = moment(d);
        var now = moment();

        if (isSameDay(m, now)) {
            return m.format(TIME_FORMAT);
        } else if (isSameWeek(m, now)) {
            return m.format(WEEK_FORMAT);
        } else if (isArchiveYear(m, now)) {
            return m.format(ARCHIVE_FORMAT);
        }

        return m.format(DATE_FORMAT);
    };

    this.longFormat = longFormat;
}

DateTimeHelperService.$inject = [];
function DateTimeHelperService() {
    /*
    * @param timestring 2016-03-01T04:45:00+0000
    * @param timezone Europe/London
    */
    this.splitDateTime = function(timestring, timezone) {
        var momentTS = moment.tz(timestring, timezone);

        return {
            date: momentTS.format(appConfig.model.dateformat),
            time: momentTS.format(appConfig.model.timeformat),
        };
    };

    this.isValidTime = function(value, format) {
        var timeFormat = format || appConfig.model.timeformat;

        return moment(value, timeFormat, true).isValid();
    };

    this.isValidDate = function(value, format) {
        var dateFormat = format || appConfig.model.dateformat;

        return moment(value, dateFormat, true).isValid();
    };

    this.mergeDateTime = function(dateStr, timeStr, timezone) {
        var tz = timezone || appConfig.default_timezone;
        var mergeStr = dateStr + ' ' + timeStr;
        var formatter = appConfig.model.dateformat + ' ' + appConfig.model.timeformat;

        // return without timezone information, which is stored separately
        return moment.tz(mergeStr, formatter, tz).format('YYYY-MM-DD[T]HH:mm:ss');
    };

    /*
    * @param timestring 2016-03-01T04:45:00+0000.
    */
    this.greaterThanUTC = function(timestring) {
        return moment(timestring, 'YYYY-MM-DDTHH:mm:ssZZ') > moment.utc();
    };

    /**
     * Remove tz info from given datetime
     *
     * it's added automatically on server for every datetime like values
     *
     * @param {String} datetime
     * @return {String}
     */
    this.removeTZ = function(datetime) {
        if (datetime) {
            return datetime.replace('+0000', '').replace('+00:00', '');
        }
    };
}

/**
 * @ngdoc module
 * @module superdesk.core.datetime
 * @name superdesk.core.datetime
 * @packageName superdesk.core
 * @description Superdesk core date & time module.
 */
export default angular.module('superdesk.core.datetime', [
    'superdesk.config',
    'ngResource',
    'superdesk.core.datetime.absdate',
    'superdesk.core.datetime.groupdates',
    'superdesk.core.datetime.relativeDate',
    'superdesk.core.datetime.reldatecomplex',
    'superdesk.core.datetime.reldate',
    'superdesk.core.translate',
])
    .directive('sdDatetime', DateTimeDirective)
    .directive('sdShortDate', ShortDateDirective)

    .filter('reldate', function reldateFactory() {
        return function reldate(date) {
            return moment(date).fromNow();
        };
    })

    /**
     * Returns the difference between given date and the
     * current datetime in hours
     *
     * @param {Datetime} date iso format datetime
     * @return {Int} hours
     */
    .filter('hoursFromNow', function hoursFromNowFactory() {
        return function hoursFromNow(date) {
            var difference = moment().diff(moment(date));
            var d = moment.duration(difference);
            var s = Math.floor(d.asHours());

            return s;
        };
    })

    // format datetime obj to time string
    .filter('time', function timeFilterFactory() {
        var CONFIG_TIME_FORMAT = appConfig.view == null || appConfig.view.timeformat == null
            ? 'h:mm'
            : appConfig.view.timeformat;

        return function timeFilter(time) {
            var m = moment(time, 'HH:mm:ss');

            return m.format(CONFIG_TIME_FORMAT);
        };
    })

    .constant('moment', moment)

    .factory('weekdays', [function() {
        return Object.freeze({
            MON: gettext('Monday'),
            TUE: gettext('Tuesday'),
            WED: gettext('Wednesday'),
            THU: gettext('Thursday'),
            FRI: gettext('Friday'),
            SAT: gettext('Saturday'),
            SUN: gettext('Sunday'),
        });
    }])

    /**
     *   A service that automatically fetches the time zone data from the
     *   server upon instantiaton and stores it internally for future use,
     *   avoiding the need to fetch it again every time when needed.
     */
    .factory('tzdata', ['$resource', function($resource) {
        const tzResource = $resource('scripts/apps/dashboard/world-clock/timezones-all.json');

        /**
         * Returns a sorted list of all time zone names. If time zone data
         * has not yet been fetched from the server, an empty list is
         * returned.
         * To determine whether or not the data has been fetched yet, the
         * $promise property should be examined.
         *
         * @method getTzNames
         * @return {Array} a list of time zone names
         */
        tzResource.prototype.getTzNames = function() {
            return _.union(
                _.keys(this.zones),
                _.keys(this.links),
            ).sort();
        };

        // return an array that will contain the fetched data when
        // it arrives from the server
        return tzResource.get();
    }])
    .service('datetime', DateTimeService)
    .service('datetimeHelper', DateTimeHelperService);