Add decimal month to Date in java - java

i need to add a decimal amount of month to a java date :
-> i can use this code with joda time api to add a natural amount of months to a date. But how can i add a decimal amount of month ( for example 3.5) to a date ?
Date date = new Date();
DateTime dateTime = new DateTime(date);
dateTime = dateTime.plusMonths(3);
Date newDate = dateTime.toDate();

This will give you an approximation. It’s the best you can get.
long oneMonthInNanos = ChronoUnit.MONTHS.getDuration().toNanos();
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Phnom_Penh"));
System.out.println("Now " + now);
System.out.println("In 3.5 months " + now.plusNanos(Math.round(3.5 * oneMonthInNanos)));
System.out.println("In 4.5 months " + now.plusNanos(Math.round(4.5 * oneMonthInNanos)));
System.out.println("In 12 months " + now.plusNanos(Math.round(12.0 * oneMonthInNanos)));
Output when I ran the code just now, was:
Now 2018-11-06T22:31:36.460573+07:00[Asia/Phnom_Penh]
In 3.5 months 2019-02-21T11:13:27.460573+07:00[Asia/Phnom_Penh]
In 4.5 months 2019-03-23T21:42:33.460573+07:00[Asia/Phnom_Penh]
In 12 months 2019-11-07T04:20:48.460573+07:00[Asia/Phnom_Penh]
As has been said in comments, there is no really good definition of a fractional number of months. When you compare the first and the last date-time you also clearly see that 12 months don’t add up to a year precisely, though pretty close. Please check yourself whether the results are good enough for your purpose.
I am using java.time. I din’t know whether something similar is possible in Joda-Time.

Related

What is a best way to find the number of days and months between two java.time.Instant objects?

NOTE: search Google before marking this question as duplicate. I did search and browse this question and all answers that I found were either for LocalDate, Joda or legacy Java Date.
It took me quite some time to investigate this so I've decided to share this as an answer.
I'd like a way to calculate the (approximate) number of months and days between two Java Instants (objects of java.time.Instant)?
First, what you are asking is not well-defined. For example between the instants 2020-03-01T06:00:00Z and 2020-03-31T05:00:00Z could be:
29 days 23 hours in Australia/Melbourne time zone;
30 days in Europe/Paris time zone;
1 month 1 day in America/Los_Angeles time zone.
Accurate result in a given time zone
ZoneId zone = ZoneId.of("America/Los_Angeles");
Instant start = Instant.parse("2020-03-01T06:00:00Z");
Instant end = Instant.parse("2020-03-31T05:00:00Z");
ZonedDateTime startZdt = start.atZone(zone);
LocalDate startDate = startZdt.toLocalDate();
ZonedDateTime endZdt = end.atZone(zone);
LocalDate endDate = endZdt.toLocalDate();
Period p = Period.between(startDate, endDate);
if (startZdt.plus(p).isAfter(endZdt)) {
// The time of day on the end date is earlier, so don’t count a full date
endDate = endDate.minusDays(1);
p = Period.between(startDate, endDate);
}
System.out.println(p);
Output:
P1M1D
Read as a period of 1 month 1 day.
Approximate result independent of time zone
Prefer to leave as much of the calculation to java.time as possible. This includes the estimate of the length of a month.
Duration diff = Duration.between(start, end);
Duration durationOfAMonth = ChronoUnit.MONTHS.getDuration();
long months = diff.dividedBy(durationOfAMonth);
diff = diff.minus(durationOfAMonth.multipliedBy(months));
long days = diff.toDays();
System.out.println("" + months + " months " + days + " days");
0 months 29 days
I've opted out to approximate solution (it assumes all months have 30.44 days). I've opted out to use something like this:
Duration duration = Duration.between(instant1, instant2).abs(); /* if want negative values remove .abs() */
long hours = duration.toHours();
double daysAndMonthsInDays = hours / 24.0;
int months = daysAndMonthsInDays / 30.44; //average number of days per month
int days = daysAndMonthsInDays - months * 30.44;
Please post another answer if there is a better solution using Duration class or something else. I've decided not to convert Instant to LocalDate and to perform the conversion on that level. That would not use an approximation of 30.44 days in a month, but rather the actual number.

Remaining years, months, days using joda time (Android)

