JavaScript Date Format

I took the time to implement some sorely missed functionality in the JavaScript core objects – Date is missing simple date formatting to text string. While the Date object can parse arbitrary textual descriptions of dates and times as input, it can only output textual description with a fixed “locale” template.

I’ve looked up on the web and there are some utilities that achieve this simple operation, but most of them are overly complex and some people even have the nerve to charge for this very simple code !

So I wrote it myself. Its really very simple- this code will add some methods to the Date object, the most important of which is format() which takes a template string as a parameter and returns the date and time stored in the object as a string formatted according to the template. I’ve also added some utility methods that allow me to retrieve some information which isn’t available using Date’s native methods, such as month names.

The text template allows for the same patterns as Java’s SimpleDateFormat object, except for pattern letters that would have required a lot more logic to be added. Basically, everything that I can get using Date’s current methods was implemented, and additionally I added “x” for epoch time stamps because I needed that functionality.

And here’s the code:

/**
 * Extension of the JavaScript internal Date object to allow various formatting of 
 * date/time values.
 * This implementation was designed to be compliant with the formatting of the
 * Java class library's SimpleDateFormat object, with the addition of the 'x' format
 * option to show number of seconds since the epoch (1/1/1970 00:00).
 *
 * See http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html for
 * full details and examples of use.
 *
 * This code can be used under the terms of the GNU General Public License, version 2 
 * (http://www.gnu.org/licenses/gpl2.txt).
 * In the context of using the below code in a web application, it means that you may make this code, 
 * as-is unchanged and in its own source file, available on a web application and then have that application
 * call methods defined here - regardless of what license the web application is distributed under. 
 * If you would like to distribute the below source included in a source file with other source not included 
 * here, and/or modify the the below source in anyway, then the resulting source file as a whole must
 * be licensed under the GPLv2, as per the original license terms. All this relates of course only to distribution -
 * Anyone may at any point use this source or parts of it, unmodified or otherwise, for any use as long 
 * as it is never distributed further. If the this source is included in a larger work which is then distributed,
 * then the above terms must be adhered to.
 *
 * The full source for this extension is always available on my blog at this page:
 * http://geek.co.il/wp/2005/07/26/javascript-date-format/
 *
 * (c) Copyright 2006 - Oded Arbel 
 * (c) Portions copyright 2006 - Jack Slocum 
 */

// Static definition of Month names
Date.MONTH_NAMES = [
    "January", "February", "March",
    "April", "May", "June",
    "July", "August", "September",
    "October", "November", "December" ];

// Static definition of weekday names
Date.WEEKDAY_NAMES = [
    "Sunday", "Monday", "Tuesday",
	"Wednesday", "Thursday", "Friday",
	"Saturday" ];

// clone the current date object and return a different object with identical value
Date.prototype.clone = function () {
  return new Date(this.getTime());
}

// clear the time information from this date and return it
Date.prototype.clearTime = function () {
  this.setHours(0); this.setMinutes(0);
  this.setSeconds(0); this.setMilliseconds(0);
  return this;
}

// return the last day of this month
Date.prototype.lastDay = function () {
  var tempDate = this.clone();
  tempDate.setMonth(tempDate.getMonth()+1);
  tempDate.setDate(0);
  return tempDate.getDate();
}

// return number of days since start of year
Date.prototype.getYearDay = function () {
  var today = new Date(this);
  today.setHours(0); today.setMinutes(0); today.setSeconds(0); 
  var tempDate = new Date(today);
  // set start of year
  tempDate.setDate(1);
  tempDate.setMonth(0);
  return Math.round(
    (today.getTime() - tempDate.getTime()) 
    / 86400 / 1000) + 1; // Jan/1 is day 1
}

// add format() to Date
Date.prototype.format = function(formatString) {
  var out = new String();
  var token = ""
  for (var i = 0; i < formatString.length; i++) {
    if (formatString.charAt(i) == token.charAt(0)) {
      token = token.concat(formatString.charAt(i));
      continue;
    }

    out = out.concat(this.convertToken(token));
    token = formatString.charAt(i);
  }
  return out + this.convertToken(token);    
}

