Search code examples
javadatejava-8simpledateformatdatetimeformatter

How to get Date object from MONTH("MMMM") or YEAR("yyyy") using DateTimeFormatter


I am new to using DateTimeFormatter package, and same thing we are able to get using SimpleDateFormat.

Date date = new SimpleDateFormat("MMMM").parse(month);//"DECEMBER"
Date date = new SimpleDateFormat("yyyy").parse(year);//"2020"

How to achive this using DateTimeFormatter?


Solution

  • The date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. Let's see them in action:

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class Main {
        public static void main(String[] args) throws ParseException {
            Date date1 = new SimpleDateFormat("MMMM").parse("DECEMBER");
            System.out.println(date1);
            Date date2 = new SimpleDateFormat("yyyy").parse("2020");
            System.out.println(date2);
        }
    }
    

    Output:

    Tue Dec 01 00:00:00 GMT 1970
    Wed Jan 01 00:00:00 GMT 2020
    

    I do not need to explain what defaults they are taking. Instead of taking such unexpected defaults, they should have raised an alarm (throw some exception) which would have become helpful to a programmer to react to.

    Because of such surprises, it is recommended to stop using them completely and switch to the modern date-time API.

    Note: For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7.

    If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

    Using the modern date-time API:

    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeParseException;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println(parse("DECEMBER", "MMMM"));
            System.out.println(parse("2020", "uuuu"));
        }
    
        static LocalDate parse(String text, String pattern) {
            try {
                return LocalDate.parse(text, DateTimeFormatter.ofPattern(pattern));
            } catch (DateTimeParseException e) {
                System.out.println(e.getMessage());
                // Return some default value
                return LocalDate.MIN;
            }
        }
    }
    

    Output:

    Text 'DECEMBER' could not be parsed at index 0
    -999999999-01-01
    Text '2020' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {Year=2020},ISO of type java.time.format.Parsed
    -999999999-01-01
    

    So, now you (the programmer) get to know that you have to do something (e.g. use your own defaults) to parse the strings.

    Demo:

    import java.time.LocalDate;
    import java.time.Month;
    import java.time.Year;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeFormatterBuilder;
    import java.time.format.DateTimeParseException;
    import java.time.temporal.ChronoField;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) {
            LocalDate date = parse("DECEMBER", "MMMM");
            Month month = parse("DECEMBER", "MMMM").getMonth();
            System.out.println(date);
            System.out.println(month);
    
            date = parse("2020", "uuuu");
            System.out.println(date);
            int year = date.getYear();
            System.out.println(year);
            Year objYear = Year.of(year);
            System.out.println(objYear);
        }
    
        static LocalDate parse(String text, String pattern) {
            LocalDate today = LocalDate.now();
    
            // Formatter using today's day, month and year as defaults
            DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                        .parseCaseInsensitive()
                        .appendPattern(pattern)
                        .parseDefaulting(ChronoField.DAY_OF_MONTH, today.getDayOfMonth())
                        .parseDefaulting(ChronoField.MONTH_OF_YEAR, today.getMonthValue())
                        .parseDefaulting(ChronoField.YEAR, today.getYear())
                        .toFormatter(Locale.ENGLISH);
    
            try {
                return LocalDate.parse(text, formatter);
            } catch (DateTimeParseException e) {
                System.out.println(e.getMessage());
                // Return some default value
                return LocalDate.MIN;
            }
        }
    }
    

    Output:

    2020-12-15
    DECEMBER
    2020-12-15
    2020
    2020
    

    If you do not want to create a date or date-time object, rather, if all you want to do is to parse your string into Month and Year, you can do it simply the following way:

    import java.time.Month;
    import java.time.Year;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeFormatterBuilder;
    import java.time.format.TextStyle;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) {
            Month month = new DateTimeFormatterBuilder()
                            .parseCaseInsensitive()
                            .appendPattern("MMMM")
                            .toFormatter(Locale.ENGLISH)
                            .parse("DECEMBER", Month::from);
    
            System.out.println(month + " | " + month.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " | "
                    + month.getDisplayName(TextStyle.FULL, Locale.ENGLISH));
            
            Year year = DateTimeFormatter.ofPattern("uuuu").parse("2020", Year::from);
            System.out.println(year);
        }
    }
    

    Output:

    DECEMBER | Dec | December
    2020
    

    Learn more about the modern date-time API at Trail: Date Time.