2

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.

Simon Martinelli
  • 34,053
  • 5
  • 48
  • 82

6 Answers6

2
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));
    }
}
  • The problem with that solution is that it's in the module java.desktop that may not be present on a server Java version created with JLINK – Simon Martinelli Oct 22 '21 at 12:34
  • You could access the `MaskFormatter` dynamically and fallback to no format when not available. Maybe with some log output for an explanation. – McPringle Oct 22 '21 at 13:25
  • @SimonMartinelli You should have stated that in your question when you originally asked it. Kinda unfair to change the requirements after receiving answers which basically invalidate the answer. – Zabuzard Oct 22 '21 at 17:09
1

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.

McPringle
  • 1,939
  • 2
  • 16
  • 19
0

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

}
Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111
0

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
0

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);
    }
}
Simon Martinelli
  • 34,053
  • 5
  • 48
  • 82
0

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      
Darkman
  • 2,941
  • 2
  • 9
  • 14