Reputation: 1537
I tried to add full months from a given start date by using java DateTime
and method plusMonths()
.
When my start time is at the beginning of a month, everything works like expected:
DateTime startOfMonth = new DateTime(2013, 1, 1, 00, 00, 00);
System.out.println(startOfMonth.toString());
for (int i = 0; i < 12; i++) {
startOfMonth = startOfMonth.plusMonths(1);
System.out.println(startOfMonth.toString());
}
The output is the first day of every month like expected and everything is great!
2013-01-01T00:00:00.000+01:00
2013-02-01T00:00:00.000+01:00
2013-03-01T00:00:00.000+01:00
2013-04-01T00:00:00.000+02:00
2013-05-01T00:00:00.000+02:00
2013-06-01T00:00:00.000+02:00
2013-07-01T00:00:00.000+02:00
2013-08-01T00:00:00.000+02:00
2013-09-01T00:00:00.000+02:00
2013-10-01T00:00:00.000+02:00
2013-11-01T00:00:00.000+01:00
2013-12-01T00:00:00.000+01:00
2014-01-01T00:00:00.000+01:00
But when I change my example to the end of a month it doesn't return what I want!
System.out.println("");
DateTime endOfMonth = new DateTime(2012, 12, 31, 23, 59, 59);
System.out.println(endOfMonth.toString());
for (int i = 0; i < 12; i++) {
endOfMonth = endOfMonth.plusMonths(1);
System.out.println(endOfMonth.toString());
}
This returns:
2012-12-31T23:59:59.000+01:00
2013-01-31T23:59:59.000+01:00
2013-02-28T23:59:59.000+01:00
2013-03-28T23:59:59.000+01:00
2013-04-28T23:59:59.000+02:00
2013-05-28T23:59:59.000+02:00
2013-06-28T23:59:59.000+02:00
2013-07-28T23:59:59.000+02:00
2013-08-28T23:59:59.000+02:00
2013-09-28T23:59:59.000+02:00
2013-10-28T23:59:59.000+01:00
2013-11-28T23:59:59.000+01:00
2013-12-28T23:59:59.000+01:00
So, why is "2013-02-28T23:59:59.000+01:00"
plus one month not "2013-03-31T23:59:59.000+01:00"
?
Where are these three days?
Upvotes: 4
Views: 7540
Reputation: 338594
Your Question is an example of why tracking a span of time is usually best done using the "half-open" approach. In Half-Open ( notation: [)
), the beginning if the time span is inclusive while the ending is exclusive. So "a month" means the first moment of the first day of the month and running up to but not including the first moment of the first day of the following month.
The Joda-Time library offers a class to represent such spans of time, Interval.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Zurich" );
DateTime firstOfYear = new DateTime( 2013, 1, 1, 0, 0, 0, timeZone ).withTimeAtStartOfDay();
Interval month01_Interval = new Interval( firstOfYear, firstOfYear.plusMonths( 1 ).withTimeAtStartOfDay() );
When run:
month01_Interval : 2013-01-01T00:00:00.000+01:00/2013-02-01T00:00:00.000+01:00
Upvotes: 1
Reputation: 29
Add one day before incrementing the month, and subtract one day afterwards:
new DateTime(date).plusDays(1).plusMonths(months).minusDays(1).toDate()
Upvotes: 0
Reputation: 1
The best solution for such calculations I think is to fix the original seed date and keep incrementing the number of months to add as you loop through. That way you won't keep accumulating any adjustments as you go along. Try this:
System.out.println("");
DateTime endOfMonth = new DateTime(2012, 12, 31, 23, 59, 59);
System.out.println(endOfMonth.toString());
for (int i = 0; i < 12; i++) {
DateTime newEndOfMonth = endOfMonth.plusMonths(i+1);
System.out.println(newEndOfMonth.toString());
}
Upvotes: 0
Reputation: 71
As mentioned in a comment above, the Joda way for getting the last day of the month is to get the maximum value on the dayOfMonth property.
public static void endOfMonth() {
DateTime startOfMonth = new DateTime(2013, 1, 1, 00, 00, 00);
for (int i = 0; i < 12; i++) {
int lastDay = startOfMonth.dayOfMonth().getMaximumValue();
System.out.println(startOfMonth.withDayOfMonth(lastDay).toString());
startOfMonth = startOfMonth.plusMonths(1);
}
}
This produces:
2013-01-31T00:00:00.000-06:00
2013-02-28T00:00:00.000-06:00
2013-03-31T00:00:00.000-05:00
2013-04-30T00:00:00.000-05:00
2013-05-31T00:00:00.000-05:00
2013-06-30T00:00:00.000-05:00
2013-07-31T00:00:00.000-05:00
2013-08-31T00:00:00.000-05:00
2013-09-30T00:00:00.000-05:00
2013-10-31T00:00:00.000-05:00
2013-11-30T00:00:00.000-06:00
2013-12-31T00:00:00.000-06:00
Upvotes: 1
Reputation: 177
This works for me:
public static void main(String[] args) {
Calendar endOfMonth = Calendar.getInstance();
endOfMonth.set(Calendar.DAY_OF_MONTH, 31);
endOfMonth.set(Calendar.MONTH, 11);
endOfMonth.set(Calendar.YEAR, 2012);
endOfMonth.set(Calendar.HOUR_OF_DAY, 23);
endOfMonth.set(Calendar.MINUTE, 59);
endOfMonth.set(Calendar.SECOND, 59);
endOfMonth.set(Calendar.MILLISECOND, 59);
System.out.println(endOfMonth.getTime());
for (int i = 0; i < 12; i++) {
endOfMonth.add(Calendar.MONTH, 1);
if(i >= 2){
endOfMonth.add(Calendar.MONTH, 1);
endOfMonth.set(Calendar.DAY_OF_MONTH, 1);
endOfMonth.add(Calendar.DAY_OF_MONTH, -1);
}
System.out.println(endOfMonth.getTime());
}
}
and the result is:
Mon Dec 31 23:59:59 GMT+04:00 2012
Thu Jan 31 23:59:59 GMT+04:00 2013
Thu Feb 28 23:59:59 GMT+04:00 2013
Sun Mar 31 23:59:59 GMT+04:00 2013
Tue Apr 30 23:59:59 GMT+04:00 2013
Fri May 31 23:59:59 GMT+04:00 2013
Sun Jun 30 23:59:59 GMT+04:00 2013
Wed Jul 31 23:59:59 GMT+04:00 2013
Sat Aug 31 23:59:59 GMT+04:00 2013
Mon Sep 30 23:59:59 GMT+04:00 2013
Thu Oct 31 23:59:59 GMT+04:00 2013
Sat Nov 30 23:59:59 GMT+04:00 2013
Tue Dec 31 23:59:59 GMT+04:00 2013
Upvotes: 0
Reputation: 328594
The problem with date operations is that months have different number of days. In January, you have 31 days, February has only 28. If you add "one month" to January 31st, the software can't guess what you want to achieve, so it adds increments the month which gives you February, 31st - which isn't valid. The next step is then to reconcile the date which yields these odd results that you're seeing.
Note: In the original Java Date classes, you'd get 2nd or 3rd of March after adding one month to January which isn't exactly better :-)
The correct way to iterate over the end of month is to iterate over the first day of then month and the subtract one day (or one millisecond):
DateTime startOfMonth = new DateTime(2013, 1, 1, 00, 00, 00);
System.out.println(startOfMonth.toString());
for (int i = 0; i < 12; i++) {
startOfMonth = startOfMonth.plusMonths(1);
DateTime endOfMonth = startOfMonth.minusDays(1); // magic here
System.out.println(startOfMonth + "-" + endOfMonth);
}
If you just need a date range, use an half open range [start,end)
where end
is always the first of a month.
Upvotes: 7
Reputation: 177
You can use java Calendar object if you want to work with date and time. Read about java Calendar object and you can make this work.
for example in Calendar object you can do this:
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.MONTH, 1);
Upvotes: 0
Reputation: 533510
You can't change to february the 31 and there is no way for the object to remember you meant the last day of the month rather than the 28th.
Instead I suggest you use the first day of each month and subtract one milli-second. This way the calculation will worth out the way you want.
Upvotes: 1
Reputation: 93842
Because there's only 28 days in Februar 2013. So when you're adding one month after, it will keep to be the 28th of each month.
This is specified in the doc :
The calculation will do its best to only change the month field retaining the same day of month. However, in certain circumstances, it may be necessary to alter smaller fields. For example, 2007-03-31 plus one month cannot result in 2007-04-31, so the day of month is adjusted to 2007-04-30.
Upvotes: 4