Java Format Number with Blanks

Viewed 183

I have a number like this:

1234567890123

and want to format it for example like this

12 34567 89012 3

or maybe like this

1-234 567-890 1 23

How can I achive that? I tried this

DecimalFormat decimalFormat = new DecimalFormat("00 00000 00000 0");
System.out.println(decimalFormat.format(1234567890123L));

But this doesn't work.

I need a way to specify a pattern that can contain any separator character.

6 Answers
import java.text.ParseException;
import javax.swing.text.MaskFormatter;
import org.junit.jupiter.api.Test;

public class FormatTest {

    @Test
    void testNumberFormat() throws ParseException {

        final String format = "#-### ###-### # ##";
        final String number = "1234567890123";

        MaskFormatter maskFormatter = new MaskFormatter(format);
        maskFormatter.setValueContainsLiteralCharacters(false);

        System.out.println(maskFormatter.valueToString(number));
    }
}

That is not possible with DecimalFormatter and NumberFormatter. But you can use a trick with String and regular expression:

Long.toString(number)
    .replaceAll("(\\d{2})(\\d{5})(\\d{5})(\\d)", "$1 $2 $3 $4");

If your format is dynamic, you could do something like this:

@Test
void simon() {
    final var input = 1234567890123L;
    assertEquals("12 34567 89012 3", formatMyNumber(input, "{2} {5} {5} {1}"));
    assertEquals("12-34567-89012-3", formatMyNumber(input, "{2}-{5}-{5}-{1}"));
    assertEquals("12_34567_89012_3", formatMyNumber(input, "{2}_{5}_{5}_{1}"));
    assertEquals("1 23456 7", formatMyNumber(1234567, "{1} {5} {1}"));
    assertEquals("1 2345 6", formatMyNumber(123456, "{1} {4} {1}"));
    assertEquals("123.45.6", formatMyNumber(123456, "{3}.{2}.{1}"));
}

private String formatMyNumber(final long number, final String format) {
    return Long.toString(number).replaceAll(createRegEx(format), createReplacement(format));
}

private String createRegEx(final String format) {
    final var separator = getSeparator(format);
    return format.replaceAll("\\{", "(\\\\d{")
            .replaceAll("}" + Pattern.quote(separator), "}\\)")
            .replaceAll("}$", "}\\)"); // could be integrated in above regex
}

private String getSeparator(final String format) {
    final var begin = format.indexOf("}");
    final var end = format.indexOf("{", begin);
    return format.substring(begin + 1, end);
}

private String createReplacement(final String format) {
    final var separator = getSeparator(format);
    var replacement = format.replaceAll("^\\{\\d", "\\$X")
            .replaceAll("}" + Pattern.quote(separator) + "\\{\\d", separator + "\\$X")
            .replaceAll("}$", "");
    var counter = 1;
    while (replacement.contains("X")) {
        replacement = replacement.replaceFirst("X", Integer.toString(counter++));
    }
    return replacement;
}

It's not my best piece of work but works with dynamic format strings.

Here's something to get you started. Feel free to modify the code to meet your needs.

public class ArbitraryFormat {

    public static void main(String[] args) {
        ArbitraryFormat arbitraryFormat = new ArbitraryFormat("00 00000 00000 0");
        System.out.println(arbitraryFormat.format(1234567890123L));
        System.out.println(arbitraryFormat.format(123));
    }
    
    private int zeroCount;
    
    private String formatter;
    
    public ArbitraryFormat(String formatter) {
        this.formatter = formatter;
        this.zeroCount = countZeros(formatter);
    }
    
    public String format(long value) {
        String tempFormatter = "%" + zeroCount + "s";
        String temp = String.format(tempFormatter, value);
        StringBuilder builder = new StringBuilder();
        
        int tempIndex = 0;
        for (int index = 0; index < formatter.length(); index++) {
            if (formatter.charAt(index) == '0') {
                builder.append(temp.charAt(tempIndex++));
            } else {
                builder.append(formatter.charAt(index));
            }
        }
        
        return builder.toString();
    }
    
    private int countZeros(String formatter) {
        int count = 0;
        int index = 0;
        
        while (index < formatter.length()) {
            int pos = formatter.indexOf('0', index);
            if (pos >= 0) {
                count++;
                index = pos + 1;
            } else {
                index = formatter.length();
            }
        }
        
        return count;
    }

}

You can define your own formatter like this.

public class MyFormat extends NumberFormat {

    final String format;

    public MyFormat(String format) {
        this.format = format;
    }

    @Override
    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
        throw new UnsupportedOperationException();
    }

    @Override
    public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
        toAppendTo.append(number);
        for (int i = toAppendTo.length() - 1, j = format.length() - 1; i >= 0 && j >= 0; --i, --j) {
            char fch = format.charAt(j);
            if (fch != '*') {
                toAppendTo.insert(i + 1, fch);
                --j;
            }
        }
        return toAppendTo;
    }

    @Override
    public java.lang.Number parse(String source, ParsePosition parsePosition) {
        throw new UnsupportedOperationException();
    }
}

And

public static void main(String[] args) throws ParseException {
    long number = 1234567890123L;
    MyFormat mf1 = new MyFormat("* ***** ***** *");
    MyFormat mf2 = new MyFormat("*-*** ***-*** * **");
    System.out.println(mf1.format(number));
    System.out.println(mf2.format(number));
}

output:

12 34567 89012 3
1-234 567-890 1 23

Thanks to @McPringle I finally implemented this solution:

public class MaskFormatter {

    private char[] separators;

    public String formatNumber(Number number, String pattern) {
        if (number == null) {
            return "";
        } else {
            separators = pattern.replace("0", "").toCharArray();

            String string = number.toString();
            String regex = createRegex(pattern);
            String replacement = createReplacement(pattern);

            return string.replaceAll(regex, replacement);
        }
    }

    private String createRegex(String pattern) {
        String[] parts = pattern.split("[" + createPatternFromSeparators() + "]");

        StringBuilder sb = new StringBuilder();
        for (String part : parts) {
            sb.append("(\\d{").append(part.length()).append("})");
        }
        return sb.toString();
    }

    private String createReplacement(String pattern) {
        String[] parts = pattern.split("[" + createPatternFromSeparators() + "]");

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            if (i > 0) {
                char separator = separators[i - 1];
                sb.append(separator);
            }
            sb.append("$").append(i + 1);
        }
        return sb.toString();
    }

    private String createPatternFromSeparators() {
        Set<String> set = new HashSet<>();
        for (char separator : separators) {
            set.add(String.valueOf(separator));
        }
        return String.join("", set);
    }
}

You can also use String.replaceAll(). Like the following example.

long value = 1234567890123L;
String str = String.valueOf(value);
        
//12 34567 89012 3
String str1 = str.replaceAll("^(.{2})(.{5})(.{5})(.{1})$", "$1 $2 $3 $4");
System.out.println(str1); //Prints: 12 34567 89012 3

//1-234 567-890 1 23
String str2 = str.replaceAll("^(.{1})(.{3})(.{3})(.{3})(.{1})(.{2})$", "$1-$2 $3-$4 $5 $6");
System.out.println(str2); //Prints: 1-234 567-890 1 23      
Related