Click here to Skip to main content
Email Password   helpLost your password?

Introduction

I needed a JavaScript date format function such as the Visual Basic Format function, in which you can pass a format string; my first approach was to issue a series of consecutive and "destructive" replace calls, but upon discovering that the 5.5 (or higher) version of JScript supported the use of a function as the replaceText argument of the replace method, I got creative.

Here's an example call of what I wanted:

SomeDiv.innerText = (new Date()).format('dddd, mmmm dd, yyyy.');

This would display:

Saturday, July 16, 2005

So in my first approach, I globally and case-insensitively replaced dddd with the corresponding string, which "destroyed" every occurrence, so that later in the code I could replace dd with the date number.

This worked just fine, but I knew that by inspecting the format specifier for a match, I could skip the search of every format specifier; say I only want the month and the date; well, by switching upon the format specifier (or rather "datepart" specifier), the year replacement will never be issued. Get it?

The fun part relies in the use of a function in the replaceText argument of the replace method; this way the $1 property as a function argument always represents the last match.

Other considerations include the format or "datepart" specifiers: none other than yyyy will be parsed as the year; months and days have the usual three flavors of fullname (mmmm), three-letter (mmm) or numeric (mm); hours (hh) can be rectified to the 12-hour format with the a/p specifier, and minutes (nn) and seconds (ss) may also be specified.

Implementation

WOFA, (Without Further Adou):

// a global month names array

var gsMonthNames = new Array(
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
);
// a global day names array

var gsDayNames = new Array(
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
);
// the date format prototype

Date.prototype.format = function(f)
{
    if (!this.valueOf())
        return ' ';

    var d = this;

    return f.replace(/(yyyy|mmmm|mmm|mm|dddd|ddd|dd|hh|nn|ss|a\/p)/gi,
        function($1)
        {
            switch ($1.toLowerCase())
            {
            case 'yyyy': return d.getFullYear();
            case 'mmmm': return gsMonthNames[d.getMonth()];
            case 'mmm':  return gsMonthNames[d.getMonth()].substr(0, 3);
            case 'mm':   return (d.getMonth() + 1).zf(2);
            case 'dddd': return gsDayNames[d.getDay()];
            case 'ddd':  return gsDayNames[d.getDay()].substr(0, 3);
            case 'dd':   return d.getDate().zf(2);
            case 'hh':   return ((h = d.getHours() % 12) ? h : 12).zf(2);
            case 'nn':   return d.getMinutes().zf(2);
            case 'ss':   return d.getSeconds().zf(2);
            case 'a/p':  return d.getHours() < 12 ? 'a' : 'p';
            }
        }
    );
}

Notes

Enjoy.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralPutting it all together so far
murray56
17:15 2 Mar '07  
Hi,

Thanks so much for this nifty script. Putting all the suggestions together and changing s/p to x (s/p conflicts with the other options so I just picked x since it was free - is there another commonly used one?)

<script type="text/javascript">
var gsMonthNames = new Array(
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
);

var gsDayNames = new Array(
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
);

String.prototype.zf = function(l) { return '0'.string(l - this.length) + this; }
String.prototype.string = function(l) { var s = '', i = 0; while (i++ < l) { s += this; } return s; }
Number.prototype.zf = function(l) { return this.toString().zf(l); }

// the date format prototype
Date.prototype.format = function(f)
{
      if (!this.valueOf())
            return 'n.a.';//&nbsp;

      var d = this;
     return f.replace(/(yyyy|yy|y|MMMM|MMM|MM|M|dddd|ddd|dd|d|HH|H|hh|h|mm|m|ss|s|t|x)/gi,
        function($1)
        {
              switch ($1)
              {
                   case 'yyyy': return d.getFullYear();
                   case 'yy':   return (d.getFullYear()%100).zf(2);
                   case 'y':      return (d.getFullYear()%100);
                   case 'MMMM': return gsMonthNames[d.getMonth()];
                   case 'MMM':   return gsMonthNames[d.getMonth()].substr(0, 3);
                   case 'MM':   return (d.getMonth() + 1).zf(2);
                   case 'M':      return (d.getMonth() + 1);
                   case 'dddd': return gsDayNames[d.getDay()];
                   case 'ddd':   return gsDayNames[d.getDay()].substr(0, 3);
                   case 'dd':   return d.getDate().zf(2);
                   case 'd':     return d.getDate();
                   case 'HH':   return d.getHours().zf(2);
                   case 'H':      return d.getHours();
                   case 'hh':   return ((h = d.getHours() % 12) ? h : 12).zf(2);
                   case 'h':      return ((h = d.getHours() % 12) ? h : 12);
                   case 'mm':   return d.getMinutes().zf(2);
                   case 'm':      return d.getMinutes();
                   case 'ss':   return d.getSeconds().zf(2);
                   case 's':      return d.getSeconds();
                   case 't':     return d.getHours() < 12 ? 'am' : 'pm';
                  case 'x': var i = d.getDate() % 10; return 'thstndrd'.substr(2 * (i < 4) * i, 2);              
               }              
            }
      );
}
</script>

Murray
GeneralRe: Putting it all together so far
murray56
18:09 2 Mar '07  
Whoops, I left the wrong bit in from my testing:

case 'x': var i = d.getDate() % 10; return 'thstndrd'.substr(2 * (i < 4) * i, 2);

should be:

case 'x':  
   switch (d.getDate())
         {
         case 1:
         case 21:
         case 31:
               return "st";
         case 2:
         case 22:
               return "nd";
         case 3:
         case 23:
               return "rd";
         default:
               return "th";
         }

Sorry 'bout dat

Murray
GeneralRe: Putting it all together so far
Tor2k
21:09 4 Mar '07  
Another commonly used date parts would be the time zone specifier (GMT, CMT, etc.) and the '07 year format. Please note that the 'x' case may yield 12nd for 12. Be careful with that, try another approach for ordinals...

There is no spoon.

GeneralRe: Putting it all together so far
murray56
22:49 5 Mar '07  
I am using:

case 'x':
switch (d.getDate())
{
case 1:
case 21:
case 31:
return "st";
case 2:
case 22:
return "nd";
case 3:
case 23:
return "rd";
default:
return "th";
}

which seems to work properly for all the numbers I have tested (including 12).

Generalhey
Frenaaa
9:09 3 Dec '06  
Jai! ai am javin som trobel loguin in to mai jotmeil acaunt. it sims dat mai pasbuord is not ricognaisd, iven doug ai meid shur dat ol de lirel lait bulbs on mai kibord ar torned on (including the guan coled caps loc)
ai guonder if llu can jelp mi bicos llu jav writen so meni artikels so ai gues llu jav jotmeil tu....

it guas very jard tu faind llu, BUT I DID SO I AM F***ING AWESOME!!! THE MINE SO BIG SO THE MINE SO FINE SO TOR2K QUE ARRECHO EL CD QUE ME MANDASTE!!! DIOS TE BENDIGA HIJO MIOOOOOOOOO! SO BIGGGGGGG!!! D.A.R.E. JAJAJAJA A3VT! LOVIN YA INZA! U CAN RUN BUT YOU CANT HIDE! JAJAJA,

there is no spoon.

PD. FREEEEENAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!!
GeneralRe: hey
Tor2k
8:22 8 Dec '06  
what up d mine!!!! hacker mardito... me alegro q t gustara el CD... disfrutenlo! i love u 2!

Try not to bend the spoon, that's impossible; instead try to realize the truth: There is no spoon.

GeneralEasy but useful
Ninghuan
23:01 25 Apr '06  
Easy but useful
GeneralC#-like formats
Kjetil Klaussen
0:15 9 Mar '06  
Great article, though it would be nice if you added the Zero-Fill functions to your article and not only in the comments Smile

Anyway; just a little update if you'd like to use C#-like formating;

return f.replace(/(yyyy|yy|y|MMMM|MMM|MM|M|dddd|ddd|dd|d|HH|H|hh|h|mm|m|ss|s|t)/gi,
function($1)
{
switch ($1)
{
case 'yyyy': return d.getFullYear();
case 'yy': return (d.getFullYear()%100).zf(2);
case 'y': return (d.getFullYear()%100);
case 'MMMM': return gsMonthNames[d.getMonth()];
case 'MMM': return gsMonthNames[d.getMonth()].substr(0, 3);
case 'MM': return (d.getMonth() + 1).zf(2);
case 'M': return (d.getMonth() + 1);
case 'dddd': return gsDayNames[d.getDay()];
case 'ddd': return gsDayNames[d.getDay()].substr(0, 3);
case 'dd': return d.getDate().zf(2);
case 'd': return d.getDate();
case 'HH': return d.getHours().zf(2);
case 'H': return d.getHours();
case 'hh': return ((h = d.getHours() % 12) ? h : 12).zf(2);
case 'h': return ((h = d.getHours() % 12) ? h : 12);
case 'mm': return d.getMinutes().zf(2);
case 'm': return d.getMinutes();
case 'ss': return d.getSeconds().zf(2);
case 's': return d.getSeconds();
case 't': return d.getHours() < 12 ? 'A.M.' : 'P.M.';
}
}
);


-------------------
Kjetil Klaussen
-------------------
GeneralZF
Tunez
2:09 1 Aug '05  
The prototype of 'zf' would be appriciated Smile
GeneralRe: ZF
Tor2k
15:00 2 Aug '05  
Ok, here it goes:

