Have a function that creates a time-only Date object. (why this is required is a long story which is irrelevant in this context but I need to compare to some stuff in XML world where TIME (i.e. time-only) is a valid concept).
private static final SimpleDateFormat DF_TIMEONLY = new SimpleDateFormat("HH:mm:ss.SSSZ");
public static Date getCurrentTimeOnly() {
String onlyTimeStr = DF_TIMEONLY.format(new Date()); // line #5
Date onlyTimeDt = null;
try {
onlyTimeDt = DF_TIMEONLY.parse(onlyTimeStr); // line #8
} catch (ParseException ex) {
// can never happen (you would think!)
}
return onlyTimeDt;
}
There are probably at least a couple other ways to create a time-only Date in Java (or more precisely one where the date part is 1970-01-01) but my question is really not about that.
My question is that this piece of code starts randomly throwing NumberFormatException on line #8 after having run in production for long time. Technically I would say that this should be impossible, right ?
Here's an extract of random NumberFormatExceptions that come from above piece of code:
java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: ".11331133EE22"
java.lang.NumberFormatException: For input string: "880044E.3880044"
java.lang.NumberFormatException: For input string: "880044E.3880044E3"
First of all I hope we can agree that formally this should be impossible? The code uses the same format (DF_TIMEONLY) as output and then input. Let me know if you disagree that it should be impossible.
I haven't been able to re-produce the problem in a standalone environment. The problem seems to come when the JVM has run for a long time (>1 week). I cannot find a pattern to the problem, i.e. summer time / winter time, AM/PM, etc. The error is sporadic, meaning that one minute it will throw NumberFormatException and the next minute it will run fine.
I suspect that there's some kind of arithmetic malfunction somewhere in either the JVM or perhaps even in the CPU. The above exceptions suggests that there's floating point numbers involved but I fail to see where they would come from. As far as I know Java's Date object is a wrapper around a long which holds the number of millis since the epoch.
I'm guessing what is happening is that there's an unexpected string onlyTimeStr created in line #5 so the problem really lies here rather than in line #8.
Here's an example of a full stacktrace:
java.lang.NumberFormatException: For input string: "880044E.3880044E3"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1241)
at java.lang.Double.parseDouble(Double.java:540)
at java.text.DigitList.getDouble(DigitList.java:168)
at java.text.DecimalFormat.parse(DecimalFormat.java:1321)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2086)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
at java.text.DateFormat.parse(DateFormat.java:355)
at org.mannmann.zip.Tanker.getCurrentTimeOnly(Tanker.java:746)
Environment: Java 7
The likely cause is the fact that SimpleDateFormat isn't threadsafe, and you're referencing it from multiple threads. While extremely difficult to prove (and about as hard to test for), there is some evidence this is the case:
.11331133EE22 - notice how everything is doubled
880044E.3880044E3 - same here
You probably have at least two threads interleaving. The E was throwing me, I was thinking it was attempting to deal with scientific notation (1E10, etc), but it's likely part of the time zone.
Thankfully, the (formatting) basic fix is simple:
private static final String FORMAT_STRING = "HH:mm:ss.SSSZ";
public static Date getCurrentTimeOnly() {
SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_STRING);
String onlyTimeStr = formatter.format(new Date());
return formatter.parse(onlyTimeStr);
}
There's a couple of other things you could be doing here, too, with a few caveats:
1 - If the timezone is UTC (or any without DST), this is trivial
public static Date getCurrentTimeOnly() {
Date time = new Date();
time.setTime(time.getTime() % (24 * 60 * 60 * 1000));
return time;
}
2 - You're going to have trouble testing this method, because you can't safely pause the clock (you can change the timezone/locale). For a better time dealing with date/time in Java, use something like JodaTime. Note that LocalTime doesn't have a timezone attached, but Date only returns an offset in integer hours (and there are zones not on the hour); for safety, you need to either return a Calendar (with the full timezone), or just return something without it:
// This method is now more testable. Note this is only safe for non-DST zones
public static Calendar getCurrentTimeOnly() {
Calendar cal = new Calendar();
// DateTimeUtils is part of JodaTime, and is a class allowing you to pause time!
cal.setTimeInMillis(DateTimeUtils.currentTimeMillis() % (24 * 60 * 60 * 1000));
return cal;
}
Joda-Time
FYI, the Joda-Time 2.3 library provides a class expressly for your purpose, time-only, without any date: LocalTime. And, it is thread-safe (immutable instances). Seems a much better option than manhandling the troublesome java.util.Date class.
LocalTime localTime = new LocalTime();
Dump to console…
System.out.println( "localTime: " + localTime );
When run…
localTime: 16:26:28.065
java.time
Java 8 brings the new java.time package, inspired by Joda-Time, defined by JSR 310.
In java.time, you will find a LocalTime class similar to the one in Joda-Time.
SimpleDateFormat is not thread safe. the following program will reproduce NumberFormatException while parsing string represented date to date object.
public class MaintainEqualThreadsPatallel {
static int parallelCount = 20;
public static void main(String[] args) throws Exception {
ExecutorService executorPool = Executors.newFixedThreadPool(parallelCount);
int numberOfThreads = 150; // Total thread count = 150*2= 300.
List<Future<Object>> futureReturns = new LinkedList<Future<Object>>();
for (int i = 0; i < numberOfThreads; i++) {
int uniqueRandomValues = uniqueRandomValues(1, 10);
// Callable Thread - call()
Future<Object> submit = executorPool.submit( new WorkerCallable(uniqueRandomValues) );
futureReturns.add(submit);
// Runnable Thread - run()
executorPool.execute( new WorkerThread(uniqueRandomValues) );
}
// WorkerCallable: Blocking main thread until task completes.
waitTillThreadsCompleteWork(futureReturns);
// Terminate Pool threads in-order to terminate main thread
executorPool.shutdown();
}
private static final SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
public static Date numberFormatEx(Date date) throws ParseException { // synchronized
String dateStr = sdf.format(date);
Date dateParsed = sdf.parse(dateStr); // NumberFormatException: For input string: "186E.2186E2"
System.out.println("Date :"+ dateParsed);
return dateParsed;
}
protected void loopFunction(int repeatCount) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName +":START");
for (int i = 1; i <= repeatCount; i++) {
try {
System.out.println(threadName +":"+ i);
sleepThread(100);
numberFormatEx(new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(threadName +":END");
}
public static void waitTillThreadsCompleteWork(List<Future<Object>> futureReturns) throws Exception {
for (Future<Object> future : futureReturns) {
int threadReturnVal = (int) future.get();
System.out.println("Future Response : "+threadReturnVal);
}
}
public static int uniqueRandomValues(int min, int max) {
int nextInt = ThreadLocalRandom.current().nextInt(min, max);
System.out.println("Random Vlaue : "+nextInt);
return nextInt;
}
public void sleepThread(long mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class WorkerThread extends MaintainEqualThreadsPatallel implements Runnable {
int randomValue = 0;
public WorkerThread(int randomValue) {
this.randomValue = randomValue;
}
#Override
public void run() {
// As separate stack run() function doesn't accepts parameters, pass to constructor.
loopFunction(randomValue);
}
}
class WorkerCallable extends MaintainEqualThreadsPatallel implements Callable<Object> {
int randomValue = 0;
public WorkerCallable(int randomValue) {
this.randomValue = randomValue;
}
public Object call() {
// As separate stack run() function doesn't accepts parameters, pass to constructor.
loopFunction(randomValue);
return randomValue;
}
}
NumberFormatException with different messages:
java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: For input string: "186E.2"
java.lang.NumberFormatException: For input string: "186E.2186E2"
java.lang.NumberFormatException: For input string: "22200222E.222002224EE4"
java.lang.NumberFormatException: For input string: "22200222E.222002224EE44"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
In Multi-Threading/Web Application with Multi-Requests concept parse function leads to NumberFormatException which can be handled using synchronized block.
To overcome NumberFormatException on parse() function use any of the following scenarios.
Separate Object: Every request/thread works on its own object.
public static Date numberFormatEx(Date date) throws ParseException {
SimpleDateFormat ObjInstance = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
String dateStr = ObjInstance.format(date);
Date dateParsed = ObjInstance.parse(dateStr);
System.out.println("Date :"+ dateParsed);
return dateParsed;
}
Unnecessary creating reusable object for each thread.
Static Object synchronized block: Every request/thread shares the common object to perform operation. As multiple threads share same object at same time then the object data gets clear/overrride ""/"186E.2186E2" at some point and leads to error.
static SimpleDateFormat objStatic = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
public static synchronized Date numberFormatEx(Date date) throws ParseException {
String dateStr = objStatic.format(date);
Date dateParsed = objStatic.parse(dateStr); // NumberFormatException: For input string: "186E.2186E2"
System.out.println("Date :"+ dateParsed);
return dateParsed;
}
NOTE: In case of Memory management it better to use synchronized block with static object which is reusable.
I have got the same question, the cause is SimpleDateFormat is not thread-safe, I just add syncronized in the method, and it doesn't happen again.
You can use "sychronized" block to make it thread safe.
Something like:
synchronized (lastUpdatedFormat) {
date =
lastUpdatedFormat.parse(lastUpdatedFormat.format(currentDate));
}
The diagnosis in the accepted answer is correct. I am providing the modern answer: do use java.time, the modern Java date and time API, for your date and time work. In Java 7 too. SimpleDateFormat is notoriously troublesome, its lack of thread safety is only one of its many problems. So don’t use that class.
OffsetTime.now() and ThreeTen Backport
You want the current time only, though with an offset from UTC, if your format pattern is to be believed. We have got a method exactly for that in java.time, the modern Java date and time API. So no reason to format into a string and parse back.
OffsetTime timeOnly = OffsetTime.now(ZoneId.systemDefault());
System.out.println(timeOnly);
When I ran the code just now in my time zone, Europe/Copenhagen, on jdk.1.7.0_67, the output was:
06:21:55.419+01:00
By the way this is also the XML format for the concept of a time with time zone. Are we done?
The Date class you were returning is poorly designed and long outdated, so avoid it if you can. If you need one for a legacy API that you cannot afford to change just now, convert like this:
Instant asInstant = LocalDate.of(1970, Month.JANUARY, 1)
.atTime(timeOnly)
.toInstant();
Date oldfashionedDateObject = DateTimeUtils.toDate(asInstant);
System.out.println("As java.util.Date: " + oldfashionedDateObject);
As java.util.Date: Thu Jan 01 06:21:55 CET 1970
Question: Does it work on Java 7?
Environment: Java 7
java.time just requires at least Java 6.
In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. In this case use Date.from(asInstant) for converting from Instant to Date instead of the way shown in the code above.
In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.time to Java 6 and 7 (ThreeTen for JSR-310).
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
Wikipedia article: ISO 8601
While the correct answer is the one by Clockwork-Muse (the cause of the problems is the fact that SimpleDateFormat isn't thread safe) I just wanted to deliver another method of creating a time-only Date object:
public static Date getCurrentTimeOnly() {
Calendar rightNow = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
int hour = rightNow.get(Calendar.HOUR_OF_DAY);
int minute = rightNow.get(Calendar.MINUTE);
int second = rightNow.get(Calendar.SECOND);
int msecond = rightNow.get(Calendar.MILLISECOND);
long millisSinceMidnight
= (hour * 60 * 60 * 1000)
+ (minute * 60 * 1000)
+ (second * 1000)
+ (msecond);
return new Date(millisSinceMidnight);
}
This method is somewhat more formally correct, i.e. it handles leap-seconds. It doesn't assume, like other methods, that all days since epoch has always had 24*60*60*1000 milliseconds in them.
It doesn't however handle the case where the leap second is on the current day.
Related
I have GMT0 as the default timezone in a system and it causes problem when I'm serializing it and deserializing it just after that.
System.setProperty("user.timezone","GMT0");
DateTimeFormatter zoneFormatter = new DateTimeFormatterBuilder()
.appendZoneOrOffsetId()
.toFormatter();
String formatted = zoneFormatter.format(ZonedDateTime.now());
System.out.println(formatted);
System.out.println(zoneFormatter.parse(formatted));
The first System.out.println prints GMT0 while the second throws the following problem.
Exception in thread "main" java.time.format.DateTimeParseException: Text 'GMT0' could not be parsed, unparsed text found at index 3
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1952)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1777)
is it an expected behavior? Is there a way to do that in a safe manner?
As you noticed in the comments, that's a bug in JDK 8, fixed only in versions >= 9.
If you're using JDK 8 and can't/won't upgrade it, there's a workaround. You can treat the "GMT" part as a literal (the text "GMT" itself) and consider the 0 as the offset seconds, using the respective ChronoField:
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset seconds
.appendValue(ChronoField.OFFSET_SECONDS)
.toFormatter();
System.out.println(zoneParser.parse("GMT0"));
Keep in mind that this works only for offset zero. For any other values (such as "GMT2" or "GMT-2") this won't work, because it'll consider the values "2" and "-2" as seconds, but they actually mean "hours".
In case you need to parse all offset values in this format ("GMTn")
Well, JDK 8 also can't handle one-digit offsets, and it always requires a signal, either + or -. So "GMT2" and "GMT-2" won't work with the current API.
There's a harder alternative, though: create your own TemporalField, representing "offset hours". All the details about how to do it are in the documentation, and I'm not sure if all methods are correctly implemented - I'm just sure about isSupportedBy, getFrom and adjustInto, the others maybe need some improvement/adjustment:
public class OffsetHours implements TemporalField {
#Override
public TemporalUnit getBaseUnit() {
return ChronoUnit.HOURS;
}
#Override
public TemporalUnit getRangeUnit() {
return ChronoUnit.FOREVER;
}
#Override
public ValueRange range() {
return ValueRange.of(ZoneOffset.MIN.getTotalSeconds() / 3600, ZoneOffset.MAX.getTotalSeconds() / 3600);
}
#Override
public boolean isDateBased() {
return false;
}
#Override
public boolean isTimeBased() {
return true;
}
#Override
public boolean isSupportedBy(TemporalAccessor temporal) {
return temporal.isSupported(ChronoField.OFFSET_SECONDS);
}
#Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
ValueRange rangeInSecs = temporal.range(ChronoField.OFFSET_SECONDS);
return ValueRange.of(rangeInSecs.getMinimum() / 3600, rangeInSecs.getMaximum() / 3600);
}
#Override
public long getFrom(TemporalAccessor temporal) {
return temporal.getLong(ChronoField.OFFSET_SECONDS) / 3600;
}
#Override
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
return (R) temporal.with(ChronoField.OFFSET_SECONDS, newValue * 3600);
}
}
Now you create an instance of this field and use it in your parser:
// the new field
OffsetHours offsetHoursField = new OffsetHours();
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset hours
.appendValue(offsetHoursField)
.toFormatter();
I also recommend creating a TemporalQuery to convert the parsed result to a ZoneOffset:
// get hours and create offset from hours value
TemporalQuery<ZoneOffset> getOffsetFromHours = temporal -> {
return ZoneOffset.ofHours((int) temporal.getLong(offsetHoursField));
};
Now you can parse it:
ZoneOffset offsetZero = zoneParser.parse("GMT0", getOffsetFromHours);
ZoneOffset offsetTwo = zoneParser.parse("GMT2", getOffsetFromHours);
ZoneOffset offsetMinusTwo = zoneParser.parse("GMT-2", getOffsetFromHours);
You can improve it letting the OffsetHours field to be a static instance (or maybe an enum), so you don't need to create it all the time.
Since Java 8, the ZoneId is responsible for handling timezone names and aliases according to IANA Time Zone Database (often referred as TZ DB, zdata). In particular, ZoneId.html#of is used for that conversion.
Different TZ DB versions are shipped by Oracle with different JREs: see tzdata versions and overview of how to update tzdata
The list of TZ DB identifiers is available on wikipedia (TZ column), as for release release 2017c, GMT0 is already listed as deprecated in there, and canonical name for this time zone is Etc/GMT
Despite there is no explicit instructions to use only canonical TZ names, it can be a best practice however, as deprecated aliases may be removed from further tzdata distributions (and thus from Zone.of support) without any special notice
I usually deal with dates with ints according to the pattern yyyyMMdd.
e.g.: today is 20170113, or better 20,170,113.
I need to calculate the distance bethween dates in days, so I wrote this:
public static int calculateDistance(int data1, int data2){
try {
SimpleDateFormat normalDateFormat = new SimpleDateFormat("yyyyMMdd");
long beginTime = normalDateFormat.parse(String.valueOf(data1)).getTime();
long endTime = normalDateFormat.parse(String.valueOf(data2)).getTime();
long diff = endTime - beginTime;
return (int) TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
And this test:
public void testCalculateDistance() {
System.out.println(Calculator.calculateDistance(20090325, 20090328));
System.out.println(Calculator.calculateDistance(20090326, 20090329));
System.out.println(Calculator.calculateDistance(20090327, 20090330));
System.out.println(Calculator.calculateDistance(20090328, 20090331));
System.out.println(Calculator.calculateDistance(20090329, 20090401));
System.out.println(Calculator.calculateDistance(20090330, 20090402));
System.out.println(Calculator.calculateDistance(20090331, 20090403));
System.out.println(Calculator.calculateDistance(20090401, 20090404));
}
The output should always be the same, since I'm making the same modifications to both begin and end. But I get 2 (so 1 day less) iff the interval (end excluded) contains March 29.I also found it happens whenever I have this day of any year within the interval I mean to measure.
Why does it happen?
How can I fix it?
EDIT: I read this and I know how to calculate the difference between dates. The point is that this doesn't seem to work with this kind of classes, so this question is not a duplicate because I needn't know how to find the difference, but I need to know which classes I should use, instead of the ones I used to use.
In this case it is probably preferable to use LocalDate and ChronoUnit from java.time package.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate beginning = LocalDate.parse("20090325",formatter);
LocalDate ending = LocalDate.parse("20090328",formatter);
System.out.println(ChronoUnit.DAYS.between(beginning,ending));
In my app I create an object that represents a high school class. This object holds 2 Calendar objects that represents the class's start and stop time each day. When a user creates an assignment I want to check if the current time is between the two times of any of the classes. If it is I know that the assignment was created during that class. Here is my current code that does not work because .getTime() returns a date that includes month, and day, while I would just like to compare hours, and minutes. SO how can I trim the returned dates to just include the time in day? Would this be easier with joda-time, and if so what classes should be used?
public void checkTimeFrame() {
time = Calendar.getInstance().getTime();
ArrayList<SchoolClass> mList = mClassList;
// Changes index if assignment falls under time frame of class
for (int a = 0; a < mList.size(); a++) {
if (mList.get(a).getStartTime() != null && mList.get(a).getEndTime() != null &&
time.after(mList.get(a).getStartTime().getTime()) && time.before(mList.get(a)
.getEndTime().getTime())) {
index = a;
updateClassEditText();
}
}
}
JDK 8 Date-Time APIs are a good approach to solving these kinds of issues. Instead of Calendar , use LocalTime to store the start and end time of the class.
LocalTime now = LocalTime.now(ZoneId.systemDefault());
LocalTime start = mList.get(a).getStartTime();
LocalTime end = mList.get(a).getEndTime();
if(now.isAfter(start) && now.isBefore(end)){
//do something
}
For Android, you can use The ThreeTenABP project which adapts the java.time APIs for Android.
You can use Calendar.get(), as mentioned in another answer. To compare minutes, though, you should use Calendar.MINUTE, too:
int minutes_in_day = time.get(Calendar.HOUR_OF_DAY)*60 + time.get(Calendar.MINUTE);
Then, you can just compare the minutes within the day of the current time with that of the start and end times. This will, of course, only work when the times are in the same day.
The Calendar class has a get method where you can get different fields
e.g.
int hr = time.get(Calendar.HOUR_OF_DAY)
I will here only provide the Joda-Time-related answer you asked for. Joda-Time has the advantage to offer a dedicated type for the clock time, namely LocalTime. The old java.util.Calendar-stuff does not offer this advantage hence your difficulties.
First you convert an instance of java.util.Date like follows:
Date time = ...;
DateTime dt = new DateTime(time, DateTimeZone.getDefault());
LocalTime lt = dt.toLocalTime();
Note that the conversion is always timezone dependent. Then you can compare two LocalTime instances using the inherited methods isAfter(...) or isBefore(...).
try {
Date date1 = sdf.parse(given time);
Date date2 = sdf.parse("08:00 AM");
Date date3 = sdf.parse("06:00 PM");
if((date1.after(date2))&&(date1.before(date3))||date1.equals(date2) ||date1.equals(date3) ) {
}
} catch (ParseException e){
e.printStackTrace();
}
i'm trying to solve a seemingly simple problem, but just can't quite get my mind around it.
i have two times startTime and stopTime, which can be considered to be in the format: hh:mm:ss [24hr format].
Now given a third time - timeToTest - i need to find out if timeToTest lies between startTime and stopTime. There is no date information involved, other than just the times.
So for example - if i have startTime = '22:30:00' and stopTime = '03:30:00', then for timeToTest = '01:14:23', the test should return true.
I've tried a solution with java.util.Date by converting the times to milliseconds using getTime(), but with any interval which rolls over the 24 hr barrier, the logic fails.
I'm trying to build a solution using Java - but i believe the logic is language independent.
So the simplest solution i could come up with, sticking to plain old java.util.Date, is shown below:
String d1 = "21:00:00";
String d2 = "04:00:00";
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String dToTest = "16:00:00";
boolean isSplit = false, isWithin = false;
Date dt1 = null, dt2 = null, dt3 = null;
dt1 = sdf.parse(d1);
dt2 = sdf.parse(d2);
dt3 = sdf.parse(dToTest);
isSplit = (dt2.compareTo(dt1) < 0);
System.out.println("[split]: " +isSplit);
if (isSplit)
{
isWithin = (dt3.after(dt1) || dt3.before(dt2));
}
else
{
isWithin = (dt3.after(dt1) && dt3.before(dt2));
}
System.out.println("Is time within interval? " +isWithin);
feel free to point out any mistakes - would love to work and fix it.
You must add a "day" where "0" == current day, "1" == next day and so on. So in fact when stopTime == '03:30:00' it should be '27:30:00' (i.e. on the next day).
In your case, if the stopTime < startTime, then add 86400 seconds.
anirvan's solution using JodaTime :
public class TimeInterval24H {
private final LocalTime start;
private final LocalTime end;
public TimeInterval24H(LocalTime start, LocalTime end) {
this.start = start;
this.end = end;
}
public TimeInterval24H(Date start, Date end) {
this(new LocalTime(start), new LocalTime(end));
}
public boolean contains(Date test) {
return contains(new LocalTime(test));
}
public boolean contains(LocalTime test) {
if (isAccrossTwoDays()) {
return (test.isAfter(getStart()) || test.isBefore(getEnd()));
} else {
return (test.isAfter(getStart()) && test.isBefore(getEnd()));
}
}
boolean isAccrossTwoDays() {
return getEnd().isBefore(getStart());
}
public LocalTime getStart() {
return start;
}
public LocalTime getEnd() {
return end;
}
}
java.time
The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
Also, quoted below is a notice from the home page of Joda-Time:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
Solution using java.time, the modern Date-Time API:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Main {
public static void main(String[] args) {
String strStartTime = "22:30:00", strStopTime = "03:30:00", strTestTime = "01:14:23";
LocalDate today = LocalDate.now();
LocalDateTime startTime = today.atTime(LocalTime.parse(strStartTime));
LocalDateTime stopTime = today.atTime(LocalTime.parse(strStopTime));
if (stopTime.isBefore(startTime))
stopTime = stopTime.plusDays(1);
LocalDateTime testTime = today.atTime(LocalTime.parse(strTestTime));
if (testTime.isBefore(startTime))
testTime = testTime.plusDays(1);
if (!testTime.isBefore(startTime) && !testTime.isAfter(stopTime))
System.out.println(strTestTime + " is at or after " + strStartTime + " and is before or at " + strStopTime);
}
}
Output:
01:14:23 is at or after 22:30:00 and is before or at 03:30:00
ONLINE DEMO
Note: If the start time and stop time are not inclusive, change the condition as follows:
if (testTime.isAfter(startTime) && testTime.isBefore(stopTime))
System.out.println(strTestTime + " is after " + strStartTime + " and is before " + strStopTime);
Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
How about:
Find the next occurrence of the specified time after the start instant
Check whether that occurrence is before the end instant or not
The first step can probably be broken down pretty easily:
Is the specified time on/after the time of the start instant?
Yes: the next occurrence is that time on the same day as the start instant
No: the next occurrence is that time on the next day from the start instant
All of this is likely to be somewhat easier to write in Joda Time than using java.util.*, by the way :)
I strongly recommend java.util.Calendar, the before() and after() can be useful. However, you'll need a date like 5/18/2011 specified together with your time. Is it possible to specify a mock date (or a pair of date in your case) to leverage the Calendar?
Is it a valid way of comparing dates:
Calendar someCalendar1 = Calendar.getInstance(); // current date/time
someCalendar1.add(Calendar.DATE, -14);
Calendar someCalendar2 = Calendar.getInstance();
someCalendar2.setTime(someDate); // someDate is in the format of MM/dd/yyyy
if(someCalendar2.compareTo(someCalendar1) < 0){
...Code...
}
...or is there a better approach?
Date implements comparable itself so there's no reason to wrap it into calendar:
Calendar someCalendar1 = Calendar.getInstance(); // current date/time
someCalendar1.add(Calendar.DATE, -14);
if (someDate.compareTo(someCalendar1.getTime()) < 0) {
...Code...
}
Date also has convenient after() and before() methods that make the above comparison easier to read:
if (someDate.before(someCalendar1.getTime())) {
...Code...
}
Finally, if you're dealing with date / time a lot, do consider using Joda Time instead of built-in java classes. It's MUCH more convenient and functional:
DateTime dt = new DateTime().minusWeeks(2);
if (new DateTime(someDate).isBefore(dt)) {
...Code...
}
It's valid, but you're slightly confused about someDate - 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.
As an aside, I would personally recommend that you avoid java.util.Date and java.util.Calendar completely and use Joda Time instead. It's a much better API.
As one example of why Joda is better, take Daylight Savings Time.
If you're measuring "one day" as 1000 * 60 * 60 * 24 milliseconds, the Date library - and the Calendar library - both forget that there's one day in the year with 25 hours, and another with 23. You will occasionally screw up date calculations if you rely solely on the Java classes built into the J2SE API.
Joda is half likely to be the drop-in replacement for GregorianCalendar, Calendar, and Date in a future version of Java.
It’s an old question now. For it to be helpful to the readers of today and tomorrow it needs a modern answer. That’s what I am providing here.
java.time
LocalDate someDate1 = LocalDate.now(ZoneId.of("Africa/Lusaka"))
.minusDays(14);
LocalDate someDate2 = LocalDate.of(2019, Month.OCTOBER, 11);
if (someDate2.isBefore(someDate1)) {
System.out.println("" + someDate2 + " is before " + someDate1);
}
When I ran this code today, the output was:
2019-10-11 is before 2019-10-13
While it was valid to use Calendar in 2009, the class was always poorly designed and is now long outdated. I certainly recommend that no one uses it anymore. Instead use java.time, the modern Java date and time API.
Link: Oracle tutorial: Date Time explaining how to use java.time.
It's OK. Also you can use before() and after():
package demo.so;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class Tester {
public static void main(String[] args) throws Exception {
Calendar someCalendar1 = Calendar.getInstance(); // current date/time
someCalendar1.add(Calendar.DATE, -11);
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
Date someDate = df.parse("10/08/2009");
Calendar someCalendar2 = Calendar.getInstance();
someCalendar2.setTime(someDate);
String cal1 = df.format(someCalendar1.getTime());
String cal2 = df.format(someCalendar2.getTime());
if (someCalendar1.equals(someCalendar2))
System.out.println( cal1 + " is the same as " + cal2);
if (someCalendar1.after(someCalendar2))
System.out.println(cal1 + " is after " + cal2);
if (someCalendar1.before(someCalendar2))
System.out.println(cal1 + " is before " + cal2);
}
}
But you shouldn't use Date, is deprecated and a source of troubles with dates handling. Build your own wrapper for GregorianCalendar or use some good library, like Joda.