How do you compare times in Java? - java

I'm wondering how I would compare times in a Java program. For example: If I choose a time block from 09:00-12:00 and set that. If i choose a second time and choose 11:00-13:00 time period, I would want to be able to have an error pop up, but I'm not exactly sure how to set that up.
For example if you are reserving a time at a massage therapy place from 9-12, you can't reserve another one for yourself at 11-1, it wouldn’t make sense. I'm trying to figure out how to put it into code. My noob level is having me think to have a string as a selection method, then parse the string to integer and compare the integer for overlapping? I want to use the Date class though because it seems very useful and I'd like to learn how to use it.

LocalTime from java.time
LocalTime blockStart = LocalTime.of(9, 0);
LocalTime blockEnd = LocalTime.of(12, 0);
assert blockEnd.isAfter(blockStart);
LocalTime newBlockStart = LocalTime.of(11, 0);
LocalTime newBlockEnd = LocalTime.of(13, 0);
assert newBlockEnd.isAfter(newBlockStart);
if (newBlockStart.isBefore(blockEnd) && newBlockEnd.isAfter(blockStart)) {
System.out.println("" + newBlockStart + '–' + newBlockEnd
+ " overlaps with " + blockStart + '–' + blockEnd);
}
Output from the snippet is:
11:00–13:00 overlaps with 09:00–12:00
A LocalTime is a time of day without a date. A potential liability is that it cannot take into account if the clock is turned forward or backward, for example when summer time (DST) begins or ends. But if you want nothing that fancy, it’s the correct class to use.
The formula for detecting an overlap is standard. I include a link to a question about it below.
I want to use the Date class though…
Don’t, don’t, please don’t. The Date is poorly designed and long outdated. You are seriously better off not learning how to use that class. Learn to use java.time, the modern Java date and time API. A possible place to start is the first link below.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Algorithm to detect overlapping periods [duplicate] (in C#, but the formula used is the same)
Determine Whether Two Date Ranges Overlap

Related

Adding 1 hour to 13 digit Timestamp

I tried the code below.
Timestamp timestampDate = scheduled.getInterviewDateAndTime(); //from DB
Map<String, Object> map = new HashMap();
map.put("eventTitle", "interview with");
map.put("startDateTime", timestampDate);
System.out.println("startDateTime : " + timestampDate);
long addTime = 1*60*60*1000;
timestampDate.setTime(timestampDate.getTime() + TimeUnit.HOURS.toMillis(addTime));
map.put("endDateTime", timestampDate);
System.out.println("endDateTime : " + timestampDate);
Is this the correct way or are there any good alternative approaches?
output is:
startDateTime : 2017-11-07 09:08:00.0
endDateTime : 2428-07-15 09:08:00.0
how to get correct output?
There are several problems here:
java.sql.Timestamp (I assume that's what it is) is a mutable class, so setting a time on it changes the timestamp state. Even if it is not apparent with your println statements, debugging the map afterwards will reveal it in a heartbeat.
You logic for computing hours is wrong (you're multiplying two times there)
First time when making addTime variable.
Second time when using TimeUnit.toMillis()
There are (of course) several ways of fixing this:
The way I like more (it, however, requires Java 8 or ThreeTen library):
Timestamp start = obtain();
Timestamp end = Timestamp.from(start.toInstant().plus(1, ChronoUnit.HOURS));
It utilizes ability to convert a Timestamp to a java.time.Instant object (or equivalent version from ThreeTen), and then a factory constructor that will take an Instant and make a Timestamp out of it (this is Java 8 version, ThreeTen will have similar factory in other class not on Timestamp). It also utilizes a much cleaner time computation logic added in java.time compared to old datetime/calendar classes from JDK.
The second variant, which is about the same, doesn't use all the fancy stuff, but as a result is also much less readable (to me):
Timestamp start = obtain();
Timestamp end = new Timestamp(start.getTime() + TimeUnit.HOURS.toMillis(1));
As you can see, second variant is almost what you have, but without unnecessary computation.
And please don't compute these values manually; we all know that hour is supposed to be 60 minutes long and a minute is 60 second, etc., but reading this all over the place blurs eyes very quickly. You yourself has seen it by computing it manually first and then still using the TimeUnit one. And it's even worse when you need to add a day, because millis will not let you to add exactly one day without pulling a lot of information about day length at some specific point in time, considering daylight savings times and historical timezone changes. Not every minute is 60 seconds as well, there are compensative measures for those too, you know.
My recommendation for you is that you stop using the long outdated Timestamp class. Either completely if you can, or at least you minimize your use of it. I will show you code for both options. The modern Java date and time API known as java.time or JSR-310 is so much nicer to work with. And even more so when it comes to time arithmetic like adding an hour to a date-time.
java.time
Change getInterviewDateAndTime() to return an Instant. Instant is the class from java.time that naturally replaces the old Timestamp class. Also change the receiver of your Map to accept a map with Instant objects in it. Modern versions of JDBC, JPA, etc., happily retrieve Instant objects from your database and store Instants back into it.
Instant instantStart = scheduled.getInterviewDateAndTime(); //from DB
Map<String, Object> map = new HashMap<>();
map.put("eventTitle", "interview with");
map.put("startDateTime", instantStart);
System.out.println("startDateTime : " + instantStart);
Instant instantEnd = instantStart.plus(1, ChronoUnit.HOURS);
map.put("endDateTime", instantEnd);
System.out.println("endDateTime : " + instantEnd);
Things to note: The code much more naturally and straightforward expresses the fact that one hour is added. No need for multiplications, no need for the reader to check that you multiplied the right constants, or that your multiplication didn’t overflow; it’s all taken care of. The Instant class is immutable, so there’s no risk of accidentally changing the Instant object that you have alrady added to the map.
In my example the above code printed:
startDateTime : 2017-11-29T09:15:00Z
endDateTime : 2017-11-29T10:15:00Z
Times are in UTC.
EDIT: As Basil Bourque helpfully pointed out in a comment, adding an hour can also be done this way:
Instant instantEnd = instantStart.plus(Duration.ofHours(1));
The result is the same.
Use with legacy APIs
Assume you cannot change the return type of getInterviewDateAndTime() and/or the receiver of your map absolutely needs Timestamp objects in it. The standard way to go about such a restriction is you still use the modern API in your own code. As soon as you receive a Timestamp, you convert it to Instant. And when you need to pass a Timestamp to your legacy code, you convert your Instant only in the last moment.
Timestamp timestampStart = scheduled.getInterviewDateAndTime(); //from DB
Instant instantStart = timestampStart.toInstant();
Map<String, Object> map = new HashMap<>();
map.put("eventTitle", "interview with");
map.put("startDateTime", timestampStart);
System.out.println("startDateTime : " + timestampStart);
Instant instantEnd = instantStart.plus(1, ChronoUnit.HOURS);
Timestamp timestampEnd = Timestamp.from(instantEnd);
map.put("endDateTime", timestampEnd);
System.out.println("endDateTime : " + timestampEnd);
You still have most of the advantages of the modern API mentioned above, but a couple of extra lines for the conversions. This code printed
startDateTime : 2017-11-29 10:15:00.0
endDateTime : 2017-11-29 11:15:00.0
The times are the same as above, but in my local time zone since Timestamp.toString() renders them this way (which confuses many).
Question: Can I use the modern API with my Java version?
If using at least Java 6, you can.
In Java 8 and later the new API comes built-in.
In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310). Conversion between Timestamp and Instant is a little different and goes through a class named DateTimeUtils, but it’s not more complicated. The rest is the same.
On Android, use the Android edition of ThreeTen Backport. It’s called ThreeTenABP, and I think that there’s a wonderful explanation in this question: How to use ThreeTenABP in Android Project.

Java Convert Interval of Days to Dates

I have a dates in format such as:
- dd. - dd.MM.yyyy
Based on this information I want to receive from date and to date.
Naive implementation in pseudo code is:
Split the date into first part and second part
Create a SimpleDateFormat for second part
Take a look, whether the number in the first part is higher than the number in a second part.
If it is:
Decrement month for the first date
Create from date which will contain the dd from the first part, decremented month from the second part and year from the second part.
If it isn't:
Create from date which will contain the dd from the first part and month and year from second part.
This solution would probably work most of the time, but it feels rather awkward. Isn't there any better solution?
I'd suggest you to check the package
org.joda.time
In particular the following classes
DateTime
Period
java.time
The java.time framework is built into Java 8 and later. Much of that functionality is back-ported to Java 6 & 7 and to Android.
I provide some scraps of example code here, but untested – never run. Should get you going in the right direction.
Two tricky parts to this problem:
YearThe Question says to assign the year. That is not the case for a stop date in January with a start date that turns out to be in previous month, December. You want the previous year in such a case. Solution is to let java.time subtract a month and handle the Jan-to-Dec math for you.
Month lengthVarious months have different lengths, different number of days, obviously. Keep mind that you cannot try to put day-of-month 31 on to month of April. If your input data is always clean and valid, and our algorithm below is correct, this should be a non-issue. Nevertheless, I would certainly add some exception-catching code to my example code below to trap any attempt to form an invalid date.
I'll skip the string-splitting part, and assume you have a number (the day-of-month) from the first part, and a string of the date from the second part.
long dayOfMonth = Long.longValue( "31" );
That date is not in standard format, so we must specify a formatting pattern. A LocalDate represents a date-only value without time-of-day and without time zone.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "dd.MM.yyyy" );
LocalDate stop = LocalDate.parse( "21.05.2016" , formatter );
We can extract the day-of-month to compare.
LocalDate start = null;
int d = localDate.getDayOfMonth();
Do the comparison.
if( dayOfMonth >=d ) { // If start is in previous month…
start = stop.minusMonths( 1 ).withDayOfMonth( dayOfMonth );
} else if (dayOfMonth < d ) { // If start is in same month…
start = stop.withDayOfMonth( dayOfMonth );
} else {
// FIXME: handle impossible condition as error. The 'if' statements are flawed.
}
By the way, the format of this input data is awkward and, frankly, silly. This kind of precious “cleverness” creates extra work, gives opportunity for confusion and errors, is completely needless without providing any benefits, and drives me nuts. If you have any control of this input data I strongly suggest either of two possible changes.
First, if exchanging data within your app, do not use strings. Use objects. Above you have seen the LocalDate object. You could pass those around. Or even define your own class LocalDateRange to house a pair of LocalDate objects. Or see this Question and especially this Answer that talks about using the Google Guava class Range to hold the pair of LocalDate objects.
Secondly, when you must serialize date-time values to strings, use the standard ISO 8601 formats. Such use is simple as the java.time classes by default use these formats when parsing/generating strings. A date-only value should be in YYYY-MM-DD order. A date range interval is a pair of those strings mated with a slash (SOLIDUS) or alternatively a pair of hyphens when a slash is inappropriate (such as file or folder naming within a Unix-related file system).

Java Built In Date Manipulation and Comparison

I'm looking to create a datetime stamp, then add 10 hours to it, then have a thread check to see if the time has elapsed.
I read through, Time comparison but it seems a bit complicated/convoluted for something so simple. Especially if your time comparison goes across midnight.
My understanding is that java's underlying datetime, is suppose to be a long, if this is true, is there a simple way to add another long to it, such as the number equivalent of 10 hours? Or some other means such as adding two dates?
Note: The solution needs to be part of core java, can't be part of a 3rd party lib.
You can use a Calendar to perform that math,
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR, 10); // Add 10 hours.
Date date2 = cal.getTime(); // Now plus 10 hours.
Date date = new Date(); // Now.
You can use the Date.getTime() method to obtain the underlying timestamp, the timestamp is basically the number of milliseconds elapsed since a defined base instant (1970-01-01 00:00:00 IIRC).
System.currentTimeMillis() allows you the get the "now" instant directly, without any detours using Date, Calendar and the like.
The timestamp can then be manipulated basic math:
timestamp += TimeUnit.MILLISECONDS.convert(10, TimeUnit.HOURS);
Example of adding 10 hours:
long nowInMilliSince1970 = System.currentTimeMillis();
long tenHoursAsMilli = TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES);
long tenHoursLater = nowInMilliSince1970 + tenHoursAsMilli;
System.out.println("now in milliseconds: \t\t" + nowInMilliSince1970);
System.out.println("10 hours in milliseconds: \t" + tenHoursAsMilli);
System.out.println("10 hours from now: \t\t" + tenHoursLater);
Checking if the timestamp is in the past is as easy as:
if (timestamp < System.currentTimeMillis()) {
System.out.println("timestamp is in the past");
}
Do note that direct timestamp math has no concept of daylight saving and time zones. If you want that, use a Calendar for math - Calendar implements the dirty exceptional rules for that.
Another way of achieving it using just JDK built in stuff is:
long tenHoursFromNow = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(10);
and then in your Thread you would check:
if(System.currentTimeMillis() > tenHoursFromNow)
{
//Do something as the time has elapsed
}
Although I would argue that the use of Calendar and Date is clearer as to what the intention of your code is trying to achieve.
The bundled java.util.Date and .Calendar are notoriously troublesome. They really should be avoided.
You stated a requirement of no added libraries. So see the java.time part of my answer, using the new package newly added to Java 8. But I urge you to reconsider your reluctance to add a library, especially if you cannot move to Java 8 yet; j.u.Date/Calendar really are that bad.
Both libraries handle anomalies such as Daylight Saving Time.
Consider specifying a time zone rather than rely on the JVM's default. Generally best to work in UTC, and then translate to a local time zone for presentation to the user.
java.time
The java.time package is newly added to Java 8. Inspired by Joda-Time but re-architected. Defined by JSR 310. Extended by the threeten-extra project.
ZonedDateTime tenHoursLater = ZonedDateTime.now().plusHours( 10 );
Joda-Time
Using the Joda-Time 2.3 library.
DateTime tenHoursLater = DateTime.now().plusHours( 10 );
For more info on this kind of use of Joda-Time, see my answer to a similar question.

Java same Dates containing different time in milli seconds [duplicate]

This question already has answers here:
How to check if 2 dates are on the same day in Java
(4 answers)
Closed 8 years ago.
I have two calendar objects, they seems to contain same dates but the compareTo() method is returning -1 as result, Can any one explain the reason behind this.
On debugging the two Calendar objects, the result is shown as :
2014-06-01T00:00:00.000Z
for both calendar objects but the compareTo() is returning -1. Even the long time in millis for both dates are different.
Well, have a look at the Calendar code (this is from JDK 1.7.0-13):
public int compareTo(Calendar anotherCalendar) {
return compareTo(getMillisOf(anotherCalendar));
}
private int compareTo(long t) {
long thisTime = getMillisOf(this);
return (thisTime > t) ? 1 : (thisTime == t) ? 0 : -1;
}
It should be obvious that if the two Calendar's have different millis, then they're different as per the second method.
In any case, the millis in your example should not both represent 2014-06-01T00:00:00.000Z so there's another problem in your code. Try this:
Timestamp ts1 = new Timestamp( 1401561000000L );
Timestamp ts2 = new Timestamp( 1401595200000L );
System.err.println( ts1 );
System.err.println( ts2 );
Outputs:
2014-05-31 20:30:00.0
2014-06-01 06:00:00.0
Cheers,
The milliseconds number is the "offical" time in Java. However, for a variety or reasons, there are numbers with the same date/time which have different numbers of milliseconds. Then normal reason is clock adjustments. E.g. Sometimes you have to add a second or two to account for irregularities in the earth's orbit. The other big source is when regions were first brought into the UTC, then some time zones moved hours.
THere is also the common source for these things: DST.
This will happen twice a year when you move to daylight saving time, on the one hand there are date/times which do not exists, as they were "skipped", and there are other times which happened twice, as the clock gets reset at midnight, so 11pm-midnight happens twice on the same day.
If you want to just compare the minutes and ignore the milliseconds or seconds do this:
You need to use
cal.set(Calendar.MILLISECOND, 0);
and possibly as well
cal.set(Calendar.SECOND, 0);
if you just need the minutes to match.
Quick explanation of what is going on:
The JavaDoc for Calendar states:
Compares the time values (millisecond offsets from the Epoch)
represented by two Calendar objects.
So you acknowledge that ".. long time in millis for both dates are different .."
#JonSkeet says in this question:
Calendar.setTime takes a java.util.Date, which is just a wrapper
around a long indicating the number of milliseconds since midnight Jan
1st 1970, UTC. It's not "in the format MM/dd/yyy" - that's a string
representation, not a java.util.Date. If it happens to print something
out in the format MM/dd/yyyy, that's just what Date.toString is doing
for you - it's not inherently part of the format.
This should answer your question about what is going on.
Note: java.util.Date has the same problem.
PS. A lot of people say use Joda Time, which I have heard is going to be in Java 8, but I have not personally tried it. If you are going to be using a lot of date code, I'd recommend you use it.
I invoked compareTo on Date instead of Calendar and got the correct result. It might be because of the fact that Calendar stores Timezone information but Date object does not.
Thanks

Sum two dates in Java

How can I add two dates in Java?
Example: The sum of "2010-01-14 19:16:17" "0000-10-03 01:10:05"
would result in "2010-11-17 20:26:22".
I know how to do it using Calendar and adding field by field.
Is any other way to sum them all (year/month/day/hour/minute/second) at once?
If you are using the Date object, you can just do:
Date d1 = ...
Date d2 = ...
long sum = d1.getTime() + d2.getTime();
Date sumDate = new Date(sum);
The code uses the .getTime() method that returns the number of milliseconds since the epoch.
Needless to say the Date class has a lot of problems and should be avoided when possible.
Do you want to sum other types instead?
Update: for Calendar, I would do the following (based on javadocs):
Calendar c1 = ...
Calendar c2 = ...
long sum = c1.getTimeInMillis() + c2.getTimeInMillis();
Calendar sumCalendar = (Calendar)c1.clone();
sumCalendar.setTimeInMillis(sum);
UPDATED: As Steve stated, this works if the Date you presented here assumes that the second date is with respect to the Java epoch. If you do want to start with year "0", then you need to account for that (by subtracting your epoch time).
Don't sum the time in millis of the two dates!
Date d1 = new Date();
Date d2 = new Date();
Date dTotal = new Date(d1.getTime() + d2.getTime());
System.out.println(dTotal); // Incorrect! Misses about 1970 years.
Just clone the Calendar and add the datetime parts one by one.
Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
Calendar cTotal = (Calendar) c1.clone();
cTotal.add(Calendar.YEAR, c2.get(Calendar.YEAR));
cTotal.add(Calendar.MONTH, c2.get(Calendar.MONTH) + 1); // Months are zero-based!
cTotal.add(Calendar.DATE, c2.get(Calendar.DATE));
cTotal.add(Calendar.HOUR_OF_DAY, c2.get(Calendar.HOUR_OF_DAY));
cTotal.add(Calendar.MINUTE, c2.get(Calendar.MINUTE));
cTotal.add(Calendar.SECOND, c2.get(Calendar.SECOND));
cTotal.add(Calendar.MILLISECOND, c2.get(Calendar.MILLISECOND));
System.out.println(cTotal.getTime()); // Correct!
Needless to say, JodaTime is smarter and cleaner with this.
As always, I would recommend the Java 8 date/time APIs or Joda for date/time work, since they are much more powerful and intuitive.
You can add durations and periods to a DateTime object trivially. You can add minutes/seconds/months equally easily.
However, you can't add two dates directly, since that doesn't really make sense. This is a powerful illustration of why Joda is a help - it stops you doing stuff that you really shouldn't be doing.
tl;dr
LocalDateTime later =
LocalDateTime
.parse (
"2010-01-14 19:16:17"
.replace ( " " , "T" )
)
.plus( Period.parse ( "P10M3D" ) )
.plus( Duration.parse ( "PT1H10M5S" ) )
;
ISO 8601
The representation of a span-of-time using the same format as a moment is creating confusion. A span is not at all the same as a moment.
Instead of using YYYY-MM-DD HH-MM-SS format for a span of time, I suggest using the standard ISO 8601 format of PnYnMnDTnHnMnS. In this format, the P marks the beginning (for "Period" presumably) and the T separates the years-month-days portion from the hours-minutes-seconds portion.
Example values:
PT1H30M → One and a half hours.
P3Y6M4DT12H30M5S → Three years, six months, four days, twelve hours, thirty minutes, and five seconds.
P10M3DT1H10M5S → Your Question’s duration of 0000-10-03 01:10:05.
java.time
The Question and the other Answers use troublesome old date-time classes now outmoded by the java.time framework built into Java 8 and later. See Oracle Tutorial. Much of the java.time functionality has been back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.
The java.time classes use ISO 8601 formats by default when parsing and generating Strings that represent date-time values.
The Question does not provide any time zone info, so here we use the LocalDateTime class. If we know an offset-from-UTC we would use the OffsetDateTime class, and if even better we knew a time zone, we would use the ZonedDateTime class.
Spans of time in java.time are divided amongst a pair of classes. Years-months-days are represented by the Period class, and hours-minutes-seconds are handled by the Duration class.
Combining these times, we can indeed perform date-time math. Here we add a span of time to an starting date-time to get a resulting date-time. And we do so in very few lines of code. The result is indeed that expected by the Question.
We convert the input strings to canonical ISO 8601 format by replacing the SPACE in the middle with a T.
LocalDateTime ldt = LocalDateTime.parse ( "2010-01-14 19:16:17".replace ( " " , "T" ) );
//"0000-10-03 01:10:05"
Period period = Period.parse ( "P10M3D" );
Duration duration = Duration.parse ( "PT1H10M5S" );
LocalDateTime result = ldt.plus ( period ).plus ( duration );
Compare to the result expected in the Question.
LocalDateTime expectation = LocalDateTime.parse ( "2010-11-17 20:26:22".replace ( " " , "T" ) );
Boolean isSame = result.equals ( expectation );
Dump to console.
System.out.println ( "ldt: " + ldt + " + period: " + period + " + duration: " + duration + " is result: " + result + " compared to expectation: " + expectation + " is the same: " + isSame );
ldt: 2010-01-14T19:16:17 + period: P10M3D + duration: PT1H10M5S is result: 2010-11-17T20:26:22 compared to expectation: 2010-11-17T20:26:22 is the same: true
You want to do getTimeInMillis() on both those Calendars so you'll have two honest-to-goodness long values you can add up. You can then take the sum and stash it in a new Calendar using that Calendar's setTimeInMillis() method.
Whether you want to add two Calendars as shown above or two Dates as shown in notnoop's answer is up to you, of course. The effect is similar, it just depends on what you want to do with the result. A Date is mostly just good for storing and/or converting to a String for printing out or displaying, whereas a Calendar will let you fiddle with the individual time values should you so choose.
As others have mentioned, you're committing some conceptual no-no's in using a Date or Calendar, which are meant to store "real" dates and times, e.g. ones in the 20th or 21st century, as intervals, i.e. time spans. The classes in the standard Java library don't give you really useful tools to handle this, which is why the Joda classes were developed. All the cool kids in date/time processing use those; but on the other hand that involves downloading and managing a 3rd party library.
notnoop answer is definitely correct. However, if you are going to do lots of processing of dates, times and intervals, I suggest that you look at class DateUtils in apache commons lang and at joda-time library.
JDK7 will come with better support for some of the features that joda-time provides. Just saying ... it might be a consideration if your app makes heavy usage of this stuff.
You need to define your EPOCH. The Java epoch (like Unix) is 1 Jan 1970 GMT/UTC. I assume you think you're adding ten months, 3 days and some odd hours from 1 Jan 0000 but you have a epoch offset until 1970. The maths may not necessarily work.
Use Calendar or Joda (as mentioned). If you just simply want to add a number of seconds and days (&c) then feel free to add said # of milliseconds to your first date object.
Use calendar class add method to add two dates in java.
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.Date,23);
calendar.add(Calendar.Month,13);
calendar.add(Calendar.Year,15);
By using add method in Calendar class we can add day,month,year to the existing date.
click here for complete program.
I am occasionally guilty of this practice too, storing time interval in a date object and using getTime() as suggested by notnoop.
It works. Contrary to certain opinion, it certainly works. I just ignore that the interval could be representative of an unintended date. It is a quick and dirty way for me to add an interval, say, [6 years, 6 months, 6 days, 6 hours, 6 minutes, 6 seconds] to a date.

Categories