Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can java datetimeformatter handle custom text in date format

I store in a database:

  • a week as yyyyWww, that is 2023W10 is 10th week of 2023
  • a month as 2023M10 meaning October 2023

I can use yyyy'W'ww and yyyy'M'MM formats in SimpleDateFormat.

DateTimeFormatter gives DateTimeParseException in these cases. Seems DateTimeFormatter will only accept a complete date (year+month+day) with limited text (dash, slash, period)

What am I missing here?

I have tried yyyy'W'ww and yyyy'M'MM formats in dateTimeFormatter, both give parseexceptions.

like image 286
user3329518 Avatar asked Sep 20 '25 04:09

user3329518


2 Answers

tl;dr

In your specific cases, you could easily use string manipulation rather than DateTimeFormatter.

org.threeten.extra.YearWeek.parse( "2023W31".replace ( "W" , "-W" ) )

… and:

java.time.YearMonth.parse( "2023M07".replace( "M" , "-" ) )

Even better: Change your data to use the standard ISO 8601 format.

  • 2023W31 ➡️ 2023-W31
  • 2023M07 ➡️ 2023-07

Details

What am I missing here?

You are missing the appropriate class.

YearWeek

For your first case, a week of the year, use the org.threeten.extra.YearWeek class from the library ThreeTen-Extra. This library adds functionality to the java.time classes built into Java 8+, defined in JSR 310.

The YearWeek class uses standard ISO 8601 formats when parsing/generating text. So you need not specify a formatting pattern.

Your input text complies with the abbreviated style of the ISO 8601 standard, but not the preferred expanded style. To comply with the expanded style, insert a hyphen, input.replace( "W" , "-W" ).

String input = "2023W31".replace ( "W" , "-W" );
YearWeek yearWeek = YearWeek.parse ( input );
System.out.println ( "yearWeek = " + yearWeek );

yearWeek = 2023-W31

Define “week”

Use this YearWeek class only if your definition of “week” agrees with the ISO 8601 standard definition:

  • Either 52 or 53 complete 7-day weeks per year.
  • Week starts on Monday.
  • Week # 1 contains the first Thursday of the calendar year.

YearMonth

For your second case, use the java.time.YearMonth class built into Java 8+.

The standard ISO 8601 format for a year and month is YYYY-MM. I recommend you use this standard format in your stored values in the database.

YearMonth yearMonth = YearMonth.parse( "2023-07" ) ;

If you insist on using your custom format, you might be able to define a DateTimeFormatter to specify your particular formatting pattern. But I would just replace the M with a hyphen.

YearMonth yearMonth = YearMonth.parse( "2023M07".replace( "M" , "-" ) ) ;

yearMonth = 2023-07

like image 80
Basil Bourque Avatar answered Sep 22 '25 19:09

Basil Bourque


You will need to use a DateTimeFormatterBuilder to apply defaults along with your pattern.

A simple approach would be to declare all known formats ahead of time and loop through them. If you want to eliminate the looping, you could check if the string contains a "W" and call the week parser, or an "M" and call the month parser.

Please note that YYYY (upper-case) denotes a week-based year, vs yyyy (lower-case) an era (normal 365 day year).

import java.time.*;
import java.time.format.*;
import java.time.temporal.*;

class DateParsing {
    private static final DateTimeFormatter
        PARSE_WEEK = new DateTimeFormatterBuilder()
            .appendPattern("YYYY'W'ww")
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter(),
        PARSE_MONTH = new DateTimeFormatterBuilder()
            .appendPattern("yyyy'M'MM")
            .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
            .toFormatter();

    private static final DateTimeFormatter[] PATTERNS = { PARSE_WEEK, PARSE_MONTH };
    
    public static LocalDate parseDate(String input) throws IllegalArgumentException {
        for (DateTimeFormatter pattern : PATTERNS) {
            try {
                return LocalDate.parse(input, pattern);
            } catch (DateTimeParseException e) {
                // System.err.println("Error: " + e.getMessage());
            }
        }
        throw new IllegalArgumentException(String.format("Unknown format: %s", input));
    }
    
    public static void main(String[] args) throws IllegalArgumentException {
        String[] timestamps = {
            "2023W10", // 10th week of 2023
            "2023M10"  // October 1, 2023
        };
        for (String timestamp : timestamps) {
            System.out.printf(">>> %s => %s%n", timestamp, parseDate(timestamp));
        }
    }
}

Output:

>>> 2023W10 => 2023-03-05
>>> 2023M10 => 2023-10-01

Update

If you want to remove the loop and exception handling, you could check to see if the input string contains an "M" or "W" as explained above.

import java.time.*;
import java.time.format.*;
import java.time.temporal.*;

class DateParsing {
    private static final DateTimeFormatter
        PARSE_WEEK = new DateTimeFormatterBuilder()
            .appendPattern("YYYY'W'ww")
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter(),
        PARSE_MONTH = new DateTimeFormatterBuilder()
            .appendPattern("yyyy'M'MM")
            .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
            .toFormatter();

    public static LocalDate parseDate(String input) throws DateTimeParseException, IllegalArgumentException {
        if (input.contains("M")) {
            return LocalDate.parse(input, PARSE_MONTH);
        }
        if (input.contains("W")) {
            return LocalDate.parse(input, PARSE_WEEK);
        }
        throw new IllegalArgumentException(String.format("Unknown format: %s", input));
    }
    
    public static void main(String[] args) throws DateTimeParseException, IllegalArgumentException {
        String[] timestamps = {
            "2023W10", // 10th week of 2023
            "2023M10"  // October 1, 2023
        };
        for (String timestamp : timestamps) {
            System.out.printf(">>> %s => %s%n", timestamp, parseDate(timestamp));
        }
    }
}
like image 29
Mr. Polywhirl Avatar answered Sep 22 '25 19:09

Mr. Polywhirl