2

I tried searching the internet and found a lot of questions on StackOverlflow somewhat regarding the same topic, but couldn't find anything that I was able to understand...

So, I have this data class that contains a dateOfOrigin of type GregorianCalendar. Using gson I convert all json and return an Observable array with all locations. Inside the json file, I added the dateOfOrigin as an object like so:

{
    "id": 6,
    "name": "Fuse",
    "image": "fuse.jpg",
    "street": "Blaesstraat 208",
    "city": "Brussels Hoofdstedelijk Gewest",
    "zip": 1000,
    "date_of_origin": {"year":1994,"month":4,"dayOfMonth":16},
    "parking": true
}

And this is what my data class looks like:

data class Location (
    val id : Int,
    val name : String,
    val image : String,
    val street : String,
    val city : String,
    val zip : Int,
    @SerializedName("date_of_origin")
    val originDate : GregorianCalendar?,
    val parking : Boolean = true,
    var imageBitmap : Bitmap? = null
)

Whenever I try to set the dateText like this:

originDate?.let {
    dateText = "${it.get(Calendar.DAY_OF_MONTH)} ${it.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault())} ${it.get(Calendar.YEAR)}"

    dateText = resources.getString(R.string.origin_date, dateText)
}

It outputs 16 May 1994 instead of 16 Apr 1994

I'm not able to figure out how to fix this...

EDIT Subtracting 1 from the month seems to fix the problem for most cases. Still, I have one result that is supposed to output 30 Jan 2016 but displays 1 Feb 2016.

"date_of_origin": {"year":2016,"month":1,"dayOfMonth":30}
Hawklike
  • 952
  • 16
  • 23
DanteC
  • 178
  • 1
  • 9
  • 2
    Calender.MONTH starts at 0 (for January, [link](https://developer.android.com/reference/java/util/Calendar#MONTH)), so you have to subtract one to get the right date. – P. Leibner May 19 '20 at 09:57
  • hello, i think it's duplicate of [this](https://stackoverflow.com/questions/344380/why-is-january-month-0-in-java-calendar). That's because android calendar MONTH starts from 0 position – Mindgamer May 19 '20 at 09:57
  • 1
    Use `java.time`... – deHaar May 19 '20 at 10:03
  • the real question is : is this even a bug or a problem ? it's not displaying what you THINK it should in terms of months, but that doesn't mean that it's wrong, maybe it's the correct date :) – a_local_nobody May 19 '20 at 10:12
  • @a_local_nobody On the other hand, it is not definitely intuitive and that is IMHO what matters the most. – Hawklike May 19 '20 at 10:13
  • what isn't intuitive @Hawklike ? the fact that you're parsing json data and displaying it like you're receiving it ? – a_local_nobody May 19 '20 at 10:16
  • @a_local_nobody Starting counting the months from 0, as it is not what we are used to doing in real life. – Hawklike May 19 '20 at 10:19
  • 1
    it doesn't really matter here how intuitive it is (or isn't) as for this case it works fine, it just wasn't the output OP was expecting, but it doesn't make it wrong @Hawklike – a_local_nobody May 19 '20 at 10:22
  • 2
    I recommend you don’t use `GregorianCalendar`. That class is poorly designed and long outdated. Instead use `LocalDate` from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. May 19 '20 at 17:36

2 Answers2

3

GregorianCalendar represents months with numbers in the range from 0 to 11. This means that the number 0 is represented as January and 11 is represented as of December.

Therefore you need to subtract 1 if your API is not using the same logic as the Java implementation.

Updated: GregorianCalendar(2016, 1, 30) is understood as 30th of February. This is internally converted to 1st of March, therefore when you subtract one month from the date, you get 1st of February. You need to create an instance of GregorianCalendar class already with the subtracted month number, ie. January as 0, February as 1 and so on.

Hawklike
  • 952
  • 16
  • 23
  • there's no reason to use Joda Time – a_local_nobody May 19 '20 at 10:09
  • also, wouldn't you need to `subtract` instead of `add` ? – a_local_nobody May 19 '20 at 10:10
  • i don't think the last part of your answer is necessary at all really, as both the api and the implementation are using the same logic - just because it isn't displaying what OP thought it would doesn't actually make it wrong in this case – a_local_nobody May 19 '20 at 10:14
  • Thanks! I still don't understand why it would be converted internally to 1st of March though... Tried creating an instance of GregorianCalendar as follows but still returns 1st of Feb instead of 30th of Feb: originDate?.let { val d = GregorianCalendar(it.get(Calendar.YEAR), it.get(Calendar.MONTH) - 1, it.get(Calendar.DAY_OF_MONTH)) //.... } – DanteC May 19 '20 at 18:37
  • Because the 30th of February is nonsense. Specifically, the year 2016 is a leap year, so February has 29 days. Therefore the 30th of February is converted to 1st of March (overflows). – Hawklike May 19 '20 at 18:55
  • To your new implementation - it doesn't change anything. You need to create `originDate` with subtracted month from scratch. In other words, in code where you create an instance of `GregorianCalendar` for the first time, in that particular place you have to pass subtracted month as one the constructor arguments. – Hawklike May 19 '20 at 19:00
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/214211/discussion-between-hawklike-and-dantec). – Hawklike May 19 '20 at 20:51
1

The Answer by Hawklike is correct. You were tricked by the crazy month-numbering scheme employed by the GregorianCalendar class. One of many reasons to avoid this class.


tl;dr

myGregCal  
.toZonedDateTime()                           // Convert from obsolete `GregorianCalendar` class to modern `java.time.ZonedDateTime` class.
.toLocalDate()                               // Extract the date portion, without time-of-day and without time zone.
.format(                                     // Generate text representing the value of this date-time object.
    DateTimeFormatter
    .ofLocalizedDate( FormatStyle.MEDIUM )   // Automatically localize.
    .withLocale(                             // Specify a locale for the human language and cultural norms used in localization. 
        Locale.UK
    )
)

23 Jan 2021

Details

Never use GregorianCalendar. This class is part of the date-time classes bundled with the earliest versions of Java. These classes were years ago supplanted by the modern java.time classes defined in JSR 310.

If you must interoperate with code not yet updated to java.time, convert. Call new conversion methods added to the old classes.

GregorianCalendar was replaced by ZonedDateTime.

ZonedDateTime zdt = myGregCal.toZonedDateTime() ;  // From legacy class to modern class.

Going the other direction.

ZonedDateTime zdt = ZonedDateTime.of( 2021 , Month.JANUARY , 23 , 12 , 0 , 0 , 0 , ZoneId.of( "America/Montreal" ) ) ;
GregorianCalendar myGregCal = GregorianCalendar.from( zdt ) ;

Or break that int multiple parts.

LocalDate ld = LocalDate.of( 2021 , Month.JANUARY , 23 ) ;
LocalTime lt = LocalTime.of( 12 , 0 ) ;
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
GregorianCalendar myGregCal = GregorianCalendar.from( zdt ) ;

Or use month number rather than Month enum for the month. Notice that java.time uses sane numbering, 1-12 for January-December, unlike GregorianCalendar.

LocalDate.of( 2021 , 1 , 23 )  // Same effect as Month.JANUARY. 

To generate text, use DateTimeFormatter class. Your desired format happens to match that of localized format used in the UK. So let java.time automatically localize for you by calling DateTimeFormatter.ofLocalizedDate.

Locale locale = Locale.UK ; 
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.MEDIUM ).withLocale( locale ) ;
String output = zdt2.toLocalDate().format( f ) ;

See this code run live at IdeOne.com.

23 Jan 2021


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.

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

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

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. Hibernate 5 & JPA 2.2 support java.time.

Where to obtain the java.time classes?

Table of which java.time library to use with which version of Java or Android

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154