// internal call to map tokens to the date data
Date.prototype.convertToken = function (str) {
  switch(str.charAt(0)) {
    case 'y': // set year
      if (str.length > 2)
        return this.getFullYear();
      return this.getFullYear().toString().substring(2);
    case 'd': // set date
      return Date.zeroPad(this.getDate(),str.length);
    case 'D': // set day in year
      return this.getYearDay();
    case 'a': 
      return this.getHours() > 11 ? "PM" : "AM";
    case 'H': // set hours
      return Date.zeroPad(this.getHours(),str.length);
    case 'h':
      return Date.zeroPad(this.get12Hours(),str.length);
    case 'm': // set minutes
      return Date.zeroPad(this.getMinutes(),2);
    case 's': // set secondes
      return Date.zeroPad(this.getSeconds(),2);
    case 'S': // set milisecondes
      return Date.zeroPad(this.getMilliseconds(),str.length);
    case 'x': // set epoch time
      return this.getTime();
    case 'Z': // set time zone
      return (this.getTimezoneOffset() / 60) + ":" + 
        Date.zeroPad(this.getTimezoneOffset() % 60,2);
    case 'M': // set month
      if (str.length > 3) return this.getFullMonthName();
      if (str.length > 2) return this.getShortMonthName();
      return Date.zeroPad(this.getMonth()+1,str.length);
    case 'E': // set dow
      if (str.length > 3) return this.getDOWName();
      if (str.length > 1) return this.getShortDOWName();
      return this.getDay();
    default:
      return str;
  }
}

// Retreive the month's name in english
Date.prototype.getFullMonthName = function() {
  return Date.MONTH_NAMES[this.getMonth()];
}

// Retreive the abberviated month name in english
Date.prototype.getShortMonthName = function() {
  return Date.MONTH_NAMES[this.getMonth()].substring(0,3);
}

// Retreive the week day name in english
Date.prototype.getDOWName = function () {
  return Date.WEEKDAY_NAMES[this.getDay()];
}

// Retreive the abberviated week day name in english
Date.prototype.getShortDOWName = function () {
  return Date.WEEKDAY_NAMES[this.getDay()].substring(0,3);
}

// Retreive the hour in a 12 hour clock (without the AM/PM specification)
Date.prototype.get12Hours = function () {
  return this.getHours() == 0 ? 12 :
    (this.getHours() > 12 ? this.getHours() - 12 : this.getHours());
}

// helper function to add required zero characters to fixed length fields
Date.zeroPad = function(num, width) {
  num = num.toString();
  while (num.length < width)
    num = "0" + num;
  return num;
}

If you have any comments on this – feel free to leave them here or email me.

