JavaScript custom date formatting function details resolution

  • 2020-03-30 01:22:43
  • OfStack

One of the more common things we do with JavaScript extensions is the date.prototype extension. Because we know that the Date class only provides several methods to getDate elements, such as getDate(),getMinute()... There is no format method for converting to a specific string. So, we use these subtle methods to encapsulate and combine the form of the date string we want. In general, this formatting function can be defined on the Date object's prototype or written as a separate method. Format = function(Date){... Format (YYYY:MM:DD) can be used directly, as if it is the native method of Date object. However, the method of defining the prototype has the disadvantage of "invading" JS prototype. You must consider this when designing an API. My advice is that users make decisions based on their own judgment, but in different ways, without affecting the logic of the process.

An example of this is the JavaScript date formatting function written as a separate function, the separate format function. Back to the format of this knowledge point, we examine how to achieve, the use of what principles. While traditional string concatenation such as indexOf()+substr() can be achieved, it is obviously not only inefficient, and the code is lengthy. It is also suitable to introduce the method of regular expression, which first writes out the string regularization and then makes a hit match of the result. Let's start with an example from Steven Levithan:


/**
 * Date Format 1.2.3
 * @credit Steven Levithan <stevenlevithan.com> 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.
 */
dateFormat = (function(){
    //Regular notes, 1. Token, (? :) represents a non-capture group; /1 backreference (think: {1,2} can be the same as /1?) ; According to the meaning here, [LloSZ] means that any character in the bracket is taken to match, which is very simple, but I don't understand the function of /L|, L|, o|, S|, Z/ in parsing the date; The last two sets of "or" match the quotation marks and the contents of the quotation marks (no double or single quotation marks).
    var token        = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])/1?|[LloSZ]|"[^"]*"|'[^']*'/g,
    //2. Timezone, [PMCEA][SDP] generates the consumption of two characters; The reg is all non-capture groups, which can speed up the regularization.
        timezone     = //b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]/d{4})?)/b/g,
        timezoneClip = /[^-+/dA-Z]/g,

        //Less than two fill characters, or the number of digits can be specified
        pad          = function (val, len){
            val = String(val);
            len = len || 2;
            while (val.length < len) val = "0" + val;
            return val;
        };
    //Why return a function? Because the variables described above are constant, and the arguments returned below are the ones that are actually executed. This is done by writing closures. As the English notes say, it can be speeded up.
    // Regexes and supporting functions are cached through closure
    //Date: date to be resolved or a new date; Mask :String format date template; Utc: the utc of the Stirng alternative.
    return function (date, mask, utc) {
        var i18n  = dateFormat.i18n;
        var masks = dateFormat.masks;
        // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
        //If there is only one parameter, and the parameter is a string without a number, consider the parameter mask. Date is generated by the new date in the next if, so date is the current date.
        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");
        //Determine what a mask is by determining the various conditions, regardless of how you specified it earlier. Pay attention to |, |.
        mask = String(masks[mask] || mask || masks["default"]);
        // Allow setting the utc argument via the mask
        if (mask.slice(0, 4) == "UTC:") {
            mask = mask.slice(4);
            utc = true;
        }
        //There are two cases, one in UTC and the other in general. Note that the literal index of JS can also return the members of a method.
        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:  i18n.dayNames[D],
                dddd: i18n.dayNames[D + 7],//Bit width :7, see dateformat.daynames.
                m:    m + 1, //Months starting at 0
                mm:   pad(m + 1),
                mmm:  i18n.monthNames[m],
                mmmm: i18n.monthNames[m + 12], //Bit width :12, see dateformat.monthnames
                yy:   String(y).slice(2),//The use of the string slice()
                yyyy: y,
                h:    H % 12 || 12, //H is 12 hours, h divided by 12 (because of decimal), the remainder is 12 hours.
                hh:   pad(H % 12 || 12),
                H:    H,
                HH:   pad(H),
                M:    M,
                MM:   pad(M),
                s:    s,
                ss:   pad(s),
                l:    pad(L, 3), // Max,999ms
                L:    pad(L > 99 ? Math.round(L / 10) : L),
                //Case matters
                t:    H < 12 ? "a"  : "p",
                tt:   H < 12 ? "am" : "pm",
                T:    H < 12 ? "A"  : "P",
                TT:   H < 12 ? "AM" : "PM",
                //So this step, timezone, is just going to do it.
                //Timezone, timezoneClip = /[^-+/ da-z]/g,
                //String returns the String form of the date, including a very long... UTC... information
                //If not, [""].pop() returns a null character
                Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
                //Four TimezoneOffset
                o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
                //Find the English ["th", "st", "nd", "rd"] according to the units digit of d
                S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
            };
        return mask.replace(token, function ($0 ) {
            //How do I detect a specific property on an object? Check with in!
            //$0. Slice (1, $0. Length - 1); ? What do you mean?
            return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
        });
    };
})();

This code is more thoughtful about date processing, let's go into the principle to see its mystery, how to deal with the date!

In the template of date string, we agree to use yyyy/mm/dd and other meaningful symbols to represent a certain element in the date respectively, such as y is a certain year, m is a certain month, d is a certain day. If it is capitalized, we should pay attention to distinguish it, capital m represents minutes, and lowercase m represents months. All in all, this is a convention that we've worked out for ourselves, what the code above calls a "mask," which allows us to enter the parameters we want to format, and then print out a printable string with the values of the date type. As for the date parsing process, first, according to all requirements of the Mask,get each element of the date one by one (getDate(),getMinute()... Can be obtained quickly), and then according to what is the real condition of the Mask, that is, the Mask. Replace (regular, element) method is used to replace the string template with the element. The replacement process is still marked by flag to match the comparison table one by one. As for the regular part, the key is to understand the process of the token and replace() functions. Participate in the code comments above to see the internal details.

Wouldn't it be tiring to type a lengthy Mask string every time? We can reduce our workload by defining constants:


dateFormat.masks = {
    "default":      "ddd mmm dd yyyy HH:MM:ss",
    shortDate:      "m/d/yy",
    shortDate2:     "yy/m/d/h:MM:ss",
    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'"
    //Add Chinese type date @edit 2010.8.11
    ,ChineseDate   :'yyyy years mm month dd day  HH when MM points '
}

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"
    ]
};

Steve's dateFormat is enough to complete most of the date conversion tasks, but in the vast code, we found a better solution, not out of 20 lines of code, the regular use of the rewind and put freely, is from the moon shadow predecessor JS!

Date.prototype.format = function(format) //author: meizz
{
  var o = {
    "M+" : this.getMonth()+1, //month
    "d+" : this.getDate(),    //day
    "h+" : this.getHours(),   //hour
    "m+" : this.getMinutes(), //minute
    "s+" : this.getSeconds(), //second
    "q+" : Math.floor((this.getMonth()+3)/3),  //quarter
    "S" : this.getMilliseconds() //millisecond
  }
  if(/(y+)/.test(format)) format=format.replace(RegExp.$1,
    (this.getFullYear()+"").substr(4 - RegExp.$1.length));
  for(var k in o)if(new RegExp("("+ k +")").test(format))
    format = format.replace(RegExp.$1,
      RegExp.$1.length==1 ? o[k] :
        ("00"+ o[k]).substr((""+ o[k]).length));
  return format;
}
alert(new Date().format("yyyy-MM-dd hh:mm:ss"));

Similar in principle to Steve's approach, but more condensed in code, it is both tricky and comprehensive. Starting at line 12 of the source code, the test() method not only checks for a match, but actually remembers the match, producing a RegExp.$1 result group to handle the year (at first I thought test() was efficient and didn't produce a result, but it didn't). Then, use the new RegExp to create an instance of the regular expression in string form, which is another clever place -- because it's directly linked to o's hash table! Then follow the gourd, test for a hit match, and replace it if there is one.

In addition, the code ("00" + o[k]).substr(String(o[k]).length) is also interesting. The original purpose was to take the last two bits of the array. This is a trick to take advantage of the substr() method, where the first parameter is the index that you start intercepting, and if you don't specify the second parameter index, you keep the string until the end (str.length). So, how many bits do we have to add to the String(o[k].length)? (P.S. "00" is a placeholder and can be replaced by another string "XX")

