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.
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.
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.
I have a date in the string that looks like MON 07:15. I'm trying to parse this to a Date using this snippet of code:
System.out.println(new SimpleDateFormat("E kk:mm").parse("MON 07:15"));
Using the above code, prints a date that reads:
Mon Jan 05 07:15:00 EET 1970
I'd like the parse the date string to the next upcoming date. At the time of posting, my local date and time is Fri Aug 08 11:45:00 EEST 2014 and the next Monday will be on the 11th so the resultant date that I'm looking for is Mon Aug 11 07:15:00 EEST 2014. How can I parse this?
The day and time object that I'll be parsing will always be in the future.
I would separate parsing from everything else.
Parse the value as you're already doing, which gives you a date in the wrong year.
From that, take the time of day and the day of week - those are the important things. You can throw everything else away.
Next, take the current date and time, and perform whatever operations you need to in order to get to the right date/time. You should consider:
What time zone are you interested in?
If the current time is 06:00 on a Monday and you've been asked for 07:15 on a Monday, does that mean today or next Monday?
If the current time is 08:00 on a Monday and you've been asked for 07:15 on a Monday, does that mean today or next Monday?
By separating out the parsing from the computations, you can make the testing simpler two: test each of the operations separately. I'd advise using a clock abstraction of some kind to indicate "an object which can get you the current instant in time" - that way you can test all kinds of combinations of "desired day/time" and "current day/time".
Ideally, use java.time from Java 8 or Joda Time - both are much nicer than the java.util.* API.
Are you looking something like following?
DateFormat df = new SimpleDateFormat("E kk:mm");
Date date = df.parse("MON 07:15");
Date today = new Date();
Calendar calendar = Calendar.getInstance();
Calendar calendar1 = Calendar.getInstance();
calendar.setTime(today);
calendar1.setTime(date);
if (calendar.get(Calendar.DAY_OF_WEEK) == calendar1.get(Calendar.DAY_OF_WEEK)) {
String time = df.format(today);
Date t1 = df.parse(time);
if (t1.before(date)) {
calendar.set(Calendar.HOUR, calendar1.get(Calendar.HOUR));
calendar.set(Calendar.MINUTE, calendar1.get(Calendar.MINUTE));
calendar.set(Calendar.SECOND, 0);
System.out.println(calendar.getTime());
} else {
calendar.add(Calendar.DATE, 7);
calendar.set(Calendar.HOUR_OF_DAY, calendar1.get(Calendar.HOUR));
calendar.set(Calendar.MINUTE, calendar1.get(Calendar.MINUTE));
calendar.set(Calendar.SECOND, 0);
System.out.println(calendar.getTime());
}
} else {
int toDay = calendar.get(Calendar.DAY_OF_WEEK);
int givenDay = calendar1.get(Calendar.DAY_OF_WEEK);
int count = 7 - toDay + givenDay;
calendar.add(Calendar.DAY_OF_MONTH, count);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.HOUR_OF_DAY, calendar1.get(Calendar.HOUR));
calendar.set(Calendar.MINUTE, calendar1.get(Calendar.MINUTE));
System.out.println(calendar.getTime());
}
Out put:
Mon Aug 11 07:15:00 IST 2014
Leave me a comment telling me whether I got your question correct.
This answer addresses the second part, getting the next logical date from today.
Avoid .Date/.Calendar
The java.util.Date & .Calendar classes bundled with Java are notoriously troublesome. Avoid them.
Joda-Time or java.time
I suggest learning how to use a sophisticated date-time library. In Java that means either:
Joda-Time
java.time (built into Java 8, inspired by Joda-Time).
Time Zone
The time zone is crucial in determining the day and day-of-week. Use proper time zone names, never the 3 or 4 letter codes.
If you ignore time zone, the JVM’s current default time zone will be applied implicitly. This means different outputs when moving your app from one machine to another, or when a sys admin changes the time zone of host machine, or when any Java code in any thread of any app within the same JVM decides to call setDefault even during your app‘s execution.
Example Code To Get Next Day-Of-Week
Here is example code using Joda-Time 2.7.
Get the time zone you desire/expect. If working in UTC, use the constant DateTimeZone.UTC.
DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
Get the date-time value you need. Here I am using the current moment.
DateTime dateTime = DateTime.now( zone );
Specify the future day-of-week you want. Note that Joda-Time uses the sensible # 1 for first day of week, rather than zero-based counting found in java.util.Calendar. First day of week is Monday, per international norms and standards (not Sunday as is common in United States).
int dayOfWeek = DateTimeConstants.SATURDAY;
The withDayOfWeek command may go back in time. So we use a ternary operator (?:) to make sure we go forwards in time by adding a week as needed.
DateTime future = ( dateTime.getDayOfWeek() < dayOfWeek )
? dateTime.withDayOfWeek( dayOfWeek )
: dateTime.plusWeeks( 1 ).withDayOfWeek( dayOfWeek );
You may want to adjust the time-of-day to the first moment of the day to emphasize the focus on the day rather than a particular moment within the day.
future = future.withTimeAtStartOfDay(); // Adjust time-of-day to first moment of the day to stress the focus on the entire day rather than a specific moment within the day. Or use `LocalDate` class.
Dump to console.
System.out.println( "Next day # " + dayOfWeek + " after " + dateTime + " is " + future );
When run.
Next day # 6 after 2015-04-18T16:03:36.146-04:00 is 2015-04-25T00:00:00.000-04:00
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