2

I want to format a date value like this:

var d = new Date();
myobj.format(d, "dddd (ddd)   S dd'd'.MM (MMM MMMM).yyyy HH:mm:ss.fff t tt T TT (o) {Z}");

I don't want to use date.js, because date.js is too large, and the problem with the format-helper from stevenlevithan is that it interchanges capital and lowercase 'M' characters, and I want to use the same formatString in C# and JavaScript.

Now I've written my own. So far, it works.
But now I have moved the "format" function (and helper functions like ord) into namespace myobj, and in the format-function I loose the this-context (myobj).

If I call the function "format" with .apply(this, arguments), I get the correct this-context, but I loose the argument "m".

I don't want to have to call ord using the namespace (myobj.ord(bla)), I'd like to call it like "this.ord(bla)" or "aaa.ord(bla)", so I can change the namespace name easily at will.

Sooo... How can I pass either an extra-argument to the anonymous function in the replace-regex, or how can I get the "this"-context without loosing m ?

/* @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. 
https://github.com/datejs/Datejs/blob/master/src/core.js
https://code.google.com/p/datejs/wiki/FormatSpecifiers
http://stackoverflow.com/questions/3552461/how-to-format-a-javascript-date
http://blog.stevenlevithan.com/archives/date-time-format
*/




// https://github.com/datejs/Datejs/blob/master/src/globalization/en-US.js
var $i18n =
{
    dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
    abbreviatedDayNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
    shortestDayNames: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
    firstLetterDayNames: ["S", "M", "T", "W", "T", "F", "S"],

    monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
    abbreviatedMonthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],

    amDesignator: "AM",
    pmDesignator: "PM",
}




var myobj =
{
    // Pad number with 0
    p: function (val, len)
    {
        val = String(val);
        len = len || 2;
        while (val.length < len) val = "0" + val;
        return val;
    }

    // Pad Milliseconds
    , mp: function (d, n)
    {
        var i = 3, res = p(d.getMilliseconds(), 3).substr(0, n);

        for (; i < n; ++i)
            res += "0";

        return res;
    }


    , tzo: function (d)
    {
        var o = d.getTimezoneOffset();
        return (o > 0 ? "-" : "+") + p(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4)
    }


    , tz: function (date)
    {
        var timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
        timezoneClip = /[^-+\dA-Z]/g;
        return (String(date).match(timezone) || [""]).pop().replace(timezoneClip, "");
    }

    , ord: function (num)
    {
        if (num <= 0)
            return num.toString();

        switch (num % 100)
        {
            case 11:
            case 12:
            case 13:
                return num + "th";
        }

        switch (num % 10)
        {
            case 1:
                return num + "st";
            case 2:
                return num + "nd";
            case 3:
                return num + "rd";
            default:
                return num + "th";
        }

    } // End Fn ord



    , format: function (x, formatString)
    {
        // "S dd'd'.MM (MMMM).yyyy ".replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g, 
        return formatString.replace(/d{1,4}|M{1,4}|f{1,7}|yy(?:yy)?|([HhmsTt])\1?|[oSZ]|"[^"]*"|'[^']*'/g,
            function (m, aaa)
            {
                console.log("foo");
                console.log(this);
                console.log(aaa);


                if (m.charAt(0) === "\\")
                {
                    return m.replace("\\", "");
                }

                x.h = x.getHours;

                switch (m)
                {
                    case "hh":
                        return p(x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12));
                    case "h":
                        return x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12);
                    case "HH":
                        return p(x.h());
                    case "H":
                        return x.h();
                    case "mm":
                        return p(x.getMinutes());
                    case "m":
                        return x.getMinutes();
                    case "ss":
                        return p(x.getSeconds());
                    case "s":
                        return x.getSeconds();
                    case "yyyy":
                        return p(x.getFullYear(), 4);
                    case "yy":
                        return p(x.getFullYear());
                    case "dddd":
                        return $i18n.dayNames[x.getDay()];
                    case "ddd":
                        return $i18n.abbreviatedDayNames[x.getDay()];
                    case "dd":
                        return p(x.getDate());
                    case "d":
                        return x.getDate();
                    case "MMMM":
                        return $i18n.monthNames[x.getMonth()];
                    case "MMM":
                        return $i18n.abbreviatedMonthNames[x.getMonth()];
                    case "MM":
                        return p((x.getMonth() + 1));
                    case "M":
                        return x.getMonth() + 1;
                    case "t":
                        return (x.h() < 12 ? $i18n.amDesignator.substring(0, 1) : $i18n.pmDesignator.substring(0, 1)).toLowerCase();
                    case "tt":
                        return (x.h() < 12 ? $i18n.amDesignator : $i18n.pmDesignator).toLowerCase();;
                    case "T":
                        return x.h() < 12 ? $i18n.amDesignator.substring(0, 1) : $i18n.pmDesignator.substring(0, 1);
                    case "TT":
                        return x.h() < 12 ? $i18n.amDesignator : $i18n.pmDesignator;
                    case "S":
                        console.log(this);
                        return this.ord(x.getDate());
                    case "fffffff":
                        return mp(x, 7);
                    case "ffffff":
                        return mp(x, 6);
                    case "fffff":
                        return mp(x, 5);
                    case "ffff":
                        return mp(x, 4);
                    case "fff":
                        return mp(x, 3);
                    case "ff":
                        return mp(x, 2);
                    case "f":
                        return mp(x, 1);
                    case "o":
                        return tzo(d);
                    case "Z":
                        return tz(d);
                    default:
                        return m;
                } // End Switch
            } // End Fn
            //.apply(this, arguments)
        );
    }

};




Edit: The solution is simple, just use bind instead of apply:

/* @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. 
https://github.com/datejs/Datejs/blob/master/src/core.js
https://code.google.com/p/datejs/wiki/FormatSpecifiers
http://stackoverflow.com/questions/3552461/how-to-format-a-javascript-date
http://blog.stevenlevithan.com/archives/date-time-format
http://stackoverflow.com/questions/6002808/is-there-any-way-to-get-current-time-in-nanoseconds-using-javascript
*/




// https://github.com/datejs/Datejs/blob/master/src/globalization/en-US.js
var $i18n =
{
    dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
    abbreviatedDayNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
    shortestDayNames: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
    firstLetterDayNames: ["S", "M", "T", "W", "T", "F", "S"],

    monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
    abbreviatedMonthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],

    amDesignator: "AM",
    pmDesignator: "PM",
}




var myobj =
{
    // Pad number with 0
    p: function (val, len)
    {
        val = String(val);
        len = len || 2;
        while (val.length < len) val = "0" + val;
        return val;
    }

    // Pad Milliseconds
    , mp: function (d, n)
    {
        var i = 3, res = this.p(d.getMilliseconds(), 3).substr(0, n);

        for (; i < n; ++i)
            res += "0";

        return res;
    }


    , tzo: function (d)
    {
        var o = d.getTimezoneOffset();
        return (o > 0 ? "-" : "+") + this.p(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4)
    }


    , tz: function (date)
    {
        var timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
        timezoneClip = /[^-+\dA-Z]/g;
        return (String(date).match(timezone) || [""]).pop().replace(timezoneClip, "");
    }

    , ord: function (num)
    {
        if (num <= 0)
            return num.toString();

        switch (num % 100)
        {
            case 11:
            case 12:
            case 13:
                return num + "th";
        }

        switch (num % 10)
        {
            case 1:
                return num + "st";
            case 2:
                return num + "nd";
            case 3:
                return num + "rd";
            default:
                return num + "th";
        }

    } // End Fn ord

    ,"formatString": function(str) 
    {
        if (!str)
            return str;

        str = str.toString();

        if (arguments.length < 2) 
            return str;

        var t = typeof arguments[1],
            args = "string" == t || "number" == t ? Array.prototype.slice.call(arguments) : arguments[1];

        for (var arg in args) 
            str = str.replace(new RegExp("\\{" + arg + "\\}", "gi"), args[arg]);

        return str
    }

    , "format": function (x, formatString)
    {
        // "S dd'd'.MM (MMMM).yyyy ".replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g, 
        return formatString.replace(/d{1,4}|M{1,4}|f{1,7}|yy(?:yy)?|([HhmsTt])\1?|[oSZ]|"[^"]*"|'[^']*'/g,
            function (m)
            {
                var  p = this.p 
                    ,mp = this.mp.bind(this) 
                    ,tzo = this.tzo.bind(this) 
                    ,tz = this.tz.bind(this) 
                    ,ord = this.ord.bind(this);


                x.h = x.getHours;

                if (m.charAt(0) === "\\")
                {
                    return m.replace("\\", "");
                }

                switch (m)
                {
                    case "hh":
                        return p(x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12));
                    case "h":
                        return x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12);
                    case "HH":
                        return p(x.h());
                    case "H":
                        return x.h();
                    case "mm":
                        return p(x.getMinutes());
                    case "m":
                        return x.getMinutes();
                    case "ss":
                        return p(x.getSeconds());
                    case "s":
                        return x.getSeconds();
                    case "yyyy":
                        return p(x.getFullYear(), 4);
                    case "yy":
                        return p(x.getFullYear());
                    case "dddd":
                        return $i18n.dayNames[x.getDay()];
                    case "ddd":
                        return $i18n.abbreviatedDayNames[x.getDay()];
                    case "dd":
                        return p(x.getDate());
                    case "d":
                        return x.getDate();
                    case "MMMM":
                        return $i18n.monthNames[x.getMonth()];
                    case "MMM":
                        return $i18n.abbreviatedMonthNames[x.getMonth()];
                    case "MM":
                        return p((x.getMonth() + 1));
                    case "M":
                        return x.getMonth() + 1;
                    case "t":
                        return (x.h() < 12 ? $i18n.amDesignator.substring(0, 1) : $i18n.pmDesignator.substring(0, 1)).toLowerCase();
                    case "tt":
                        return (x.h() < 12 ? $i18n.amDesignator : $i18n.pmDesignator).toLowerCase();;
                    case "T":
                        return x.h() < 12 ? $i18n.amDesignator.substring(0, 1) : $i18n.pmDesignator.substring(0, 1);
                    case "TT":
                        return x.h() < 12 ? $i18n.amDesignator : $i18n.pmDesignator;
                    case "S":
                        return ord(x.getDate());
                    case "fffffff":
                        return mp(x, 7);
                    case "ffffff":
                        return mp(x, 6);
                    case "fffff":
                        return mp(x, 5);
                    case "ffff":
                        return mp(x, 4);
                    case "fff":
                        return mp(x, 3);
                    case "ff":
                        return mp(x, 2);
                    case "f":
                        return mp(x, 1);
                    case "o":
                        return tzo(x);
                    case "Z":
                        return tz(x);
                    default:
                        return m;
                } // End Switch
            } // End Fn
            .bind(this)
            //.apply(this, arguments)
        );
    }

};


