2

I am currently struggling with Java.Util.Calendar
I have a method that creates a Calendar with random values for year, month and day.

public Calendar getRandomGeburtstag() {
        int year = 1999 - (int) (Math.random() * 80);
        int month = (int) (Math.random() * 12) + 1;
        int monthLength = 28;
        int day = (int) (Math.random() * monthLength) + 1;      
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(year, month, day);
        return calendar;
} 

I store this calendar within a "Person" class, which has a simple getter method.
Then I request that calendar on two separate occasions. The object isn't altered in the meantime. Printing the calendar gives two different outputs :

java.util.GregorianCalendar[areAllFieldsSet=false,ERA=?,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=?,HOUR=?,HOUR_OF_DAY=?,MINUTE=?,SECOND=?,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]  

And

java.util.GregorianCalendar[areAllFieldsSet=true,ERA=1,WEEK_OF_YEAR=52,WEEK_OF_MONTH=5,DAY_OF_YEAR=362,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=4,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=3600000,DST_OFFSET=0]

(I omitted the fields that stayed the same, they were all set in both printouts)
As you can see, all fields which were previously not set got a default or calculated value somewhere in between the two request. The objected isn't used in between the requests so it's probably the JVM.
The problem is, that the field ZONE_OFFSET is used by a method I don't have access to. The desired output is only printed if ZONE_OFFSET=? is given.
Is there any way I can stop the automatic allocation of these values?

Mindbomber
  • 21
  • 4
  • 1
    I'd try debugging your code while setting a breakpoint in `Calendar.internalSet(int, int)` method to see where your object is being changed. It's unlikely that JVM changes your object since `Calendar` is not a singleton. A workaround would be to use an immutable type but `LocalDate` is available only in 8+. – Karol Dowbecki Mar 13 '18 at 10:32
  • For Java 6 and 7 `LocalDate` is avaialble through [ThreeTen Backport](http://www.threeten.org/threetenbp/). @KarolDowbecki I agree in recommending it. – Ole V.V. Mar 13 '18 at 20:28
  • So this took a rather embarassing turn. I debugged the code with @KarolDowbecki's suggestion. And it turns out i just don't know what my legacy code actually does. At some point (for not really understandable reasons) it actually changes the calendar i deliver to it. Before i thought it would do all changes in clones. Giving the legacy code a clone solved the problem. – Mindbomber Mar 14 '18 at 14:10
  • Now i don't really know how to deal with this question. The question itself is now invalid(because the problem described didn't exist in the first place) but the answers are still valid. Stackoverflow tells me not to delete questions so other people can use the information given. But I am not sure whether or not this question can be regarded as helpful. – Mindbomber Mar 14 '18 at 14:16
  • You are right, you shold leave the question here so other readers may benefit from the answers. Good you solved it. – Ole V.V. Mar 14 '18 at 16:51

2 Answers2

2

That might not be the answer you are looking for but it would be much easier to write using the java.time package

public LocalDate getRandomGeburtstag() {
  LocalDate min = LocalDate.of(1919, 1, 1);
  LocalDate max = LocalDate.of(1999, 12, 31);
  long diff = max.toEpochDay() - min.toEpochDay();
  return LocalDate.ofEpochDay(min.toEpochDay() + (long) (Math.random() * diff));
} 

You current approach won't generate all the possible days e.g. 29, 30, 31. I assume this is a workaround but it's not needed when you operate on epoch millis.

Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
  • Since the project i am working on is not converted to java 8 yet, I sadly can't use `java.time` (would make a lot of things easier). About feb 30th : The current approach limits the range of `day` to 1-28. This limitation obviously is resolved with java 8. I will update the tags and variable names to clarify. – Mindbomber Mar 13 '18 at 09:58
  • Indeed you can use java.time in Java 6 and 7. Add [ThreeTen Backport](http://www.threeten.org/threetenbp/) to your project and (1) you can use the same functionality and (2) you’re future-proof. If you need `Calendar` objects for your legacy code, use [`DateTimeUtils.toGregorianCalendar(localDateFromAnswer.atStartOfDay(ZoneId.systemDefault()))`](http://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html#toGregorianCalendar(org.threeten.bp.ZonedDateTime)). – Ole V.V. Mar 13 '18 at 12:40
1

Perhaps you can find more info about Calendar's bizarre behaviour in these answers:

Basically, all these internal fields are not recalculated automatically when you call set, and the internal recalculations are arbitrary and you don't have much control over when it's done and what fields are recomputed.

The best alternative is to use the ThreeTen Backport, as suggested in the comments, so you can use LocalDate and all other functionalities of the java.time API - which doesn't have all this huge mess of Calendar.

But if you insist on using the old API, then there's an (ugly) alternative:

SimpleDateFormat sdf = new SimpleDateFormat("d-M-yyyy");
Date date = sdf.parse(day + "-" + month + "-" + year);

Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

The Date will have the time fields set to zero and the Calendar will have all the fields set (no ? when printing it).

Just reminding that the month value above is from 1 to 12, while in calendar.set methods, the accepted values are from 0 to 11 (another annoying behaviour of this terrible API).

diston
  • 129
  • 3