简体   繁体   中英

Is Android's Calendar.Day_Of_Month zero based?

According to the android documentation, Day_Of_Month is 1-based (ie '1' means the 1st day of the month) (see https://developer.android.com/reference/java/util/Calendar#DAY_OF_MONTH ).

However, when I run the below code on both my android devices, I get "2018-01-02" as the result. Am I missing something?

Calendar cal = new GregorianCalendar();
TimeZone tzone = TimeZone.getTimeZone("GMT");
cal.setTimeZone(tzone);

cal.set(Calendar.HOUR, 12);
cal.set(2018, 0, 1);

DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String date = df.format(cal.getTime());

The reason I am not just assuming the documentation is wrong is I have some evidence that one of my customer's devices is reporting the date as "2018-01-01"

tl;dr

Is Android's Calendar.Day_Of_Month zero based?

No.

I get "2018-01-02" …

… reporting the date as "2018-01-01"

Your issue of 1st vs 2nd is related to something else: time zone.

Use java.time instead of those terribly troublesome legacy classes.

OffsetDateTime.of(
    LocalDate.of( 2018 , Month.JANUARY , 1 ) , 
    LocalTime.NOON ,
    ZoneOffset.UTC
)

.toString(): 2018-01-01T12:00Z

.format(
    DateTimeFormatter.ISO_LOCAL_DATE
)

2018-01-01

Or override the zone/offset.

.format(
    DateTimeFormatter.ISO_LOCAL_DATE
                     .withZone( ZoneId.of( "Pacific/Kiritimati" ) )  // Using zone 14 hours ahead of UTC. So noon UTC is “tomorrow” in Kiribati.
)

2018-01-02

Time Zone

If here in America/Los_Angeles time zone at 16:00 I run your code, I get 2018-01-01 . But if I change your code to set hour to 23 instead of 12 , I get 2018-01-02 as a result.

So there is an issue about time zones. What is "today" and what is "tomorrow" depends on your time zone.

Rather than stain my brain any further, let me suggest the real solution: Stop using these terrible date-time classes .

java.time

Those old date-time classes ( Date , Calendar , SimpleDateFormat ) were supplanted years ago by the modern java.time classes.

Apparently you want noon on the first of the year. Here's how. Notice the sane numbering : Months are 1-12 for January-December (unlike the legacy classes), day-of-month is 1-31 for first-last (like the legacy classes).

Get the date.

LocalDate ld = LocalDate.of( 2018 , 1 , 1 ) ; // January 1, 2018.

Or use the more readable Month enum.

LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 1 ) ; // January 1, 2018.

Generate a String representing that value in standard ISO 8601 format.

ld.toString(): 2018-01-01

Get the time-of-day, noon. The LocalTime class has an constant for that.

LocalTime lt = LocalTime.NOON ; 

Specify an offset-from-UTC of zero, that is, UTC itself. The ZoneOffset class has a constant for that.

ZoneOffset offset = ZoneOffset.UTC ;

Combine to represent a moment as a OffsetDateTime object.

OffsetDateTime odt = OffsetDateTime.of( ld , lt , offset ) ;

Generate a String representing that value in standard ISO 8601 format.

odt.toString(): 2018-01-01T12:00Z

If you want just the date portion, extract a LocalDate .

LocalDate ld = odt.toLocalDate() ;

Or just print a String using only the date portion by defining a DateTimeFormatter .

DateTimeFormatter f = DateTimeFormatter.ISO_LOCAL_DATE ; 
String outputOdtDateOnly = odt.format( f ) ;

2018-01-01

By default, the DateTimeFormatter object uses the offset or zone of the object it is representing with a new String. You can optionally override that offset/zone. Let's try that.

ZoneId z = ZoneId.of( "Pacific/Kiritimati" );  // Most eastern (earliest) time zone is in Kiribati. https://en.wikipedia.org/wiki/Kiribati
DateTimeFormatter fKiritimati = f.withZone( z );
String outputOdtDateOnlyInKiribati = odt.format( fKiritimati );

Be clear that we altered the formatter object, not the data object, not the OffsetDateTime object. We added a time zone to the new formatter object where the previous formatter held a null , as documented . And notice how java.time uses immutable objects , where a fresh object is instantiated based on the original's values rather than alter (“mutate”) the original. So we got a second DateTimeFormatter object based on the first but with the addition of our specified override-zone.

Let's see what we get.

System.out.println( outputOdtDateOnlyInKiribati );

2018-01-02

Surprise! Back to the issue in your Question. Parts of Kiribati are 14 hours ahead of UTC. So when it is noon in UTC, simultaneously it is 14 hours later in Pacific/Kiritimati zone, therefore “tomorrow” the 2nd.


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?

Actually you are doing three things that will affect your result.

(1) You are using Calendar.HOUR which is for the 12-hour clock!

Java Docs:

HOUR:
Field number for get and set indicating the hour of the morning or afternoon. HOUR is used for the 12-hour clock (0 - 11). Noon and midnight are represented by 0, not by 12. Eg, at 10:04:15.250 PM the HOUR is 10.

What you want to use is the Calendar.HOUR_OF_DAY which is for the 24-hour clock!

Java Docs:

HOUR_OF_DAY:
Field number for get and set indicating the hour of the day. HOUR_OF_DAY is used for the 24-hour clock. Eg, at 10:04:15.250 PM the HOUR_OF_DAY is 22.

(2) You are setting the time to 12. Which will cause some issues when you use the Calendar.HOUR .

(3) You are using the TimeZone "GMT" in combination with the Calendar.HOUR setting will cause the date to increment of decrement depending in the actual timezone the user is located.

Example:

I am in the timezone CST and I ran this code:

Calendar cal = new GregorianCalendar();
cal.set(2018, 0, 1);
int h = 0;

cal.set(Calendar.HOUR_OF_DAY, h);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String date1 = df.format(cal.getTime());
Log.e(TAG, "date1 = " + date1)

cal.set(Calendar.HOUR, h);
TimeZone tzone = TimeZone.getTimeZone("GMT");
cal.setTimeZone(tzone);
String date2 = df.format(cal.getTime());
Log.e(TAG, "date2 GMT = " + date2)

and got these results with h = 0:

date1 = 2018-01-01 00:54
date2 GMT = 2017-12-31 18:54

setting h = 12:

date1 = 2018-01-01 12:54
date2 GMT = 2018-01-01 18:54

Seven hours later I would get:

date1 = 2018-01-01 19:54
date2 GMT = 2018-01-02 01:54

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