I am trying to obtaining remaining years, months, and days between two dates:
So I have used Joda Time to do so:
DateTime endDate = new DateTime(2018,12,25,0,0);
DateTime startDate = new DateTime();
Period period = new Period(startDate,endDate,PeriodType.yearMonthDay());
PeriodFormatter formatter = new PeriodFormatterBuilder().appendYears().appendSuffix(" Year ").
appendMonths().appendSuffix(" Month ").appendDays().appendSuffix(" Day ").appendHours()..toFormatter();
String time = formatter.print(period);
This gives me string time: 2 Year 4 Month 22 Day
However, I want integer values of each number of remaining years, months, days.
So, Instead of "2 Year 4 Month 22 Day", I want to set my variables:
int year = 2
int month = 4
int day = 22
Is there any way to obtain these values separately instead of obtaining one string? Thank you so much! :)
i had the same requirement once ,here is the code snippet
LocalDate d=LocalDate.of(yy,mm,dd);
LocalDate d2=LocalDate.of(yy, mm, dd);
Period p=Period.between(d, d2);
long day,month,year;
day=p.getDays();
month=p.getMonths();
year=p.getYears();
System.out.println(day+" : "+month+" : "+year);
Invoke the methods provided by the DateTime class and just subtract them. An example for years is below:
int year = (int) dateTime#year#getField() - (int) dateTime2#year#getField()
UNTESTED code!! I'll be looking into it later but the general idea is the same, get the field information then just subtract it to get a value

Java date comparison off by a day

