I want to create time events in Java.
For example: between March 1st to March 31st I want to create some events with start and end dates like:
Event1: March 3 to March 5
Event2: March 6 to March 10
Event2: March 13 to March 25
and so on.
These events should not overlap and an event should not contain March 1st and March 31st.
How I can do it using Java date and Time or Joda-Time?
I have an only basic idea that create a class with four variables as follows:
monthStart
monthEnd
eventStart
eventEnd
One alternative is to use the Time4J API, and create a DateInterval using PlainDate as start and end dates:
// March 1st and 31st
PlainDate start = PlainDate.of(2018, 3, 1);
PlainDate end = PlainDate.of(2018, 3, 31);
DateInterval interval = DateInterval.between(start, end);
With this, you can check if an event is inside this interval:
// Event1: March 3 to March 5
PlainDate eventStart = PlainDate.of(2018, 3, 3);
PlainDate eventEnd = PlainDate.of(2018, 3, 5);
DateInterval event1 = DateInterval.between(eventStart, eventEnd);
if (interval.encloses(event1)) {
// event1 is inside interval
}
You can also check if 2 events overlap:
// Event2: March 3 to March 5
eventStart = PlainDate.of(2018, 3, 6);
eventEnd = PlainDate.of(2018, 3, 10);
DateInterval event2 = DateInterval.between(eventStart, eventEnd);
if (event1.overlaps(event2)) {
// events 1 and 2 overlap
}
Your algorithm would be: create the full interval (such as from March 1st to March 31st) and create your events intervals, and then use the methods above (encloses and overlaps) accordingly.
Plain Java
With just Java's API, assuming you have Java 8, it's similar. The only difference is that Java doesn't have an Interval class and you have to compare the dates manually:
// March 1st and 31st
LocalDate start = LocalDate.of(2018, 3, 1);
LocalDate end = LocalDate.of(2018, 3, 31);
// Event1: March 3 to March 5
LocalDate eventStart = LocalDate.of(2018, 3, 3);
LocalDate eventEnd = LocalDate.of(2018, 3, 5);
// check if event1 is inside start and end dates
if (start.isBefore(eventStart) && eventEnd.isBefore(end)) {
// event is inside March 1st and 31st
}
LocalDate has the methods isBefore and isAfter to check if another date is before or after the date, and it also has the equals method to know if 2 dates are the same. The logic to know if 2 events overlap can be achieved by only using those methods as well, and "it's left as an exercise to the reader" :-)
If you have Java 7 or below, you can use the threeten backport, which has the LocalDate class as well.
Threeten Extra
In this API, there's an Interval class, but it works only with Instant, not with LocalDate.
You can make some workaround on this and assume that your dates are in UTC, and then use the Interval. I created an auxiliary method to do such conversion:
public Instant toInstant(LocalDate date) {
// convert to midnight in UTC
return date.atStartOfDay(ZoneOffset.UTC).toInstant();
}
And then use this to create the intervals:
// March 1st and 31st
Instant start = toInstant(LocalDate.of(2018, 3, 1));
Instant end = toInstant(LocalDate.of(2018, 3, 31));
Interval interval = Interval.of(start, end);
// Event1: March 3 to March 5
Instant eventStart = toInstant(LocalDate.of(2018, 3, 3));
Instant eventEnd = toInstant(LocalDate.of(2018, 3, 5));
Interval event1 = Interval.of(eventStart, eventEnd);
if (interval.encloses(event1)) {
// event1 is inside interval
}
// Event2: March 3 to March 5
eventStart = toInstant(LocalDate.of(2018, 3, 6));
eventEnd = toInstant(LocalDate.of(2018, 3, 10));
Interval event2 = Interval.of(eventStart, eventEnd);
if (event1.overlaps(event2)) {
// events 1 and 2 overlap
}
This is a workaround because it artificially sets the dates to midnight in UTC. As we only care about the day, month and year, though, this should do the trick (or you can also download the threeten extra's code and create another Interval class that works with LocalDate, and base your code on the original).
Joda-Time
Joda-Time is a discontinued project and the team is advising the migration to java.time API. Check in Joda-Time's website, there's a warning there saying:
Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310)
If you use Java 6 or 7, I recommend the threeten backport. For Java >= 8, use java.time with threeten extra or time4j. Only if you still use Java 5, then the best alternative is to use Joda-Time (actually, the ideal is to upgrade to a newer Java version, but anyway).
In Joda-Time, there are similar classes: LocalDate and Interval, and you need to convert the LocalDate to DateTime (assuming midnight in UTC) in order to work with intervals (similar to the conversion to Instant that we made above):
DateTime start = new LocalDate(2018, 3, 1).toDateTimeAtStartOfDay(DateTimeZone.UTC);
DateTime end = new LocalDate(2018, 3, 31).toDateTimeAtStartOfDay(DateTimeZone.UTC);
Interval interval = new Interval(start, end);
DateTime eventStart = new LocalDate(2018, 3, 3).toDateTimeAtStartOfDay(DateTimeZone.UTC);
DateTime eventEnd = new LocalDate(2018, 3, 5).toDateTimeAtStartOfDay(DateTimeZone.UTC);
Interval event1 = new Interval(eventStart, eventEnd);
// contains accepts the same start or end dates, it needs additional checks to make sure dates are different
if (interval.contains(event1)
&& event1.getStart().isAfter(interval.getStart())
&& event1.getEnd().isBefore(interval.getEnd())) {
// interval contains event1, and start and end dates are not the same
}
eventStart = new LocalDate(2018, 3, 6).toDateTimeAtStartOfDay(DateTimeZone.UTC);
eventEnd = new LocalDate(2018, 3, 10).toDateTimeAtStartOfDay(DateTimeZone.UTC);
Interval event2 = new Interval(eventStart, eventEnd);
if (event1.overlaps(event2)) {
// events 1 and 2 overlap
}
Related
How can I check if specific time will occur between two dates, for example:
time -> 11:34
dates 1.12 17:00 <-> 2.12 17:01
LocalDateTime startDateTime = LocalDateTime.of(2017, Month.DECEMBER, 1, 17, 0);
LocalDateTime endDateTime = LocalDateTime.of(2017, Month.DECEMBER, 2, 17, 1);
LocalTime timeToTest = LocalTime.of(11, 34);
// Does the timeToTest occur some time between startDateTime and endDateTime?
LocalDateTime candidateDateTime = startDateTime.with(timeToTest);
if (candidateDateTime.isBefore(startDateTime)) {
// too early; try next day
candidateDateTime = candidateDateTime.plusDays(1);
}
if (candidateDateTime.isAfter(endDateTime)) {
System.out.println("No, " + timeToTest + " does not occur between " + startDateTime + " and " + endDateTime);
} else {
System.out.println("Yes, the time occurs at " + candidateDateTime);
}
This prints
Yes, the time occurs at 2017-12-02T11:34
It’s a little bit tricky. I am exploiting the fact that LocalTime implements the TemporalAdjuster interface, which allows me to adjust one into another date-time class, in this case startDateTime. I don’t know at first whether this will adjust the time forward or backward, so I need to test that in a subsequent if statement.
Please consider whether you wanted your date-time interval to be inclusive/closed, exclusive/open or half-open. The standard recommendation is the last: include the start time, exclude the end time; but only you know your own requirements.
Also be aware that using LocalDateTime prevents taking summer time (DST) and other transitions into account. For example, if moving the clock forward in spring, some times of day will not exist that day, but the above code will be happy to tell you they do exist.
The idea would be calculating the dates between start and end date. Then pair it with your specific time and check if any date time matches the following constraint: start <= date + time <= end.
public boolean isTimeInBetween(LocalDateTime start, LocalDateTime end, LocalTime time) {
return start.toLocalDate().datesUntil(end.plusDays(1).toLocalDate())
.anyMatch(d -> !(d.atTime(time).isBefore(start) || d.atTime(time).isAfter(end)));
}
You can define 3 variables, start, end and a test time. Using Java 8's LocaleDateTime makes this simple enough. See example below with 3 test cases:
public static void main(String[] args) {
LocalDateTime start = LocalDateTime.of(2017, 12, 1, 17, 0);
LocalDateTime end = LocalDateTime.of(2017, 12, 2, 17, 1);
System.out.println("Test with time before range");
System.out.println(isInRange(start, end, LocalDateTime.of(2017, 12, 1, 12, 0)));
System.out.println("Test with time in range");
System.out.println(isInRange(start, end, LocalDateTime.of(2017, 12, 2, 11, 34)));
System.out.println("Test with time after range");
System.out.println(isInRange(start, end, LocalDateTime.of(2017, 12, 2, 20, 0)));
}
private static boolean isInRange(LocalDateTime start, LocalDateTime end, LocalDateTime test) {
return !(test.isBefore(start) || test.isAfter(end));
}
Output:
Test with time before range
false
Test with time in range
true
Test with time after range
false
I'm using Joda-Time Duration to get the duration between two DateTime:
DateTime startTimeDate = new DateTime(startTimeDateInLong, DateTimeZone.UTC);
DateTime endTimeDate = new DateTime(endTimeDateInLong, DateTimeZone.UTC);
Duration duration = new Duration(startTimeDate, endTimeDate);
I want to convert per following rules:
0-60 seconds --> 1 minute ..
1.5 - 1 hour --> 1 hour
1.6 hour - 2 hour --> 2 hour
I am using duration.toStandardHours(), but for 96 minutes it gives 1 hour instead I want 2 hours.
The Duration class doesn't round the values the way you want. Even if you get a duration of 1 hour, 59 minutes, 59 seconds and 999 milliseconds, toStandardHours() will return 1.
To get the results you want, you must get the total in seconds, and then manipulate this value accordingly. You can use the java.math.BigDecimal class, with a java.math.RoundingMode to control how the values are rounded:
// 96-minutes duration
Duration duration = new Duration(96 * 60 * 1000);
long secs = duration.toStandardSeconds().getSeconds();
if (secs >= 3600) { // more than 1 hour
BigDecimal secondsPerHour = new BigDecimal(3600);
int hours = new BigDecimal(secs).divide(secondsPerHour, RoundingMode.HALF_DOWN).intValue();
System.out.println(hours + " hour" + (hours > 1 ? "s" : "")); // 2 hours
} else {
int mins;
if (secs == 0) { // round zero seconds to 1 minute
mins = 1;
} else {
// always round up (1-59 seconds = 1 minute)
BigDecimal secondsPerMin = new BigDecimal(60);
mins = new BigDecimal(secs).divide(secondsPerMin, RoundingMode.UP).intValue();
}
System.out.println(mins + " minute" + (mins > 1 ? "s" : ""));
}
This will print 2 hours for a 96-minutes duration, 1 minute for durations between 0 and 60 seconds, and so on.
To get the difference in seconds, you can also use the org.joda.time.Seconds class:
long secs = Seconds.secondsBetween(startTimeDate, endTimeDate).getSeconds();
Java new Date/Time API
Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".
If you can't (or don't want to) migrate from Joda-Time to the new API, you can ignore this section.
If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.
If you're using Java 6 or 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP (more on how to use it here).
The code below works for both.
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.
First, to get the corresponding instant from an epoch milliseconds value, you can use the Instant class (no need to set timezone to UTC, as Instant represents an UTC instant). Then, to calculate the difference, you can use a Duration:
long startTimeDateInLong = // long millis value
long endTimeDateInLong = // long millis value
// get the corresponding Instant
Instant start = Instant.ofEpochMilli(startTimeDateInLong);
Instant end = Instant.ofEpochMilli(endTimeDateInLong);
// get the difference in seconds
Duration duration = Duration.between(start, end);
long secs = duration.getSeconds();
// perform the same calculations as above (with BigDecimal)
You can also use a ChronoUnit to get the difference in seconds:
long secs = ChronoUnit.SECONDS.between(start, end);
The only way I could find was to get the time in smaller unit first then convert to unit of desire and round it. So, for example, for the use case mentioned, the way to get rounded minutes would be something like this:
public Minutes getRoundedMinutes(DateTime dateTime1, DateTime dateTime2) {
return Minutes.minutes(
(int) round((double) secondsBetween(dateTime1, dateTime2).getSeconds() / Minutes.ONE.toStandardSeconds().getSeconds()));
}
#Test
public void should_round_minutes() throws Exception {
DateTime dateTime1 = new DateTime(2018, 1, 1, 1, 0, 0);
DateTime dateTime2 = new DateTime(2018, 1, 1, 1, 0, 29);
DateTime dateTime3 = new DateTime(2018, 1, 1, 1, 0, 30);
DateTime dateTime4 = new DateTime(2018, 1, 1, 1, 1, 1);
DateTime dateTime5 = new DateTime(2018, 1, 1, 1, 1, 31);
assertThat(getRoundedMinutes(dateTime1, dateTime2).getMinutes()).isEqualTo(0);
assertThat(getRoundedMinutes(dateTime1, dateTime3).getMinutes()).isEqualTo(1);
assertThat(getRoundedMinutes(dateTime1, dateTime4).getMinutes()).isEqualTo(1);
assertThat(getRoundedMinutes(dateTime1, dateTime5).getMinutes()).isEqualTo(2);
}
I need to populate JComboBox with days as follows:
April 1, 2014
April 2, 2014
...
April 10,2014
I am using JodaTime to define dates. However, I don't know how to create an iterater over days in JodaTime.
JComboBox<String> days = new JComboBox<String>();
DateTime startD = new DateTime(2014, 4, 1, 0, 0, 0);
for (int i=0; i<10; i++)
{
// DateTime nextD = ...
days.addItem(startD.toString(DateTimeFormat.forPattern("yyyyMMdd")));
}
DateTime currentDate = startD.plusDays(i);
You should have found that easily by reading the javadoc.
Note that unless you really want the items to represent a precise instant (i.e. the first april at midnight in your timezone), you should probably use a LocalDate instead of a DateTime.
How to check a time period is overlapping another time period in the same day.
For example,
7:00AM to 10:30AM is overlapping 10:00AM to 11:30AM
7:00AM to 10:30AM is overlapping 8:00AM to 9:00AM
7:00AM to 10:30AM is overlapping 5:00AM to 8:00AM
There is a simple solution, expressed here as a utility method:
public static boolean isOverlapping(Date start1, Date end1, Date start2, Date end2) {
return start1.before(end2) && start2.before(end1);
}
This code requires there to be at least one millisecond to be shared between the two periods to return true.
If abutting time periods are considered to "overlap" (eg 10:00-10:30 and 10:30-11:00) the logic needs to be tweaked ever so slightly:
public static boolean isOverlapping(Date start1, Date end1, Date start2, Date end2) {
return !start1.after(end2) && !start2.after(end1);
}
This logic more often comes up in database queries, but the same approach applies in any context.
Once you realise just how simple it is, you at first kick yourself, then you put it in the bank!
tl;dr
( startA.isBefore( stopB ) ) && ( stopA.isAfter( startB ) )
LocalTime
If you really want to work with a generic time-of-day without the context of a date and time zone, use the LocalTime class.
LocalTime startA = LocalTime.of( 7 , 0 );
LocalTime stopA = LocalTime.of( 10 , 30 );
LocalTime startB = LocalTime.of( 10 , 0 );
LocalTime stop2B = LocalTime.of( 11 , 30 );
Validate the data, being sure the ending is after the beginning (or equal). A briefer way of saying that is “beginning is not after ending”.
Boolean validA = ( ! startA.isAfter( stopA ) ) ;
Boolean validB = ( ! startB.isAfter( stop2B ) ) ;
Per this Answer by Meno Hochschild, using the Half-Open approach to defining a span of time where the beginning is inclusive while the ending is exclusive, we can use this logic:
(StartA < EndB) and (EndA > StartB)
Boolean overlaps = (
( startA.isBefore( stopB ) )
&&
( stopA.isAfter( startB ) )
) ;
Note that LocalTime is constrained to a single generic 24-hour day. The times cannot go past midnight, cannot wrap around into another. There are no other days to consider. Validate your inputs to verify the beginning time comes before the end, or they are equal (if that suits your business rules).
if( stopA.isBefore( startA ) ) { … handle error }
if( stopB.isBefore( startB ) ) { … handle error }
ZonedDateTime
If you want to test actual moments on the timeline, you must adjust these time-of-day objects into the context of dates and a time zone. Apply a ZoneId to get a ZonedDateTime object.
ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( z );
ZonedDateTime zdt = ZonedDateTime.of( today , startA , z);
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
If interval is opened (for example, some process is not finished yet) and end date might be null:
public static boolean isOverlapping(Date start1, Date end1, Date start2, Date end2)
{
return
((null == end2) || start1.before(end2)) &&
((null == end1) || start2.before(end1)) ;
}
JOda Time has this functionality baked in. It's very well-built and on JSR route to replace the broken Java Calendar API. You should probably considering using it.
EDIT:
Here is the working method:
public boolean isOverlapping(Date start1, Date end1, Date start2, Date end2) {
return start1.compareTo(end2) <= 0 && end1.compareTo(start2) >= 0;
}
And here is proof for everyone to try it:
#Test
public void isOverlapping_base() {
Assert.assertTrue(isOverlapping(getDate(2014, 1, 1),
getDate(2014, 3, 31), getDate(2014, 1, 2),
getDate(2014, 4, 1)));
Assert.assertTrue(isOverlapping(getDate(2014, 1, 2),
getDate(2014, 4, 1), getDate(2014, 1, 1),
getDate(2014, 3, 31)));
Assert.assertTrue(isOverlapping(getDate(2014, 1, 1),
getDate(2014, 4, 1), getDate(2014, 1, 2),
getDate(2014, 3, 31)));
Assert.assertTrue(isOverlapping(getDate(2014, 1, 2),
getDate(2014, 3, 31), getDate(2014, 1, 1),
getDate(2014, 4, 1)));
Assert.assertFalse(isOverlapping(getDate(2014, 1, 1),
getDate(2014, 1, 31), getDate(2014, 3, 1),
getDate(2014, 3, 31)));
Assert.assertFalse(isOverlapping(getDate(2014, 3, 1),
getDate(2014, 3, 31), getDate(2014, 1, 1),
getDate(2014, 1, 31)));
}
Date getDate(int year, int month, int date) {
Calendar working = Calendar.getInstance();
working.set(year, month - 1, date, 0, 0, 0);
working.set(Calendar.MILLISECOND, 0);
return working.getTime();
}
Java Q: On any given day, I want to determine the date on which (say) last Friday fell.
Example: If I run my program today (ie. Wednesday, 05th Sep 12), I should get the result as "Last Friday was on 31st Aug 12". If I run it on Saturday, 08th Sep 12, the result should be 07th Sep 12, and so on (The date formatting is not strictly an issue here though)
Is there any available api, or do I need to write a program at length going back that many days based on the current day, etc?
Thank you!
How about this:
Calendar cal = Calendar.getInstance();
int day = cal.get(Calendar.DAY_OF_WEEK);
cal.add(Calendar.DAY_OF_MONTH, -((day + 1) % 7));
Date lastFriday = cal.getTime();
We can always go back to the previous Friday by subtracting the Calendar.DAY_OF_WEEK value for the current date, plus 1. For example, if the current day is Monday (value=2) and we subtract (2 + 1) we go back 3 days to Friday. If we do the same thing on a Tuesday we go back (3 + 1) days - also to a Friday.
If the current day is either Friday or Saturday we need to be sure that we only go back 0 or 1 day respectively, so we just take mod 7 of the (day + 1) value.
int day = cal.get(Calendar.DAY_OF_WEEK);
int dayDiff = (day+1)%7;
if(dayDiff == 0)
dayDiff = 7;
cal.add(Calendar.DAY_OF_MONTH, - dayDiff);
I recently developed Lamma Date which is particularly designed for this use case:
new Date(2014, 7, 1).previous(DayOfWeek.FRIDAY); // 2014-06-27
new Date(2014, 7, 2).previous(DayOfWeek.FRIDAY); // 2014-06-27
new Date(2014, 7, 3).previous(DayOfWeek.FRIDAY); // 2014-06-27
new Date(2014, 7, 4).previous(DayOfWeek.FRIDAY); // 2014-06-27
new Date(2014, 7, 5).previous(DayOfWeek.FRIDAY); // 2014-07-04
new Date(2014, 7, 6).previous(DayOfWeek.FRIDAY); // 2014-07-04
new Date(2014, 7, 7).previous(DayOfWeek.FRIDAY); // 2014-07-04