Sometimes the date object gives me the wrong day of the week. My code is extremely simple.
//Setting to 2017, April, 12th
Date dt = new Date(2017, 3, 11);
System.out.printf("%TA", dt);
This gives me 'WEDNESDAY' which is correct. In fact it seems to work fine for any year in the recent past and near future.
However, if I set the time to 2999, August, 5th. Which is
//Setting to 2999, August, 5th
Date dt = new Date(2999, 7, 4);
System.out.printf("%TA", dt);
It returns 'TUESDAY' when actually August the 5th 2999 is a Monday.
Why is this happening? And how do I get around it?
Cheers!
Why is this happening?
The important part of your answer is already in the comments. That deprecatred constructor is confusing alright. You managed to get the month correct, which a lot of people don’t; you got the year wrong and the day-of-month wrong. Since you are using System.out.printf(), you may use it for checking too:
// Setting to 2017, April, 12th -- NOT
Date dt = new Date(2017, 3, 11);
System.out.printf("%1$tm %1$te,%1$tY%n", dt);
This prints
04 11,3917
What a coincidence that it happens to be a Wednesday as you had expected. I would suspect this coincidence to hold for years 2001 through 2099. It doesn’t hold in February 2000. February 12 that year was a Saturday, but:
// Incorrect attempt at setting to 2000, February, 12th
dt = new Date(2000, 1, 11);
System.out.printf("%TA%n", dt);
This prints
SUNDAY
And how do I get around it?
If you can use Java 8 or later, I recommend you stay away not only from the deprecated constructor, but from the Date class altoghether. These days we have a bunch of better alternatives.
// Setting to 2999, August, 5th
LocalDate ld = LocalDate.of(2999, Month.AUGUST, 5);
System.out.println(ld);
System.out.printf("%TA", ld);
This prints the correct
2999-08-05
MONDAY
I usually use a DateTimeFormatter with the new classes, but LocalDate works with System.out.printf() too as just shown. According to the docs it works with TemporalAccessor, which includes DayOfWeek, Instant, IsoEra, LocalDate, LocalDateTime, LocalTime, Month, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, ZoneOffset and more.
Related
I'm using following code. Values (hour, minute, second, date, month, year, day_of_week) are hardcoded for simplicity.
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 10);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.DATE, 2);
c.set(Calendar.MONTH, Calendar.APRIL);
c.set(Calendar.YEAR, 2018);
c.setFirstDayOfWeek(Calendar.MONDAY);
c.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
String date = String.format(Locale.US, "%1$tA %1$tb %1$td %1$tY at %1$tI:%1$tM %1$Tp", c);
Log.e("date", date);
Observation 1
This code prints the the following on the Titanium S1 (Android 4.1.2)
E/date: Monday Mar 26 2018 at 10:00 AM
This code prints the the following on the Moto G4+ (Android 7.0), Moto G5+ (Android 7.0)
E/date: Monday Apr 02 2018 at 10:00 AM
Observation 2
This difference appears only on 2nd April & 2nd July to my knowledge. I am seeing the first day of month is Sunday for both the April and July month. It works fine for rest of the days
Question
1) Does the first day of month (MONDAY) cause this difference? Changing it to SUNDAY seems working on these two days. But I am not sure this works in all the scenarios.
This is affecting users on my app. Please help. Thanks.
I've made some test in JDK 7 and the results don't seem to be affected by setFirstDayOfWeek. Actually, it seems to be related to the JVM default locale (java.util.Locale).
When the default locale is Japanese or Thai, the final result is March. Somehow, the JVM default locale affects the internals of Calendar, in some misterious ways that, I must admit, are beyond my understanding. But anyway, one way to fix it is to set in the Calendar the same locale you used in the output:
Calendar c = Calendar.getInstance(Locale.US);
This fixed the issue for me.
java.time API
This bizarre behaviour is one of the reasons to not use the old Calendar API (there are many others, btw).
In Android, it's possible to use the java.time API. In API level 26, the java.time classes are available. For lower levels, you can use this backport - and in this link there are instuctions to configure it in Android.
With this new API, it's much easier - and less error prone - to get what you want. To build a specific date/time, use a ZonedDateTime class, and a DateTimeFormatter to convert it to a String in a specific format:
// April 2nd 2018, 10 AM
ZonedDateTime dt = ZonedDateTime.of(2018, 4, 2, 10, 0, 0, 0, ZoneId.systemDefault());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEEE MMM dd yyyy 'at' hh:mm a", Locale.US);
String formattedDate = dt.format(fmt); // Monday Apr 02 2018 at 10:00 AM
Note that the day of week is adjusted automatically, based on the day/month/year values provided.
To get the current date/time, just use ZonedDateTime.now(ZoneId.systemDefault()) (to get the current date/time in JVM's default timezone), or use ZoneId.of("zone name") to use a specific timezone - replace "zone name" with any valid IANA zone's names.
When I run the following code:
int year = 2017;
int month = 7;
int dayOfMonth = 10;
Calendar dateOfBirth = new GregorianCalendar(year, month, dayOfMonth);
dateOfBirth.getTime(); // See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4827490
dateOfBirth.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println("Original Day Of Month:\t\t" + dayOfMonth);
System.out.println("Calendar:\t\t\t" + dateOfBirth);
System.out.println("Calendar substring:\t\t" + dateOfBirth.toString().substring(dateOfBirth.toString().indexOf("DAY_OF_MONTH"), dateOfBirth.toString().indexOf("DAY_OF_MONTH") + 15));
System.out.println("Formatted date:\t\t\t" + new SimpleDateFormat("dd").format(dateOfBirth.getTime()));
System.out.println("get(Calendar.DAY_OF_MONTH):\t" + dateOfBirth.get(Calendar.DAY_OF_MONTH));
I get this output:
Original Day Of Month: 10
Calendar: java.util.GregorianCalendar[time=1502312400000,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=7,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=10,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
Calendar substring: DAY_OF_MONTH=10
Formatted date: 10
get(Calendar.DAY_OF_MONTH): 9
As you can see, the value returned by dateOfBirth.get(Calendar.DAY_OF_MONTH) is incorrect.
I am using UTC because it's a date of birth, and I don't want it to be dependent on a particular time zone. And in order to do so correctly, I need to make the call to dateOfBirth.getTime() - see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4827490.
If I take out that call, this works correctly, but then I encounter other bugs.
Any idea what I can do to accurately get the correct day of the month?
I'm using Java v1.7.0_79, and, in case it matters, I'm currently in GMT+3.
Just set the date values after setting the timezone:
int year = 2017;
int month = Calendar.JULY;
int dayOfMonth = 10;
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.set(year, month, dayOfMonth);
System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
This outputs the day of the month correctly:
DAY_OF_MONTH: 10
Glad that the answer by Robby Cornelissen solved your problem. I should like to explain what happened and why that answer helped.
When you are using the three-arg GregorianCalendar constructor, it “Constructs a GregorianCalendar with the given date set in the default time zone with the default locale.” Quoting the docs; italics are mine. In your case this is GMT+3 (now; the important thing is that in August it will still be GMT plus something).
What the docs are not so clear about is that hour of day is set to 0, so you’ve really got a point in time of 10 August 2017 0:00 at zone offset +3:00. You would really have wanted the GregorianCalendar not to worry about time of day, but there’s no such thing. This is one of the reasons why I recommend LocalDate instead as I mentioned in a comment.
When you change the time zone, the point in time stays the same, but since you were ahead of GMT in your own time zone, you now have 9 August 2017 21:00 UTC. So 9 is the correct day-of-month value now.
I think the greatest surprise is that the toString method still produces DAY_OF_MONTH=10. When GregorianCalendar computes which fields is obscure to me, but obviously is still remembers the old value at this point.
For formatting you use new SimpleDateFormat("dd"). This formatter uses your default time zone of GMT+3, and the point in time from the calendar object, correctly producing 10. If you had wanted it to produce the day-of-month from the calendar, you might have set its time zone to UTC too.
dateOfBirth.get(Calendar.DAY_OF_MONTH) produces the correct value of 9.
The funny thing is that if after this call I repeat your line:
System.out.println("Calendar substring:\t\t"
+ dateOfBirth.toString().substring(dateOfBirth.toString().indexOf("DAY_OF_MONTH"),
dateOfBirth.toString().indexOf("DAY_OF_MONTH") + 15));
This time it produces:
Calendar substring: DAY_OF_MONTH=9,
So that field has now been computed.
In Robby Cornelissen’s code the calendar object is born with UTC time zone, so when you set the date, it is really set to — well, 10 July 2017 this time, but 0:00 UTC. This corresponds to 3:00 in your time zone, but the date is also 10 July there, so the rest of your code will produce the 10 you expected. However, users in time zones west of Greenwich will have problems now because their SimpleDateFormat will use their time zone, which is behind GMT, so they will see a formatted date of 09.
For the sake of completeness here’s the ThreeTen (Backport) version:
LocalDate date = LocalDate.of(2017, Month.AUGUST, 10);
System.out.println("Day of month:\t" + date.getDayOfMonth());
This prints
Day of month: 10
A LocalDate has got neither any time of day nor any time zone, leaving a lot less room for confusion. Contrary to Calendar it also numbers the months the way humans do, so 7 is July and 8 is August.
I have a GregorianCalendar instance for Tuesday September 2nd. The valñue is checked in milliseconds and is OK. I want another calendar which is the next Sunday (7th) at 23:59:59. So:
GregorianCalendar currentCalendar = MyClock.INSTANCE.getCurrentCalendar();
GregorianCalendar nextSunday =
(GregorianCalendar)currentCalendar.clone();
// GregorianCalendar uses Sunday as first day of week, so we must
// advance one week
int currentWeek = nextSunday.get(GregorianCalendar.WEEK_OF_YEAR);
nextSunday.set(GregorianCalendar.WEEK_OF_YEAR,
currentWeek + this.THIS_WEEK);
nextSunday.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.SUNDAY);
nextSunday.set(GregorianCalendar.HOUR_OF_DAY, 23);
nextSunday.set(GregorianCalendar.MINUTE, 59);
nextSunday.set(GregorianCalendar.SECOND, 59);
nextSunday.set(GregorianCalendar.MILLISECOND, 0);
So, since sunday is day number 1 of the week for the GregorianCalendar, and I am at week of year number 36, I add one week and then set the day to sunday.
The real problems comes now: when I execute in my development machine, with OpenJDK 1.7.0_55, it works perfectly. If I go to my test machine with OpenJDK 1.7.0_51, it does it all wrong:
Adds one week until tuesday 9th, and then goes to sunday 14th instead of sunday 7th.
I don't know if I am doing it right or wrong: what is really killing me is that the result depends on the machine, and I haven't found any difference at GregorianCalendar at those OpenJDK versions. Any explanation for this behaviour?
PD: Please stick to GregorianCalendar. I know is a bit shitty, but I don't want to use Joda Calendar or any other at current stage of development.
EDIT: I found method setWeekDate(year, week_of_year, day_of_week). One would think that setting year, week and day of week into the same method will grant it will succeed. It does not: still going from 2nd to 14th. What monkey wrote this?
I've made slight alterations to your code:
SimpleDateFormat sdf = new SimpleDateFormat("dd MM yyyy - HH:mm:ss.SSSS Z");
GregorianCalendar currentCalendar = (GregorianCalendar) Calendar.getInstance();
currentCalendar.set(Calendar.DAY_OF_MONTH, 2);
System.out.println(sdf.format(currentCalendar.getTime()));
GregorianCalendar nextSunday = (GregorianCalendar) currentCalendar.clone();
// GregorianCalendar uses Sunday as first day of week, so we must
// advance one week
int currentWeek = nextSunday.get(GregorianCalendar.WEEK_OF_YEAR);
nextSunday.set(GregorianCalendar.WEEK_OF_YEAR, currentWeek + 1);
nextSunday.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.SUNDAY);
nextSunday.set(GregorianCalendar.HOUR_OF_DAY, 23);
nextSunday.set(GregorianCalendar.MINUTE, 59);
nextSunday.set(GregorianCalendar.SECOND, 59);
nextSunday.set(GregorianCalendar.MILLISECOND, 0);
System.out.println(sdf.format(nextSunday.getTime()));
This outputs:
02 12 2014 - 19:40:46.0250 +0200
07 12 2014 - 23:59:59.0000 +0200
Which is correct. However, I have two things to point out:
check the value of this.THIS_WEEK. I have substituted it for the value 1 and it works ok on my machine.
check the timezone on both machines (in my case GMT+2). Since both machines use the same code which both initialize the values and use them, there shouldn't really be problems. But if you use the values as milliseconds on a different machine (e.g. exposing the value through a webservice or something), you might hit problems.
I would try using java.util.Calendar method add(int field, int amount) instead.
nextSunday.add(Calendar.DAY_OF_MONTH,5). One line of code instead of three.
I'd like to have a function which returns the next week and year given a week and year. It looks something like:
public static int[] getNextWeek(int year, int week) {
Calendar c = Calendar.getInstance(); // I'm Locale.US and TimeZone "America/New_York"
c.clear();
c.set(Calendar.YEAR, year);
c.set(Calendar.WEEK_OF_YEAR, week);
c.add(Calendar.WEEK_OF_YEAR, 1);
return new int[] {c.get(Calendar.YEAR), c.get(Calendar.WEEK_OF_YEAR)}
}
This sometimes does not work around year boundaries. It seems to depend on what day you invoke it and for what parameters you use! For example, if I invoke it with year 2012 and week 52 then I expect the result to be year 2013 and week 1. If you invoke it today (Tuesday July 17 2012) it works. If you set the day of week to yesterday it does not; and oddly results in year 2012 week 1. Weird. What is going on? It appears to relate to the day of the week because it doesn't work if invoked with SUNDAY or MONDAY, which are the last two days of 2012! If I set the day of the week to the last day of the week the function seems to work; before calling Calendar.add() I do:
// Must set to last day of week; initially set to first day due to API
c.set(Calendar.DAY_OF_WEEK, c.getFirstDayOfWeek()); // SUNDAY
c.add(Calendar.DAY_OF_WEEK, 6); // Must roll forward not backwards
There doesn't seem to be any weirdness like this if I create a getPreviousWeek method. Is this a java.util.Calendar bug? Next time I guess I'll use Joda time!
Have you considered the fact that ISO8601 week algorithm considers the first week of the year to be the one that has at least 4 days fall within that year?
So if the Jan 1 is on thurdsay, that week is actually considered week 52 of the previous year, not week one of the current year.
You might want to at least consider joda-time for this kind of calculation, as it has proper handling of the ISO standard.
LocalDate ld = new LocalDate();
ld = ld.withWeekOfWeekyear(29);
ld = ld.withWeekyear(2012);
System.out.println(ld.getWeekOfWeekyear());
System.out.println(ld.getWeekyear());
// 29
// 2012
System.out.println(ld.plusWeeks(1).getWeekOfWeekyear());
System.out.println(ld.plusWeeks(1).getWeekyear());
// 30
// 2012
And this will work across year boundaries.
Just do an explicit check to see if there's a rollover.
return new int[] {c.get(Calendar.WEEK_OF_YEAR) == 1 ? c.get(Calendar.YEAR) + 1 :
c.get(Calendar.YEAR), c.get(Calendar.WEEK_OF_YEAR)};
As far as I can tell, the reason why WEEK_OF_YEAR doesn't add correctly is because it's not an actual property of Calendar like SECOND, MINUTE, MONTH, DAY, or YEAR. It's a derived property like WEEK_OF_MONTH. One way to get around this is to use c.add(Calendar.DAY, 7) to add exactly 7 days instead of incrementing WEEK_OF_YEAR.
At this point I'm going to assume this really is a bug. I've filled a bug report with Oracle here:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7197761
I have set this calendar in my app:
Calendar cal = Calendar.getInstance();
cal.set(2010, 1, 10);
I'm using this SimpleDateFormat to get the month as a word:
SimpleDateFormat formatMonth = new SimpleDateFormat("MM");
In one class it comes out as February 10th, 2010.
In another it comes out as March 10th, 2010.
Which is correct?
The month argument to Calendar.set() is zero-based, so 1 means February. The first behavior is the correct one.
I suspect something in your other class is mistakenly trying to compensate for zero-based month indexes, and that results in an off-by-one error.
The calendar class has constants for months
Calendar.JANUARY
for example. You should be using those.
Given all the hassles associated with the JDK date handling, you should really be looking to use Joda time instead. The above code would be rewritten as
DateTime dt = new DateTime().withDate(2010,2,10).withTime(12,13,14,0);
and represents 10 February 2010 at 12:13:14.000 in UTC. No ambiguity, thread safe and immutable.
You should note that SimpleDateFormat is not thread safe.