简体   繁体   中英

Get the same day from this year

I need get the same day in this year.

Example: Now is 2019 year and the variable contains value 15 July 2022, so I need to get 15 July 2019 then. It works for all dates except February when it has an extra day in one year and doesn't have this day in this year, example: 29 February 2020 will return me the next day: 1 March 2019, but I need in this case to return the previous day: 28 February 2019. How I can adjust my logic so that it will work in this way?

public static java.util.Date getThisDateInThisYear(java.util.Date date) {
    GregorianCalendar gc = new GregorianCalendar();
    gc.setTime(date);
    Date today = new Date();
    GregorianCalendar gcToday = new GregorianCalendar();
    gcToday.setTime(today);
    gc.set(GregorianCalendar.YEAR, gcToday.get(GregorianCalendar.YEAR));
    return gc.getTime();
  }

first calc the difference of years and add the result to date

public Date getThisDateInThisYear(Date date) {
   Calendar c = Calendar.getInstance();
   int thisYear = c.get(Calendar.YEAR)
   c.setTime(date);

   int diff = thisYear  - c.get(Calendar.YEAR);
   c.add(Calendar.YEAR, diff);

   return c.getTime();

}

I tested with 2016-02-29 and 2020-02-29, and both return 2019-02-28.

tl;dr

Use modern java.time classes. Never use Date / Calendar .

29 February 2020 will return me next day: 1st March 2019

LocalDate                           // Represent a date-only value, without time-of-day and without time zone.
.of( 2020 , Month.FEBRUARY , 29 )   // Specify a date. Here, Leap Day of 2020. Returns a `LocalDate` object.
.minusYears( 1 )                    // Intelligently move backwards in time one year. Returns another `LocalDate` object, per immutable objects pattern.
.toString()                         // Generate text representing the value of this date, in standard ISO 8601 format of YYYY-MM-DD.

When run at IdeOne.com :

2019-02-28

Avoid legacy date-time classes

You are using terrible date-time classes that are now legacy, supplanted entirely by the java.time classes.

LocalDate

The LocalDate class represents a date-only value without time-of-day and without time zone or offset-from-UTC .

A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec .

If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.

Specify a proper time zone name in the format of Continent/Region , such as America/Montreal , Africa/Casablanca , or Pacific/Auckland . Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

If you want to use the JVM's current default time zone, ask for it and pass as an argument. If omitted, the code becomes ambiguous to read in that we do not know for certain if you intended to use the default or if you, like so many programmers, were unaware of the issue.

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

Or specify a date. You may set the month by a number, with sane numbering 1-12 for January-December.

LocalDate ld = LocalDate.of( 2020 , 2 , 29 ) ;  // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.

Or, better, use the Month enum objects pre-defined, one for each month of the year. Tip: Use these Month objects throughout your codebase rather than a mere integer number to make your code more self-documenting, ensure valid values, and provide type-safety . Ditto for Year & YearMonth .

LocalDate ld = LocalDate.of( 2020 , Month.FEBRUARY , 29 ) ;

Date-time math

You can do date-time math in a few ways with *java.time. One way is by calling plus or minus and passing a Period or Duration . Another way is calling the convenience methods such as plusYears or minusYears .

The LocalDate class seems to give just the behavior you want with these methods.

To be clear:

  • 2020 is a Leap Year . So 2020-02-29 is a valid date.
  • 2018, 2019, and 2021 are not. The 29th is not a valid date in these years.

See the examples below run live at IdeOne.com .

From leap year

Let's start on Leap Day, the 29th, then add a year and subtract a year. We expect to see 28th on both.

LocalDate leapDay2020 = LocalDate.of( 2020 , Month.FEBRUARY , 29 );
LocalDate yearBeforeLeap = leapDay2020.minusYears( 1 );
LocalDate yearAfterLeap = leapDay2020.plusYears( 1 );

System.out.println( "leapDay2020.toString(): " + leapDay2020 );
System.out.println( "yearBeforeLeap.toString(): " + yearBeforeLeap );
System.out.println( "yearAfterLeap.toString(): " + yearAfterLeap );

Indeed that is what we get.

leapDay2020.toString(): 2020-02-29

yearBeforeLeap.toString(): 2019-02-28

yearAfterLeap.toString(): 2021-02-28

From non-Leap Year

Now let's start in a non-Leap Year on the 28th of February, then add & subtract a year. We expect to see 28th in all three. The 29th in the Leap year of 2020 is ignored.

LocalDate nonLeap2019 = LocalDate.of( 2019 , Month.FEBRUARY , 28 );
LocalDate yearBeforeNonLeapIntoNonLeap = nonLeap2019.minusYears( 1 );
LocalDate yearBeforeNonLeapIntoLeap = nonLeap2019.plusYears( 1 );

System.out.println( "nonLeap2019.toString(): " + nonLeap2019 );
System.out.println( "yearBeforeNonLeapIntoNonLeap.toString(): " + yearBeforeNonLeapIntoNonLeap );
System.out.println( "yearBeforeNonLeapIntoLeap.toString(): " + yearBeforeNonLeapIntoLeap );

nonLeap2019.toString(): 2019-02-28

yearBeforeNonLeapIntoNonLeap.toString(): 2018-02-28

yearBeforeNonLeapIntoLeap.toString(): 2020-02-28


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat .

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes.

To learn more, see the Oracle Tutorial . And search Stack Overflow for many examples and explanations. Specification is JSR 310 .

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval , YearWeek , YearQuarter , and more .

Also you can check if year is leap year based on that you can subtract the date

public static boolean isLeapYear(int year) {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, year);
    return cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
}

I don't see any problem with java.util.Calendar. In JDK 1.8 this Java class got a major rework. So the following example code works fine:

    SimpleDateFormat sf = new SimpleDateFormat("dd MMM yyyy");
    Date date=sf.parse("29 Feb 2020");
    System.out.println("date="+date);

    Calendar calendar=Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.YEAR, -1);
    System.out.println("date="+calendar.getTime());

    calendar.add(Calendar.YEAR, 1);
    System.out.println("date="+calendar.getTime());

It gives the same result as Basil Bourque wants it to have:

date=Sat Feb 29 00:00:00 CET 2020
date=Thu Feb 28 00:00:00 CET 2019
date=Fri Feb 28 00:00:00 CET 2020

Basically the Java class now uses the same ZoneInfo etc.. classes as the new classes also do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM