carl
carl

Reputation: 4426

moment.calendar() without the time

I would like to use the moment.calendar() option without the time... so instead of "Last Tuesday at 5pm" I want "Last Tuesday". Does anybody know whether moment has a solution for that by now? I found this fiddle http://jsfiddle.net/nawxZ/, which apparently shows a solution for that, but I can't see how this is supposed to work? thanks carl

function log(str) {
    $('body').append('<p>' + str + '</p>');
}

log(moment().calendar());
log(moment().calendar(true));

Upvotes: 41

Views: 38552

Answers (9)

htmn
htmn

Reputation: 1675

Assuming moment.js locales won't change, I wrote a simple wrapper function around moment().calendar() for the following languages that I use:

en, ar, bg, cs, da, de, el, es, et, fi, fr-ca, fr, he, hr, hu, hy-am, it, ja, kk, ko, lt, nl, nb, pl, pt, ro, ru, sk, sl, sr, sv, th, tr, uk, vi, zh-cn, zh-tw

You can add new or remove existing cases inside the switch statement based on your needs and it should still work just fine.

const timeRegExp = /[0-9:,]/g;
const dateRegExp = /[0-9.-/]/g;

const removeLastNWords = (str, n, separator) => {
  return `${str.split(' ').splice(0, n).join(separator)}`;
}

const removeDigitsAndTimeChars = (str) => {
  return `${str.replace(timeRegExp, '').trimEnd()}`;
}

const removeLastNWordsStripped = (str, n, separator) => {
  return removeDigitsAndTimeChars(removeLastNWords(str, n, separator));
}

const calendarNoTime = (inp, strict) => {
  const originalCalendarDate = moment(inp, strict).calendar();
  const parts = originalCalendarDate.split(' ');
    
  /**
   * Most languages use the 'L' format for calendar: { sameElse },
   * these have parts of length 1. zh-cn does not use a space to separate 
   * the time from the date, so remove it if present otherwise keep 
   * the 'L' format. If `cn` not needed, simply return parts[0]. 
   * `sr/sl` uses spaces to separate day, month, year.  
   * 
   */
  if (parts.length === 1 || dateRegExp.test(parts[0])) {
    return parts[0].includes(':') ? removeDigitsAndTimeChars(parts[0]) : parts.join(' ');
  }

  switch (moment.locale()) {

    /**
     * These languages have a preposition with a length of < 3 
     * before the time. Find that preposition and keep the words
     * till that preposition. If `ru` not needed,
     * use removeLastNWords(originalCalendarDate, prepositionIndex, ' ')
     * 
     */
    case 'bg':
    case 'cs':
    case 'de':
    case 'es':
    case 'en':
    case 'fr':
    case 'fr-ca':
    case 'hr':
    case 'nl':
    case 'pl':
    case 'pt':
    case 'ro':
    case 'ru':
    case 'sk':
    case 'sl':
    case 'sr':
    case 'uk':
      const prepositionIndex = parts.findIndex((part, i) => i !== 0 && part.length < 3);
      return removeLastNWordsStripped(originalCalendarDate, prepositionIndex, ' ');

      /**
       * Less strict than the above, these languages put their preposition at
       * next to last index inside the `parts` array to every wording.
       * 
       */
    case 'da':
    case 'fi':
    case 'it':
    case 'kk':
    case 'nb':
    case 'th':
    case 'vi':
      return removeLastNWords(originalCalendarDate, parts.length - 2, ' ');

      // Uses [dddd, XX:XX] format, remove digits and special characters
    case 'et':
    case 'ja':
    case 'ko':
    case 'lt':
    case 'sv':
    case 'zh-tw':
    case 'zh-cn':
      return removeDigitsAndTimeChars(originalCalendarDate);

      // Same as above expect the preposition is at parts.length - 3
    case 'el':
      return removeLastNWords(originalCalendarDate, parts.length - 3, ' ');

      // Same as above + [dddd LT] format for some days
    case 'hy-am':
      return parts.length < 3 ? parts[0] : removeLastNWords(originalCalendarDate, parts.length - 3, ' ');

      // Same as above, preposition is at parts.length - 2
    case 'tr':
      return parts.length < 3 ? parts[0] : removeLastNWords(originalCalendarDate, parts.length - 2, ' ');

      // Appends `kor` to time, remove the word that contains this substring
    case 'hu':
      return originalCalendarDate.replace(/ *\b\S*?kor\S*\b/g, '');

      // Custom formats 
      // https://momentjs.com/docs/#/displaying/calendar-time/
    case 'ar':
      return moment(inp).calendar({
        sameDay: '[اليوم]',
        nextDay: '[غدًا]',
        nextWeek: 'dddd',
        lastDay: '[أمس]',
        lastWeek: 'dddd',
        sameElse: 'L',
      });
    case 'he':
      return moment(inp).calendar({
        sameDay: '[היום]',
        nextDay: '[מחר]',
        nextWeek: 'dddd',
        lastDay: '[אתמול]',
        lastWeek: 'dddd',
        sameElse: 'L',
      });

      // Unsupported language, use default format with time
    default:
      return originalCalendarDate;
  }
}


const today = new Date();
const yesterday = (new Date()).setDate((new Date()).getDate() - 1);
const coupleDaysAgo = (new Date()).setDate((new Date()).getDate() - 2);
const fewDaysAgo = (new Date()).setDate((new Date()).getDate() - 4);
const manyDaysAgo = (new Date()).setDate((new Date()).getDate() - 7);
const supportedLanguages = ['en', 'ar', 'bg', 'cs', 'da', 'de', 'el', 'es', 'et', 'fi', 'fr-ca', 'fr', 'he', 'hr', 'hu', 'hy-am', 'it', 'ja', 'kk', 'ko', 'lt', 'nl', 'nb', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-cn', 'zh-tw'];
let allDays = [];

supportedLanguages.forEach((lang) => {
  moment.locale(lang);
  allDays = [calendarNoTime(today), calendarNoTime(yesterday), calendarNoTime(coupleDaysAgo), calendarNoTime(fewDaysAgo), calendarNoTime(manyDaysAgo)];

  document.write(`${lang}: `);
  document.write(allDays.join(', '));
  document.write('</br>');
});

And for the TypeScript fans:

const removeLastNWords = (str: string, n: number, separator: string): string => {}
const removeDigitsAndDateChars = (str: string): string => {}
const removeLastNWordsStripped = (str: string, n: number, separator: string): string => {}
const calendarNoTime = (inp?: moment.MomentInput, strict?: boolean): string => {}

And also a fiddle.

Edit 1
Fixed he, sr, sl languages. Updated fiddle to display all the supported language dates.

Upvotes: 0

JabbyPanda
JabbyPanda

Reputation: 871

Since Moment 2.25.0 it is now possible to call moment.calendar(formats) without reference time value

https://momentjs.com/docs/#/displaying/calendar-time/

Upvotes: 2

Clouren
Clouren

Reputation: 412

I have the same requirement and unfortunately this is not supported currently (version 2.24.0).

If you look at an arbitrary language file, like en-gb.js, you can see that all the calendar variants contain the time.
There is no option to leave out the time. That would require a variant of calendar without the time for every language.

Upvotes: 1

Jaime Pajuelo
Jaime Pajuelo

Reputation: 11

This code works partially. The problem lies on separating the word at from [Today at].

In the example below, a las (means at in English) is part of the result.

moment().locale()
// "es"

moment().calendar(null, {
    sameDay: function (now) {
        var sameDay = now._locale._calendar.sameDay

        if (typeof sameDay === 'function') {
            sameDay = sameDay.call(this)
        }

        return sameDay.replace(' LT', '')
    }
})
// "hoy a las"

If the translations were "[Today] [at] LL", we would be able to separate the word at.

Upvotes: 1

Fabian von Ellerts
Fabian von Ellerts

Reputation: 5211

If you don't have custom times set and are only working on a day base, you can use this simple and multilang (tested for EN/DE/FR/IT) solution. (full days are always saved with a 00:00 timetag)

let dateParts = moment(DATE).calendar().split(' '),
    index = dateParts.indexOf('00:00');

// ['Yesterday', 'at', '00:00', 'PM'] -> ['Yesterday']
dateParts.length = (index !== -1 ? (index - 1) : dateParts.length);

Upvotes: 0

Mahfuzur Rahman
Mahfuzur Rahman

Reputation: 1545

this is working perfectly. try it.

(moment(time).calendar().split(" at"))[0]

Upvotes: 4

Fareed Alnamrouti
Fareed Alnamrouti

Reputation: 32174

starting from moment 2.10.5 you can do:

moment(/*your date*/).calendar(null,{
    lastDay : '[Yesterday]',
    sameDay : '[Today]',
    nextDay : '[Tomorrow]',
    lastWeek : '[last] dddd',
    nextWeek : 'dddd',
    sameElse : 'L'
})

see: http://momentjs.com/docs/#/displaying/calendar-time/

Upvotes: 55

Jonathan
Jonathan

Reputation: 4918

I created an angular directive that provides a calendar with the date only. If you don't use angular, just use the function.

 app.filter('amCalendarDate', function($translate) {
    return function(dt) {
        var md = moment(dt), key = '';

        if (!dt || !md || (md + '').toLowerCase() == 'invalid date')
            return '';

        var today = moment();
        var diff = today.diff(md, 'days');

        if (!diff)
            key = 'Today';
        else if (diff == -1)
            key = 'Tomorrow';
        else if (diff == 1)
            key = 'Yesterday';
        else if (Math.abs(diff) <= 6) {
            if (diff < 0)
                key = 'Next';
            else
                key = 'Last';

            var days = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');

            today.day(diff);

            key += ' ' + days[today.day()];
        }

        if (key) {
            // if you don't have translate, just return the key
            return $translate.amCalendarDate[key];
        } else
            return md.format('L');
    }
});

Upvotes: 0

Henrik Andersson
Henrik Andersson

Reputation: 47182

moment().calendar() supports custom formatted strings and formatting functions.

moment().calendar();
>> "Today at 9:06 AM"

Then set your formatted strings

moment.locale('yourlang', {
    calendar: {
        lastDay: function () {
            return '[last]';
        },
        sameDay: function () {
            return '[Today]';
        }
    }
});

moment().calendar();

// Will now output
>> "Today"

should do the trick. The docs are an invaluable source

Upvotes: 8

Related Questions