111

Is there a way to write this on fewer lines, but still easily readable?

var month = '';

switch(mm) {
    case '1':
        month = 'January';
        break;
    case '2':
        month = 'February';
        break;
    case '3':
        month = 'March';
        break;
    case '4':
        month = 'April';
        break;
    case '5':
        month = 'May';
        break;
    case '6':
        month = 'June';
        break;
    case '7':
        month = 'July';
        break;
    case '8':
        month = 'August';
        break;
    case '9':
        month = 'September';
        break;
    case '10':
        month = 'October';
        break;
    case '11':
        month = 'November';
        break;
    case '12':
        month = 'December';
        break;
}
John Slegers
  • 45,213
  • 22
  • 199
  • 169
Leon Gaban
  • 36,509
  • 115
  • 332
  • 529
  • 7
    IMHO [vidriduch's answer](http://stackoverflow.com/a/29828363/124319) is the most appropriate. This is probably not the only part of your code that requires Date manipulations (even though the one you showed is particulary easy to code). You should seriously consider using existing, tested Date libraries. – coredump Apr 23 '15 at 21:18
  • 2
    I don't know javascript, but doesn't it have a hashmap, like Python's dictionary or C++'s std::map? – Masked Man Apr 24 '15 at 06:13
  • @Happy check [this answer](http://stackoverflow.com/a/29828180/1673391) using JavaScript object – Grijesh Chauhan Apr 24 '15 at 06:30
  • 28
    Isn't this suppose to be for http://codereview.stackexchange.com/? – Loko Apr 24 '15 at 09:00
  • 2
    So many answers changing the behavior of the code by not taking the default '' into account which results in undefined output, which is different as what the original does. – Pieter B Apr 24 '15 at 13:04
  • @Loko cool thanks for that link, didn't know about that substack? Neat... so many awesome answers here :) I've gotten to a point where I'm trying to optimize everywhere, even `switch case` statements to replace an if else that has 3 checks... using `ternary` functions where I can etc etc... – Leon Gaban Apr 24 '15 at 14:49
  • 2
    This is **not** a duplicate question >:( this is asking a completely different question, the answer may be the same however. – Leon Gaban May 09 '15 at 22:05
  • 1
    @LeonGaban completely agree. And since you have just passed 3k rep you can now vote to reopen above! – CupawnTae May 10 '15 at 17:55
  • 1
    (Actually I didn't cop you were the OP - you could have cast a reopen vote at 250 rep :-) – CupawnTae May 10 '15 at 18:50
  • 1
    Hehe, yeah I didn't know I could do that, but I just cast my vote :) perhaps I should edit my title, however I feel the added info is useful. Either way I think this help lot of devs. – Leon Gaban May 10 '15 at 18:54

13 Answers13

201

Define an array, then get by index.

var months = ['January', 'February', ...];

var month = months[mm - 1] || '';
xdazz
  • 158,678
  • 38
  • 247
  • 274
  • 23
    instead of `mm - 1` you can also set `undefined` as the first value (index 0) so the array indices will match month numbers – Touffy Apr 23 '15 at 15:40
  • 1
    @Touffy Yes, this is just personal taste :) – xdazz Apr 23 '15 at 15:41
  • 9
    `var month = month[(mm -1) % 12]` – mpez0 Apr 23 '15 at 17:25
  • 77
    @mpez0 I think I'd prefer knowing that someone's managed to come up with month number 15, rather than hide what is probably bad data – Izkata Apr 23 '15 at 18:23
  • 21
    @Touffy I think I'd stick with `mm-1`, so that `months.length==12`. – Teepeemm Apr 23 '15 at 20:28
  • As the poster said, it's really just a matter of taste. There's no immediate practical use to having length==12 since you can safely hardcode it. – Touffy Apr 23 '15 at 20:40
  • 48
    @Touffy I would argue that's not a matter of taste, but a matter of [avoiding clever code](http://programmers.stackexchange.com/questions/91854/how-to-train-yourself-to-avoid-writing-clever-code). Imagine yourself reading some else's `[undefined, 'January', 'February', ...]` - I best your first reaction is _WTF?!_, which is usually not a good sign... – miraculixx Apr 23 '15 at 22:52
  • 1
    @Touffy Re hard coding `length==12`, not a good idea either, at most simplify to `months.length` (your suggestion already makes for `month.length==13`, gotcha ...) – miraculixx Apr 23 '15 at 22:59
  • Wouldn't `['January' = 1, 'February', ...]` work here like in some other languages? I'm new to JS ;P – Neithan Max Apr 24 '15 at 00:46
  • @miraculixx I may be cleverer than the ideal strawman programmer used in this example, but my reaction would be that, considering the contents and name of the array, it's very obvious what's going on. And I'm not suggesting assigning 12 to `months.length`, but hard-coding the value 12 instead when you want to compare something to the number of months in a year. – Touffy Apr 24 '15 at 05:05
  • @CarlesAlcolea even with the correct syntax, what you suggest would be indexed the wrong way around (you'd have to lookup the month number in a loop instead of just accessing the month name by its number). – Touffy Apr 24 '15 at 05:09
  • @CupawnTae makes a valid point about change in behaviour in their answer below. – Mauro Apr 24 '15 at 10:19
  • 2
    @izkata Translating the 200th month of my 25-year time series, modulo works well. – mpez0 Apr 24 '15 at 13:19
  • @PieterB That would be very easy to fix it :) – xdazz Apr 24 '15 at 14:46
  • @Touffy you do realize that your 'obvious' fact that there are only 12 months in the array is at odds with your suggestion to have an undefined element at index 0? If experience tells me anything, nothing is obvious unless you know, and any code that assumes the future reader has the same insight as the current programmer is notoriously hard to change due to all the hidden obviousness... – miraculixx Apr 24 '15 at 18:00
  • 1
    Or a hashmap, with key/values: '1'=>'January', '2'=>'February', et al – ChuckCottrill Apr 25 '15 at 00:42
  • @Touffy A pedantic note: using `undefined` as the first value more closely preserves the original behaviour. As this answer is currently written, the subtraction in `months[mm - 1]` forces `mm` to be parsed as an integer, such that `'03'` also gets converted to `'March'`. (That might actually be desirable, but it's technically not what the OP asked.) – 200_success Apr 25 '15 at 06:50
  • BTW, you don't need to write that `undefined`: `[ , 'January', ...` is the same as `[undefined, 'January', ...` – Walter Tross Apr 28 '15 at 21:43
82

what about not to use array at all :)

var objDate = new Date("10/11/2009"),
    locale = "en-us",
    month = objDate.toLocaleString(locale, { month: "long" });

console.log(month);

// or if you want the shorter date: (also possible to use "narrow" for "O"
console.log(objDate.toLocaleString(locale, { month: "short" }));

as per this answer Get month name from Date from David Storey

Community
  • 1
  • 1
vidriduch
  • 4,753
  • 8
  • 41
  • 63
  • 2
    Given the problem statement in question, your answer is not really solving that problem but some different solution which might be correct in a different context. Selected answer is still the best and most efficient. – TechMaze Apr 24 '15 at 05:56
  • 6
    Only ``new Date("2009-11-10")`` format is guaranteed to be parsed (see this specifiation: http://www.ecma-international.org/publications/standards/Ecma-262.htm). Other date formats (including one in your answer) may be parsed if browser chooses so, and hence are not portable. – jb. Apr 25 '15 at 15:44
59

Try this:

var months = {'1': 'January', '2': 'February'}; //etc
var month = months[mm];

Note that mm can be an integer or a string and it will still work.

If you want non-existing keys to result in empty string '' (instead of undefined), then add this line:

month = (month == undefined) ? '' : month;

JSFiddle.

  • 4
    On larger datasets than "months of the year" this will probably be more efficient. – DGM Apr 24 '15 at 00:48
  • 3
    This is effectively an enum (i.e. make it immutable), define it as `var months = Object.freeze({'1': 'January', '2': 'February'}); //etc` See [Enums in JavaScript?](http://stackoverflow.com/a/5040502/1796930) – Alexander Apr 24 '15 at 18:19
  • 1
    @Alexander If you swap the key and values, then yes it's similar to an enum. – But I'm Not A Wrapper Class Apr 24 '15 at 19:01
26

You could create an array instead and lookup the month name:

var months = ['January','February','March','April','May','June','July','August','September','October','November','December']


var month = months[mm-1] || '';

See the answer by @CupawnTae for the rational behind the code || ''

Alex
  • 21,273
  • 10
  • 61
  • 73
  • rather than start with 0 index you could keep `undefined` at `0` as `var months = [ undefined, 'January','February','March', .....` In this way you will use `month = months[mm];` – Grijesh Chauhan Apr 24 '15 at 06:28
  • @GrijeshChauhan: please avoid 'clever' code. The next person's first reaction whould be wtf. It's only a '-1', months.Length will then be 13, wtf^2. http://programmers.stackexchange.com/questions/91854/how-to-train-yourself-to-avoid-writing-clever-code – RvdK Apr 24 '15 at 08:02
19

Be careful!

The thing that should immediately trigger alarm bells is the first line: var month = ''; - why is this variable being initialized to an empty string, rather than null or undefined? It may just have been habit or copy/pasted code, but unless you know that for sure, it is not safe to ignore it when you're refactoring code.

If you use an array of month names and change your code to var month = months[mm-1]; you are changing the behaviour, because now for numbers outside the range, or non-numeric values, month will be undefined. You may know that this is ok, but there are many situations where this would be bad.

For example, let's say your switch is in a function monthToName(mm), and someone is calling your function like this:

var monthName = monthToName(mm);

if (monthName === '') {
  alert("Please enter a valid month.");
} else {
  submitMonth(monthName);
}

Now if you change to using an array and returning monthName[mm-1], the calling code will no longer function as intended, and it will submit undefined values when it is supposed to display a warning. I'm not saying this is good code, but unless you know exactly how the code is being used, you can't make assumptions.

Or maybe the original initialization was there because some code further down the line assumes that month will always be a string, and does something like month.length - this will result in an exception being thrown for invalid months and potentially kill the calling script completely.

If you do know the entire context - e.g. it's all your own code, and no-one else is ever going to use it, and you trust yourself not forget you made the change sometime in the future - it may be safe to change the behaviour like this, but soooo many bugs come from this kind of assumption that in real life you're far better off programming defensively and/or documenting the behaviour thoroughly.

Wasmoo's answer gets it right (EDIT: a number of other answers, including the accepted one, have now been fixed too) - you can use months[mm-1] || '' or if you would prefer to make it more obvious at a glance what's happening, something like:

var months = ['January', 'February', ...];

var month;

if (mm >= 1 && m <= 12) {
  month = months[mm - 1];
} else {
  month = ''; // empty string when not a valid month
}
Community
  • 1
  • 1
CupawnTae
  • 14,192
  • 3
  • 29
  • 60
  • 1
    Nobody else has mentioned the change in behaviour yet, so this should be taken into account when re-factoring code. – Mauro Apr 24 '15 at 10:18
  • This answer is spot on. Most of the other answers change the behavior of the code in a subtle way. This may not matter or it may become that irritatingly hard to find bug. – Pieter B Apr 24 '15 at 13:03
  • Ah so it's always best to init a var to `undefined`? Does that save performance if the type gets converted? – Leon Gaban Apr 24 '15 at 14:52
  • 2
    @LeonGaban it's not about performance: the original question initialized the variable to an empty string and left it at that if no valid month was selected, whereas a lot of the other answers here ignored that fact and changed the behaviour by returning `undefined` when the input wasn't `1..12`. Except in *very* exceptional circumstances, correct behaviour trumps performance every time. – CupawnTae Apr 24 '15 at 15:06
17

For completeness I'd like to supplement to current answers. Basically, you can ommit the break keyword and directly return an appropriate value. This tactic is useful if the value cannot be stored in a precomputed look-up table.

function foo(mm) {
    switch(mm) {
        case '1':  return 'January';
        case '2':  return 'February';
        case '3':  return 'March';
        case '4':  return 'April';
        // [...]
        case '12': return 'December';
    }
    return '';
}

Once again, using a look-up table or date functions is more succinct and subjectively better.

Gerard
  • 831
  • 6
  • 15
16

You could do it using an array:

var months = ['January', 'February', 'March', 'April', 
              'May', 'June', 'July', 'August', 
              'September', 'October', 'November', 'December'];

var month = months[mm - 1] || '';
Eugenio Miró
  • 2,398
  • 2
  • 28
  • 38
Stuart Wagner
  • 1,997
  • 1
  • 14
  • 22
12

Here's another option that uses only 1 variable and still applies the default value '' when mm is outside of range.

var month = ['January', 'February', 'March',
             'April', 'May', 'June', 'July',
             'August', 'September', 'October',
             'November', 'December'
            ][mm-1] || '';
Wasmoo
  • 1,130
  • 9
  • 14
  • Range checking, and throwing an exception could work also. And returning "Error", or "Undefined" might be alternative to the empty string. – ChuckCottrill Apr 25 '15 at 00:45
9

You could write it as an expression instead of a switch, using conditional operators:

var month =
  mm == 1 ? 'January' :
  mm == 2 ? 'February' :
  mm == 3 ? 'March' :
  mm == 4 ? 'April' :
  mm == 5 ? 'May' :
  mm == 6 ? 'June' :
  mm == 7 ? 'July' :
  mm == 8 ? 'August' :
  mm == 9 ? 'September' :
  mm == 10 ? 'October' :
  mm == 11 ? 'November' :
  mm == 12 ? 'December' :
  '';

If you haven't seen chained conditional operators before this may seem harder to read at first. Writing it as an expression makes one aspect even easier to see than the original code; it's clear that the intention of the code is to assign a value to the variable month.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    I meant to suggest this one as well. It's actually very readable while remaining concise, and would work well for sparse mappings and non-numeric keys, which the array solution doesn't. PS I got a random unexplained downvote on my answer too - probably the same drive-by artist. – CupawnTae Apr 29 '15 at 09:06
6

Building on Cupawn Tae's answer previous I'd shorten it to :

var months = ['January', 'February', ...];
var month = (mm >= 1 && mm <= 12) ? months[mm - 1] : '';

Alternatively, yes, I appreciate, less readable:

var month = months[mm - 1] || ''; // as mentioned further up
Community
  • 1
  • 1
  • You can skip `(!!months[mm - 1])` and simply do `months[mm - 1]`. – YingYang Apr 29 '15 at 11:56
  • That would result in undefined if the array index was out of range! – NeilElliott-NSDev Apr 30 '15 at 12:49
  • `months[mm - 1]` will return `undefined` for an index that is out of range. Since `undefined` is falsy you will end up with `''` as the value of `month`. – YingYang Apr 30 '15 at 12:53
  • As stated in other answers you can simplify this line even more: `var month = months[mm - 1] || '';` – YingYang Apr 30 '15 at 12:54
  • Although I've noticed further up (wasn't around when I posted), var month = months[mm - 1] || ''; Which would be neater still. – NeilElliott-NSDev Apr 30 '15 at 12:56
  • OK your initial response was unclear I now see we're coming from the same place with this ;) bear in mind I was being mindful of the initial questions "[...] but still easily readable?" which is quite subjective. – NeilElliott-NSDev Apr 30 '15 at 12:57
5

Like @vidriduch, I would like to underline the importance of i20y ("internationalisability") of code in nowadays' context and suggest the following concise and robust solution together with the unitary test.

function num2month(month, locale) {
    if (month != Math.floor(month) || month < 1 || month > 12)
        return undefined;
    var objDate = new Date(Math.floor(month) + "/1/1970");
    return objDate.toLocaleString(locale, {month: "long"});
}

/* Test/demo */
for (mm = 1; mm <= 12; mm++)
    document.writeln(num2month(mm, "en") + " " +
                     num2month(mm, "ar-lb") + "<br/>");
document.writeln(num2month("x", "en") + "<br/>");
document.writeln(num2month(.1, "en") + "<br/>");
document.writeln(num2month(12.5, "en" + "<br/>"));

I try to stay as close as possible to the original question, i.e. transform numbers 1 to 12 into month names, not only for one special case, but return undefined in case of invalid arguments, using some of the formerly added criticism to and contents of other answers. (The change from undefined to '' is trivial, in case exact matching is needed.)

Dirk
  • 267
  • 2
  • 10
4
var getMonth=function(month){
   //Return string to number.
    var strMonth = ['January', 'February', 'March',
             'April', 'May', 'June', 'July',
             'August', 'September', 'October',
             'November', 'December'
            ];
    //return number to string.
    var intMonth={'January':1, 'February':2, 'March':3,
             'April':4, 'May':5, 'June':6, 'July':7,
             'August':8, 'September':9, 'October':10,
             'November':11, 'December':12
            };
    //Check type and return 
    return (typeof month === "number")?strMonth[month-1]:intMonth[month]
}
Laxmikant Dange
  • 7,606
  • 6
  • 40
  • 65
0

I'd go for wasmoo's solution, but adjust it like this :

var month = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December'
][mm-1] || '';

It is the exact same code, really, but differently indented, which IMO makes it more readable.

Community
  • 1
  • 1
John Slegers
  • 45,213
  • 22
  • 199
  • 169