// Zero-Fill
String.prototype.zf = function(l) { return '0'.string(l - this.length) + this; }

As you can see, it depends on the string prototype, an VB-like string concatenator:

// VB-like string
String.prototype.string = function(l) { var s = '', i = 0; while (i++ < l) { s += this; } return s; }


Just bear in mind there's no check for the l (length) parameter, so you must always provide a number, and finally, you must create a number prototype for it (kind of an override) in order to use it directly on numbers:

Number.prototype.zf = function(l) { return this.toString().zf(l); }
I hope this helps.
GeneralA couple of bugs
Richard Deeming
8:08 26 Jul '05  
Since you're using a case-insensitive regular expression, you should ensure that the switch statement is also case-insensitive. For example, a format string of YYYY will not return anything, since it won't match any of the case labels.

You should also specify each valid option within the expression, rather than relying on two or more matches of any characters from a specified group. For example, try using the format string yyyymmdd, which will return nothing.

return f.replace(/(yyyy|mmmm|mmm|mm|dddd|ddd|dd|hh|nn|ss|a\/p)/gi,
function($1)
{
switch ($1.toLowerCase())
{



"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
GeneralRe: A couple of bugs
Tor2k
11:42 26 Jul '05  
hmm... thanks. you're right, and your suggestions work.

"There is a strength in the union even of very sorry men." - Homer

GeneralRe: A small addition
Dougww
4:13 5 Aug '05  
Some may find this small addition useful:
return f.replace(/(yyyy|mmmm|mmm|mm|dddd|ddd|dd|hh|nn|ss|a\/p|sup)/gi,
This adds a new case, "sup", for a superscript on the date:
case 'sup':  
switch (d.getDate())
{
case 1:
case 21:
case 31:
return "st";
case 2:
case 22:
return "nd";
case 3:
case 23:
return "rd";
default:
return "th";
}
}
Thanks for this extremely useful function!
GeneralRe: A small addition
Tor2k
8:16 6 Aug '05  
Hey what a great idea. You can also group all the cases with a mod operation:

case 'sup':
switch (d.getDate() % 10)
{
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}

Be careful when using format specifiers, though; "sup" may occur in the format string in a phrase; that's why I came up with "a/p" i.o. simply "ap".

Thanks, I'm glad it helped you!
GeneralRe: A small addition
Richard Deeming
3:21 10 Aug '05  
No you can't - instead of 11th, 12th and 13th, your code would return 11st, 12nd and 13rd!


"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
GeneralRe: A small addition
Tor2k
7:18 10 Aug '05  
Too bad you're right. Ok, so I guess a switch is in order.
GeneralRe: A small addition
shadowcreeper
11:05 20 Apr '09  
Why not just use this?
switch ( ( d.getDate() % 100 > 3 && d.getDate() % 100 < 21 ) ? 0 : d.getDate() % 10)

GeneralRe: A small addition
Tor2k
6:47 21 Apr '09  
If it works, that's fine!

I would save some d.getDate() calls by setting up a variable, though. Not that users will notice, but programmers should!

There is no spoon.

GeneralRe: A small addition: string 'choose' io switch
Tor2k
10:30 8 Aug '05  
Ok, I changed the 'sup' format specifier with 's/p' and replaced the switch with a math operation:

case 's/p': var i = d.getDate() % 10; return 'thstndrd'.substr(2 * (i < 4) * i, 2);
You see, i (date mod 10) ranges from 0 to 9, but only 1, 2 and 3 have specific ordinal suffixes, so with a little math, and in a fashion similar to a choose statement in VB, I got rid of the switch statement for (hopefully) faster execution.

The math goes as follows: (i < 4) yields true for 0, 1, 2, 3 and false for everything else; fortunately for us, booleans yield 1 or 0 when used in arithmetic operations (in VB, 'true' actually equals -1). So this simple expression filters out the 'uninteresting' cases.

Next, we need the actual i value to pinpoint each case, that's why we multiply. This is also why we calculate and store i ahead of time, for we'll use it twice in the expression.

The rest involves doubling the value to get a 2-char index into the strange string we sub into, which is nothing but the ordinal suffixes, th st nd rd.

Incidentally, I realized you would need another date specifier to avoid presenting '06th', that is, skip the zero fill. But then again this datepart specifier itself might be enough (or maybe a 5d, ddddd that is) if you simply prepend the date.

Again, I just had fun figuring out an alternative to the switch. Richard Deeming corrected this anyway by pointing out that we'll get 11st i.o. 11th, 12nd and 13rd which are totally wrong; I guess the switch is in order, or an even messier math op.


Last Updated 8 Aug 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010