0

I have a text file full of data that I am putting into a Red Black tree, using the date as the key and the rest of the data from that line as the value. The problem is that some of the dates have this format:

"yyyy-MM-dd"

while some of the other dates have this format:

"M/dd/yyyy"

I need a way to compare these two dates, since I am using them as the key in my Red Black tree, so I need a common format. I'm not sure which would be easier for comparison, but I just know that they need to be in the same format. I've read a bit about SimpleDateFormat, but since I'm thinking of reading through the file, having some sort of if statement to determine if the date is in the wrong format, and then formatting it to the one I want, I'm just not sure how to do that with SimpleDateFormat since I've never used it before. Any help would be appreciated!

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
potroast12
  • 55
  • 5
  • Essentially what you're trying to do as determine what format the value is in. Since the two formats use different separators, you can easily check which format might be in use. If you have different formats using the same separator, it becomes more difficult, for [example](https://stackoverflow.com/questions/20231539/java-check-the-date-format-of-current-string-is-according-to-required-format-or/20232680#20232680). Personally, I prefer to convert the `String` value to a `LocalDate` value and deal with it from there – MadProgrammer Nov 09 '21 at 21:17
  • Can’t you just store all the dates as `LocalDate` objects rather than strings? I wish you could, at least. – Ole V.V. Nov 09 '21 at 21:21
  • When you mention `SimpleDateFormat`, I recommend you don’t use that class. It is notoriously troublesome and long outdated. Instead use `DateTimeFormatter` and also `LocalDate`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Nov 09 '21 at 21:23

4 Answers4

3

java.time

I recommend that you use java.time, the modern Java date and time API, for your date work.

If you are reading data from a text file, as soon as possible parse your date strings into LocalDate objects and store those in your red-black tree. For parsing strings in your two formats, if you don’t know in advance which format you get, the following method handles both simply by testing whether the string contains a slash or not.

private static final DateTimeFormatter DATE_FORMATTER
        = DateTimeFormatter.ofPattern("M/d/y", Locale.ROOT);
    
/** Parses yyyy-MM-dd or M/d/yyyy */
private static LocalDate parseDateString(String dateString) {
    if (dateString.contains("/")) {
        return LocalDate.parse(dateString, DATE_FORMATTER);
    } else {
        return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE);
    }
}

Let’s try it out:

    System.out.println(parseDateString("2023-11-20"));
    System.out.println(parseDateString("9/18/2019"));

Output:

2023-11-20
2019-09-18

Comparison

LocalDate implements comparable, so you can compare them using compareTo(). For a simple demonstration not involving any tree:

    LocalDate dateFromIso = parseDateString("2023-11-20");
    LocalDate dateFromSlashFormat = parseDateString("9/18/2019");
    LocalDate[] dates = { dateFromIso, dateFromSlashFormat };
    
    System.out.println("Before sort: " + Arrays.toString(dates));
    
    Arrays.sort(dates);
    
    System.out.println("After sort:  " + Arrays.toString(dates));
Before sort: [2023-11-20, 2019-09-18]
After sort:  [2019-09-18, 2023-11-20]

Tutorial link

Oracle tutorial: Date Time explaining how to use java.time.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
3

java.time

The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.

Solution using java.time, the modern Date-Time API: You can specify multiple optional patterns using square brackets inside a DateTimeFormatter. Parse the date strings into LocalDate and compare them using LocalDate#isBefore, LocalDate#isAfter, LocalDate#isEqual etc.

Demo:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("[uuuu-MM-dd][M/dd/uuuu]", Locale.ENGLISH);

        // Test
        Stream.of(
                "2021-11-09",
                "11/09/2021",
                "5/09/2021"
        ).forEach(s -> System.out.println(LocalDate.parse(s, dtf)));
    }
}

Output:

2021-11-09
2021-11-09
2021-05-09

ONLINE DEMO

Learn more about the modern Date-Time API from Trail: Date Time.


* 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. Note that Android 8.0 Oreo already provides support for java.time. Check this answer and this answer to learn how to use java.time API with JDBC.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
2

Personally, I hate dealing with date/time values that are String (or numerically) based. Java (now) has a nice date/time API and we might as well take advantage of it where ever possible (IMHO)

Since you have two formats which are relatively easy to distinguish (either they use / or -), it would be a simple matter to parse them, for example...

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        DateTimeFormatter yearMonthDayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter monthDayYearFormatter = DateTimeFormatter.ofPattern("d/MM/yyyy");

        String yearMonthDayValue = "2020-10-10";
        String monthDayYearValue = "10/10/2020";

        List<String> values = new ArrayList<>();
        values.add("2020-10-10");
        values.add("10/10/2020");

        for (String value : values) {
            LocalDate dateValue = null;
            if (value.contains("-")) {
                dateValue = LocalDate.parse(value, yearMonthDayFormatter);
            } else if (value.contains("/")) {
                dateValue = LocalDate.parse(value, monthDayYearFormatter);
            } else {
                System.err.println("Bad format [" + value + "]");
            }

            if (dateValue != null) {
                // Deal with it
            }
        }
    }
}

I would suggest having a look at the Date/Time trail for more details

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
2

Another possibility to complement MadProgrammer's answer would be just delegating the distinction between date formats to the parser itself with exception handling.

Something like defining an array of possible formats (in your case for now just two):

private final DateTimeFormatter[] formats = {
        DateTimeFormatter.ofPattern("yyyy-MM-dd"),
        DateTimeFormatter.ofPattern("d/MM/yyyy")
};

And creating a method to normalize a "raw" date read in input:

String normalize(String dateRaw) {
    if (dateRaw == null) {
        throw new IllegalArgumentException("Null date");
    }
    LocalDate standardized = null;
    for (DateTimeFormatter fmt : formats) {
        try {
            standardized = LocalDate.parse(dateRaw, fmt);
            break;
        } catch (final DateTimeParseException e) {
            // ignore or log if you need to
        }
    }
    if (standardized == null) {
        // do something about it, parse failed, invalid date
    }
    return standardized.toString();
}

EDIT

As mentioned in the comments, it's easier to just compare LocalDate objects if your spec allows it:

LocalDate toLocalDate(String dateRaw) {
    if (dateRaw == null) {
        throw new IllegalArgumentException("Null date");
    }
    LocalDate localDate = null;
    for (DateTimeFormatter fmt : formats) {
        try {
            localDate = LocalDate.parse(dateRaw, fmt);
            break;
        } catch (final DateTimeParseException e) {
            // ignore or log if you need to
        }
    }
    if (localDate == null) {
        // do something about it, parse failed, invalid date
    }
    return localDate;
}

And then if you need a String representation, you can get it directly from the LocalDate returned

geco17
  • 5,152
  • 3
  • 21
  • 38
  • Yeah, but why would you want to return a `String`? Keep the `LocalDate`. Comparing `LocalDate` objects is clearer than comparing strings. The upside of this solution is it’s straightforward to add a third format if one turns up. – Ole V.V. Nov 10 '21 at 01:41
  • 1
    you're right, it would make more sense to just return the `LocalDate`, I updated the answer accordingly – geco17 Nov 10 '21 at 12:40