var x = new Date();
myobj.format(x, "dddd (ddd)   S dd'd'.MM (MMM MMMM).yyyy HH:mm:ss.fff t tt T TT (o) {Z}")



myobj.formatString("hello {foo} name", { foo: "bar" });
// myobj.formatString("hello {foo} name");
Stefan Steiger
  • 68,404
  • 63
  • 337
  • 408
  • 1
    Are you looking for [How to access the correct `this` / context inside a callback?](http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback) `apply` doesn't work, because it calls the function. You need to use `bind` to create a new function with a bound `this` (but that does allow you to pass in `apply`-style argument array; arguments must be supplied argument-by-argument, `call`-style). – apsillers Jun 09 '15 at 13:14
  • Have you considered [moment.js](http://momentjs.com/)?. Lightweight library with 12.1 kb prod version and syntax very similar to C# date format. – Andriy Horen Jun 09 '15 at 14:25
  • @Andriy Horen: My solution gets the payload down to 3kb. – Stefan Steiger Jun 09 '15 at 14:34
  • I agree, however it is a small price to pay for a well-tested library. It is up to you anyway – Andriy Horen Jun 09 '15 at 14:43
  • @Andriy Horen: I took a look, and it doesn't look easy to maintain. That's a much bigger minus than the payload. Mine is short, easy, understandable, and no tools required, and no dependencies of any kind. – Stefan Steiger Jun 09 '15 at 19:47

1 Answers1

2

Move the method inside of the replace to its own method in your object and use bind when referencing the method so you keep scope.

...
dateRegExp : /d{1,4}|M{1,4}|f{1,7}|yy(?:yy)?|([HhmsTt])\1?|[oSZ]|"[^"]*"|'[^']*'/g,
format: function (x, formatString) {
        return formatString.replace(this.dateRegExp, this._formatFnc.bind(this, x));
},
_formatFnc : function (x, m, aaa) { 
    /* replace code here */ 
    return "qwerty"; 
}
...

If you do not want to do i that way, you can always put this into a varoable and reference it inside the replace method.

var that = this;
return formatString.replace(..., function() {
    console.log(that);
    return "qwerty";
};
epascarello
  • 185,306
  • 18
  • 175
  • 214