-1

My friend has asked me to do what is apparently a giant pain, which is to convert dates from a date picker into natural language in first english then also french because he is a notary who wants a tool to copy/paste natural dates into his documents so he doesn't make errors.

The desired output format would be: twenty-fourth day of January two thousand and twenty-two

I've gone through about 8 existing libraries but none seem to do calendar day or year in natural language.

  • You may be interested in https://stackoverflow.com/questions/14766951/transform-numbers-to-words-in-lakh-crore-system – code Jan 25 '22 at 04:29
  • had a look but that just converts numbers, which is cool but doesn't seem like it will add the 'st' 'nd' or 'th' or that I'll need. i.e. 24th or 31st – Mitch Schwartz Jan 25 '22 at 04:51
  • You might start with [*How to format a JavaScript date*](https://stackoverflow.com/questions/3552461/how-to-format-a-javascript-date), [*Convert Numbers to Hyphenated Words in Javascript*](https://stackoverflow.com/questions/36749997/convert-numbers-to-hyphenated-words-in-javascript) and [*JavaScript numbers to Words*](https://stackoverflow.com/questions/5529934/javascript-numbers-to-words). – RobG Jan 25 '22 at 04:53
  • @rob I've been there, it's not covered. So far nothing I've found seems to cover having the year and day of the month in plain english. – Mitch Schwartz Jan 25 '22 at 04:54
  • The *Date* methods get you the numbers, the other links convert numbers to words. – RobG Jan 25 '22 at 04:55
  • Ah Thanks, I think I saw it before you edited in the second post, too quick. – Mitch Schwartz Jan 25 '22 at 04:57

3 Answers3

0

The way to do this would be to convert each entity of date separately. (The answer uses a reference to Transform numbers to words in lakh / crore system to convert year to words which is modified to your use case.)

var a = ['','one ','two ','three ','four ', 'five ','six ','seven ','eight ','nine ','ten ','eleven ','twelve ','thirteen ','fourteen ','fifteen ','sixteen ','seventeen ','eighteen ','nineteen '];
var b = ['', '', 'twenty','thirty','forty','fifty', 'sixty','seventy','eighty','ninety'];



function yearInWords (num) {
    if ((num = num.toString()).length > 9) return 'overflow';
    n = ('000000000' + num).substr(-9).match(/^(\d{2})(\d{2})(\d{2})(\d{1})(\d{2})$/);
    if (!n) return; var str = '';
    str += (n[1] != 0) ? (a[Number(n[1])] || b[n[1][0]] + ' ' + a[n[1][1]]) + 'crore ' : '';
    str += (n[2] != 0) ? (a[Number(n[2])] || b[n[2][0]] + ' ' + a[n[2][1]]) + 'lakh ' : '';
    str += (n[3] != 0) ? (a[Number(n[3])] || b[n[3][0]] + ' ' + a[n[3][1]]) + 'thousand ' : '';
    str += (n[4] != 0) ? (a[Number(n[4])] || b[n[4][0]] + ' ' + a[n[4][1]]) + 'hundred ' : '';
    str += (n[5] != 0) ? ((str != '') ? 'and ' : '') + (a[Number(n[5])] || b[n[5][0]] + (a[n[5][1]] ? '-' : '') + a[n[5][1]]) : '';
    return str;
}

 var days = ['first','second','third','fourth','fifth', 'sixth','seventh','eigth','ninth','tenth','eleventh','twelth','thirteenth','fouteenth','fifteenth','sixteenth','seventeen',
 'eighteenth','nineteenth', 'twentieth', 'twenty', 'thirtieth', 'thirty'];

 function dayInWords (num) {
    if(num > 31) return 'invalid day';
    if(num < 21) return days[num-1];
    if(num < 30) return days[20] + '-' + days[(num % 10)-1];
    if(num == 30) return days[21];
    return days[22] + '-' + days[(num % 10)-1];
    return str;
}

let date = new Date("01/31/2022");
let day = dayInWords(date.getDate());
let month = date.toLocaleString('default', { month: 'long' });
let year = yearInWords(date.getFullYear());
finalDateInWords = day + ' day of ' + month + ' ' + year;
console.log(finalDateInWords);
0

Yep, that did it, Thanks!. Made a quick mod to the last bit so I could call it from my main script:

function englisDate(_date){
  let date = new Date(_date);
  let day = dayInWords(date.getDate());
  let month = date.toLocaleString('default', { month: 'long' });
  let year = yearInWords(date.getFullYear());
  finalDateInWords = day + ' day of ' + month + ' ' + year;
  console.log(finalDateInWords);
  return(finalDateInWords);
}
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 25 '22 at 09:27
0

If you're only interested in dates with numbers less than 10,000, the following may suit.

The mapping of number to name could be condensed, but making the map simpler makes the logic more complex and for dates, I don't see the point of added complexity. But it could be cleaned up and made more concise.

Part of the complexity is working out whether to write the number name or ordinal (e.g. "one" or "first"), and whether and where to add "and" (e.g. "one hundred and six" or "one thousand and twenty-six").

function toSpeakableDate(date = new Date()) {
  let toWord = (num, ord) => {
    let numberMap = {
     0: {name: 'zero', ordinal: 'zeroth'},
     1: {name: 'one', ordinal: 'first'}, 
     2: {name: 'two', ordinal: 'second'},
     3: {name: 'three', ordinal: 'third'}, 
     4: {name: 'four', ordinal: 'fourth'}, 
     5: {name: 'five', ordinal: 'fifth'}, 
     6: {name: 'six', ordinal: 'sixth'}, 
     7: {name: 'seven', ordinal: 'seventh'}, 
     8: {name: 'eight', ordinal: 'eighth'}, 
     9: {name: 'nine', ordinal: 'ninth'},
     10: {name: 'ten', ordinal: 'tenth'},
     11: {name: 'eleven', ordinal: 'eleventh'},
     12: {name: 'twelve', ordinal: 'twelfth'},
     13: {name: 'thirteen', ordinal: 'thirteenth'},
     14: {name: 'fourteen', ordinal: 'fourteenth'},
     15: {name: 'fifteen', ordinal: 'fifteenth'},
     16: {name: 'sixteen', ordinal: 'sixteenth'},
     17: {name: 'seventeen', ordinal: 'seventeenth'},
     18: {name: 'eighteen', ordinal: 'eighteenth'},
     19: {name: 'nineteen', ordinal: 'nineteenth'},
     20: {name: 'twenty', ordinal: 'twentyth'},
     30: {name: 'thirty', ordinal: 'thirtyth'},
     40: {name: 'fourty', ordinal: 'fourtyth'},
     50: {name: 'fifty', ordinal: 'fiftyth'},
     60: {name: 'sixty', ordinal: 'sixtyth'},
     70: {name: 'seventy', ordinal: 'sventyth'},
     80: {name: 'eighty', ordinal: 'eightyth'},
     90: {name: 'ninety', ordinal: 'ninetieth'}
    };
    
    let n = String(num);
    let text;
    
    if (n < 21) {
      text = numberMap[n][ord? 'ordinal' : 'name'];
    } else if (n < 1e2) {
      let units = n % 10;
      let tens = Math.floor(n / 10) * 10;
      text = units? `${numberMap[tens].name}-${toWord(units, ord)}` : `${numberMap[tens].ordinal}`;
    } else if (n < 1e3) {
      let hundreds = Math.floor(n / 1e2);
      let remainder = n % 1e2;
      text = `${toWord(hundreds)} hundred` +
        (remainder? ` and ${toWord(remainder)}` : ''); 
    } else if (n < 1e4) {
      let thousands = Math.floor(n / 1e3);
      let noHundreds = String(n).slice(-3,-2) == '0';
      let remainder = n % 1e3;
      text = `${toWord(thousands)} thousand` +
        `${noHundreds? ' and' : ''}` +
        (remainder? ` ${toWord(remainder)}` : ''); 
    }
    return text;
  }
  
  let fullDate = '';
  let day = toWord(date.getDate(), true);
  fullDate += day + ' day of ';
  let month = date.toLocaleString('en',{month:'long'});
  fullDate += month + ' ';
  let year = toWord(date.getFullYear());
  fullDate += year;

  return fullDate;
}

// Examples
// Simple date formatter
let f = date => date.toLocaleString('en-GB', {
 year:'numeric',month:'short',day:'2-digit'});

[new Date(666,0,1),
 new Date(1215,4,31),
 new Date(),
 new Date(2222,5,13)
].forEach(d => console.log(f(d) + '\n' + toSpeakableDate(d)));
RobG
  • 142,382
  • 31
  • 172
  • 209