Still find this code a lot more difficult? We tried to rewrite the function of the shadow of the moon as a readable code, principle tends to be consistent but not so much skill, I believe that this can save you time, go back to see the shadow of the code is not too late.


date = {
 format: function(date, format){
  date = new Date(date); // force con.
  date = {
    year : date.getFullYear()
   ,month : date.getMonth() + 1 //Months, months count from zero
   ,day : date.getDate()
   ,hour : date.getHours()
   ,minute : date.getMinutes()
   ,second : date.getSeconds()
   ,milute : date.getMilliseconds()
  };
  var 
    match
   ,reg = /(y+)|(Y+)|(M+)|d+|h+|m+|s+|u+/g;
  while((match = reg.exec(format)) != null){
      match = match[0];
      if(/y/i.test(match)){
       format = format.replace(match, date.year);
      }
   if(match.indexOf('M') != -1){
       format = format.replace(match, date.month);
      }
   if(match.indexOf('d') != -1){
       format = format.replace(match, date.day);
      }       
   if(match.indexOf('h') != -1){
       format = format.replace(match, date.hour);
      }
   if(match.indexOf('m') != -1){
       format = format.replace(match, date.minute);
      } 
   if(match.indexOf('s') != -1){
       format = format.replace(match, date.second);
      } 
   if(match.indexOf('u') != -1){
       format = format.replace(match, date.milute);
      }           
  }
  return format;
 }
};

2011-1-7:

Date formatting code panned from ext 4.0, how to convert string to js standard date? What does the new ext do?


    /**
     *  Format the date according to a specific format pattern. 
     * Parse a value into a formatted date using the specified format pattern.
     * @param {String/Date} value  The value to be formatted (string must match JavaScript Date object format requirements, see <a href="http://www.w3schools.com/jsref/jsref_parse.asp" mce_href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> ) The value to format (Strings must conform to the format expected by the javascript 
     * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp" mce_href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method)
     * @param {String} format  Any (optional) date format string. (the default is 'm/d/Y' ) (optional) Any valid date format string (defaults to 'm/d/Y')
     * @return {String}  Formatted string. The formatted date string
     */
    date: function(v, format) {
        if (!v) {
            return "";
        }
        if (!Ext.isDate(v)) {
            v = new Date(Date.parse(v));
        }
        return v.dateFormat(format || Ext.util.Format.defaultDateFormat);
    }

The date constructor can also determine a date by figuring out how many milliseconds have passed since 1970? -- indeed, this is also ok, -- that is to say, from this problem, it is shown that the smallest unit of js date is milliseconds.

Final version:


/**
 *  Date formatting. See the blog post: http://blog.csdn.net/zhangxin09/archive/2011/01/01/6111294.aspx
 * e.g: new Date().format("yyyy-MM-dd hh:mm:ss")
 * @param  {String} format
 * @return {String}
*/
Date.prototype.format = function (format) {
    var $1, o = {
        "M+": this.getMonth() + 1,  //Months, starting at 0
        "d+": this.getDate(),     //  The date of 
        "h+": this.getHours(),     //  hours 
        "m+": this.getMinutes(),   //  minutes 
        "s+": this.getSeconds(),   //  seconds 
                //Quarter quarter
        "q+": Math.floor((this.getMonth() + 3) / 3),
        "S": this.getMilliseconds() //  Thousands of seconds 
    };
    var key, value;
    if (/(y+)/.test(format)) {
        $1 = RegExp.$1, 
        format = format.replace($1, String(this.getFullYear()).substr(4 - $1));
    }
    for (key in o) { //If this parameter is not specified, the substring continues to the end of stringvar.
        if (new RegExp("(" + key + ")").test(format)) {
            $1  = RegExp.$1,
      value = String(o[key]),
      value = $1.length == 1 ? value : ("00" + value).substr(value.length),
      format = format.replace($1, value);
        }
    }
    return format;
}


Related articles: