Java: Calculate month end date based on timezone

2020-02-02 02:51发布

问题:

I have method to find month end date based on the timezone.

Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CET"));
calendar.set(
    Calendar.DAY_OF_MONTH, 
    calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
);
System.out.println(calendar.getTime());`

It displays output: Thu Aug 30 18:04:54 PDT 2018.

It should, however, give me an output in CET.

What am I missing?

回答1:

The Calendar.getTime() method returns a Date object, which you then printed in your code. The problem is that the Date class does not contain any notion of a timezone even though you had specified a timezone with the Calendar.getInstance() call. Yes, that is indeed confusing.

Thus, in order to print a Date object in a specific timezone, you have to use the SimpleDateFormat class, where you must call SimpleDateFormat.setTimeZone() to specify the timezone before you print.

Here's an example:

import java.util.Calendar;
import java.util.TimeZone;
import java.text.SimpleDateFormat;

public class TimeZoneTest {

    public static void main(String argv[]){
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CET"));
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        System.out.println("calendar.getTime(): " + calendar.getTime());

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss z");
        sdf.setTimeZone(TimeZone.getTimeZone("CET"));
        System.out.println("sdf.format(): " + sdf.format(calendar.getTime()));
     }
}

Here is the output on my computer:

calendar.getTime(): Fri Aug 31 01:40:17 UTC 2018
sdf.format(): 2018-Aug-31 03:40:17 CEST


回答2:

This is because Date object doesn't have timezone as part of its state, and getTime() actually returns a date which corresponds to the JVM's timezone, instead you need SimpleDateFormat to format and print the date in your required timezone.

If you try adding the following line of code, you could see that the timezone in the calendar is actually CET.

System.out.println(calendar.getTimeZone().getDisplayName()); 


回答3:

tl;dr

YearMonth                         // Represent a year-month without day-of-month.
.now(                             // Capture the current year-month as seen in the wall-clock time used by the people of a particular region (a time zone).
    ZoneId.of( "Africa/Tunis" )   // Specify your desired time zone. Never use 3-4 letter pseudo-zones such as `CET`. 
)                                 // Returns a `YearMonth` object.
.atEndOfMonth()                   // Determine the last day of this year-month. Returns a `LocalDate` object.
.atStartOfDay(                    // Let java.time determine the first moment of the day. Not necessarily 00:00:00, could be 01:00:00 or some other time-of-day because of anomalies such as Daylight Saving Time (DST). 
    ZoneId.of( "Africa/Tunis" )  
)                                 // Returns a `ZonedDateTime` object, representing a date, a time-of-day, and a time zone.

java.time

You are using the terrible old Calendar class that was supplanted years ago but the modern java.time classes.

LocalDate

If you need only a date, use LocalDate class. Then the time zone is irrelevant for your output.

But time zone is very relevant for determining the current date. For any given moment, the date varies around the globe by zone.

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

ZoneId z = ZoneId.of( "Europe/Paris" ) ;  // Or "Africa/Tunis" etc.
LocalDate today = LocalDate.now( z ) ;    // Capture the current date as seen by the wall-clock time used by the people of a certain region (a time zone).

YearMonth

Get the month for that date. Represent a year-month with, well, YearMonth.

YearMonth ym = YearMonth.from( today ) ;

Or skip the LocalDate.

YearMonth ym = YearMonth.now( z ) ;

Get the end of the month.

LocalDate endOfThisMonth = ym.atEndOfMonth() ;

ISO 8601

To generate a String representing that LocalDate object’s value, call toString. The default format is taken from the ISO 8601 standard. For a date-only value that will be YYYY-MM-DD such as 2018-01-23.

String output = endOfThisMonth.toString() ;

If you need another format, use DateTimeFormatter class. Search Stack Overflow for many examples and discussions.

Moment

If you need a moment, you can add a time-of-day and time zone to your LocalDate to get a ZonedDateTime. Or let ZonedDateTime determine the first moment of the day (which is not always 00:00:00!).

ZonedDateTime zdt = LocalDate.atStartOfDay( z ) ;

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?

  • Java SE 8, Java SE 9, Java SE 10, and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

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.