Search code examples
javasimpledateformatdate-format

Java - Swap Month & Day in Date Format


I have a String date format (e.g. dd/MM/yyyy) and want to convert it to a US-style with the month first (e.g. MM/dd/yyyy), programatically.

The use-case of this is to read some data and determine which format fits best.

This sounds trivially easy, but having actually tried implementing it, my solution seems sub-optimal.

Below is my attempt, including a test.

public class DateSwapperExample
{
    private static final char dateFormatDayLetter = 'd', dateFormatMonthLetter = 'M';

    /**
     * Swaps the Day & Month component in a Date Format, if both are present <br>
     * When swapping, ensures the frequency is retained - e.g. dd/MMM -> MMM/dd <br>
     * TODO Only handles one instance of each tag <br>
     * TODO This doesn't handle quoted elements in the Date Format (e.g. "dd/mm 'since dave made the best cakes' yyyy")
     */
    private static String swapDayAndMonthInDateFormat(final String dateFormat)
    {
        // Get the position of the groups
        final int[] dayIndex = new int[] {dateFormat.indexOf(dateFormatDayLetter), dateFormat.lastIndexOf(dateFormatDayLetter)};
        final int[] monthIndex = new int[] {dateFormat.indexOf(dateFormatMonthLetter), dateFormat.lastIndexOf(dateFormatMonthLetter)};

        if ((dayIndex[0] == -1) || (monthIndex[0] == -1))
        {
            // Cannot swap as dateFormat does not contain both dateFormatDayLetter & dateFormatMonthLetter
            return dateFormat;
        }
        else
        {
            final int[] firstGroup, secondGroup;

            // Work out which group comes first
            if (dayIndex[0] < monthIndex[0])
            {
                firstGroup = dayIndex;
                secondGroup = monthIndex;
            }
            else
            {

                firstGroup = monthIndex;
                secondGroup = dayIndex;
            }

            // Split the string up into segments, re-organise and combine

            // The other parts of the format at the start
            return substringConstrained(dateFormat, 0, firstGroup[0])
                    // The second group
                    + substringConstrained(dateFormat, secondGroup[0], secondGroup[1] + 1)
                    // The other parts of the format in the middle
                    + substringConstrained(dateFormat, firstGroup[1] + 1, secondGroup[0])
                    // The first group
                    + substringConstrained(dateFormat, firstGroup[0], firstGroup[1] + 1)
                    // The other parts of the format at the end
                    + substringConstrained(dateFormat, secondGroup[1] + 1, dateFormat.length());
        }
    }

    /** Extension of {@link String#substring(int, int)} that constrains the index parameters to be within the allowed range */
    private static String substringConstrained(final String str, final int beginIndex, final int endIndex)
    {
        return str.substring(constrainToRange(beginIndex, 0, str.length()), constrainToRange(endIndex, 0, str.length()));
    }

    /** Copy of {@link com.google.common.primitives.Ints#constrainToRange(int, int, int)} to avoid the need of Guava in this example */
    private static int constrainToRange(int value, int min, int max)
    {
        return Math.min(Math.max(value, min), max);
    }

    @org.junit.Test
    public void testSwapDayAndMonthInDateFormat()
    {
        org.junit.Assert.assertEquals("Md", swapDayAndMonthInDateFormat("dM"));
        org.junit.Assert.assertEquals("MMd", swapDayAndMonthInDateFormat("dMM"));
        org.junit.Assert.assertEquals("Mdy", swapDayAndMonthInDateFormat("dMy"));
        org.junit.Assert.assertEquals("Myd", swapDayAndMonthInDateFormat("dyM"));
        org.junit.Assert.assertEquals("yMd", swapDayAndMonthInDateFormat("ydM"));
        org.junit.Assert.assertEquals("aMbdc", swapDayAndMonthInDateFormat("adbMc"));
        org.junit.Assert.assertEquals("MM/dd/yyyy", swapDayAndMonthInDateFormat("dd/MM/yyyy"));
        org.junit.Assert.assertEquals("MMM/dd/yyyy", swapDayAndMonthInDateFormat("dd/MMM/yyyy"));

        for (final String str : new String[] {"ydy", "yMy", "yDy", "ymy", "Dm", "Dmm", "DD/mm/yyyy", "DD/mmm/yyyy"})
        {
            org.junit.Assert.assertEquals(str, swapDayAndMonthInDateFormat(str));
        }
    }
}

Solution

  • private static String swapDayAndMonthInDateFormat(final String dateFormat)
    {
        return dateFormat.replaceFirst("(d+)(.*?)(M+)", "$3$2$1");
    }
    

    I am far from convinced that this is the good solution to your real problem. But it makes you test pass.

    Also you should not want to use SimpleDateFormat. That class is notoriously troublesome and along with Date and friends long outdated. Instead use DateTimeFormatter and other classes from java.time, the modern Java date and time API. Format pattern strings still look similar, though, so it could be that this answer is still relevant.