10

I've got a tool which seems to be giving me dates without specifying the year that I need to convert and I'm using Java for the task (actually Groovy but close enough in this case). An example date is "13 Dec 12:00:00", which should refer to 12/13/2011 since the year is unspecified and it is 2011. The following Groovy script does the trick:

import java.text.*
println new SimpleDateFormat("dd MMM HH:mm:ss").parse("13 Dec 12:00:00")

My problem with this script is that SimpleDateFormat seems to be leaving the year unset at 1970 after conversion. I could explicitly set it to 2011 because that is the current year but there seems to be some lag between current year and the dates set so when New Years comes this script will get it wrong for that lag time. How can I fix it up simply? One solution would be to check if the date with current year is after now then use the last year but I'm hoping a simpler solution exists (or state that's the simplest if not).

dromodel
  • 9,581
  • 12
  • 47
  • 65
  • Apparently I wasn't clear in the question. The idea is to set it to the most recent year such that the resulting date is not after now. Imagine you're running this script on Feb 1, 2012. In that case, you want the returned date to be 12/13/2011 but on that date if you asked for Jan 1, 2012 the returned date would be 1/1/2012. – dromodel Dec 15 '11 at 17:33
  • Check out my edit, it's what you are asking for. – Filip Roséen - refp Dec 16 '11 at 11:50
  • I'd recommend Ole VV's answer followed by an adjustment from karakuricoder's answer. – Barett Jun 16 '18 at 18:12
  • 1
    FYI, the terribly troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Nov 19 '18 at 18:57

5 Answers5

14

Modern answer (valid from 2014 and on): I first declare an auxiliary method:

private static LocalDateTime parseWithDefaultYear(String stringWithoutYear, int defaultYear) {
    DateTimeFormatter parseFormatter = new DateTimeFormatterBuilder()
            .appendPattern("dd MMM HH:mm:ss")
            .parseDefaulting(ChronoField.YEAR, defaultYear)
            .toFormatter(Locale.ENGLISH);
    LocalDateTime dateTime = LocalDateTime.parse(stringWithoutYear, parseFormatter);
    return dateTime;
}

Then we can do:

    ZoneId zone = ZoneId.of("America/Argentina/Jujuy");
    String stringWithoutYear = "13 Dec 12:00:00";
    LocalDateTime now = LocalDateTime.now(zone);
    int defaultYear = now.getYear();
    LocalDateTime dateTime = parseWithDefaultYear(stringWithoutYear, defaultYear);
    if (dateTime.isAfter(now)) { // in the future
        defaultYear--;
        dateTime = parseWithDefaultYear(stringWithoutYear, defaultYear);
    }
    System.out.println(dateTime);

When running today (June 2018) this prints:

2017-12-13T12:00

You may ask why I want to parse a second time with a new formatter in case the first attempt gives a date in the future. Wouldn’t it be simpler just to subtract 1 year? While java.time certainly has good support for this, I chose a second parsing to handle the corner case of February 29 in leap years better. Imagine the code is run in the first couple of months of 2021 and the string is 29 Feb 12:00:00. Since 2021 is not a leap year, Java will choose February 28, the last day of the month. Subtracting 1 year would give February 28, 2020, which would be incorrect since 2020 is a leap year. Instead my new parsing with 2020 as default year gives the correct February 29, 2020.

Messages:

  • While the other answers were good answers in 2011, the classes Date, SimpleDateFormat, Calendar and GregorianCalendar are now long outdated, so I recommend avoiding them. The modern classes generally are more programmer friendly and come with fewer unpleasant surprises.
  • Always give correct time zone. Using an incorrect time zone is likely to cause the test for a future date-time to be inaccurate, risking that the result is off by 1 year.
  • Always give explicit locale. In this case I took “Dec” for English and provided Locale.ENGLISH.
  • Since you consider it safe to assume that the date is within the last year, I invite you to consider whether is it also safe to assume it’s within the last 4, 6 or 8 months, for example? If so, testing whether this is the case will give you a better validation:

    if (dateTime.isBefore(now.minusMonths(5))) {
        throw new IllegalArgumentException("Not within the last 5 months: " + stringWithoutYear);
    }
    
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
6

You can use java.util.Calendar to get the current year, like in the below example.

Calendar.getInstance ().get (Calendar.YEAR);

Example snippet

OP elaborated his question and therefor this snippet has been rewritten to match his needs.

  public static String fixDate (String data) throws Exception {
    SimpleDateFormat dateInputFormat  = new SimpleDateFormat("dd MMM HH:mm:ss");
    SimpleDateFormat dateOutputFormat = new SimpleDateFormat("yyyy dd MMM HH:mm:ss");

    Calendar currentCal = Calendar.getInstance ();
    Calendar parsedCal  = Calendar.getInstance ();

    int CAL_YEAR  = Calendar.YEAR;

    parsedCal.setTime  (dateInputFormat.parse (data));
    parsedCal.set      (CAL_YEAR, currentCal.get (CAL_YEAR));

    if (parsedCal.after (currentCal))
      parsedCal.set (CAL_YEAR, parsedCal.get (CAL_YEAR) - 1);

    // Date parsedDate = parsedCal.getTime ();

    return dateOutputFormat.format (parsedCal.getTime ());
  }

  public static void main (String[] args) throws Exception {
    System.out.println (fixDate ("13 Dec 12:00:00"));
    System.out.println (fixDate ("31 Dec 12:00:00"));
  }

output

2011 13 Dec 12:00:00

2010 31 Dec 12:00:00


Remember to watch out for thrown Exceptions!

Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • I know I didn't create the format. I don't even know for sure what the format is so I have to guess. It is being provided by TeamCity for CVS builds not managed by the server or agent. There is a bug report about it but it hasn't been fixed yet and as a workaround they suggest that I convert the date to a legal one in the mean time. – dromodel Dec 15 '11 at 17:35
  • Okay, well the above solution should work for you and upon an erroneous date being given to Date it will throw an exception, so watch out for that. – Filip Roséen - refp Dec 15 '11 at 18:02
  • 1
    FYI, the terribly troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Nov 19 '18 at 18:57
1
  String date_string = "13 Dec 12:00:00";
  SimpleDateFormat sdf = new SimpleDateFormat("dd MMM HH:mm:ss yyyy");
  Date date = sdf.parse(date_string + " " + Calendar.getInstance ().get(Calendar.YEAR));          
  System.out.println(date);

OUTPUT:

Tue Dec 13 12:00:00 IST 2011
dku.rajkumar
  • 18,414
  • 7
  • 41
  • 58
1

The simplest solution is to append the current year to the date string before you parse it. For example:

def currentYear = new Date()[Calendar.YEAR]
def d = Date.parse("dd MMM HH:mm:ss yyyy", "13 Dec 12:00:00 " + currentYear)
println d
===> Tue Dec 13 12:00:00 MST 2011
ataylor
  • 64,891
  • 24
  • 161
  • 189
0
    GregorianCalendar now = new GregorianCalendar();
GregorianCalendar goal= new GregorianCalendar(now.get(Calendar.YEAR), month-1, day);
if (goal.after(now)) {
    goal.add(Calendar.YEAR, -1);
}

goal has your corrected date.

karakuricoder
  • 1,065
  • 8
  • 8