What is the best way to parse a date in MM/DD/YY format and adjust it to the current / previous century?
Asked Answered
N

6

11

One of our customers wants to be able to enter a date with only 2 digits for the year component. The date will be in the past, so we want it to work for the previous century if the 2 digit year is after the current year, but work for the current century if the 2 digit year is equal to or less than the current year.

as of today 10/30/2008

01/01/01 = 01/01/2001

01/01/09 = 01/01/1909

This is a strange requirement, and I solved the problem, I just don't like my solution. It feels like there is a better way to do this.

Thanks for the help.

public static String stupidDate(String dateString)
{
    String twoDigitYear = StringUtils.right(dateString, 2);
    String newDate = StringUtils.left(dateString, dateString.length() - 2);
    int year = NumberUtils.toInt(twoDigitYear);
    Calendar c = GregorianCalendar.getInstance();
    int centuryInt = c.get(Calendar.YEAR) - year;
    newDate = newDate + StringUtils.left(Integer.toString(centuryInt), 2) + twoDigitYear;
    return newDate;
}
Naive answered 30/10, 2008 at 19:57 Comment(0)
M
12

Groovy script (easy enough to throw into java) demonstrating the point @bobince made about SimpleDateFormat.

import java.text.SimpleDateFormat

SimpleDateFormat sdf = new SimpleDateFormat('MM/dd/yy')
SimpleDateFormat fmt = new SimpleDateFormat('yyyy-MM-dd')

Calendar cal = Calendar.getInstance()
cal.add(Calendar.YEAR, -100)
sdf.set2DigitYearStart(cal.getTime())

dates = ['01/01/01', '10/30/08','01/01/09']
dates.each {String d ->
  println fmt.format(sdf.parse(d))
}

Yields

2001-01-01
2008-10-30
1909-01-01
Moneybag answered 30/10, 2008 at 21:25 Comment(1)
Update for the readers: The terribly flawed Calendar, SimpleDateFormat, and Date classes have been supplanted by the modern java.time classes defined in JSR 310. See new solution in Answer by Arvind Kumar Avinash.Orv
S
9

SimpleDateFormat already does two-digit year parsing for you, using the two-letter ‘yy’ format. (It'll still allow four digits, obviously.)

By default it uses now-80→now+20, so it's not exactly the same rule you propose, but it's reasonable and standardised (in the Java world at least), and can be overridden using set2DigitYearStart() if you want.

DateFormat informat= new SimpleDateFormat("MM/dd/yy");
DateFormat outformat= new SimpleDateFormat("MM/dd/yyyy");
return outformat.format(informat.parse(dateString));

In the longer term, try to migrate to ISO8601 date formatting (yyyy-MM-dd), because MM/dd/yy is approximately the worst possible date format and is bound to cause problems eventually.

Sahara answered 30/10, 2008 at 20:49 Comment(3)
He said his client explicitly asked for that particular 2 digit year rule. :)Julie
Yeah if it were up to me I'd say we require a four digit year. Problem solved.Naive
Indeed - clients are fools! :-) Which is why it needs to be approached sneakily!Sahara
F
8

How about this:

public static String anEasierStupidDateWithNoStringParsing(String dateString) {
    DateFormat df = new SimpleDateFormat("MM/dd/yyyy");

    //handling ParseExceptions is an exercise left to the reader!
    Date date = df.parse(dateString);
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);

    Calendar now = Calendar.getInstance();
    if (cal.after(now)) {
        cal.add(Calendar.YEAR, -100);
    }

    return cal;
}

In other words, let SimpleDateFormat parse the String and just adjust the year to be the previous century if SimpleDateFormat (which has it's own rules for interpreting year strings) returns a date that is after the current date.

This would guarantee that all dates returned are in the past. However, it doesn't account for any dates that might be parsed as before this past century - for example, with the format MM/dd/yyyy, a date string like "01/11/12" parses to Jan 11, 12 A.D.

Florida answered 30/10, 2008 at 20:9 Comment(0)
J
5

If Joda Time is an option:

String inputDate = "01/01/08";
// assuming U.S. style date, since it's not clear from your original question
DateTimeFormatter parser = DateTimeFormat.forPattern("MM/dd/yy");
DateTime dateTime = parser.parseDateTime(inputDate);
// if after current time
if (dateTime.isAfter(new DateTime())) {
    dateTime = dateTime.minus(Years.ONE);
}

return dateTime.toString("MM/dd/yyyy");

I know Joda Time isn't part of Java SE, and as I've said in another thread, I usually do not condone using a third-party library when there's a Java library that does the same thing. However, the person who is developing Joda Time is also leading JSR310 - the Date and Time API that'll make it into Java 7. So I Joda Time is basically going to be in future Java releases.

Julie answered 30/10, 2008 at 20:49 Comment(2)
You should use joda time Freaky Formatters to append2digitYear joda-time.sourceforge.net/userguide.html#Freaky_FormattersLobachevsky
I thought about doing something like that, but that would mean that the current year will always be treated as the current year, even if it's some time in the future (i.e., 12/31/08 would be 12/31/2008 instead of 12/31/1908). I guess the correct solution would depend on the client's requirements.Julie
E
2

The accepted answer uses legacy date-time API which was the correct thing to do in 2008 when the question was asked. In March 2014, java.time API supplanted the error-prone legacy date-time API. Since then, it has been strongly recommended to use this modern date-time API.

java.time API

  1. You can put optional patterns between DateTimeFormatterBuilder#optionalStart and DateTimeFormatterBuilder#optionalEnd and create a formatter which can parse a date string with either a four-digit year or a two-digit year.
  2. Using the DateTimeFormatterBuilder#appendValueReduced, you can specify a base value for the year as per your requirement.

Demo:

import java.time.LocalDate;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter parser = new DateTimeFormatterBuilder()
                .appendPattern("M/d/")
                .optionalStart()
                .appendPattern("uuuu")
                .optionalEnd()
                .optionalStart()
                .appendValueReduced(ChronoField.YEAR, 2, 2, Year.now().minusYears(100).getValue())
                .optionalEnd()
                .toFormatter(Locale.ENGLISH);

        // Test
        Stream.of(
                "1/2/2022",
                "01/2/2022",
                "1/02/2022",
                "01/02/2022",
                "1/2/22",
                "1/2/21",
                "1/2/20",
                "1/2/23",
                "1/2/24"
            )
            .map(s -> LocalDate.parse(s, parser))
            .forEach(System.out::println);
    }
}

Output:

2022-01-02
2022-01-02
2022-01-02
2022-01-02
1922-01-02
2021-01-02
2020-01-02
1923-01-02
1924-01-02

Note that the dates with a two-digit year greater than the current year are parsed into a LocalDate with the last century.


How to switch from the legacy to the modern date-time API?

You can switch from the legacy to the modern date-time API using Date#toInstant on a java-util-date instance. Once you have an Instant, you can easily obtain other date-time types of java.time API. An Instant represents a moment in time and is independent of a time-zone i.e. it represents a date-time in UTC (often displayed as Z which stands for Zulu-time and has a ZoneOffset of +00:00).

Demo:

public class Main {
    public static void main(String[] args) {
        Date date = new Date();
        Instant instant = date.toInstant();
        System.out.println(instant);

        ZonedDateTime zdt = instant.atZone(ZoneId.of("Asia/Kolkata"));
        System.out.println(zdt);

        OffsetDateTime odt = instant.atOffset(ZoneOffset.of("+05:30"));
        System.out.println(odt);
        // Alternatively, using time-zone
        odt = instant.atZone(ZoneId.of("Asia/Kolkata")).toOffsetDateTime();
        System.out.println(odt);

        LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Kolkata"));
        System.out.println(ldt);
        // Alternatively,
        ldt = instant.atZone(ZoneId.of("Asia/Kolkata")).toLocalDateTime();
        System.out.println(ldt);
    }
}

Output:

2022-11-20T20:32:42.823Z
2022-11-21T02:02:42.823+05:30[Asia/Kolkata]
2022-11-21T02:02:42.823+05:30
2022-11-21T02:02:42.823+05:30
2022-11-21T02:02:42.823
2022-11-21T02:02:42.823

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

Evonevonne answered 20/11, 2022 at 20:52 Comment(0)
C
0
Date deliverDate = new SimpleDateFormat("MM/dd/yy").parse(deliverDateString);
String dateString2 = new SimpleDateFormat("yyyy-MM-dd").format(deliverDate);

Working for me.

Contend answered 30/10, 2014 at 8:0 Comment(1)
How does this answer address the Question's issue of switching century?Orv

© 2022 - 2024 — McMap. All rights reserved.