JavaScript Date Format

Update: The documentation below has been updated for the new Date Format 1.2. Get it now!

Although JavaScript provides a bunch of methods for getting and setting parts of a date object, it lacks a simple way to format dates and times according to a user-specified mask. There are a few scripts out there which provide this functionality, but I've never seen one that worked well for me… Most are needlessly bulky or slow, tie in unrelated functionality, use complicated mask syntaxes that more or less require you to read the documentation every time you want to use them, or don't account for special cases like escaping mask characters within the generated string.

When choosing which special mask characters to use for my JavaScript date formatter, I looked at PHP's date function and ColdFusion's discrete dateFormat and timeFormat functions. PHP uses a crazy mix of letters (to me at least, since I'm not a PHP programmer) to represent various date entities, and while I'll probably never memorize the full list, it does offer the advantages that you can apply both date and time formatting with one function, and that none of the special characters overlap (unlike ColdFusion where m and mm mean different things depending on whether you're dealing with dates or times). On the other hand, ColdFusion uses very easy to remember special characters for masks.

With my date formatter, I've tried to take the best features from both, and add some sugar of my own. It did end up a lot like the ColdFusion implementation though, since I've primarily used CF's mask syntax.

Before getting into further details, here are some examples of how this script can be used:

var now = new Date();

now.format("m/dd/yy");
// Returns, e.g., 6/09/07

// Can also be used as a standalone function
dateFormat(now, "dddd, mmmm dS, yyyy, h:MM:ss TT");
// Saturday, June 9th, 2007, 5:46:21 PM

// You can use one of several named masks
now.format("isoDateTime");
// 2007-06-09T17:46:21

// ...Or add your own
dateFormat.masks.hammerTime = 'HH:MM! "Can\'t touch this!"';
now.format("hammerTime");
// 17:46! Can't touch this!

// When using the standalone dateFormat function,
// you can also provide the date as a string
dateFormat("Jun 9 2007", "fullDate");
// Saturday, June 9, 2007

// Note that if you don't include the mask argument,
// dateFormat.masks.default is used
now.format();
// Sat Jun 09 2007 17:46:21

// And if you don't include the date argument,
// the current date and time is used
dateFormat();
// Sat Jun 09 2007 17:46:22

// You can also skip the date argument (as long as your mask doesn't
// contain any numbers), in which case the current date/time is used
dateFormat("longTime");
// 5:46:22 PM EST

// And finally, you can convert local time to UTC time. Either pass in
// true as an additional argument (no argument skipping allowed in this case):
dateFormat(now, "longTime", true);
now.format("longTime", true);
// Both lines return, e.g., 10:46:21 PM UTC

// ...Or add the prefix "UTC:" to your mask.
now.format("UTC:h:MM:ss TT Z");
// 10:46:21 PM UTC

Following are the special characters supported. Any differences in meaning from ColdFusion's dateFormat and timeFormat functions are noted.

Mask Description
d Day of the month as digits; no leading zero for single-digit days.
dd Day of the month as digits; leading zero for single-digit days.
ddd Day of the week as a three-letter abbreviation.
dddd Day of the week as its full name.
m Month as digits; no leading zero for single-digit months.
mm Month as digits; leading zero for single-digit months.
mmm Month as a three-letter abbreviation.
mmmm Month as its full name.
yy Year as last two digits; leading zero for years less than 10.
yyyy Year represented by four digits.
h Hours; no leading zero for single-digit hours (12-hour clock).
hh Hours; leading zero for single-digit hours (12-hour clock).
H Hours; no leading zero for single-digit hours (24-hour clock).
HH Hours; leading zero for single-digit hours (24-hour clock).
M Minutes; no leading zero for single-digit minutes.
Uppercase M unlike CF timeFormat's m to avoid conflict with months.
MM Minutes; leading zero for single-digit minutes.
Uppercase MM unlike CF timeFormat's mm to avoid conflict with months.
s Seconds; no leading zero for single-digit seconds.
ss Seconds; leading zero for single-digit seconds.
l or L Milliseconds. l gives 3 digits. L gives 2 digits.
t Lowercase, single-character time marker string: a or p.
No equivalent in CF.
tt Lowercase, two-character time marker string: am or pm.
No equivalent in CF.
T Uppercase, single-character time marker string: A or P.
Uppercase T unlike CF's t to allow for user-specified casing.
TT Uppercase, two-character time marker string: AM or PM.
Uppercase TT unlike CF's tt to allow for user-specified casing.
Z US timezone abbreviation, e.g. EST or MDT. With non-US timezones or in the Opera browser, the GMT/UTC offset is returned, e.g. GMT-0500
No equivalent in CF.
o GMT/UTC timezone offset, e.g. -0500 or +0230.
No equivalent in CF.
S The date's ordinal suffix (st, nd, rd, or th). Works well with d.
No equivalent in CF.
'…' or "…" Literal character sequence. Surrounding quotes are removed.
No equivalent in CF.
UTC: Must be the first four characters of the mask. Converts the date from local time to UTC/GMT/Zulu time before applying the mask. The "UTC:" prefix is removed.
No equivalent in CF.

And here are the named masks provided by default (you can easily change these or add your own):

Name Mask Example
default ddd mmm dd yyyy HH:MM:ss Sat Jun 09 2007 17:46:21
shortDate m/d/yy 6/9/07
mediumDate mmm d, yyyy Jun 9, 2007
longDate mmmm d, yyyy June 9, 2007
fullDate dddd, mmmm d, yyyy Saturday, June 9, 2007
shortTime h:MM TT 5:46 PM
mediumTime h:MM:ss TT 5:46:21 PM
longTime h:MM:ss TT Z 5:46:21 PM EST
isoDate yyyy-mm-dd 2007-06-09
isoTime HH:MM:ss 17:46:21
isoDateTime yyyy-mm-dd'T'HH:MM:ss 2007-06-09T17:46:21
isoUtcDateTime UTC:yyyy-mm-dd'T'HH:MM:ss'Z' 2007-06-09T22:46:21Z

A couple issues:

  • In the unlikely event that there is ambiguity in the meaning of your mask (e.g., m followed by mm, with no separating characters), put a pair of empty quotes between your metasequences. The quotes will be removed automatically.
  • If you need to include literal quotes in your mask, the following rules apply:
    • Unpaired quotes do not need special handling.
    • To include literal quotes inside masks which contain any other quote marks of the same type, you need to enclose them with the alternative quote type (i.e., double quotes for single quotes, and vice versa). E.g., date.format('h "o\'clock, y\'all!"') returns "6 o'clock, y'all". This can get a little hairy, perhaps, but I doubt people will really run into it that often. The previous example can also be written as date.format("h") + "o'clock, y'all!".

Here's the code:

/*
 * Date Format 1.2.3
 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
 * MIT license
 *
 * Includes enhancements by Scott Trenda <scott.trenda.net>
 * and Kris Kowal <cixar.com/~kris.kowal/>
 *
 * Accepts a date, a mask, or a date and a mask.
 * Returns a formatted version of the given date.
 * The date defaults to the current date/time.
 * The mask defaults to dateFormat.masks.default.
 */

var dateFormat = function () {
	var	token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
		timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
		timezoneClip = /[^-+\dA-Z]/g,
		pad = function (val, len) {
			val = String(val);
			len = len || 2;
			while (val.length < len) val = "0" + val;
			return val;
		};

	// Regexes and supporting functions are cached through closure
	return function (date, mask, utc) {
		var dF = dateFormat;

		// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
		if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
			mask = date;
			date = undefined;
		}

		// Passing date through Date applies Date.parse, if necessary
		date = date ? new Date(date) : new Date;
		if (isNaN(date)) throw SyntaxError("invalid date");

		mask = String(dF.masks[mask] || mask || dF.masks["default"]);

		// Allow setting the utc argument via the mask
		if (mask.slice(0, 4) == "UTC:") {
			mask = mask.slice(4);
			utc = true;
		}

		var	_ = utc ? "getUTC" : "get",
			d = date[_ + "Date"](),
			D = date[_ + "Day"](),
			m = date[_ + "Month"](),
			y = date[_ + "FullYear"](),
			H = date[_ + "Hours"](),
			M = date[_ + "Minutes"](),
			s = date[_ + "Seconds"](),
			L = date[_ + "Milliseconds"](),
			o = utc ? 0 : date.getTimezoneOffset(),
			flags = {
				d:    d,
				dd:   pad(d),
				ddd:  dF.i18n.dayNames[D],
				dddd: dF.i18n.dayNames[D + 7],
				m:    m + 1,
				mm:   pad(m + 1),
				mmm:  dF.i18n.monthNames[m],
				mmmm: dF.i18n.monthNames[m + 12],
				yy:   String(y).slice(2),
				yyyy: y,
				h:    H % 12 || 12,
				hh:   pad(H % 12 || 12),
				H:    H,
				HH:   pad(H),
				M:    M,
				MM:   pad(M),
				s:    s,
				ss:   pad(s),
				l:    pad(L, 3),
				L:    pad(L > 99 ? Math.round(L / 10) : L),
				t:    H < 12 ? "a"  : "p",
				tt:   H < 12 ? "am" : "pm",
				T:    H < 12 ? "A"  : "P",
				TT:   H < 12 ? "AM" : "PM",
				Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
				o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
				S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
			};

		return mask.replace(token, function ($0) {
			return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
		});
	};
}();

// Some common format strings
dateFormat.masks = {
	"default":      "ddd mmm dd yyyy HH:MM:ss",
	shortDate:      "m/d/yy",
	mediumDate:     "mmm d, yyyy",
	longDate:       "mmmm d, yyyy",
	fullDate:       "dddd, mmmm d, yyyy",
	shortTime:      "h:MM TT",
	mediumTime:     "h:MM:ss TT",
	longTime:       "h:MM:ss TT Z",
	isoDate:        "yyyy-mm-dd",
	isoTime:        "HH:MM:ss",
	isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
	isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};

// Internationalization strings
dateFormat.i18n = {
	dayNames: [
		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	],
	monthNames: [
		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
	]
};

// For convenience...
Date.prototype.format = function (mask, utc) {
	return dateFormat(this, mask, utc);
};

Download it here (1.2 KB when minified and gzipped).

Note that the day and month names can be changed (for internationalization or other purposes) by updating the dateFormat.i18n object.

If you have any suggestions or find any issues, lemme know.


My new blog: Want to learn about aphantasisa and hyperphantasia, the Shen Yun cult, or establishing South Dakota residency to take advantage of nomad friendly laws? All of these and more await at Life After Tech.

372 thoughts on “JavaScript Date Format”

  1. Kris Kowal integrated Date Format 1.0 as a module into his innovative, emerging JavaScript library called Chiron. In the process, he changed it so that if only one argument is provided to dateFormat and that argument contains no numbers, it’s treated as a mask and applied to the current date and time. I think that was a great change, so I’ve added it in version 1.1, and updated the above documentation. Thanks, Kris!

  2. Great work! It appears however the documentation has month and minutes backwards. MM is returning month and mm is returning minutes. According to the doc it is suppose to be the reverse.

    Thanks

  3. @Pipnaintez, I can’t reproduce the problem you mention. “mm” returns months, and “MM” returns minutes, like the docs say.

  4. Hi Steve!

    I used your code as a base for reconstructing the PHP date() function in JavaScript. It supports most features that PHP’s date() function does, but takes milliseconds rather than seconds as the timestamp.

    I haven’t implemented all format characters yet (they will be replaced by ‘?’), so if anyone feels up for it, feel free to code them as well. The code hasn’t been tested thoroughly either.

    Here it is: http://javascript.mezane.org/date/date.js

  5. Is there any way to display time with the timezone that matches the offset rather than the browser’s local timezone? I want to display the time somewhere else in the world with their local timezone – not my local timezone.

  6. Hi Steve,

    I was googling around and just happen to stumble upon this post. Nice job with the formatting. I’ve been working on a comprehensive JavaScript Date library (http://www.datejs.com/) for a while. I would appreciate any feedback you have.

    I too implemented a Date formatting option, but tapped into .toString() to pass an optional “format” parameter.

    Example

    Date.today().toString(“MM/dd/yy”);

    If no format parameter is passed the function will return the native .toString functionality.

  7. Steven, thanks for writing this great function. I like your nod to ColdFusion and the similarity your function shares.

  8. Very nice work, thanks! I will try to create a version with syntax closer to the Java Date class.

  9. Hi,

    Can u tell me how to use this function with a timestamp in isoFullDateTime format, from the xml and convert it to a UTC format. I’m getting and invalid date error when i pass this as
    var d= segment.traveltime;
    alert(d); //is fine
    var datestring22 = dateFormat(d, “fullDate”); //gives invalid date error

  10. Hi I’m sorry but I don’t understand something..I wrote “dateFormat(date,”dd-mm-yyyy”);” I allways have a message “uncaught exception: invalid date”.
    Does someone can help me please ?
    (Sorry for my English…)

  11. @RM, what is UTC format? It sounds like you’re running into the same issue as jp clutier (see below).

    @jp clutier, this function uses the native Date.parse method to convert a date passed in as a string to a Date object. The format you specified is not recognized by the somewhat limited Date.parse. You’ll need to either use a string that the parse method recognizes, or create the date using the Date constructor (e.g., new Date(year, month, date)).

  12. Thanks for an awesome script! Works like a charm!

    I have a suggestion though that might be useful… I ran into the need to have the ‘st’, ‘nd’, ‘rd’, or ‘th’ appended to the end of the day (eg: 1st, 2nd, 3rd, 4th, etc…).

    The solution was simple, Just add a method as follows:

    postFix = function (value) {
    if (value == 1 || value == 21 || value == 31) {
    return value + “st”;
    } else if (value == 2 || value == 22) {
    return value + “nd”;
    } else if (value == 3 || value == 23) {
    return value + “rd”;
    } else {
    return value + “th”;
    }
    }

    Then you can create new flags as follows:

    dp: postFix(d),
    ddp: postFix(pad(d)),

    It’s really simple, I know, but I just thought it might be something you’d like to include.

  13. Ok… scratch that about the flags… for some reason, if you put ‘dp’ (for example) into the mask, it comes back with something like ‘9p’ instead of ‘9th’!?

    Any clues?

  14. @switch, you probably didn’t add your new flags to the token regex.

    I liked the idea of a flag for ordinal suffixes, so I’ve gone ahead and added it to the script and upped the version to 1.2. Here are the changes (the above post has been updated accordingly):

    – The new S flag outputs date ordinal suffixes (“st”, “nd”, “rd”, “th”). Works well with d. E.g., dateFormat("mmmm dS, yyyy") returns “July 4th, 2008”.
    – Added support for converting local time to UTC time before applying the format mask, via the third argument (Boolean) or the UTC: mask prefix (which allows applying UTC time via a named mask).
    – Several changes to the provided, named masks.

    The named mask changes:

    default changed from “ddd mmm d yyyy HH:MM:ss” to “ddd mmm dd yyyy HH:MM:ss” so that the default format is a fixed width that is easier to parse, and matches Firefox’s Date.prototype.toString (minus the time zone info).
    – Removed isoFullDateTime (was “yyyy-mm-dd’T’HH:MM:ss.lo”).
    – Added isoUtcDateTime (is “UTC:yyyy-mm-dd’T’HH:MM:ss’Z'”, which uses the new UTC conversion prefix).

  15. Can I convert ISO formatted date(string) ex: 2008-08-08T10:10+00:00 to a Date object using dateFormat?

  16. Hi

    great job, but there is small bug in dayNames array:
    dayNames: [
    “Sun”, “Mon”, “Tue”, “Wed”, “Thr”, “Fri”, “Sat”,
    “Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”
    ]

    The short name for thursday should be “Thu”.

    Regards.

  17. @ArunB, this function only returns a string; never a Date object. The input strings it can interpret as a date are only those supported by JavaScript’s native Date.parse (which doesn’t support the format you specified). In any case, there is a lot of variety in the formats that are considered ISO formatted date strings. This library includes named masks to output the most common formats.

    @Tyler, good catch. Thanks. I’ve corrected the abbreviation and upped the version to 1.2.2.

  18. The date format method is great and has saved me a bunch of time, but I’ve just noticed that the following line causes the function to fail in firefox 3:

    if (isNaN(date)) throw new SyntaxError(“invalid date”);

    Has anyone else noticed this?

    i.e.
    alert(cs_formatDate(‘2008-08-15 15:30:11’));

  19. Please ignore my ignorance regarding the above, it turns out that the dashes in the date weren’t ALL being replaced (for forward slashes) prior to being passed to objDate.format(), so absolutely nothing to do with the script… ahem.. cough!

    The date format script is great! Many thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *