NOTE THIS IS NOT A DUPLICATE OF EITHER OF THE FOLLOWING
Calculating the difference between two Java date instances
calculate months between two dates in java [duplicate]
I have two dates:
Start date: "2016-08-31"
End date: "2016-11-30"
Its 91 days duration between the above two dates, I expected my code to return 3 months duration, but the below methods only returned 2 months. Does anyone have a better suggestion? Or do you guys think this is a bug in Java 8? 91 days the duration only return 2 months.
Thank you very much for the help.
Method 1:
Period diff = Period.between(LocalDate.parse("2016-08-31"),
LocalDate.parse("2016-11-30"));
Method 2:
long daysBetween = ChronoUnit.MONTHS.between(LocalDate.parse("2016-08-31"),
LocalDate.parse("2016-11-30"));
Method 3:
I tried to use Joda library instead of Java 8 APIs, it works. it loos will return 3, It looks like Java duration months calculation also used days value. But in my case, i cannot use the Joda at my project. So still looking for other solutions.
LocalDate dateBefore= LocalDate.parse("2016-08-31");
LocalDate dateAfter = LocalDate.parse("2016-11-30");
int months = Months.monthsBetween(dateBefore, dateAfter).getMonths();
System.out.println(months);
Since you don't care about the days in your case. You only want the number of month between two dates, use the documentation of the period to adapt the dates, it used the days as explain by Jacob. Simply set the days of both instance to the same value (the first day of the month)
Period diff = Period.between(
LocalDate.parse("2016-08-31").withDayOfMonth(1),
LocalDate.parse("2016-11-30").withDayOfMonth(1));
System.out.println(diff); //P3M
Same with the other solution :
long monthsBetween = ChronoUnit.MONTHS.between(
LocalDate.parse("2016-08-31").withDayOfMonth(1),
LocalDate.parse("2016-11-30").withDayOfMonth(1));
System.out.println(monthsBetween); //3
Edit from #Olivier Grégoire comment:
Instead of using a LocalDate and set the day to the first of the month, we can use YearMonth that doesn't use the unit of days.
long monthsBetween = ChronoUnit.MONTHS.between(
YearMonth.from(LocalDate.parse("2016-08-31")),
YearMonth.from(LocalDate.parse("2016-11-30"))
)
System.out.println(monthsBetween); //3
Since Java8:
ChronoUnit.MONTHS.between(startDate, endDate);
//Backward compatible with older Java
public static int monthsBetween(Date d1, Date d2){
if(d2==null || d1==null){
return -1;//Error
}
Calendar m_calendar=Calendar.getInstance();
m_calendar.setTime(d1);
int nMonth1=12*m_calendar.get(Calendar.YEAR)+m_calendar.get(Calendar.MONTH);
m_calendar.setTime(d2);
int nMonth2=12*m_calendar.get(Calendar.YEAR)+m_calendar.get(Calendar.MONTH);
return java.lang.Math.abs(nMonth2-nMonth1);
}
The documentation of Period#between states the following:
The start date is included, but the end date is not.
Furthermore:
A month is considered if the end day-of-month is greater than or equal to the start day-of-month.
Your end day-of-month 30 is not greater than or equal to your start day-of-month 31, so a third month is not considered.
Note the parameter names:
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)
To return 3 months, you can increment the endDateExclusive by a single day.
In case you want stick to java.time.Period API
As per java.time.Period documentation
Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)
where
#param startDateInclusive the start date, inclusive, not null
#param endDateExclusive the end date, exclusive, not null
So it is better to adjust your implementation to make your end date inclusive and get your desired result
Period diff = Period.between(LocalDate.parse("2016-08-31"),
LocalDate.parse("2016-11-30").plusDays(1));
System.out.println("Months : " + diff.getMonths());
//Output -> Months : 3
You have to be careful, never use LocalDateTime to calculate months between two dates the result is weird and incorrect, always use LocalDate !
here's is some code to prove the above:
package stack.time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class TestMonthsDateTime {
public static void main(String[] args) {
/**------------------Date Time----------------------------*/
LocalDateTime t1 = LocalDateTime.now();
LocalDateTime t2 = LocalDateTime.now().minusMonths(3);
long dateTimeDiff = ChronoUnit.MONTHS.between(t2, t1);
System.out.println("diff dateTime : " + dateTimeDiff); // diff dateTime : 2
/**-------------------------Date----------------------------*/
LocalDate t3 = LocalDate.now();
LocalDate t4 = LocalDate.now().minusMonths(3);
long dateDiff = ChronoUnit.MONTHS.between(t4, t3);
System.out.println("diff date : " + dateDiff); // diff date : 3
}
}
My 2%
This example checks to see if the second date is the end of that month. If it is the end of that month and if the first date of month is greater than the second month date it will know it will need to add 1
LocalDate date1 = LocalDate.parse("2016-08-31");
LocalDate date2 = LocalDate.parse("2016-11-30");
long monthsBetween = ChronoUnit.MONTHS.between(
date1,
date2);
if (date1.isBefore(date2)
&& date2.getDayOfMonth() == date2.lengthOfMonth()
&& date1.getDayOfMonth() > date2.getDayOfMonth()) {
monthsBetween += 1;
}
After the short investigation, still not totally fix my question, But I used a dirty solution to avoid return the incorrect duration. At least, we can get the reasonable duration months.
private static long durationMonths(LocalDate dateBefore, LocalDate dateAfter) {
System.out.println(dateBefore+" "+dateAfter);
if (dateBefore.getDayOfMonth() > 28) {
dateBefore = dateBefore.minusDays(5);
} else if (dateAfter.getDayOfMonth() > 28) {
dateAfter = dateAfter.minusDays(5);
}
return ChronoUnit.MONTHS.between(dateBefore, dateAfter);
}
The Java API response is mathematically accurate according to the calendar. But you need a similar mechanism, such as rounding decimals, to get the number of months between dates that matches the human perception of the approximate number of months between two dates.
Period period = Period.between(LocalDate.parse("2016-08-31"), LocalDate.parse("2016-11-30"));
long months = period.toTotalMonths();
if (period.getDays() >= 15) {
months++;
}
Related
I would like to get all the weeks between 2 dates with weeks that cross 2 months counted twice for each month. For example, in 2021 week 14 of the year hosted both March and April so in this case, I would like that week counted twice (once for March and once for April). I've looked and found just libraries that count the number of weeks between 2 dates. I think I could get week numbers and month numbers and form a unique array but this seems a bit over the top. Has anyone got any suggestions?
weeks that cross 2 months counted twice
The code below allows to do that by utilizing only the standard LocalDate class and it's methods isBefore(), plusWeeks(), plusDays().
Keep in mind the days of the week and months are represented by enums from the java.time package.
I've made a couple of assumptions:
week starts with Sunday;
chunks of the week at the start and at the end of the given period have to be taken into account as well as full-length weeks.
public static void main(String[] args) {
System.out.println(getWeekCount(LocalDate.of(2022, 1, 1),
LocalDate.of(2022, 2, 1)));
System.out.println(getWeekCount(LocalDate.of(2022, 1, 1),
LocalDate.of(2022, 3, 1)));
}
public static int getWeekCount(LocalDate date1, LocalDate date2) {
int weekCount = 0;
LocalDate cur = date1;
LocalDate finish = date2;
// assumption: week starts with sunday
// assumption: chunk of week at the start and at the end have to be taken into account as well as full weeks
if (cur.getDayOfWeek() != DayOfWeek.SUNDAY) { // adjusting current date
LocalDate next = cur.plusDays(DayOfWeek.SUNDAY.ordinal() - cur.getDayOfWeek().ordinal() + 1);
weekCount += getWeeksIncrement(cur, next);
cur = next;
}
if (finish.getDayOfWeek() != DayOfWeek.SUNDAY) { // adjusting finish date
LocalDate previous = finish.minusDays(finish.getDayOfWeek().ordinal() + 1);
weekCount += getWeeksIncrement(previous, finish);
finish = previous;
}
while (cur.isBefore(finish) || cur.equals(finish)) {
LocalDate next = cur.plusWeeks(1);
weekCount += getWeeksIncrement(cur, next);
cur = next;
}
return weekCount;
}
public static int getWeeksIncrement(LocalDate cur, LocalDate next) {
return weekIsSharedBetweenTwoMonth(cur, next) ? 2 : 1;
}
public static boolean weekIsSharedBetweenTwoMonth(LocalDate cur, LocalDate next) {
return next.getMonth() != cur.getMonth() &&
next.withDayOfMonth(1).isAfter(cur);
}
Output
7 - weeks between: 2022-01-01 and 2022-02-01
12 - weeks between: 2022-01-01 and 2022-03-01
Well, this is achievable with a combination of the Java Date and Time API (java.time) and the Java Streams API (java.util.stream):
long weeksBetween(LocalDate start, LocalDate endInclusive) {
LocalDate normalizedStart = start.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate normalizedEndExclusive = endInclusive.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
return Stream.iterate(normalizedStart, d -> d.plusWeeks(1))
.takeWhile(d -> d.isBefore(normalizedEndExclusive))
.mapToInt(d -> d.getMonthValue() == d.plusDays(6).getMonthValue() ? 1 : 2)
.sum();
}
What happens here, is as follows.
First, the dates are normalized, that is, they are set to the start of the week (Monday according to ISO standards).
Then we walk over the Monday of each week, and check if its last day of the week (Sunday) lies within the same month. If it is, then it yields 1, otherwise it yields 2.
At last, we sum all yielded values.
Note that I assumed that a week starts on Monday (ISO). The code also considers the week of both the start date as the end date as full ones.
You can get the weeknumber like this using java.time:
LocalDate date = LocalDate.of(year, month, day);
int weekOfYear = date.get(ChronoField.ALIGNED_WEEK_OF_YEAR);
You did not mention which java version you are using. java.time was introduced in java 8. There are other solutions available for pre-java 8.
Based on the above, you should be able to solve your problem.
This question already has answers here:
Calculate number of weekdays between two dates in Java
(20 answers)
Closed 1 year ago.
Am very beginner and new to Java platform. I have the below 3 simple Java date difference calculation functions. I wanted to exclude weekends on the below calculations in all the 3 methods. Can anyone please help how to exclude weekends for the below dateDiff calculations?
public static String getDatesDiff(String date1, String date2) {
String timeDiff = "";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d1 = null;
Date d2 = null;
try {
d1 = format.parse(date1);
d2 = format.parse(date2);
long diff = d2.getTime() - d1.getTime();
timeDiff = ""+diff;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return timeDiff;
}
public static String getDatesDiffAsDays(String date1, String date2) {
String timeDiff = "";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d1 = null;
Date d2 = null;
try {
d1 = format.parse(date1);
d2 = format.parse(date2);
long diff = d2.getTime() - d1.getTime();
String days = ""+(diff / (24 * 60 * 60 * 1000));
timeDiff = days;
timeDiff = timeDiff.replaceAll("-", "");
timeDiff = timeDiff+" days";
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return timeDiff;
}
public static String getDatesDiffAsDate(String date1, String date2) {
String timeDiff = "";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d1 = null;
Date d2 = null;
try {
d1 = format.parse(date1);
d2 = format.parse(date2);
long diff = d2.getTime() - d1.getTime();
String days = (diff / (24 * 60 * 60 * 1000))+" days";
String hours = (diff / (60 * 60 * 1000) % 24)+"h";
String minutes = (diff / 1000 % 60)+"mts";
String seconds = (diff / (60 * 1000) % 60)+"sec";
timeDiff = days;
timeDiff = timeDiff.replaceAll("-", "");
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return timeDiff;
}
This code is fundamentally broken. java.util.Date doesn't represent a date, it represents a timestamp. But if you're working with moments in time, you have a problem: not all days are exactly 24 hours long. For example, daylight savings exists, making some days 25 or 23 hours. At specific moments in time in specific places on the planet, entire days were skipped, such as when a place switches which side of the international date line it is on, or when Russia was the last to switch from Julian to Gregorian (the famed October Revolution? Yeah, that happened in November actually!)
Use LocalDate which represents an actual date, not a timestamp. Do not use Date, or SimpleDateFormat – these are outdated and mostly broken takes on dates and times. The java.time package is properly thought through.
When is 'the weekend'? In some places, Friday and Saturday are considered the weekend, not Saturday and Sunday.
If you're excluding weekends, presumably you'd also want to exclude mandated holidays. Many countries state that Jan 1st, regardless of what day that is, counts as a Sunday, e.g. for the purposes of government buildings and services being open or not.
Lessons you need to take away from this:
Dates are incredibly complicated, and as a consequence, are a horrible idea for teaching basic principles.
Do not use java.util.Date, Calendar, GregorianCalendar, or SimpleDateFormat, ever. Use the stuff in java.time instead.
If you're writing math like this, you're probably doing it wrong – e.g. ChronoUnit.DAYS.between(date1, date2) does all that math for you.
You should probably just start at start date, and start looping: check if that date counts as a working day or not (and if it is, increment a counter), then go to the next day. Keep going until the day is equal to the end date, and then return that counter. Yes, this is 'slow', but a computer will happily knock through 2 million days (that covers over 5000 years worth) in a heartbeat for you. The advantage is that you can calculate whether or not a day counts as a 'working day' (which can get incredibly complicated. For example, most mainland European countries and I think the US too mandates that Easter is a public holiday. Go look up and how to know when Easter is. Make some coffee first, though).
If you really insist on going formulaic and defining weekends as Saturday and Sunday, it's better to separately calculate how many full weeks are between the two dates and multiply that by 5, and then add separately the half-week 'on the front of the range' and the half-week at the back. This will be fast even if you ask for a hypothetical range of a million years.
That is not how you handle exceptions. Add throws X if you don't want to deal with it right now, or, put throw new RuntimeException("unhandled", e); in your catch blocks. Not this, this is horrible. It logs half of the error and does blindly keeps going, with invalid state.
Almost all interesting questions, such as 'is this date a holiday?' are not answerable without knowing which culture/locale you're in. This includes seemingly obvious constants such as 'is Saturday a weekend day?'.
rzwitserloot has already brought up many valid points about problems in your code.
This is an example of how you could count the working days:
LocalDate startDate = ...;
LocalDate endDateExclusive = ...;
long days = startDate.datesUntil(endDateExclusive)
.filter(date -> isWorkingDay(date))
.count();
And, of course, you need to implement the isWorkingDay method. An example would be this:
public static boolean isWorkingDay(LocalDate date) {
DayOfWeek dow = date.getDayOfWeek();
return (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY);
}
I used LocalDate to illustrate the example. LocalDate fits well if you are working with concepts like weekend days and holidays. However, if you want to also include the time component, then you should also take clock adjustments like DST into account; otherwise a "difference" does not make sense.
I assume the user to input an object representing some datetime value, not a String. The parsing of a string does not belong to this method, but should be handled elsewhere.
Already been said, but I repeat: don't use Date, Calendar and SimpleDateFormat. They're troublesome. Here are some reasons why.
If you want to take the time into consideration, it'll get a little more complex. For instance, ChronoUnit.DAYS.between(date1, date2) only supports a single, contiguous timespan. Gaps in the timespan, like excluding certain periods of time, is not. Then you have to walk over each date and get the associated duration of that portion of date.
First, we could create a LocalTimeRange class, which represents a time span at a certain day.
public record LocalTimeRange(LocalTime start, LocalTime endExclusive) {
public static final LocalTimeRange EMPTY = new LocalTimeRange(null, null);
public Duration toDuration(LocalDate date, ZoneId zone) {
if (this.equals(EMPTY)) {
return Duration.ZERO;
}
var s = ZonedDateTime.of(date, Objects.requireNonNullElse(start, LocalTime.MIN), zone);
var e = (endExclusive != null ? ZonedDateTime.of(date, endExclusive, zone) : ZonedDateTime.of(date.plusDays(1), LocalTime.MIN, zone));
return Duration.between(s, e);
}
}
Calculations are not done immediately, because the duration in between the two wall clock times, depends on the date and timezone. The toDuration method calculates this.
Then we'll create a method which defines what times on each day are counted as a non-weekend day. In this example, I have defined a weekend to be from Friday, 12:00 (noon) until Sunday, 23:59 (midnight).
private static Duration nonWeekendHours(LocalDate date, ZoneId zone) {
var result = switch (date.getDayOfWeek()) {
case MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY -> new LocalTimeRange(LocalTime.MIDNIGHT, null);
case FRIDAY -> new LocalTimeRange(LocalTime.MIDNIGHT, LocalTime.NOON);
case SATURDAY,
SUNDAY -> new LocalTimeRange(null, null);
};
return result.toDuration(date, zone);
}
The LocalTimeRange::toDuration method is called with the passed LocalDate and ZoneId arguments.
Note that passing null as LocalTimeRange's second argument means 'until the end of the day'.
At last we could stream over all dates of a certain period and calculate how much time are the non-weekend hours for each day, and then reduce them to get the total amount of time:
LocalDate startDate = ...;
LocalDate endDate = ...;
ZoneId zone = ...;
Duration result = startDate.datesUntil(endDate)
.map(date -> nonWeekendHours(date, zone))
.reduce(Duration.ZERO, Duration::plus);
With the retrieved Duration instance, you can easily get the time parts with the get<unit>Part() methods,
Online demo
I am trying to figure out find out list of calendar dates from two specific dates. But I am getting only within a same year not fix two dates.
What I have done:
private ArrayList<Calendar> weekendList = null;
public void findWeekendsList(long startDate, long endDate)
{
weekendList = new ArrayList();
Calendar calendarStart = null;
Calendar calendarEnd=null;
calendarStart= Calendar.getInstance();
calendarStart.setTimeInMillis(startDate);
/*calendarEnd= Calendar.getInstance();
calendarEnd.setTimeInMillis(endDate);
*/
// The while loop ensures that you are only checking dates in the current year
while(calendar.get(Calendar.YEAR) == Calendar.getInstance().get(Calendar.YEAR)){
// The switch checks the day of the week for Saturdays and Sundays
switch(calendar.get(Calendar.DAY_OF_WEEK)){
case Calendar.SATURDAY:
case Calendar.SUNDAY:
weekendList.add(calendar);
break;
}
// Increment the day of the year for the next iteration of the while loop
calendar.add(Calendar.DAY_OF_YEAR, 1);
}
}
I am getting 102 weekends and it was like jan 9 to dec 31 but i want to today date(jan 9, 2018) to up to next year( like jan 9 2019), all the weekends in arrayList.
If anyone have any idea, that would great help for me.
So, immediately, you have a number of issues which stand out...
Using an out of date API. Calendar should be avoided at all costs, it's troublesome, clumsy and all way to easy to screw up
The while loop is looping only so long as the year is the same, so it's ignoring the endDate altogether
Because Calendar is mutable, all you are producing is a List with the same date/time value contained within it
You algorithm considers a weekend to be both "Saturday" or "Sunday", where normally, I'd consider a weekend to be the time between "Friday" and "Monday" - Don't know if this is deliberate on your part, but it stands out to me.
Since Java 8 included a newer Date/Time API, you should start using it. If you're not using Java 8+ (and I'd be asking some serious questions as to why not), you should be using a more reliable API - JodaTime comes to mind, but there is also a compatible back port of the Java 8 Date/Time for earlier JDK/JVMs
Now, having said all that, I'd do something more like...
public List<LocalDate> findWeekendsBetween(long startTime, long endTime) {
LocalDate startDate = LocalDate.ofEpochDay(startTime);
LocalDate endDate = LocalDate.ofEpochDay(endTime);
System.out.println("Starting from " + startDate);
System.out.println("Ending at " + endDate);
List<LocalDate> weekends = new ArrayList<>(25);
while (startDate.isBefore(endDate) || startDate.equals(endDate)) {
switch (startDate.getDayOfWeek()) {
case SATURDAY:
weekends.add(startDate);
startDate = startDate.plusDays(2);
break;
default:
startDate = startDate.plusDays(1);
}
}
return weekends;
}
In fact, I'd even change it so that callers were required to pass you LocalDate values...
public List<LocalDate> findWeekendsBetween(LocalDate startDate, LocalDate endDate) {
as long is ambiguous.
The above algorithm is inclusive of the startTime and endTime and considers a "weekend" to be the time between "Friday" and "Monday", so it will only return "Saturday" values
Then you could call it using something like...
LocalDate startDate = LocalDate.now();
LocalDate endDate = startDate.plusMonths(1);
List<LocalDate> weekends = findWeekendsBetween(startDate.toEpochDay(), endDate.toEpochDay());
System.out.println("Found " + weekends.size() + " Saturday/Sundays");
for (LocalDate date : weekends) {
System.out.println(date);
}
(as you can see, I'm to lazy to calculate a fixed point in time and just use LocalDate and convert it to a long value)
Which in my testing printed out something like...
Starting from 2018-01-09
Ending at 2018-02-09
Found 4 Weekends
2018-01-13
2018-01-20
2018-01-27
2018-02-03
but you have only added Saturday, it has to be Sundays also.
Yes, as I said, I made it accept only Saturday, as for me, a weekend is inclusive of Saturday AND Sunday.
It would be easily fixed by simply including SUNDAY into the switch statement...
switch (startDate.getDayOfWeek()) {
case SATURDAY:
case SUNDAY:
weekends.add(startDate);
break;
default:
startDate = startDate.plusDays(1);
}
I think, it might be 52*2=104 list size.
I've not tested a full year, I've only checked a month's worth. You could just change the end date, something like...
LocalDate endDate = startDate.plusYears(1);
I have a map of string values which represent down times for different components.
dependencyMap.put ("sut", "14:26:12,14:27:19,00:01:07;15:01:54,15:02:54,00:01:00;15:44:30,15:46:30,00:02:00;16:10:30,16:11:30,00:01:00");
dependencyMap.put ("jms", "14:26:12,14:28:12,00:02:00;15:10:50,15:12:55,00:02:05;15:42:30,15:43:30,00:01:00;16:25:30,16:27:30,00:02:00");
The strings represent the start, end and duration of down times.
(start)14:26:12,(end)14:27:19,(duration)00:01:07
I read the values in, then add them to a list of DependencyDownTime objects which hold the Long values startTime, endTime and duration.
jArray.forEach (dependency ->{
String downTimeValues = knownDowntimesMap.get(dependency);
final String[] downtime = downTimeValues.split (";");
for (final String str : downtime) {
final DependencyDownTime depDownTime = new DependencyDownTime ();
final String[] strings = str.split (",");
if (strings.length == 3) {
final DateFormat dateFormat = new SimpleDateFormat ("HH:mm:ss");
try {
depDownTime.setStartTime(dateFormat.parse (strings[0]).getTime ());
depDownTime.setEndTime (dateFormat.parse (strings[1]).getTime ());
depDownTime.setDuration (dateFormat.parse (strings[2]).getTime ());
downTimes.add (depDownTime);
} catch (final ParseException e) {
//logger.warn (e.getMessage (), e);
}
} else {
//logger.warn ("");
}
}
I then perform simple arithmetic on the values, which calculates the total down time for each component.
// sort the list by start time
Collections.sort(downTimes, Comparator.comparing (DependencyDownTime::getStartTime));
int i = 1;
Long duration = 0L;
for(DependencyDownTime dts: downTimes){
Long curStart = dts.getStartTime ();
Long curEnd = dts.getEndTime();
Long nextStart = downTimes.get(i).getStartTime ();
Long nextEnd = downTimes.get(i).getEndTime ();
if(duration == 0){
duration = dts.getDuration();
}
if(curStart.equals(nextStart) && curEnd < nextEnd){
duration += (nextEnd - curEnd);
}
else if(nextStart > curEnd){
duration += downTimes.get(i).getDuration();
}
else if( curStart < nextStart && curEnd > nextStart){
duration += (nextEnd - curEnd);
}
else if(curEnd == nextStart){
duration += downTimes.get(i).getDuration();
}
i++;
if(i == downTimes.size ()){
componentDTimeMap.put (application, duration);
return;
}
The expected values should be something like 1970-01-01T 00:14:35 .000+0100, a matter of minutes. The actual result is usually extremely high off by a matter of hours in the difference 1969-12-31T 15:13:35 .000+0100
I have 2 questions.
Am I parsing the values correctly?
If my calculations are a little off when adding and subtracting the long values. When I convert the values back to Date format will there be a drastic difference in the expected value?
As explained in your other question, don't mistake those 2 different concepts:
a time of the day: it represents a specific point of a day, such as 10 AM or 14:45:50
a duration: it represents an amount of time, such as "1 hour and 10 minutes" or "2 years, 3 months and 4 days". The duration doesn't tell you when it starts or ends ("1 hour and 10 minutes" relative to what?), it's not attached to a chronology, it doesn't correspond to a specific point in the timeline. It's just the amount of time, by itself.
In your input, you have:
(start)14:26:12,(end)14:27:19,(duration)00:01:07
The start and end represents times of the day, and the duration represents the amount of time. SimpleDateFormat is designed to work with dates and times of the day, but not with durations. Treating the duration as a time of the day might work, but it's a hack as explained in this answer.
Another problem is that when SimpleDateFormat parses only a time, it defaults the day to January 1st 1970 at the JVM default timezone, leading to all the strange results you see. Unfortunately there's no way to avoid that, as java.util.Date works with full timestamps. A better alternative is to use the new date/time API.
As in your other question you're using Java 8, I'm assuming you can also use it here (but if you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same).
As you're working only with times, there's no need to consider date fields (day/month/year), we can use a LocalTime instead. You can parse the strings directly, because they are in ISO861 compliant format:
LocalTime start = LocalTime.parse("14:26:12");
LocalTime end = LocalTime.parse("14:27:19");
Unfortunately there are no built-in parsers for a duration, so you'll have to parse it manually:
// parse the duration manually
String[] parts = "00:01:07".split(":");
Duration d = Duration
// get hours
.ofHours(Long.parseLong(parts[0]))
// plus minutes
.plusMinutes(Long.parseLong(parts[1]))
// plus seconds
.plusSeconds(Long.parseLong(parts[2]));
Another alternative is to remove the durations from your input (or ignore them) and calculate it using the start and end:
Duration d = Duration.between(start, end);
Both will give you a duration of 1 minute and 7 seconds.
My suggestion is to change the DependencyDownTime to store start and end as LocalTime objects, and the duration as a Duration object. With this, your algorithm would be like this:
Duration total = Duration.ZERO;
for (...) {
LocalTime curStart = ...
LocalTime curEnd = ...
LocalTime nextStart = ...
LocalTime nextEnd = ...
if (total.toMillis() == 0) {
duration = dts.getDuration();
}
if (curStart.equals(nextStart) && curEnd.isBefore(nextEnd)) {
total = total.plus(Duration.between(curEnd, nextEnd));
} else if (nextStart.isAfter(curEnd)) {
total = total.plus(downTimes.get(i).getDuration());
} else if (curStart.isBefore(nextStart) && curEnd.isAfter(nextStart)) {
total = total.plus(Duration.between(curEnd, nextEnd));
} else if (curEnd.equals(nextStart)) {
total = total.plus(downTimes.get(i).getDuration());
}
i++;
if (i == downTimes.size()) {
// assuming you want the duration as a total of milliseconds
componentDTimeMap.put(application, total.toMillis());
return;
}
}
You can either store the Duration object, or the respective value of milliseconds. Don't try to transform it to a Date, because a date is not designed nor supposed to work with durations. You can adapt this code to format a duration if you want (unfortunately there are no native formatters for durations).
Limitations
The code above assumes that all start and end times are in the same day. But if you have start at 23:50 and end at 00:10, should the duration be 20 minutes?
If that's the case, it's a little bit trickier, because LocalTime is not aware of the date (so it considers 23:50 > 00:10 and the duration between them is "minus 23 hours and 40 minutes").
In this case, you could do a trick and assume the dates are all at the current date, but when start is greater than end, it means that end time is in the next day:
LocalTime start = LocalTime.parse("23:50");
LocalTime end = LocalTime.parse("00:10");
// calculate duration
Duration d;
if (start.isAfter(end)) {
// start is after end, it means end is in the next day
// current date
LocalDate now = LocalDate.now();
// start is at the current day
LocalDateTime startDt = now.atTime(start);
// end is at the next day
LocalDateTime endDt = now.plusDays(1).atTime(end);
d = Duration.between(startDt, endDt);
} else {
// both start and end are in the same day
// just calculate the duration in the usual way
d = Duration.between(start, end);
}
In the code above, the result will be a Duration of 20 minutes.
Don't format dates as durations
Here are some examples of why SimpleDateFormat and Date aren't good to handle durations of time.
Suppose I have a duration of 10 seconds. If I try to transform it to a java.util.Date using the value 10 to a date (AKA treating a duration as a date):
// a 10 second duration (10000 milliseconds), treated as a date
Date date = new Date(10 * 1000);
System.out.println(date);
This will get a date that corresponds to "10000 milliseconds after unix epoch (1970-01-01T00:00Z)", which is 1970-01-01T00:00:10Z. But when I print the date object, the toString() method is implicity called (as explained here). And this method converts this millis value to the JVM default timezone.
In the JVM I'm using, the default timezone is America/Sao_Paulo, so the code above outputs:
Wed Dec 31 21:00:10 BRT 1969
Which is not what is expected: the UTC instant 1970-01-01T00:00:10Z corresponds to December 31st 1969 at 9 PM in São Paulo timezone.
This happens because I'm erroneously treating the duration as a date (and the output will be different, depending on the default timezone configured in the JVM).
A java.util.Date can't (must not) be used to work with durations. Actually, now that we have better API's, it should be avoided whenever possible. There are too many problems and design issues with this, just don't use it if you can.
SimpleDateFormat also won't work properly if you handle the durations as dates. In this code:
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
Date d = dateFormat.parse("10:00:00");
The input has only time fields (hour, minute and second), so SimpleDateFormat sets the date to January 1st 1970 at the JVM default timezone. If I System.out.println this date, the result will be:
Thu Jan 01 10:00:00 BRT 1970
That's January 1st 1970 at 10 AM in São Paulo timezone, which in UTC is equivalent to 1970-01-01T13:00:00Z - so d.getTime() returns 46800000.
If I change the JVM default timezone to Europe/London, it will create a date that corresponds to January 1st 1970 at 10 AM in London (or UTC 1970-01-01T09:00:00Z) - and d.getTime() now returns 32400000 (because 10 AM in London and 10 AM in São Paulo happened at different instants).
SimpleDateFormat isn't the right tool to work with durations - it isn't even the best tool to work with dates, actually.
First I get:
LocalDate today = LocalDate.now();
and second
Date date = new Date();
date.setDate(Integer.valueOf(s[0]));
date.setMonth(Integer.valueOf(s[1]));
date.setYear(Integer.valueOf(s[2]));
LocalDate topicDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
How to check whether the differences between the first date and the second is 7 days?
For example, today is 03-08-2015 and the second date is 20-07-2015 and the difference between is more than 7 days, but how to check this?
Should I convert date to millisecond?
I Believe that is still the best way at the moment.
You can view some insights on the subject here:
Calculate date/time difference in java
You could convert to milliseconds or you could individually check if the year was larger, then if they are the same check to see if the month is larger then check day. Converting to milliseconds would be very easy though.
I believe you are looking for something like this:
Date date = /*your date object you want to compare*/;
Instant now = Instant.now();
Instant sevenDaysFromYourDate = Instant.ofEpochMilli(date.getTime()).plus(Duration.ofDays(7));
if (now.isAfter(sevenDaysFromYourDate)) {
//today is more than seven days past date
}
LocalDate today = LocalDate.now();
if (topicDate.isAfter(today.plusDays(7))) {
System.out.println("Yes");
}
else {
System.out.println("No");
}
Since you are using Java 8 LocalDate, you can use the plusDays or minusDays methods of the LocalDate class.
Furthermore, you shouldn't be using an (old, not recommended for use) java.util.Date object to create your second date. It's better to use LocalDate.of which builds a date from the year, month and day.
Example code:
LocalDate today = LocalDate.now();
LocalDate topicDate = LocalDate.of(
Integer.valueOf(s[2]),
Integer.valueOf(s[1]),
Integer.valueOf(s[0]));
System.out.println(today);
System.out.println(topicDate);
if ( today.minusDays(7).equals(topicDate)) {
System.out.println( "Exactly a week difference between today and topicDate");
} else if ( today.minusDays(7).compareTo(topicDate) > 0 ) {
System.out.println("TopicDate is more than a week before today");
} else {
System.out.println("TopicDate is less than a week before today");
}
Note that you can use the compareTo for exact equality as well - I just wanted to demonstrate that for equality, equals also works.
And of course, there are the isAfter and isBefore methods that also do the comparison in an elegant way.