0

I have a string "Mon Jan 01 00:00:00 AEDT 1990" and I need to convert it into the format "yyyyMMdd" so in this case it would be "19900101".

I think it's possible to do this with Regular Expressions so that I could pull out the year, month(but would need to convert Jan to 01 and etc) and day from the string but I am not well versed in Regular Expressions. Anyone have any ideas?

John-Michael
  • 99
  • 1
  • 7
  • Try using `SimpleDateFormat` – Albatross May 30 '19 at 01:51
  • That will not work as he is using a custom date format. – Matthew May 30 '19 at 01:58
  • @Matthew I have given a code sample which outputs `DAY : 19900101` – Albatross May 30 '19 at 02:16
  • I see, didn't know it works with custom formats, good work! – Matthew May 30 '19 at 02:36
  • @Matthew I beg to differ, sorry, getting `SimpleDateFormat` to work is a challenge sometimes, but advising it as a solution is poor advice. That class is notoriously troublesome and fortunately long outdated. We should never use it anymore. – Ole V.V. May 30 '19 at 08:26
  • 1
    @Ole V.V I was not recommending its usage or advising it as a solution, I was just explaining that I didn't know that was possible because I don't have enough knowledge about it and then proceeded to say *"good work"* to signal that his solution was better then mine, that's it. – Matthew May 30 '19 at 08:29
  • John-Michael, your string, `Mon Jan 01 00:00:00 AEDT 1990`, very much looks like a string returned from `Date.toString`. So I have added an original question and also provided [a new answer for your here](https://stackoverflow.com/a/56374732/5772882) (the new answer doesn’t cover formatting into `19900101`, but that has been covered in the answer by Basil Bourque and is also covered in the first original question). – Ole V.V. May 30 '19 at 08:57

3 Answers3

6

tl;dr

Regex is overkill.

Here is a one-liner solution using java.time classes built into Java.

ZonedDateTime                            // Represent a moment as seen through the wall-clock time used by the people of a certain region (a time zone).
.parse(                                  // Parse the input text.
    "Mon Jan 01 00:00:00 AEDT 1990" ,     
    DateTimeFormatter.ofPattern( 
        "EEE MMM dd HH:mm:ss z uuuu" ,   // Specify a custom formatting pattern to match our input.
        Locale.US                        // Specify a `Locale` for the human language to use in translating the name of month& day-of-week.
    )                                    // Returns a `DateTimeFormatter` object.
)                                        // Returns a `ZonedDateTime` object.
.toLocalDate()                           // Extract the date, without time-of-day and without time zone. 
.format(                                 // Generate text to represent the value of our `LocalDate` object.
    DateTimeFormatter.BASIC_ISO_DATE     // Use the predefined formatting pattern YYYYMMDD.
)                                        // Returns a String.

19900101

java.time

Regex is overkill for this.

The modern approach uses java.time classes.

Specify a custom formatting pattern to fit your input.

Specify a locale to facilitate translating the name of day-of-week and name of month.

ZonedDateTime

Parse as a ZonedDateTime, a moment as seen through the wall-clock time used by the people of a specific region (a time zone).

String input = "Mon Jan 01 00:00:00 AEDT 1990";
Locale locale = Locale.US;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "EEE MMM dd HH:mm:ss z uuuu" , locale );
ZonedDateTime zdt = ZonedDateTime.parse( input , f );

System.out.println( "zdt: " + zdt );

zdt: 1990-01-01T00:00+11:00[Australia/Sydney]

By the way, your input string is in a terrible format. It uses the 2-4 character pseudo-zones that are not actual time zones, not standardized, and are not unique! Another problem is depending on English. And it is difficult to parse. Educate the people publishing your data about the beauty of the ISO 8601 standard, created for exchanging date-time values as text.

LocalDate

You want only the date. So extract a LocalDate.

LocalDate ld = zdt.toLocalDate() ;  // Extract only the date, leaving behind the time-of-day and the time zone.

Your desired output format has already been defined in the DateTimeFormatter class. The standard ISO 8601 format for a date is YYYY-MM-DD. A variation of that is known as "Basic" meaning it minimizes the use of delimiters: YYYYMMDD.

String output = ld.format( DateTimeFormatter.BASIC_ISO_DATE ) ;

19900101

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Great answer... it's putting mine too shame, but at least I got some practice out of it. Gonna bookmark it as it will come in handy in the future, thank you. – Matthew May 30 '19 at 02:34
  • @Matthew It good to have a working regex example here as well. – Basil Bourque May 30 '19 at 02:36
  • Does your solution work for any custom date format? If not can you give me an example of a scenario where a regex would be a better solution? I would like to make an implementation for scenarios where using `java.time` is not feasible to make my answer not completely useless :D – Matthew May 30 '19 at 02:42
  • @BasilBourque Thanks for this informative answer. Yes, the input string is in a terrible format but that is how the ERP system we are using outputs it as. – John-Michael May 30 '19 at 02:42
  • @Matthew For multiple formats, you can go either of two ways. You can define a collection of `DateTimeFormatter` objects, each with a custom formatting pattern for one of your expected formats. Then for each input you loop the collection, trying each formatter. If an exception is thrown, you try the next. Alternatively, you can build a more flexible formatter by using the helper class, `DateTimeFormatterBuilder` which can set some optional parts and some default parts. Search Stack Overflow to learn more as this has been addressed many times already. Look esp. for builder examples by Ole V.V. – Basil Bourque May 30 '19 at 03:15
  • @John-Michael I suggest you file an issue ticket with the vendor of your ERP system. Once educated about ISO 8601, it might be trivial (maybe 1 line of code) for them to provide an ISO 8601 compliant string as an alternative to their current output. The *java.time* classes use the ISO 8601 standard formats by default when parsing/generating text, so you simply call `parse` or `toString` without any `DateTimeFormatter`. See comment by Ole V.V. on your Question for link for more info. – Basil Bourque May 30 '19 at 12:00
2

Check if something like this helps

//date string
String soTime = "Mon Jan 04 12:30:23 AEDT 1990";

//Format
SimpleDateFormat so = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");

SimpleDateFormat desiredFormat = new SimpleDateFormat("yyyyMMdd");
desiredFormat.setTimeZone(TimeZone.getTimeZone("Australia/Sydney"));

Date sodate = so.parse(soTime);
System.out.println("DAY : " + desiredFormat.format(sodate));

Parse date with AEDT and AEST time zone in java

Albatross
  • 669
  • 7
  • 24
  • 1
    These terrible classes were supplanted years ago by the modern *java.time* classes. Sun, Oracle, and the JCP community all gave up on these classes with the adoption of [JSR 310](https://jcp.org/en/jsr/detail?id=310). So should you. – Basil Bourque May 30 '19 at 02:25
  • Please don’t teach the young ones to use the long outdated and notoriously troublesome `SimpleDateFormat` class. At least not as the first option. And not without any reservation. Today we have so much better in [`java.time`, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) and its `DateTimeFormatter`. – Ole V.V. May 30 '19 at 08:08
  • 1
    Point noted Thanks. – Albatross May 30 '19 at 18:15
1

Now assuming that each month and day names inside each passed string is matching one of enum name values (i.e "Mar" matches the value of field name in Month.MARCH, while "Marc" or "March" do not) and the format of the sample string you gave us is truly consistent, as in it is no subject to change during runtime and will always remain <day-name> <month> <day> <time> <zone> <year> where year is always a 4 digit number, the following code should answer give you exactly what you want:

Main Class

public static void main(String[] args) {

    String time = "Mon Jul 05 00:00:00 AEDT 1990";
    int result = CustomDateFormat.parseToInt(time);
    System.out.println("Parsed in format [yyyyMMdd]: " + result);
}

CustomDateFormat Class

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CustomDateFormat {

    private static final Pattern STANDARD_PATTERN =
    Pattern.compile("^(?:[a-zA-Z]{3})\\s([a-zA-Z]{3})\\s([0-9]{2}).*([0-9]{4})");

    /*
     * This is just in case you want
     * the name of the day as well
     */
    public enum Day {

        MONDAY("Mon", "Monday"),
        TUESDAY("Tue", "Tuesday"),
        WEDNESDAY("Wed", "Wednesday"),
        THURSDAY("Thu", "Thursday"),
        FRIDAY("Fri", "Friday"),
        SATURDAY("Sat", "Saturday"),
        SUNDAY("Sun", "Sunday");

        final String shortName;
        final String fullName;

        Day(String name1, String name2) {
            this.shortName = name1;
            this.fullName = name2;
        }

        public static String getFullName(String alias) {
            for (Day d : Day.values()) {
                if (d.shortName.equals(alias))
                    return d.fullName;
            }
            return "";
        }
    }

    public enum Month {

        JANUARY("Jan", 1), FEBRUARY("Feb", 2),
        MARCH("Mar", 3), APRIL("Apr", 4),
        MAY("May", 5), JUNE("Jun", 6),
        JULY("Jul", 7), AUGUST("Aug", 8),
        SEPTEMBER("Sep", 9), OCTOBER("Oct", 10),
        NOVEMBER("Nov", 11), DECEMBER("Dec", 12);

        final String name;
        final int value;

        Month(String name, int value) {
            this.name = name;
            this.value = value;
        }

        public static int getMonth(String month) {
            for (Month m : Month.values()) {
                if (m.name.equals(month))
                    return m.value;
            }
            return 0;
        }
    }

    public static int parseToInt(String date) {

        System.out.println("Parsing date: " + date);
        Matcher matcher = STANDARD_PATTERN.matcher(date);

        if (matcher.find() && matcher.groupCount() == 3)
        {
            int month = Month.getMonth(matcher.group(1));
            int day = Integer.valueOf(matcher.group(2));
            int year = Integer.valueOf(matcher.group(3));

            if (day == 0 || month == 0) {
                throw new IllegalStateException("Unable to parse day or month from date " + date);
            }
            else return Integer.valueOf(year + "0" + month + "0" + day);
        }
        else throw new IllegalStateException("Unable to parse date " + date);
    }
}

Output

Parsing date: Mon Jul 05 00:00:00 AEDT 1990
Parsed in format [yyyyMMdd]: 19900705

Let me know if this meets your requirements and if any other conditions need to be met or special case scenarios considered. It's a fairly simple implementation so it should take no time to adjust it to more specific needs.

EDIT: Fix some implementation mistakes, change sample string to a custom one and remove redundant output line.

Matthew
  • 1,905
  • 3
  • 19
  • 26