12 Responses to “JavaScript Date Format”

  1. Milena:

    Wow! This helped me a lot and spared me a consierable amount of work, thanks!

  2. Thijs Lambrecht:

    Great stuff!
    Do you also have a javasript parser? i.e. if you know the formatstring and you have a formatted string, a method that converts the string back to a date.

  3. Guss:

    Not currently (never needed the capability).
    the Date constructor (new Date() ) can understand and parse correctly quite a few well defined formats, so I think you should first try to see what that can do for you.

    Parsing is generally more difficult then generating, so I think it will take some more code for this capability. If you want to hack on it, I’ll be glad to offer help and to host the result, otherwise I might work on it if I feel like it 🙂

  4. Thijs Lambrecht:

    Hi Guss,

    Date.parse can parse already some dates, but is not able to handle dates like 12 10 2005 (space as separator) and it wil take the first two digits as month in 12/10/2005 which is not correct if the formatstring was dd/MM/yyyy.
    I’ll see if I can come up with something that ‘reverses’ your formatting. I’ll let you know when I have it.
    But I currently haven’t got the time to keep myself busy with it…

    Bye for now,
    Thijs

  5. Guss:

    Thanks. I’ll see what I can do about it, but as everyone – I also have my hands quite full at this time 🙂

  6. Jack:

    Great stuff. Here’s a small change you might want to incorporate. It changes it so you don’t define the day and month names on every call to getMonthName, etc.

    Date.MONTH_NAMES = [
    “January”, “February”, “March”,
    “April”, “May”, “June”,
    “July”, “August”, “September”,
    “October”, “November”, “December” ];

    Date.prototype.getFullMonthName = function() {
    return Date.MONTH_NAMES[this.getMonth()];
    }

    Date.prototype.getShortMonthName = function() {
    return Date.MONTH_NAMES[this.getMonth()].substring(0,3);
    }

    Date.WEEKDAY_NAMES = [
    “Sunday”, “Monday”, “Tuesday”,
    “Wednesday”, “Thursday”, “Friday”,
    “Saturday” ];

    Date.prototype.getDOWName = function () {
    return Date.WEEKDAY_NAMES[this.getDay()];
    }

    Date.prototype.getShortDOWName = function () {
    return Date.WEEKDAY_NAMES[this.getDay()].substring(0,3);
    }

  7. Lee Underwood:

    Guss,

    I work for Jupitermedia and am the Managing Editor over at JavaScript Source. I would like to post your JavaScript Date Format script. Could you contact me at my e-mail address? Thanks! Great work!

  8. bumperbox:

    can i use this in a commercial applications ?
    maybe you could add a licence to the header ?

  9. bumperbox:

    not sure if this is useful to anyone, its an extension to guess the date
    sort of like date parsing but more useful i guess

    it is the first cut, so there might be some bugs in it
    please let me know if you find bugs or improvements

    // Date Guess Extension
    // 12 Oct 2006 – bumperbox {at} gmail.com

    // Static definition of time in millis for convenience
    Date.MILLIS_SECOND = 1000;
    Date.MILLIS_MINUTE = 60000;
    Date.MILLIS_HOUR = 3600000;
    Date.MILLIS_DAY = 86400000;
    Date.MILLIS_WEEK = 604800000;

    // 2 digit years less then turn of century are assumed to be this century
    Date.TURN_OF_CENTURY = 1950;

    // Guess date based on the following rules
    // date order can either be DMY or MDY
    // you can use . / (space) – as separators or no separators
    //
    // 0 = today
    // = = today
    // + = today + 1 day
    // – = today – 1 day
    // +n = today + n days
    // -n = today – n days
    // yyyy-mm-dd = iso date
    // dd = date, assuming current month and year
    // mm/dd or dd/mm = date and month, assuming current year
    // 2 digits years 1) {
    // matched on iso date, ignore first value in array it is empty, put in YMD order
    nYear = aryIsoDate[2];
    nMonth = aryIsoDate[3];
    nDayOfMonth = aryIsoDate[4];

    } else {

    // convert all delimiters to spaces, add more delimiters here if required
    sDate = sDate.replace(/[\/\.-]/g, ” “).trim();

    // if there are no delimiters, it must be ddmmyy or ddmmyyyy, add delimiters
    if (sDate.indexOf(” “) == -1) {
    if (sDate.length == 6 || sDate.length == 8) {
    sDate = sDate.substr(0,2) + ” ” + sDate.substr(2, 2) + ” ” + sDate.substr(4);
    }
    }

    // split date into parts
    var aryDate = sDate.split(” “);

    if (sDateOrder == “DMY”) {
    nDayOfMonth = aryDate[0];
    nMonth = aryDate[1];
    nYear = aryDate[2];
    } else if (sDateOrder == “MDY”) {
    if (sDate.length

  10. Guss:

    I added licensing terms to the header. I have no problems with this being used for a commercial product as long as the copyright notices are never removed or modified and all other terms discussed above are adhered to.

  11. Hank:

    I’ve been fiddeling around with this issue for quite some time already. I want to be able to retrieve month names and day names from the users locale settings. I’ve come up with some code, using the Date.toLocaleDateString() method:

    function diffArray(arr) {

    for (var i = 0; i < arr[0].length; i++) {

    for (var j = 0; j < arr.length – 1; j++) {

    if (arr[j][i].length && arr[j + 1][i].length && (arr[j][i] != arr[j + 1][i])) return i

    }
    }

    return -1

    }

    function setLocaleNames(arr) {

    var tmp = new Array();

    for (var i in arr) {

    tmp[i] = arr[i][diffArray(arr)].replace(/[\,\.]/g, “”)

    }

    return tmp

    }

    function setLocaleMonthStrings() {

    var tmp = new Array();

    for (var i = 0; i < 12; i++) {

    var d = new Date(new Date().getFullYear(), i, 8);
    d.setDate(d.getDate() – d.getDay());

    tmp[i] = d.toLocaleDateString().replace(/\d/g, “”).split(” “)

    }

    return tmp

    }

    try {

    /* Try to retrieve locale month names… */
    var nameMonth = setLocaleNames(setLocaleMonthStrings())

    }
    catch(err) {

    /* …else use… */
    var nameMonth = new Array(“January”,”February”,”March”,”April”,”May”,”June”,”July”,”August”,”September”,”October”,”November”,”December”)

    }

    function setLocaleDayStrings() {

    var tmp = new Array();

    for (var i = 0; i var arr = setLocaleMonthStrings().
    For day names, use 7 dates, while making sure that the day of week is unique, but the month is the same for all.
    ==> var arr = setLocaleDayStrings().

    Second, from these arrays, retrieve those fields that are different into another array, getting rid of points and comma’s in the process, using the diffArray(arr) function to find out which index must be used.
    ==> var nameArray = setLocaleNames(arr).

    Since these functions are not flawless (yet ;-), I use try {} catch(err) {} to default to English locale names in case of errors.

    Feel free to use, comment, improve.

  12. Oded:

    Interestingly, toLocaleDateString() on my computer returns a “dd/mm/yyyy” formatted string with no names. Is there something that I have to do to get a “long text” date format?

Leave a Reply