I have many timestamps (start, end) which define an interval and want to efficiently check if they overlap another single interval. If yes, compute overlap duration, otherwise return 0.
interval: 18:00 same day until 08:00 the next day.
start | end
2018-01-02 14:59:18.922|2018-01-02 14:59:38.804
2018-01-02 18:32:59.348|2018-01-02 20:30:41.192
2018-01-02 01:54:59.363|2018-01-02 01:54:59.363
2018-01-03 00:10:38.831|2018-01-03 00:11:53.103
I am unsure how to efficiently define the next day efficiently.
edit
LocalDate
has a method toInterval().overlaps(anotherInterval). I simply am unsure how to get fitting interval (18:00 - 08:00 next day) in a generic way, i.e. without manually reading the YYYMMDD and then creating a new object.
a bit similar is Find if hours ranges overlap regardless of the date
edit 2
toInterval is only present for jodatime - not java.time / JSR-310. What would be a viable way to calculate overlap duration with java.time?
edit3
A solution with jodaTime:
val begin = new DateTime(new java.sql.Timestamp().getTime())
val stop = new DateTime(new java.sql.Timestamp().getTime())
val i1 = new Interval(begin, stop)
val start = new DateTime(begin.year.get , begin.monthOfYear.get, begin.dayOfMonth.get, startHour, 0, 0, 0);
val endIntermediate =stop.toDateTime.plusDays(1)
val end = new DateTime(endIntermediate.year.get , endIntermediate.monthOfYear.get, endIntermediate.dayOfMonth.get, endHour, 0, 0, 0);
val i2 = new Interval(start, end)
val overlap = i1.overlap(i2)
val overlapDurationOrNull = overlap.toDuration
seems to work, but still is clumsy.
I believe that the following method gives you the equivalent of your Joda-Time solution.
private static final LocalTime START = LocalTime.of(18, 0);
private static final LocalTime END = LocalTime.of(8, 0);
public static Duration overlap(ZonedDateTime currentStart, ZonedDateTime currentEnd) {
ZonedDateTime singleIntervalStart = currentStart.with(START);
ZonedDateTime singleIntervalEnd = currentStart.plusDays(1).with(END);
if (currentEnd.isBefore(singleIntervalStart)) {
// no overlap
return Duration.ZERO;
}
ZonedDateTime overlapStart = currentStart.isBefore(singleIntervalStart)
? singleIntervalStart : currentStart;
ZonedDateTime overlapEnd = currentEnd.isBefore(singleIntervalEnd)
? currentEnd : singleIntervalEnd;
return Duration.between(overlapStart, overlapEnd);
}
For trying it out with the timestamps from your question I am using the following utility method:
private static void demo(String from, String to) {
ZoneId zone = ZoneId.of("Atlantic/Stanley");
Duration overlapDuration = overlap(LocalDateTime.parse(from).atZone(zone),
LocalDateTime.parse(to).atZone(zone));
System.out.println("" + from + " - " + to + ": " + overlapDuration);
}
Now I call it like this:
demo("2018-01-02T14:59:18.922", "2018-01-02T14:59:38.804");
demo("2018-01-02T18:32:59.348", "2018-01-02T20:30:41.192");
demo("2018-01-02T01:54:59.363", "2018-01-02T01:54:59.363");
demo("2018-01-03T00:10:38.831", "2018-01-03T00:11:53.103");
And the output is:
2018-01-02T14:59:18.922 - 2018-01-02T14:59:38.804: PT0S
2018-01-02T18:32:59.348 - 2018-01-02T20:30:41.192: PT1H57M41.844S
2018-01-02T01:54:59.363 - 2018-01-02T01:54:59.363: PT0S
2018-01-03T00:10:38.831 - 2018-01-03T00:11:53.103: PT0S
In the first example 14:59 is before 18:00, so the result is an overlap of 0. In the second example the whole interval is counted as overlap (nearly 2 hours). Note that in the last two examples no overlap is reported because the the times are many hours before 18:00. I am unsure whether this is what you wanted since the times are also before 08:00.
You can simply use LocalDate.plusDays to add a day.
Assuming an iteration where the following are to be compared:
LocalDateTime d1 = LocalDateTime.parse("2018-01-02T14:59:18"),
d2 = LocalDateTime.parse("2018-01-02T14:59:38");
You can create the 18:00 and 08:00 date/time objects using:
LocalDateTime start = LocalDateTime.of(d1.toLocalDate(), LocalTime.of(18, 0));
LocalDateTime end = LocalDateTime.of(d1.toLocalDate().plusDays(1),
LocalTime.of(8, 0));
I've assumed that 18:00 is on the same day as d1.
Related
I am trying to make a list that by default contains a list of available timeslots from one timepoint to another timepoint. It has to avoid making timeslots in a specific timeframe.
For example: I have a first timepoint being 09:00 and the end timepoint being 17:00. The duration of the timeslots is 15. Only sometimes I can't have any time timeslots in a specific timeframe. Which,for example, can be from 12:00 to 12:30.
So eventually the slots should be filled with TimeSlots from 09:00 to 12:00. And from 12:30 to 17:00. This is my code so far, I am using java.time.LocalTime. So far, it's not going that well... Thanks for looking!
data class TimeSlot(
val startTime: LocalTime,
val endTime: LocalTime,
)
private fun initializeSlots(
slots: ArrayList<TimeSlot>,
startTimeShift: LocalTime,
appointmentDuration: Long,
amountOfWorkingHours: Long,
breakTime: LocalTime,
breakDuration: Long
) {
slots.add(TimeSlot(startTime = startTimeShift, endTime = startTimeShift.plusMinutes(appointmentDuration)))
val possibleTotalAppointments = (amountOfWorkingHours * appointmentDuration) - 2 // -2 because index starts at 0 and first timeslot is already added.
for (i in 0..(amountOfWorkingHours * appointmentDuration).toInt()) {
if (slots[i].endTime == breakTime) {
val endTimeOfBreak = breakTime.plusMinutes(breakDuration)
val isTargetWithinTimeFrame = (!breakTime.isBefore(slots[i].startTime) && breakTime.isBefore(endTimeOfBreak))
if (isTargetWithinTimeFrame) {
slots.remove(slots[i])
continue
}
} else {
slots.add(TimeSlot(startTime = slots[i].endTime, endTime = slots[i].endTime.plusMinutes(appointmentDuration)))
}
}
}
If your timeslots are static(Always from 09:00 till 17:00 and always 15 min duration) you can actually hardcode them into an array, and operate through indexes further. So for example if every hour have 4 indexes starting from 0 your specific timeframe 12:00 to 12:30. will have indexes of 12, 13, 14. Such code won't be too beautiful, but still a lot more readable.
Any way, LocalTime is overall not the best idea. I would just use Long, with System.currentTimeMillis() This is the best solution for handling timezones(Formatting them only for UI, not for logic). And also it will make your time calculations much easier, because you can actually divide and subtract millis.
I took another approach to finding the answer, so if anyone comes across the same problem, here it is. You can also give multiple timeframes you want to avoid. Feel free to alter it any way. It can also return Pairs instead of 'TimeSlot's.
fun generateTimeSlots(
startTime: LocalTime,
endTime: LocalTime,
duration: Long,
avoid: List<TimeSlot>
): List<TimeSlot> {
val timeSlots = mutableListOf<TimeSlot>()
var currentTime = startTime
while (currentTime < endTime) {
val nextTime = currentTime.plusMinutes(duration)
val timeSlot = TimeSlot(currentTime, nextTime)
if (avoid.none { timeSlot.startTime in it.startTime..it.endTime.minusMinutes(1) }) {
timeSlots.add(timeSlot)
}
currentTime = nextTime
}
return timeSlots
}
Say I want my week to start on Tuesday, and the day should start at 5:30 am.
This means, code like this should work:
// LocalDateTimes created with the "standard" ISO time
LocalDateTime tuesday_4_30 = LocalDateTime.now()
.with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
.withHour(4).withMinute(30);
LocalDateTime tuesday_6_30 = tuesday_4_30.withHour(6).withMinute(30);
LocalDateTime previous_monday = tuesday_4_30.minusDays(1);
// eventual adjustment using TemporalAdjusters here? like this?
// tuesday_4_30 = tuesday_4_30.with(new MyTemporalAdjuster(DayOfWeek.TUESDAY, 5, 30));
// <do the same for 6_30 and previous monday>
// or possibly change some global parameter like Chronology, Locale, or such..
Assert.assertEquals(tuesday_4_30.getDayOfWeek(), DayOfWeek.MONDAY);
Assert.assertEquals(tuesday_6_30.getDayOfWeek(), DayOfWeek.TUESDAY);
// there is 1 week between the previous monday and the next tuesday 6:30
Assert.assertEquals( ChronoUnit.WEEKS.between(previous_monday,tuesday_6_30), 1);
// there is 0 week between the previous monday and the next tuesday 4:30
Assert.assertEquals( ChronoUnit.WEEKS.between(previous_monday,tuesday_4_30), 0);
// 1 day between tuesday_4_30 and tuesday_6_30
Assert.assertEquals( ChronoUnit.DAYS.between(tuesday_4_30,tuesday_6_30), 1);
// 0 day between previous_monday and tuesday_4_30
Assert.assertEquals( ChronoUnit.DAYS.between(previous_monday,tuesday_4_30), 1);
I am tempted to use temporal adjusters here, and I'm quite sure I could offset the hours and minute so that the day starts at 5:30, but I can't figure out how to modify the start of the week.
Note that I looked into WeekFields but I can't make it work with ChronoUnit.XXX.between(), so I didn't go too far. It looks like I would have to code my own Chronology, which seemed too far strectched.
Can you help me?
Note: ChronoUnit.WEEKS.between counts the number of entire weeks (a period of 7 days) between two dates. In your case, there is only one days between the Monday and the Tuesday so it will return 0. You probably meant to compare the week of year fields instead.
Unless you want to write your own chronology (that's going to be a pain), you could "fake" your calendar by:
converting back and forth between UTC and UTC+5:30 to represent your cut-off time / or just subtract 5:30 from the dates
adding some simple logic for the week calculations
See below a rough example based on your code, that makes all the tests pass - you may want to extract the logic into a separate class etc. This is a bit hacky but may be enough for your use case.
#Test
public void test() {
LocalDateTime tuesday_4_30 = LocalDateTime.now()
.with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
.withHour(4).withMinute(30);
LocalDateTime tuesday_6_30 = tuesday_4_30.withHour(6).withMinute(30);
LocalDateTime previous_monday = tuesday_4_30.minusDays(1);
// eventual adjustment using TemporalAdjusters here? like this?
// tuesday_4_30 = tuesday_4_30.with(new MyTemporalAdjuster(DayOfWeek.TUESDAY, 5, 30));
// <do the same for 6_30 and previous monday>
// or possibly change some global parameter like Chronology, Locale, or such..
assertEquals(dayOfWeek(tuesday_4_30), DayOfWeek.MONDAY);
assertEquals(dayOfWeek(tuesday_6_30), DayOfWeek.TUESDAY);
// there is 1 week between the previous monday and the next tuesday 6:30
assertEquals(weekBetween(previous_monday, tuesday_6_30), 1);
// there is 0 week between the previous monday and the next tuesday 4:30
assertEquals(weekBetween(previous_monday, tuesday_4_30), 0);
// 1 day between tuesday_4_30 and tuesday_6_30
assertEquals(weekBetween(tuesday_4_30, tuesday_6_30), 1);
// 0 day between previous_monday and tuesday_4_30
assertEquals(weekBetween(previous_monday, tuesday_4_30), 0);
}
private static DayOfWeek dayOfWeek(LocalDateTime date) {
return date.atOffset(ZoneOffset.ofHoursMinutes(5, 30)).withOffsetSameInstant(UTC).getDayOfWeek();
}
private static int weekBetween(LocalDateTime date1, LocalDateTime date2) {
OffsetDateTime date1UTC = date1.atOffset(ZoneOffset.ofHoursMinutes(5, 30)).withOffsetSameInstant(UTC);
OffsetDateTime date2UTC = date2.atOffset(ZoneOffset.ofHoursMinutes(5, 30)).withOffsetSameInstant(UTC);
int w1 = date1UTC.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
if (dayOfWeek(date1).getValue() >= TUESDAY.getValue()) w1++;
int w2 = date2UTC.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
if (dayOfWeek(date2).getValue() >= TUESDAY.getValue()) w2++;
return w2 - w1;
}
Alternative implementation, maybe cleaner:
private static DayOfWeek dayOfWeek(LocalDateTime date) {
return adjust(date).getDayOfWeek();
}
private static int weekBetween(LocalDateTime date1, LocalDateTime date2) {
int w1 = adjust(date1).get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
if (dayOfWeek(date1).getValue() >= TUESDAY.getValue()) w1++;
int w2 = adjust(date2).get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
if (dayOfWeek(date2).getValue() >= TUESDAY.getValue()) w2++;
return w2 - w1;
}
private static LocalDateTime adjust(LocalDateTime date) {
return date.minusHours(5).minusMinutes(30);
}
I want to calculate difference between two times which is calculate correctly then i have to half it so i divide it with 2 results are okay. but when i am trying to add the timdedifferencemillis to startTime its not giving me the correct result...
starttime= 05:53
endtime= 17:57
i want results 11:55
but my code giving me 06:55
please help.....
protected String transitTime2(String endtime, String starttime) {
SimpleDateFormat dt = new SimpleDateFormat("hh:mm");
Date startTime = null;
Date endTime;
long timdedifferencemillis = 0;
try {
startTime = dt.parse(starttime);
endTime = dt.parse(endtime);
long diff=startTime.getTime();
timdedifferencemillis = (endTime.getTime() - startTime.getTime())/2;
//timdedifferencemillis
} catch (ParseException e) {
e.printStackTrace();
}
long timdedifferencemillis1=startTime.getTime()+timdedifferencemillis;
int minutes = Math
.abs((int) ((timdedifferencemillis1 / (1000 * 60)) % 60));
int hours = Math
.abs((int) ((timdedifferencemillis1 / (1000 * 60 * 60)) % 24));
String hmp = String.format("%02d %02d ", hours, minutes);
return hmp;
}
The problem is probably time zone; when you parse endtime and starttime initially, by default (in the absence of an explicit time zone indicated in the format string and represented in the input), Java assumes that the times provided are relative to the local time zone of the system. Then, when you call getTime(), it returns
the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date object
One solution is to tell your SimpleDateFormat object to assume that all strings it parses are in GMT, rather than in the local time zone. Try adding this line after you initialize dt, but before calling dt.parse(...):
dt.setTimeZone(TimeZone.getTimeZone("GMT"));
This is quite easy to do with the new java.time API in Java 8 or with the JODA Time library:
import java.time.Duration;
import java.time.LocalTime;
public class TimeDiff {
public static void main(String[] args) {
LocalTime start = LocalTime.parse("05:53");
LocalTime end = LocalTime.parse("17:57");
// find the duration between the start and end times
Duration duration = Duration.between(start, end);
// add half the duration to the start time to find the midpoint
LocalTime midPoint = start.plus(duration.dividedBy(2));
System.out.println(midPoint);
}
}
Output:
11:55
By using LocalTime objects, you avoid any problems with time zones.
I think the problem is the types "long" and "int" in your code ;when we divide with 2 ( long timdedifferencemillis )the result must be "double".
I have two date strings and I want to know how many seconds difference there is between them.
2014-05-19 16:37:36:690 // formattedDate
2014-05-19 19:38:00:000 // expString
I use the following code:
SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss:SSS");
Date d1 = null;
Date d2 = null;
d1 = sdf.parse(expString);
d2 = sdf.parse(formattedDate);
long diff = d1.getTime() - d2.getTime();
long exp = diff / 1000 % 60;
In this particular example exp is 23. What is the problem here?
.getTime() returns the time in milliseconds since January 1, 1970, 00:00:00 GMT. So diff has the time in milliseconds between the two dates.
long diff = d1.getTime() - d2.getTime();
// diff = 10882310
You user integer division to get to seconds, which drops the extra milliseconds.
long temp = diff / 1000;
// temp = 10823
Then you modulus by 60, which gets you seconds and ignores seconds that were attributed to minutes.
long exp = temp % 60;
// exp = 23
If you want the total time in seconds between the two dates, you don't want to do that last operation.
Don't use modulus division! Just use plain division:
long diff = d1.getTime() - d2.getTime();
long exp = diff / 1000;
Better yet, use the TimeUnit enum from the JDK:
long exp = TimeUnit.MILLISECONDS.toSeconds(d1.getTime() - d2.getTime());
Joda-Time offers a Seconds class to just what you want.
The Joda-Time library also has classes to represent spans of time: Duration, Interval, and Period. You don’t strictly need them for this specific question, but they will be handy for related work.
Below is some untested code off the top of my head.
For simplicity, convert your strings to strict ISO 8601 format. Replace the SPACE with a T.
String inputStart = "…".replace( " ", "T" );
// same for stop
Create date-time objects. Explicitly assign a time zone by which to parse those strings. Are that UTC?
DateTime startDateTime = new DateTime( inputStart, DateTimeZone.UTC );
// repeat for stop
long secs = Seconds.secondsBetween( startDateTime, stopDateTime ).getSeconds();
I have a date and a number and want to check if this date and this number occurs in a list of other dates within:
+-20 date intervall with the same number
so for example 1, 1.1.2013 and 1,3.1.2013 should reuturn false.
I tried to implement the method something like that:
private List<EventDate> dayIntervall(List<EventDate> eventList) throws Exception {
List<EventDate> resultList = new ArrayList<EventDate>();
for (int i = 0; i < eventList.size(); i++) {
String string = eventList.get(i).getDate();
Date equalDate = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN).parse(string);
for (int j = 0; j < eventList.size(); j++) {
String string1 = eventList.get(i).getDate();
Date otherDate = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN).parse(string1);
if (check number of i with number of j && check Date) {
//do magic
}
}
}
return resultList;
}
The construction of the iteration method is not that hard. What is hard for me is the date intervall checking part. I tried it like that:
boolean isWithinRange(Date testDate, Date days) {
return !(testDate.before(days) || testDate.after(days));
}
However that does not work because days are not takes as days. Any suggestions on how to fix that?
I really appreciate your answer!
You question is difficult to follow. But given its title, perhaps this will help…
Span Of Time In Joda-Time
The Joda-Time library provides a trio of classes to represent a span of time: Interval, Period, and Duration.
Interval
An Interval object has specific endpoints that lie on the timeline of the Universe. A handy contains method tells if a DateTime object occurs within those endpoints. The beginning endpoint in inclusive while the last endpoint is exclusive.
Time Zones
Note that time zones are important, for handling Daylight Saving Time and other anomalies, and for handling start-of-day. Keep in mind that while a java.util.Date seems like it has a time zone but does not, a DateTime truly does know its own time zone.
Sample Code
Some code off the top of my head (untested)…
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Berlin" );
DateTime dateTime = new DateTime( yourDateGoesHere, timeZone );
Interval interval = new Interval( dateTime.minusDays( 20 ), dateTime.plusDays( 20 ) );
boolean didEventOccurDuringInterval = interval.contains( someOtherDateTime );
Whole Days
If you want whole days, call the withTimeAtStartOfDay method to get first moment of the day. In this case, you probably need to add 21 rather than 20 days for the ending point. As I said above, the end point is exclusive. So if you want whole days, you need the first moment after the time period you care about. You need the moment after the stroke of midnight. If this does not make sense, see my answers to other questions here and here.
Note that Joda-Time includes some "midnight"-related methods and classes. Those are no longer recommended by the Joda team. The "withTimeAtStartOfDay" method takes their place.
DateTime start = dateTime.minusDays( 20 ).withTimeAtStartOfDay();
DateTime stop = dateTime.plusDays( 21 ).withTimeAtStartOfDay(); // 21, not 20, for whole days.
Interval interval = new Interval( start, stop );
You should avoid java.util.Date if at all possible. Using the backport of ThreeTen (the long awaited replacement date/time API coming in JDK8), you can get the number of days between two dates like so:
int daysBetween(LocalDate start, LocalDate end) {
return Math.abs(start.periodUntil(end).getDays());
}
Does that help?
You can get the number of dates in between the 2 dates and compare with your days parameter. Using Joda-Time API it is relatively an easy task: How do I calculate the difference between two dates?.
Code:
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);
Date startDate = format.parse("1.1.2013");
Date endDate = format.parse("3.1.2013");
Days d = Days.daysBetween(new DateTime(startDate), new DateTime(endDate));
System.out.println(d.getDays());
Gives,
2
This is possible using Calendar class as well:
Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
System.out.println(cal.fieldDifference(endDate, Calendar.DAY_OF_YEAR));
Gives,
2
This 2 can now be compared to your actual value (20).