I have a Java method which compares two Dates and returns the number of days between them, but it's off by a day.
Even after I 0 out the hours, min, and sec the calculation is still off.
public long compareDates(Date exp, Date today){
TimeZone tzone = TimeZone.getTimeZone("America/New_York");
Calendar expDate = Calendar.getInstance();
Calendar todayDate = Calendar.getInstance();
expDate.setTime(exp);
todayDate.setTime(today);
expDate.set(Calendar.HOUR_OF_DAY, 0);
expDate.set(Calendar.MINUTE, 0);
expDate.set(Calendar.SECOND, 0);
todayDate.set(Calendar.HOUR_OF_DAY, 0);
todayDate.set(Calendar.MINUTE, 0);
todayDate.set(Calendar.SECOND, 0);
logger.info("Today = " + Long.toString(todayDate.getTimeInMillis()) + " Expiration = " + Long.toString(expDate.getTimeInMillis()));
expDate.setTimeZone(tzone);
todayDate.setTimeZone(tzone);
return (expDate.getTimeInMillis()-todayDate.getTimeInMillis())/86400000;
}
Output
Today = 1453939200030 Expiration = 1454544000000
There's 7 days between 1/28 and 2/4 but this returns 6.
Well, as you can see, you didn't clear the milliseconds, and 1454544000000 - 1453939200030 = 604799970 and dividing by 86400000 gets you 6.99999965277777..., which means 6 when truncated to int.
Now, if you clear the milliseconds too, today becomes 1453939200000, which will lead to you answer 7.
Note: This doesn't mean you're done, because of Daylight Savings Time. With DST, one of the timestamps may be ±1 hour from the other, so you may still get that truncation issue.
This was an answer to your particular issue. Try searching for how to correctly find days between dates in Java.
Today = 1453939200030
The times are given in milliseconds, and it looks like somehow your inputted Date has 30 extra milliseconds on it.
When I subtract the 30 milliseconds, then do the math on a calculator, I get 7 days. With your figures as is, I get 6.9999996527777777777777777777778, and in long math, the decimal figures get truncated to 6.
Zero out the milliseconds also.
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
java.time
The Question and other Answers use outmoded classes. The old date-time classes such as java.util.Date/.Calendar bundled with the earliest versions of Java have proven to be quite troublesome. Those old classes have been supplanted by the java.time framework in Java 8 and later.
As the other Answers point out correctly, the issue is that the start long has 30 on the right side, precluding a whole-day calculation.
Count-Of-Days Definition
Furthermore you must define what you mean by a count-of-days. Do you mean a count by date, so any time on the 3rd of January to any time on the 4th is one day even if the times were a minute before and after midnight? Or do you mean a count of generic 24-hour blocks of time while ignoring the fact that particular days in particular time zones are not always 24-hours long because of Daylight Saving Time (DST) and other anomalies?
Count Days By Date
If you want the former, count by dates, then make use of the LocalDate class (a date-only without time-of-day nor time zone) and the Period class (a span of time defined as a count of years, months, days) found in java.time.
Define your inputs. Use long rather than int. These numbers apparently represent a count of milliseconds since the first moment of 1970 in UTC.
long startMilli = 1_453_939_200_030L;
long stopMilli = 1_454_544_000_000L;
Convert those long numbers into Instant objects, a moment on the timeline in UTC.
Instant startInstant = Instant.ofEpochMilli ( startMilli );
Instant stopInstant = Instant.ofEpochMilli ( stopMilli );
Define the time zone in which you want to consider the calendar dates. Note that time zone is crucial in defining dates. The date is not simultaneously the same around the globe. The date varies by time zone.
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
Apply that time zone to each Instant to produce ZonedDateTime.
ZonedDateTime startZdt = ZonedDateTime.ofInstant ( startInstant , zoneId );
ZonedDateTime stopZdt = ZonedDateTime.ofInstant ( stopInstant , zoneId );
To get a Period, we need “local” dates. By “local” we mean any particular locality, a generic date value. The LocalDate class contains no time zone, but the time zone contained with in the ZonedDateTime is applied when determining a LocalDate.
LocalDate startLocalDate = startZdt.toLocalDate ();;
LocalDate stopLocalDate = stopZdt.toLocalDate ();
Define our span of time as a count of generic days, in Period.
Period period = Period.between ( startLocalDate , stopLocalDate );
Interrogate the Period to ask for the number of generic days contained within.
int days = period.getDays ();
Dump to console.
System.out.println ( "milli: " + startMilli + "/" + stopMilli + " | Instant: " + startInstant + "/" + stopInstant + " | ZonedDateTime: " + startZdt + "/" + stopZdt + " | LocalDate: " + startLocalDate + "/" + stopLocalDate + " | period: " + period + " | days: " + days );
milli: 1453939200030/1454544000000 | Instant: 2016-01-28T00:00:00.030Z/2016-02-04T00:00:00Z | ZonedDateTime: 2016-01-27T19:00:00.030-05:00[America/Montreal]/2016-02-03T19:00-05:00[America/Montreal] | LocalDate: 2016-01-27/2016-02-03 | period: P7D | days: 7
Count Of Whole Days
If you want a count of whole days, use the Days class from ThreeTen-Extra. Notice in the output below that we get a count of six (6) days rather than seven (7) as seen above.
ThreeTen-Extra
The ThreeTen-Extra project extends java.time. Run by the same folks who built java.time.
The behavior of the between method is not documented clearly. Experimenting shows that it seems to based on 24-hour chunks of time, not dates. Replace the 030 with 000, and also try replacing in the stopMilli the last 000 with 030, to see the behavior for yourself.
Days daysObject = Days.between ( startZdt , stopZdt );
int daysObjectCount = daysObject.getAmount ();
Dump to console. The P6D string you see in the output was generated according to the formats defined in the ISO 8601 standard. This standard is used by default in java.time for all parsing and generating of textual representations of date-time values. These standard formats are quite sensible and useful so do glance at that linked Wikipedia page.
System.out.println ( "daysObject: " + daysObject + " | daysObjectCount: " + daysObjectCount );
daysObject: P6D | daysObjectCount: 6
To fix my problems, I have zeroed out the milliseconds as mentioned, as well as casted the longs to doubles in order to maintain accuracy and round when necessary.
expDate.setTime(exp);
todayDate.setTime(today);
expDate.setTimeZone(tzone);
todayDate.setTimeZone(tzone);
expDate.set(Calendar.HOUR_OF_DAY, 0);
expDate.set(Calendar.MINUTE, 0);
expDate.set(Calendar.SECOND, 0);
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.HOUR_OF_DAY, 0);
todayDate.set(Calendar.MINUTE, 0);
todayDate.set(Calendar.SECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
double diff = ((double)expDate.getTimeInMillis()-(double)todayDate.getTimeInMillis())/86400000;
return Math.round(diff);

Calculating years between from a leap year

When calculating years between two dates, where the second date is calculated from the first one (this is a simplified example of what I'm working on), LocalDate and Period seem to calculate a year slightly differently.
For example,
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plusYears(1);
System.out.println(Period.between(date, plusYear).getYears());
while
LocalDate date = LocalDate.of(1996, 3, 29);
LocalDate plusYear = date.plusYears(1);
System.out.println(Period.between(date, plusYear).getYears());
Despite having explicitly added a year, first Period return the years as 0, while the second case returns 1.
Is there a neat way around this?
This question has a philosophical nature and spans few problems like time measurements, and date format conventions.
LocalDate is an implementation of ISO 8601 date exchange standard.
Java Doc states explicitly that this class does not represent time but provides only standard date notation.
The API provides only simple operations on the notation itself and all calculations are done by incrementing the Year, or Month, or Day of a given date.
In other words, when calling LocalDate.plusYears() you are adding conceptual years of 365 days each, rather than the exact amount of time within a year.
This makes Day the lowest unit of time which one can add to a date expressed by LocalDate.
In human understanding, date is not a moment in time, but it is a period.
It starts with 00h 00m 00s (...) and finishes with 23h 59m 59s (...).
LocalDate however avoids problems of time measurement and vagueness of human time units (hour, day, month, and a year can all have different length) and models date notation simply as a tuple of:
(years, months within a year, days within a month )
calculated since the beginning of the era.
In this interpretation, it makes sense that Day is the smallest unit affecting the date.
As an example following:
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusSecond = date.plus(1, ChronoUnit.SECONDS);
returns
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
... which shows, that using LocalDate and adding the number of seconds (or smaller units to drive the precision), you could not overcome the limitation listed in your question.
Looking at the implementation you find that LocalDate.plusYears() after adding the years, calls resolvePreviousValid(). This method then checks for leap year and modifies the day field in the following manner:
day = Math.min(day, IsoChronology.INSTANCE.isLeapYear((long)year)?29:28);
In other words it corrects it by effectively deducting 1 day.
You could use Year.length() which returns the number of days for given year and will return 366 for leap years. So you could do:
LocalDate plusYear = date.plus(Year.of(date.getYear()).length(), ChronoUnit.DAYS);
You will still run into following oddities (call to Year.length() replaced with the day counts for brevity):
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns
1997-02-28
0y 11m 30d
then
LocalDate date = LocalDate.of(1996, 3, 29);
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns
1997-03-29
1y 0m 0d
and finally:
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plus(366, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns:
1997-03-01
1y 0m 1d
Please note that moving the date by 366 instead of 365 days increased the period from 11 months and 30 days to 1 year and 1 day (2 days increase!).

How should I properly compare two dates?

Having some trouble implementing this simple task.
Basically I want to compare two dates(some older date vs new date). I want to know if the older date is more than x months old and y days old.
int monthDiff = new Date().getMonth() - detail.getCdLastUpdate().getMonth();
int dayDiff = new Date().getDay() - detail.getCdLastUpdate().getMonth();
System.out.println("\tthe last update date and new date month diff is --> " + monthDiff);
System.out.println("\tthe last update date and new date day diff is --> " + dayDiff);
If older date is 2012-09-21 00:00:00.0, currently, it will return negative numbers. I need to find out if the older date is EXACTLY 6 months and 4 days before new Date(). I'm thinking of using absolute values of both but just can't brain today.
Edit: I know about joda but I cannot use it. I must use Java JDK.
Edit 2: I'll try out the methods listed, if all failed I'll use Joda.
JDK dates have before and after methods, returning boolean, to accomplish your task:
Date now = new Date();
Calendar compareTo = Calendar.getInstance();
compareTo.add(Calendar.MONTH, -6);
compareTo.add(Calendar.DATE, -4);
if (compareTo.getTime().before(now)) {
// after
} else {
// before or equal
}
The best way I can think of is to use Joda-Time library. Example from their site:
Days d = Days.daysBetween(startDate, endDate);
int days = d.getDays();
Or number of months:
Months m = Months.monthsBetween(startDate, endDate)
int months = m.getMonths();
where:
DateTime startDate = new DateTime(/*jdk Date*/);
DateTime endDate = new DateTime(/*jdk Date*/);
Sigh, it is up to me to add the inevitable "use JodaTime" answer.
JodaTime gives you specific data types for all significant time distances.
Date yourReferenceDate = // get date from somewhere
int months = Months.monthsBetween(
new DateTime(yourReferenceDate),
DateTime.now()
).getMonths();

Categories