How do I compare two Periods in java 8?
E.g.
Period one = Period.of(10,0,0);
Period two = Period.of(8,0,0);
here in this case one is greater than two.
It is true that the comparison of two Period objects does not make sense in a general case, due to the undefined standard length of a month.
However, in many situations you can quite well live with an implementation similar to that which follows. The contract will be similar to the contract of compareTo():
public int comparePeriodsApproximately(Period p1, Period p2) {
return period2Days(p1) - period2Days(p2);
}
private int period2Days(Period p) {
if (p == null) {
return 0;
}
return (p.getYears() * 12 + p.getMonths()) * 30 + p.getDays();
}
Rightly said by JB Nizet.
You cannot compare Periods, as per java doc in Period class there is similar class to Period (Duration) available in java, you can use that depends on your business requirement.
"Durations and periods differ in their treatment of daylight savings time when added to ZonedDateTime. A Duration will add an exact number of seconds, thus a duration of one day is always exactly 24 hours. By contrast, a Period will add a conceptual day, trying to maintain the local time."
Period period = Period.of(10, 0, 0);
Period period2 = Period.of(10, 0, 0);
// No compareTo method in period
System.out.println(period.compareTo(period2));
Duration duration = Duration.ofDays(3);
Duration duration2 = Duration.ofDays(3);
// Can compare durations as it gives you the exact time
System.out.println(duration.compareTo(duration2));
In case you have a period of months and period of years you can do as follows:
Period periodOfMonths = Period.ofMonths(16);
Period periodOfYears = Period.ofYears(1);
// Check if period of months is ghreather that the period of years
System.out.println(periodOfMonths.toTotalMonths() > periodOfYears.toTotalMonths());
i like the answer of Martin Cassidy, but propose to use a hard coded LocalDate instead of LocalDate.now, for example LocalDate.MIN
public static int compareTo(final Period first, final Period second)
{
return LocalDate.MIN.plus(first).compareTo(LocalDate.MIN.plus(second));
}
This one should be bullet-proof. Whether it’s useful?
I claim that you get the smallest possible amount of days from a given number of years and months by counting forward from February 1, 2097 since you are first counting February (28 days) and it takes a long time until you get two months with 31 days in a row (July and August) and still longer until you hit a leap year (2104). Also 2200 and 2300 are non-leap years, should the counting reach that far. Conversely you get the greatest possible number of days when counting backward from the same date. You start by counting through 2 months # 31 days (January and December). The first February you encounter is in 2096, a leap year, so 29 days. And 2000 is a leap year.
So my trick is to count the difference between the two periods both ways from the mentioned date. If counts agree that the difference is positive (or negative), then two (or one) will always be longer no matter which day we count from.
In some cases the counts will not agree. Then I refuse to call a longer period.
Period one = Period.of(10, 0, 0);
Period two = Period.of(8, 0, 0);
if (one.normalized().equals(two.normalized())) {
System.out.println("They are the same.");
} else {
Period diff = two.minus(one);
LocalDate anchor = LocalDate.of(2097, Month.FEBRUARY, 1);
LocalDate plusDiff = anchor.plus(diff);
LocalDate minusDiff = anchor.minus(diff);
if (plusDiff.isAfter(anchor) && minusDiff.isBefore(anchor)) {
System.out.println("Two is greater (unambiguously)");
} else if (plusDiff.isBefore(anchor) && minusDiff.isAfter(anchor)) {
System.out.println("One is greater (unambiguously)");
} else {
System.out.println("Cannot decide");
}
}
Output in this case (using the periods from the question):
One is greater (unambiguously)
The normalized method I am using in the first comparison converts between years and months ensuring that the months are in the interval from -11 through +11 and have the same sign as the years (except if years are 0). This can be done unambiguously since there are always 12 months in a year.
You may be able to refine it to report that one period is longer than or equal to the other. For example if one is a month and two is 31 days, we know that two is at least as long as one, even though we can’t decide whether it’s also strictly longer.
This can be done with a proper compareTo() approach by borrowing from LocalDate.
public static int compareTo(final Period first, final Period second)
{
final LocalDate now = LocalDate.now();
return now.plus(first).compareTo(now.plus(second));
}
Related
I'm trying to find how many months are between 2 dates. My code is something like this right now
ChronoUnit.MONTHS.between(d1, d2)
The problem is that the result is a long. For example if the dates differ only in a few days I should get a result something like 0.34 instead of 0.
Also I need my code to account for the calendar, I cant assume each month has 31 days.
Diff between 1999-05-12 and 1999-08-24
Assuming all months have 31 days for simplicity
result = (19/31 + 31/31 + 31/31 + 24/31) = 2.793
According to the calendar we replace the 31s with the correct number of days for that specific year and month
Here is my solution:
public static double monthsBetween(LocalDate start, LocalDate end) {
if (start.isAfter(end)) throw new IllegalArgumentException("Start must be before end!");
var lastDayOfStartMonth = start.with(TemporalAdjusters.lastDayOfMonth());
var firstDayOfEndMonth = end.with(TemporalAdjusters.firstDayOfMonth());
var startMonthLength = (double)start.lengthOfMonth();
var endMonthLength = (double)end.lengthOfMonth();
if (lastDayOfStartMonth.isAfter(firstDayOfEndMonth)) { // same month
return ChronoUnit.DAYS.between(start, end) / startMonthLength;
}
long months = ChronoUnit.MONTHS.between(lastDayOfStartMonth, firstDayOfEndMonth);
double startFraction = ChronoUnit.DAYS.between(start, lastDayOfStartMonth.plusDays(1)) / startMonthLength;
double endFraction = ChronoUnit.DAYS.between(firstDayOfEndMonth, end) / endMonthLength;
return months + startFraction + endFraction;
}
The idea is that you find the last day of start's month (lastDayOfStartMonth), and the first day of end's month (firstDayOfEndMonth) using temporal adjusters. These two dates are very important. The number you want is the sum of:
the fractional number of a month between start and lastDayOfStartMonth
the whole number of months between lastDayOfStartMonth and firstDayOfEndMonth.
the fractional number of a month between firstDayOfEndMonth and end.
Then there is the edge case of when both dates are within the same month, which is easy to handle.
By using this definition, the nice property that the number of months between the first day of any two months is always a whole number is maintained.
Note that in the first calculation, you have to add one day to lastDayOfStartMonth, because ChronoUnit.between treats the upper bound as exclusive, but we actually want to count it as one day here.
To approach this problem, you need to consider the following cases:
dates belong to the same year and month;
dates belong to different year and/or month;
dates are invalid.
When dates belong to the same year and month, then the result would be the difference in days between the two dates divided by the number of days in this month, which can be found using LocalDate.lengthOfMonth().
In the general case, the range of dates can be split into three parts:
two fractional parts at the beginning and at the end of the given range of dates (both could be evaluated using the approach for the simplest case when both data belong to the same year/month)
the whole part, we can use ChronoUnit.MONTHS.between() to calculate it.
Here's how implementation might look like (d1 - inclusive, d2 - exclusive):
public static double getFractionalMonthDiff(LocalDate d1, LocalDate d2) {
if (d1.isAfter(d2)) throw new IllegalArgumentException(); // or return a value like -1
if (d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth()) { // dates belong to same month and year
return getFractionalPartOfMonth(d2.getDayOfMonth() - d1.getDayOfMonth(), d1.lengthOfMonth());
}
int monthLen1 = d1.lengthOfMonth();
return getFractionalPartOfMonth(monthLen1 - (d1.getDayOfMonth() - 1), monthLen1) // from the given day of month of the First Date d1 Inclusive to the Last day of month
+ getFractionalPartOfMonth(d2.getDayOfMonth() - 1, d2.lengthOfMonth()) // from the First day of month to given day of month of the Second Date d2 Exclusive (for that reason 1 day was subtracted, and similarly on the previous line 1 day was added)
+ ChronoUnit.MONTHS.between(d1.withDayOfMonth(monthLen1), d2.withDayOfMonth(1));
}
public static double getFractionalPartOfMonth(int daysInterval, int monthLength) {
return daysInterval / (double) monthLength;
}
I am trying to convert a string like, 4hours or 1day or 2months to milliseconds. But how would I do this efficiently without having to check if string.contains("h") etc....?
Node has ms, would Java happen to have something similar?
Edit based on some responses: Yes the actual number can change, I gave 4 hours as an example but that can be 1, 2, 3, 5 etc. Same for the days and months. And yes they are directly next to each other. No spaces.
Example string: remind 1h Go to school
The output should be: 3 600 000 (milliseconds)
Well, you can break this down into smaller pieces.
Find such occurrences within the string;
Take both the number part and the unit part and multiply their values in order to get the milliseconds.
To find the occurrences, you could use regular expressions. A regular expression like (-?\d+)(h|days|min|sec) would match such strings. Using Pattern and Matcher would help.
To convert to number to milliseconds, the units need to be mapped to a value matching the number of milliseconds for that unit. For example, days would map to 86400000.
While it’s not built in, I don’t find it that bad.
String[] exampleStrings = { "4hours", "1day", "2months" };
for (String example : exampleStrings) {
long millis;
if (example.contains("hour") || example.contains("minute")
|| example.contains("second")) {
String durationString = "PT" + unitToAbbreviation(example);
Duration dur = Duration.parse(durationString);
millis = dur.toMillis();
} else {
String periodString = "P" + unitToAbbreviation(example);
Period per = Period.parse(periodString);
Duration estimatedDuration = Duration.ZERO;
for (TemporalUnit unit : per.getUnits()) {
estimatedDuration = estimatedDuration.plus(
unit.getDuration().multipliedBy(per.get(unit)));
}
millis = estimatedDuration.toMillis();
}
System.out.format("%7s -> %10d milliseconds%n", example, millis);
}
Output from this sample is:
4hours -> 14400000 milliseconds
1day -> 86400000 milliseconds
2months -> 5259492000 milliseconds
I have assumed that you have got only one number and one unit in the string. It may be 2months or 1day but not 2months1day.
The length of days, months and years are inaccurate estimates since their real length vary. A month may be from 28 to 31 days, and a day may be from 23 to 25 hours.
I am using the following little auxiliary method for converting 4hours into just 4h as required by Duration.parse():
String unitToAbbreviation(String withFullUnit) {
return withFullUnit.replaceFirst("(\\d+[a-z])[a-z]*", "$1");
}
In case of a Period (a day-based rather than a time-based interval) I am iterating over the units that a Period supports — years, months and days. For each I am using TemporalUnit.getDuration() to get its estimated duration and Period.get() for getting the corresponing value. The call to mulitpliedBy() (don’t be surprised) multiplies the two.
I'm using LocalData objects in my code, and I've noticed that using
compareTo method is not consistent.
I use the compareTo method in order to get the difference (in days)
between the two dates.
However, it seems like it only works for the dates in the same months.
I'm attaching a code snippet to demonstrate this issue:
import java.text.MessageFormat;
import java.time.LocalDate;
public class timeComparison {
public static void main(String[] args) {
LocalDate valentinesDay = LocalDate.of(2020, 2, 14);
LocalDate darwinDay = LocalDate.of(2020, 2, 12);
LocalDate leapDay = LocalDate.of(2020, 2, 29);
LocalDate superTuesday = LocalDate.of(2020, 3, 3);
String valentinesVsDarwin = MessageFormat.format(
"Valentine day is {0} days after Darwin day",
valentinesDay.compareTo(darwinDay)
);
System.out.println(valentinesVsDarwin);
String darwinVsLeap = MessageFormat.format(
"Leap day is {0} days after Darwin day",
leapDay.compareTo(darwinDay)
);
System.out.println(darwinVsLeap);
String superTuesdayVsLeap = MessageFormat.format(
"Super Tuesday is {0} days after leap day",
superTuesday.compareTo(leapDay)
);
System.out.println(superTuesdayVsLeap);
}
}
The output I get is:
Valentine day is 2 days after Darwin day
Leap day is 17 days after Darwin day
Super Tuesday is 1 days after leap day
I was expecting to get Super Tuesday is 3 days after leap day.
I would like to know what causes the problem
and how can I get the difference between two separate dates.
TL;DR
The compareTo method is not meant for this use.
It is meant to be an indicator of order, not show time differences.
Solving the Mystery
In order to understand this better one can look at the source code of LocalDate.
On any IDE you can choose "Go To => Implementation" to view the source code.
public int compareTo(ChronoLocalDate other) {
return other instanceof LocalDate ? this.compareTo0((LocalDate)other) : super.compareTo(other);
}
int compareTo0(LocalDate otherDate) {
int cmp = this.year - otherDate.year;
if (cmp == 0) {
cmp = this.month - otherDate.month;
if (cmp == 0) {
cmp = this.day - otherDate.day;
}
}
return cmp;
}
From the above source code, you can learn that the compareTo method
does not return the difference between the date in days, but rather the
difference between dates in the first parameter that has a difference
(it could be years, could be months, or could be days).
What Is the Purpose of compareTo Methods
In order the understand the above code and its purpose,
one would need to understand a little bit how comparison in Java works.
Java's LocalDate class implements ChronoLocalDate,
which is a comparable object.
This means that this object is capable of comparing itself with another object.
In order to do so, the class itself must implement the java.lang.Comparable
interface to compare its instances.
As described in Java's API documentation:
This interface imposes a total ordering on the objects of each class that
implements it. This ordering is referred to as the class's natural ordering,
and the class's compareTo method is referred to as its natural
comparison method.
Moreover, the method detail of the compareTo method is the following:
Compares this object with the specified object for order.
Returns a negative integer, zero, or a positive integer as this object
is less than, equal to, or greater than the specified object.
Meaning, that in order for the comparison to work we need to return an integer that corresponds with the above ruling.
Therefore, the number itself doesn't have any meaning,
rather than being an indication of the "order" of the objects.
That is why the method, as shown above, does not calculate the difference in days but rather seeks the immediate indication for the natural order of the objects: first in years, then in months, and in the ends in days.
When you were comparing dates that had a different mount - the method showed just that.
The same would apply if you were to compare between a date in
2020 and a date in 2018 - you would get 2 / -2 as a result.
How You Should Measure Data Difference
We found out that compareTo isn't meant to be used as a measurement tool of date difference.
You can use either Period or ChronoUnit, depending on your needs.
From Java's Period and Duration tutorial:
ChronoUnit
The ChronoUnit enum, discussed in the The Temporal Package, defines the
units used to measure time.
The ChronoUnit.between method is useful when
you want to measure an amount of time in a single unit of time only,
such as days or seconds.
The between method works with all temporal-based
objects, but it returns the amount in a single unit only.
Period
To define an amount of time with date-based values (years, months, days),
use the Period class.
The Period class provides various get methods,
such as getMonths, getDays, and getYears, so that you can extract the
amount of time from the period.
The total period of time is represented by all three units together:
months, days, and years.
To present the amount of time measured in a single
unit of time, such as days, you can use the ChronoUnit.between method.
Here's a code snippet demonstrating the usage of the mentioned above methods:
jshell> import java.time.LocalDate;
jshell> LocalDate darwinDay = LocalDate.of(2020, 2, 12);
darwinDay ==> 2020-02-12
jshell> LocalDate leapDay = LocalDate.of(2020, 2, 29);
leapDay ==> 2020-02-29
jshell> leapDay.compareTo(darwinDay);
==> 17
jshell> import java.time.Period;
jshell> Period.between(darwinDay, leapDay).getDays();
==> 17
jshell> import java.time.temporal.ChronoUnit;
jshell> ChronoUnit.DAYS.between(darwinDay, leapDay);
==> 17
jshell> LocalDate now = LocalDate.of(2020, 3, 28);
now ==> 2020-03-28
jshell> LocalDate yearAgo = LocalDate.of(2019, 3, 28);
yearAgo ==> 2019-03-28
jshell> yearAgo.compareTo(now);
==> -1
jshell> Period.between(yearAgo, now).getDays();
==> 0
jshell> ChronoUnit.DAYS.between(yearAgo, now);
==> 366
I was expecting to get Super Tuesday is 3 days after leap day.
This was a wrong expectation. See how Java compared two instances of LocalDate:
int compareTo0(LocalDate otherDate) {
int cmp = (year - otherDate.year);
if (cmp == 0) {
cmp = (month - otherDate.month);
if (cmp == 0) {
cmp = (day - otherDate.day);
}
}
return cmp;
}
Since the year was same (2020), it gave you the difference between months which is 3 - 2 = 1.
You should use java.time.temporal.ChronoUnit::between to find the number of days between two instances of LocalDate.
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
System.out.println(ChronoUnit.DAYS.between(LocalDate.of(2020, 2, 29), LocalDate.of(2020, 4, 3)));
}
}
Output:
34
I am trying to display number of days in every month of the year
LocalDate start = LocalDate.of(2016, 01, 01);
LocalDate end = start.plusYears(1);
Period everyMonth = Period.ofMonths(1);
for (;start.isBefore(end); start = start.plus(everyMonth)) {
System.out.println(Period.between(start, start.plus(everyMonth)).getDays());
}
Why do I get 12 0s?
You are not using correctly the Period class here. start represents the date 01/01/2016 (in dd/MM/yyyy format). When you are adding a period of 1 month, the result is the date 01/02/2016.
The period between those two dates, as defined by the Period class is "1 month". If you print the period, you will have "P1M", which is the pattern to say that:
A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
As such, getDays(), which return the amount of days in the period, will return 0. The result is different than the number of days between the two dates. You can convince yourself of that by printing the result of getMonths, it would return 1:
public static void main(String[] args) {
LocalDate start = LocalDate.of(2016, 01, 01);
Period period = Period.between(start, start.plus(Period.ofMonths(1)));
System.out.println(period.getDays()); // prints 0
System.out.println(period.getMonths()); // prints 1
}
Now, in your question, you want to print the number of days in every month. You can simply have the following:
for (Month month : Month.values()) {
System.out.println(month.length(Year.now().isLeap()));
}
In Java Time, there is an enum Month for all the months, and the method length(leapYear) return the length of this month, i.e. the number of days in the month. Since this depends on whether the current year is a leap year or not, there is a boolean argument for that.
To check for the current year, we can call Year.now() and return if it's a leap year or not with isLeap().
As a side-note, if you truly wanted to print the number of days between two dates, you would need to use ChronoUnit.DAYS.between(start, end).
You are doing everything correctly except one thing. You try to print days in the period, but since you always add 1 month to the date the period is 0 years, 1 month, 0 days. When you call getDays() it returns number of days in period which is 0.
final Period period = Period.between(start, start.plus(everyMonth);
System.out.println(period.getDays()); // 0
System.out.println(period.getMonths()); // 1
I think what you are looking for is:
System.out.println(ChronoUnit.DAYS.between(start, start.plus(everyMonth)));
This question already has answers here:
How do I calculate someone's age in Java?
(28 answers)
Closed 9 years ago.
I am a newbie and appreciate if someone help me out.
When I tried to calculate age using the below source , it does not give me the value of what I want . For example : date->29/12/2010 , dob->30/12/1992 , it will give me 18 instead of 17.
Is there any method that I can code to return me 17yrs 11mths based on the above 2 dates instead of 18yrs0mths?
public double getAgeAsOf( Date date ) {
return ((date.getTime() - dob.getTime())/(1000*60*60*24))/365;
}
Thanks.
You can use Joda Time and compute a Period between two LocalDate values (which is what you've got here) using months and years as the units.
Sample code:
import org.joda.time.*;
public class Test {
public static void main(String[] args) {
LocalDate dob = new LocalDate(1992, 12, 30);
LocalDate date = new LocalDate(2010, 12, 29);
Period period = new Period(dob, date, PeriodType.yearMonthDay());
System.out.println(period.getYears() + " years and " +
period.getMonths() + " months");
}
}
(This uses a period type which includes days as well, but that won't affect the answer.)
In general, Joda Time is a much better API than using Date/Calendar - and you really don't want to get into the business of performing date calculations yourself if you can avoid it. It gets messy really quickly.
As per aioobe's answer, if you divide two integer expressions the arithmetic will be performed in integer arithmetic, which may not be what you want - but for date and time arithmetic, just let someone else do the hard work in the first place :)
The code above will use the ISO-8601 calendar by the way, which is basically the Gregorian calendar. If you want to use something else, specify it as another constructor argument after the year/month/day for LocalDate.
You have a few problems with your code:
You're doing integer division, which truncates the result to the closest lower integer.
For example, if you divide 729 by 365 you'll get 1 (and you've lost the fractional part, which you would need when calculating months etc)
Another problem is that you're using 365 days for one year, while it is actually closer to 365.25 (when including extra days due to leap years).
Here's a slightly improved snippet of code:
import java.util.Date;
public class Test {
static double msPerGregorianYear = 365.25 * 86400 * 1000;
static Date dob = new Date(1992, 12, 30);
public static double getAgeAsOf(Date date) {
return (date.getTime() - dob.getTime()) / msPerGregorianYear;
}
public static void main(String[] args) {
double years = getAgeAsOf(new Date(2010, 12, 29));
// years == 17.99315537303217
int yy = (int) years;
int mm = (int) ((years - yy) * 12);
// Prints "17 years and 11 moths."
System.out.printf("%d years and %d moths.", yy, mm);
}
}
If you're ever doing anything more complicated than figuring out the number of years given the number of milliseconds, I suggest you turn to some time-library such as Joda time as suggested by Jon Skeet.