Fork me on GitHub

Learn how to do it in JavaScript. Explore boundaries porting languages. Enjoy functions that turn out to be useful.

JavaScript sprintf function

A JavaScript equivalent of PHP’s sprintf

strings/sprintf.js raw on github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
function sprintf () {
  // http://kevin.vanzonneveld.net
  // +   original by: Ash Searle (http://hexmen.com/blog/)
  // + namespaced by: Michael White (http://getsprink.com)
  // +    tweaked by: Jack
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +      input by: Paulo Freitas
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +      input by: Brett Zamir (http://brett-zamir.me)
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: Dj
  // +   improved by: Allidylls
  // *     example 1: sprintf("%01.2f", 123.1);
  // *     returns 1: 123.10
  // *     example 2: sprintf("[%10s]", 'monkey');
  // *     returns 2: '[    monkey]'
  // *     example 3: sprintf("[%'#10s]", 'monkey');
  // *     returns 3: '[####monkey]'
  // *     example 4: sprintf("%d", 123456789012345);
  // *     returns 4: '123456789012345'
  var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g;
  var a = arguments,
    i = 0,
    format = a[i++];

  // pad()
  var pad = function (str, len, chr, leftJustify) {
    if (!chr) {
      chr = ' ';
    }
    var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
    return leftJustify ? str + padding : padding + str;
  };

  // justify()
  var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
    var diff = minWidth - value.length;
    if (diff > 0) {
      if (leftJustify || !zeroPad) {
        value = pad(value, minWidth, customPadChar, leftJustify);
      } else {
        value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
      }
    }
    return value;
  };

  // formatBaseX()
  var formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
    // Note: casts negative numbers to positive ones
    var number = value >>> 0;
    prefix = prefix && number && {
      '2': '0b',
      '8': '0',
      '16': '0x'
    }[base] || '';
    value = prefix + pad(number.toString(base), precision || 0, '0', false);
    return justify(value, prefix, leftJustify, minWidth, zeroPad);
  };

  // formatString()
  var formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
    if (precision != null) {
      value = value.slice(0, precision);
    }
    return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
  };

  // doFormat()
  var doFormat = function (substring, valueIndex, flags, minWidth, _, precision, type) {
    var number;
    var prefix;
    var method;
    var textTransform;
    var value;

    if (substring === '%%') {
      return '%';
    }

    // parse flags
    var leftJustify = false,
      positivePrefix = '',
      zeroPad = false,
      prefixBaseX = false,
      customPadChar = ' ';
    var flagsl = flags.length;
    for (var j = 0; flags && j < flagsl; j++) {
      switch (flags.charAt(j)) {
      case ' ':
        positivePrefix = ' ';
        break;
      case '+':
        positivePrefix = '+';
        break;
      case '-':
        leftJustify = true;
        break;
      case "'":
        customPadChar = flags.charAt(j + 1);
        break;
      case '0':
        zeroPad = true;
        break;
      case '#':
        prefixBaseX = true;
        break;
      }
    }

    // parameters may be null, undefined, empty-string or real valued
    // we want to ignore null, undefined and empty-string values
    if (!minWidth) {
      minWidth = 0;
    } else if (minWidth === '*') {
      minWidth = +a[i++];
    } else if (minWidth.charAt(0) == '*') {
      minWidth = +a[minWidth.slice(1, -1)];
    } else {
      minWidth = +minWidth;
    }

    // Note: undocumented perl feature:
    if (minWidth < 0) {
      minWidth = -minWidth;
      leftJustify = true;
    }

    if (!isFinite(minWidth)) {
      throw new Error('sprintf: (minimum-)width must be finite');
    }

    if (!precision) {
      precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type === 'd') ? 0 : undefined;
    } else if (precision === '*') {
      precision = +a[i++];
    } else if (precision.charAt(0) == '*') {
      precision = +a[precision.slice(1, -1)];
    } else {
      precision = +precision;
    }

    // grab value using valueIndex if required?
    value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

    switch (type) {
    case 's':
      return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar);
    case 'c':
      return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
    case 'b':
      return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'o':
      return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'x':
      return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'X':
      return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
    case 'u':
      return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'i':
    case 'd':
      number = +value || 0;
      number = Math.round(number - number % 1); // Plain Math.round doesn't just truncate
      prefix = number < 0 ? '-' : positivePrefix;
      value = prefix + pad(String(Math.abs(number)), precision, '0', false);
      return justify(value, prefix, leftJustify, minWidth, zeroPad);
    case 'e':
    case 'E':
    case 'f': // Should handle locales (as per setlocale)
    case 'F':
    case 'g':
    case 'G':
      number = +value;
      prefix = number < 0 ? '-' : positivePrefix;
      method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
      textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
      value = prefix + Math.abs(number)[method](precision);
      return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
    default:
      return substring;
    }
  };

  return format.replace(regex, doFormat);
}

Example 1

This code

example
1
sprintf("%01.2f", 123.1);

Should return

returns
1
123.10

Example 2

This code

example
1
sprintf("[%10s]", 'monkey');

Should return

returns
1
'[    monkey]'

Example 3

This code

example
1
sprintf("[%'#10s]", 'monkey');

Should return

returns
1
'[####monkey]'

Other PHP functions in the strings extension

Legacy comments

These were imported from our old site. Please use disqus below for new comments

Brett Zamir on 2012-09-19 03:23:45
@Brent: Sorry for the very late reply, but is the functionality you seek already a part of PHP? You are free to share extension ideas here, but we really try to stick with PHP behavior to maintain limits on our project scope. @kernel: Thank you for the reference, but is there some functionality present in the other implementation which is not in ours?
Brett Zamir on 2012-09-18 16:28:11
@Dj: Fixed in Git, thanks!
Dj on 2012-08-08 23:28:13
Note that the regexp does not include the char "F" (uppercase), so expressions like "%01.2F" will not works. The function is already done to process it, but it just was missed in the regexp so it is not captured.
Brent on 2011-09-29 02:49:33
As far as adding a thousands separator, I don’t think it is currently in the sprintf function code above. However, this regex does it:

s/\d{1,3}(?=(\d{3})+(?!\d))/$&,/g
This javascript code (based on the regex) does it:


    function thousands (a){
    
     return a.replace (/\d{1,3}(?=(\d{3})+(?!\d))/g,"$&,");
     
    }
The only flaw is that it will happily keep adding commas every three characters to the right of the decimal point as well as to the left. So you have to apply it to only the integer side of the number. It works fine as-is for integers and, for example, currency (which only has max 2 decimal places). If you want to use another separator character than "," make the change in the second argument to a.replace. I can’t quite see how to easily add this to the sprintf function (and also the usual flag to add the thousands separator is ‘, which is used for something else in the code above) but there is a start for anyone who like to do it. Regex source is from: http://remysharp.com/2007/10/19/thousand-separator-regex/
Rafa? Kukawski on 2011-01-14 07:46:33
@Dj: Thanks for your feedback. I will add the fix today. BTW. The same can be done without the need to call parseInt and isNaN.
number = (+value) | 0;
+value casts the data to Number, | 0 drops floating point part and if the +value gives NaN, changes it to 0.
Dj on 2011-01-14 00:07:29
A suggest: check the result of parseInt() function, and set it to 0 if is NaN

case 'd':
                number = parseInt(+value, 10);
if (isNaN(number) {
    number = 0;
}
so when a %d is replaced with a non integer value, the result is 0 instead of NaN like PHP do. Example without check NaN: result = sprintf(‘Number is: %d’, ‘non numeric’); //result is: ‘Number is NaN’ Example checking NaN: result = sprintf(‘Number is: %d’, ‘non numeric’); //result is: ‘Number is 0’ try it in php and compare
kernel on 2010-10-09 18:35:14
please consult this project, that will be helpful http://www.diveintojavascript.com/projects/javascript-sprintf
Brett Zamir on 2009-08-30 04:59:41
@Sandro Franchi: Good catch… But shouldn’t it be:
if (precision !== undefined) {
? If it is as you had it, 0 will also be ignored.
Sandro Franchi on 2009-08-29 17:48:32
In Line 054

if (precision != null) {...
should be changed to

if (precision) {...
because precision (when not used) is "undefined", not null, also this helps to make the script validated by jslint.
Kevin van Zonneveld on 2009-01-08 17:03:36
@ ejsanders: Very well, thank you!
ejsanders on 2009-01-08 12:29:19
Here is the related function vsprintf, which takes the arguments as an array (can be useful):

function vsprintf(format, args) {
    return sprintf.apply(this, [format].concat(args));
}

Kevin van Zonneveld on 2008-12-01 09:13:31
@ Paulo Ricardo F. Santos: Fixed it, thanks for pointing that out.
Paulo Ricardo F. Santos on 2008-11-28 11:53:19
Hey, the current implementation misses the custom padding character:
printf(&quot;[%'#10s]\n&quot;,  $s); // use the custom padding character '#'
;)
Kevin van Zonneveld on 2008-11-09 14:28:59
@ David Portabella: I did some testing and as far as I can tell PHP itself does not support that notation. And a general rule of thumb for us is: if php doesn’t do it, we don’t either. This ensures maximum compatibilty. If I’m in error on this, let me know. Otherwise, the function: number_format might be what you’re looking for.
Mike on 2008-11-09 12:57:29
If you want to serialize strings with multibyte chars (special chars) you must replace this line: val = "s:" + mixed_value.length + ":\"" + mixed_value + "\""; with these: var stringLen = encodeURIComponent(mixed_value).replace(/%../g, ‘x’).length val = "s:" + stringLen + ":\"" + mixed_value + "\"";
David Portabella on 2008-11-04 20:03:40
Hello, Congratulations for this great library!! one question, Is it possible to specify a thousand separator for sprintf? something like sprintf(‘%,.2f&quot;, 1234567.89) =&gt; &quot;1,234,567.89&quot; Regards, DAvid

Comments