Easiest way to get overlapping times - java

I am facing a validation that get's my head smoking quite a bit.
I have an Object which I call Downtime for now and it looks like this:
public class Downtime {
/** The start date of the downtime. */
private ZonedDateTime downtimeFrom;
/** The end date of the downtime. */
private ZonedDateTime downtimeTo;
/**
* Gets the downtime from.
*
* #return the downtime from
*/
public ZonedDateTime getDowntimeFrom()
{
return downtimeFrom;
}
/**
* Gets the downtime to.
*
* #return the downtime to
*/
public ZonedDateTime getDowntimeTo()
{
return downtimeTo;
}
/**
* Sets the downtime from.
*
* #param downtimeFrom the new downtime from
*/
protected void setDowntimeFrom( ZonedDateTime downtimeFrom )
{
this.downtimeFrom = downtimeFrom;
}
/**
* Sets the downtime to.
*
* #param downtimeTo the new downtime to
*/
protected void setDowntimeTo( ZonedDateTime downtimeTo )
{
this.downtimeTo = downtimeTo;
}
}
When I create a new downtime over a CRUD implementation, I already validate that the start time is actually before the end time and such things.
Now I have to add the validation that when I create a new Downtime, it doesn't interfere with the already created downtimes. Meaning the start date of the new downtime is not already existent and is not in another downtime. ( between the start and end of an already created downtime ).
So my way of doing this right now, since I am terrible at date/time oriented things when it comes to localization, would be something like this:
private boolean isNewDowntimeValid(Downtime newDowntime, List<Downtime> createdDowntimes){
// let's just assume I already filtered out that the list only contains the same day. That's actually pretty easy.
List<ZonedDateTime> dateRange = new LinkedList<>();
ZonedDateTime newTime = newDowntime.getDowntimeFrom();
for(Downtime downtime : createdDowntimes){
ZonedDateTime downtimeStart = downtime.getDowntimeFrom();
ZonedDateTime downtimeEnd = downtime.getDowntimeTo();
for(ZonedDateTime start = downtimeStart; !start.isAfter(downtimeEnd); start = start.plusHours(1)){
dateRange.add(start);
}
}
if(dateRange.contains(newTime)){
return false;
}
return true;
}
The code is written out of my head in here, so there might be syntax errors but I think you can get the idea of what I want.
And now to my question.
This code above seems like such an overhead and I would like to know how I can validate it faster and with less code.
EDIT:
Let me provide a clear Example
I have a List of Downtimes like this:
List<Downtime> createdDowntimes = [
{
start:2015-01-10T00:00Z,
end:2015-01-10T02:00Z
},
{
start:2015-01-10T04:00Z,
end:2015-01-10T06:00Z
},
{
start:2015-01-10T07:00Z,
end:2015-01-10T09:00Z
}
]
and then I have the new Downtime I want to create:
Downtime newDowntime =
{
start:2015-01-10T05:00Z,
end:2015-01-10T05:30Z
}
In this example the new downtime is not valid since it actually is during a period that is already in another already created downtime.
Hopefully it makes things more clear.
EDIT 2:
While the marked duplicate consists of the reason and offers a solution I also want to give credits to Hugo who provided a good answer with considering my criterias.
Here is another solution I prepared which offers a lot of more detailed exception and information handling
/*
* Collision 1 = the new downtime starts before the created ones but ends in their span
* Collision 2 = the new downtime starts after created ones and also ends after their span
* Collision 3 = the new downtime starts after created ones and ends in their span
*/
List<Downtime> collision1 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isAfter( newTimeStart ) )
.filter( e -> e.getDowntimeTo().isAfter( newTimeEnd ) ).collect( Collectors.toList() );
List<Downtime> collision2 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isBefore( newTimeStart ) )
.filter( e -> e.getDowntimeTo().isBefore( newTimeEnd ) ).collect( Collectors.toList() );
List<Downtime> collision3 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isBefore( newTimeStart ) )
.filter( e -> e.getDowntimeTo().isAfter( newTimeEnd ) ).collect( Collectors.toList() );
Keep in mind that my "solution" is one of many and also pretty intensive in terms of performance since streams are heavy operations. So if you don't need to know exactly how many collided and why they collided consider Hugo's answer.

Considering that:
Meaning the start date of the new downtime is not already existent and is not in another downtime. (between the start and end of an already created downtime).
In this case, you need to compare the startDate (downtimeFrom) of the new Downtime with all the existent Downtime's:
private boolean isNewDowntimeValid(Downtime newDowntime, List<Downtime> createdDowntimes) {
// the start of the new downtime
ZonedDateTime newStartTime = newDowntime.getDowntimeFrom();
for (Downtime downtime : createdDowntimes) {
ZonedDateTime downtimeStart = downtime.getDowntimeFrom();
ZonedDateTime downtimeEnd = downtime.getDowntimeTo();
if (newStartTime.equals(downtimeStart)) {
// start date of new downtime already exists
return false;
}
// start date of new downtime is in the existent downtime
// (existent startDate < new startDate and new startDate < existent endDate)
if (downtimeStart.isBefore(newStartTime) && newStartTime.isBefore(downtimeEnd)) {
return false;
}
}
// no invalid cases found, it's valid
return true;
}
Note:
In this code, the Downtime is valid if the new startDate is equals the end of an existent Downtime. If you don't want that, you can change the second if to:
if (downtimeStart.isBefore(newStartTime) && (! newStartTime.isAfter(downtimeEnd))) {
return false;
}

Check this question
Or have this in mind: two periods will overlap if and only if
(StartA <= EndB) and (EndA >= StartB)
So you would have to check your new Downtime against all the previous one you have

suppose you have to LocalTime objects,
endA represents when the event-A ends and
beginB represents when the event-B begins
then
LocalTime endA = ...;//LocalTime.of(13, 00, 00);
LocalTime beginB = ...;//LocalTime.of(12, 00, 00);
Duration duration = Duration.between(endA, beginB);
System.out.println(duration.getSeconds());
if getSeconds() is negative, then they overlap

Related

Creating an order-slot system with 2 array lists

I need to write a daymanagerr that assigns time slots for orders in a restaurant.
That's the daymanager:
public DayManager (LocalDate date, int numberOfTimeSlots, int capacityPerSlot) {
this.date = date;
this.capacityPerSlot = capacityPerSlot;
this.numberOfTimeSlots = numberOfTimeSlots;
For each day I can choose the number of available slots and the capacity per slot (so if I choose 3 slots and a capacity of 3 per slot, that's 9 slots in total).
Every customer can state their preferred time slot (here: 0, 1 or 2) with their order.
Here's my class for adding orders:
public Optional<Integer> addOrder(Order order, int preferredSlot) {
int givenSlot = 0, count = 1;
List<Integer> slots = new ArrayList<Integer>();
List<Integer> slotsPerSlot = new ArrayList<Integer>();
if ((slots.size() * slotsPerSlot.size()) <= (numberOfTimeSlots * capacityPerSlot)) {
if (slots.contains(preferredSlot) == false) {
givenSlot = preferredSlot;
slots.add(preferredSlot);
slotsPerSlot.add(count);
count++;
} else if (slotsPerSlot.size() <= capacityPerSlot) {
givenSlot = preferredSlot;
slots.add(preferredSlot);
slotsPerSlot.add(slotsPerSlot.size() + 1);
} else {
givenSlot = slots.get(slots.lastIndexOf(count));
}
return Optional.of(givenSlot);
}
return Optional.empty();
}
What I need help with:
With every new order I get, I need to check if there is still capacity left in that customers preferred slot. If there is capacity left, I assign that slot to him. If there is no capacity left in that slot, I assign the slot with the next lowest index (and available capacity). If there is no capacity left for that day, I simply return nothing.
I just can't figure out how to create a slotsPerSlot list for each slot and additionally I don't really know, how I could get the next lowest slot number.
Multimap
You could use a multimap. A Map is a pairing of key that leads to a value. In a multimap, the value is actually a collection of values rather than a single value.
Imagine, for example, a hierarchy of three time slots where the first and last contain an empty list (no orders yet), while the middle time slot has a list of a single order.
2022-01-23T13:00-07:00[America/Edmonton]
[]
2022-01-23T14:00-07:00[America/Edmonton]
[Order[id=2d8e5cc2-26ac-474d-a081-2c71207fd6c5, customerName=Basil]]
2022-01-23T15:00-07:00[America/Edmonton]
[]
(Minor detail: The 2d8e5cc2-26ac-474d-a081-2c71207fd6c5 text is a hexadecimal string representation of a 128-bit UUID value used to identify that particular order.)
Example code
Here is some example code. This code is incomplete, but will get you going in the right direction.
First, define our Order class. We will make it a record, using the new feature in Java 16. You could just as well define a conventional class, but a record is so much briefer. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.
package work.basil.example.orders;
import java.util.UUID;
public record Order(UUID id , String customerName)
{
}
Define the DayManager class.
The constructor is the place to set up your data structure for tracking orders. You were doing that work inside your addOrder method which makes no sense.
The goal of the constructor is to populate a NavigableMap, a Map that maintains its keys it a sorted order. We use TreeMap as the concrete implementation of the NavigableMap interface.
The keys to our map are ZonedDateTime, a date with time-of-day within the context of a time zone. Each ZonedDateTime object is the start of each time slot. I use this approach rather than your mere integer number to identify each slot.
The values of our map are a list of Order objects. As we add orders to our tracking system, they land in one of these lists. Each list is tied to a ZonedDateTime object as the time slot in our map. The problem with using a List is that the class is meant to be a resizable collection. So our code here manages the size limits, checking the current size to get a count of how many orders are already present in the list. We compare that count to our capacity-per-time-slot number we keep as a member field on DayManager class.
If we are under capacity, we add our order to the list, and return an Optional containing the ZonedDateTime to identify the time slot.
If we are at capacity, we need to move on to the next time slot — this I leave as an exercise for the reader.
Hint: We have a ZonedDateTime in hand, the current key used to access our map. So if we add the Duration stored as a member field, we will obtain the next key in our map. Use that key to get the next list of orders. Lather, rinse, repeat, until moving past the last time slot of the day.
By the way, in real work, I would look to a third-party library such as Eclipse Collections or Google Guava for a fixed-size list class. Better to rely on code that is already written and tested than rely on our size-checking code here.
We pre-populate our map with time slots and empty lists in the constructor. So we have a data structure in place when we begin adding orders.
The addOrder method looks for the requested time slot as the key in the map. Doing a get on the map returns a List of orders for us to inspect.
package work.basil.example.orders;
import java.time.*;
import java.util.*;
public class DayManager
{
// Member fields.
final ZoneId zoneId;
final LocalDate workDate;
final LocalTime startTime;
final Duration timeSlice;
final int numberOfTimeSlots, capacityPerTimeSlot;
private final NavigableMap < ZonedDateTime, List < Order > > ordersPerTimeSlot;
// Constructor
public DayManager ( final ZoneId zoneId , final LocalDate localDate , final LocalTime startTime , final Duration timeSlice , final int numberOfTimeSlots , final int capacityPerSlot )
{
this.zoneId = zoneId;
this.workDate = localDate;
this.startTime = startTime;
this.timeSlice = timeSlice;
this.numberOfTimeSlots = numberOfTimeSlots;
this.capacityPerTimeSlot = capacityPerSlot;
this.ordersPerTimeSlot = new TreeMap <>();
this.populateMap();
}
// Subroutine.
private void populateMap ( )
{
ZonedDateTime start = ZonedDateTime.of( this.workDate , this.startTime , this.zoneId );
for ( int i = 0 ; i < this.numberOfTimeSlots ; i++ )
{
Duration d = this.timeSlice.multipliedBy( i );
ZonedDateTime zdt = start.plus( d );
List < Order > list = new ArrayList <>( this.capacityPerTimeSlot );
this.ordersPerTimeSlot.put( zdt , list );
}
System.out.println( "this.ordersPerTimeSlot = " + this.ordersPerTimeSlot );
}
// Business logic.
public Optional < ZonedDateTime > addOrder ( final Order order , final ZonedDateTime zdt )
{
List < Order > orders = this.ordersPerTimeSlot.get( zdt );
if ( Objects.isNull( orders ) ) { return Optional.empty(); }
if ( orders.size() > this.capacityPerTimeSlot )
{
String msg = "ERROR - Capacity per time slot exceeded. ";
System.out.println( msg );
throw new IllegalStateException( msg );
} else if ( orders.size() == this.capacityPerTimeSlot )
{
String msg = "INFO - This time slot filled.";
System.out.println( msg );
throw new IllegalStateException( msg );
} else if ( orders.size() < this.capacityPerTimeSlot )
{
// Room in this time slot to place order.
orders.add( order );
return Optional.of( zdt );
} else
{
String msg = "ERROR - Should never reach this point. Error in IF-THEN logic of adding orders.";
System.out.println( msg );
throw new IllegalStateException( msg );
}
}
// Debugging
public String dumpOrders ( )
{
return this.ordersPerTimeSlot.toString();
}
}
Here is an App class for exercising our DayManager & Order classes.
package work.basil.example.orders;
import java.time.*;
import java.util.Optional;
import java.util.UUID;
public class App
{
public static void main ( String[] args )
{
ZoneId z = ZoneId.of( "America/Edmonton" );
LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 );
DayManager dm = new DayManager( z , ld , LocalTime.of( 13 , 0 ) , Duration.ofHours( 1 ) , 3 , 3 );
Order order = new Order( UUID.fromString( "2d8e5cc2-26ac-474d-a081-2c71207fd6c5" ) , "Basil" );
Optional < ZonedDateTime > optionalTimeSlot = dm.addOrder( order , ZonedDateTime.of( ld , LocalTime.of( 14 , 0 ) , z ) );
System.out.println( "order = " + order );
System.out.println( "optionalTimeSlot.toString() = " + optionalTimeSlot );
System.out.println( dm.dumpOrders() );
}
}
When run.
this.ordersPerTimeSlot = {2022-01-23T13:00-07:00[America/Edmonton]=[], 2022-01-23T14:00-07:00[America/Edmonton]=[], 2022-01-23T15:00-07:00[America/Edmonton]=[]}
order = Order[id=2d8e5cc2-26ac-474d-a081-2c71207fd6c5, customerName=Basil]
optionalTimeSlot.toString() = Optional[2022-01-23T14:00-07:00[America/Edmonton]]
{2022-01-23T13:00-07:00[America/Edmonton]=[], 2022-01-23T14:00-07:00[America/Edmonton]=[Order[id=2d8e5cc2-26ac-474d-a081-2c71207fd6c5, customerName=Basil]], 2022-01-23T15:00-07:00[America/Edmonton]=[]}

Static method in multithreading

I have the following code in my class
private static final SimpleDateFormat SDF_ISO_DATE = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat SDF_ISO_TIME = new SimpleDateFormat("HH:mm:ss");
public static String getTimeStampAsString(final long time) {
TimeZone tz = TimeZone.getTimeZone("UTC");
SDF_ISO_DATE.setTimeZone(tz);
SDF_ISO_TIME.setTimeZone(tz);
return SDF_ISO_DATE.format(
new Date(time)) + " " + SDF_ISO_TIME.format(new Date(time)
);
}
In my multi threaded application the following method returns date in future, even for the current date, is the static method or variable is responsible for this?
edit:
I had the following code to reproduce and prove what are mentioned in the answers,but still not able to.Can some one help me for the same.
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> task = new Callable<String>(){
public String call() throws Exception {
return DateUtil.getTimeStampAsString(1524567870569L);
}
};
//pool with 50 threads
ExecutorService exec = Executors.newFixedThreadPool(50);
List<Future<String>> results = new ArrayList<Future<String>>();
//perform 10 date conversions
for(int i = 0 ; i < 50 ; i++){
results.add(exec.submit(task));
}
exec.shutdown();
//look at the results
for(Future<String> result : results){
System.out.println(result.get());
}
}
is the static method or variable is responsible for this?
Static variables. SimpleDateFormat isn't thread-safe, which should be obvious since you're modifying its internal state by calling setTimeZone(). It means that several threads could be doing that at the same time, which should feel like producing unpredictable results.
You need to build your formats locally rather than reuse some defined statically. Or better yet, drop Java's old time-managing classes and use java.time.* instead.
As an answer to your edit: how to reproduce the problem with thread-unsafety (not sure whether that really ought to be a separate question). Formatting the same date in two or more threads using the same SimpleDateFormat seems to go well (at least most often, no guarantee that it always will). Try formatting different date-times, and it will be very easy to get wrong results. I changed your task like this:
AtomicLong time = new AtomicLong(1_524_567_870_569L);
Callable<String> task = new Callable<String>(){
#Override
public String call() {
return DateUtil.getTimeStampAsString(time.getAndAdd(2_768_461_000L));
}
};
It’s easiest to see that the results are wrong when I also sort them in the output, so I have done that. I am only quoting the first few results from one run since this is enough to demonstrate the problem:
2018-04-24 11:04:30
2018-05-26 12:05:31
2018-06-11 13:06:32
2018-07-29 14:07:33
2018-08-08 15:08:34
2018-10-01 16:09:35
…
The expected result was (obtained by declaring getTimeStampAsString() synchronized; also sorted afterward):
2018-04-24 11:04:30
2018-05-26 12:05:31
2018-06-27 13:06:32
2018-07-29 14:07:33
2018-08-30 15:08:34
2018-10-01 16:09:35
…
Already the fifth printed result has the day-of-month all wrong, 08 instead of 30, and there are many more errors in the full list. You may try it yourself. As you probably know, exact results are not reproducible, but you should get results that are wrong somehow.
PS Here’s my code for printing the results in sorted order in case you want to try it:
//look at the results
SortedSet<String> sorted = new TreeSet<>();
for (Future<String> result : results){
sorted.add(result.get());
}
sorted.forEach(System.out::println);
tl;dr
To capture the current moment and generate a string in your desired format (which is a modified form of standard ISO 8601 format), use the java.time classes. These classes are much simpler and vastly better designed. They are also thread-safe.
Instant.now().toString().replace( "T" , " " )
Current moment
Your method is named getCurrentTimeStamp(final Date date) yet you are passing an existing Date object set to a specific moment rather than capturing the current moment.
Nowhere in your code do I see you capturing the current moment. If you want the current moment, call Instant.now() as shown below.
Avoid legacy date-time classes
The legacy date-time classes such as Date & SimpleDateFormat are not thread-safe. One of many reasons to avoid these troublesome classes. They were supplanted years ago by the java.time classes.
java.time
As a moment in UTC, the java.util.Date class is replaced by the Instant class. Same idea, but Instant has a resolution in nanoseconds rather than milliseconds. And Instant::toString does not inject a time zone dynamically as Date::toString does.
To capture the current moment in UTC, call the static Instant.now() method.
Instant instant = Instant.now() ; // Capture current moment in UTC.
Parse your input number as a count of milliseconds since the epoch reference of first moment of 1970 in UTC.
Instant instant = Instant.ofEpochMilli( 1_524_567_870_569L ) ;
instant.toString(): 2018-04-24T11:04:30.569Z
No need for must of your code. No need for your DateUtil, as seen in code above. No need for custom formatting patterns, as your desired format happens to comply with the ISO 8601 standard used by default in the java.time classes. If the T in the middle bothers you or your users, replace with a SPACE.
String output = instant.toString().replace( "T" , " " ) ;
2018-04-24T11:04:30.569Z
ExecutorService blocking
You seem to misunderstand ExecutorService::shutdown. That method does not block to wait for tasks to complete. As your code is written, some tasks may not yet be done running until after you report results (partially-completed results).
Add a call to ExecutorService::awaitTermination, as seen in code below. Set a time-out long enough that if exceeded it must mean some problem occurred. To quote the doc:
Block until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.
See example code below. For more discussion see this Question, ExecutorService - How to wait for completition of all tasks in non-blocking style
Threads
The java.time classes are thread-safe by design. They use the immutable objects pattern, returning fresh object based on existing values rather than changing (“mutating”) the original.
Example code. Your Question is confused about whether you want a hard-coded moment or the current moment. Switch to either by enabling the commented-out line in this example.
Callable < String > task = new Callable < String >() {
public String call () throws Exception {
long threadId = Thread.currentThread().getId();
// String moment = Instant.ofEpochMilli( 1524567870569L ).toString().replace( "T" , " " );
String moment = Instant.now().toString().replace( "T" , " " );
String output = ( moment + " | " + threadId );
return output;
}
};
// Pool with 5 threads
ExecutorService exec = Executors.newFixedThreadPool( 5 );
List < Future < String > > results = new ArrayList < Future < String > >();
// Perform a certain number of tasks.
int countAssignedTasks = 500;
for ( int i = 0 ; i < countAssignedTasks ; i++ ) {
results.add( exec.submit( task ) );
}
// Wait for tasks to complete.
Boolean completedBeforeTimeOut = null;
try {
exec.shutdown();
completedBeforeTimeOut = exec.awaitTermination( 5 , TimeUnit.SECONDS ); // Block until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.
} catch ( InterruptedException e ) {
e.printStackTrace();
}
// Report results.
System.out.println( "completedBeforeTimeOut: " + completedBeforeTimeOut );
for ( Future < String > result : results ) {
try {
System.out.println( result.get() );
} catch ( InterruptedException e ) {
e.printStackTrace();
} catch ( ExecutionException e ) {
e.printStackTrace();
}
}
System.out.println( "BASIL - done." );
When run.
Note that the times are not chronological. In multi-threaded code, you cannot predict which tasks will be executed when.
2018-04-24 20:24:06.991225Z | 13
2018-04-24 20:24:06.991246Z | 14
2018-04-24 20:24:06.991236Z | 15
2018-04-24 20:24:06.991232Z | 16
2018-04-24 20:24:06.991222Z | 17
2018-04-24 20:24:07.067002Z | 16
2018-04-24 20:24:07.067009Z | 17
tz is effectively constant and the setters don't do anything after the first invocation of either method. Use a static initialiser to set the timezone right away to make the methods thread-safe.
private static final SimpleDateFormat SDF_ISO_DATE = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat SDF_ISO_TIME = new SimpleDateFormat("HH:mm:ss");
static {
TimeZone tz = TimeZone.getTimeZone("UTC");
SDF_ISO_DATE.setTimeZone(tz);
SDF_ISO_TIME.setTimeZone(tz);
}
public static String getCurrentTimeStamp(final Date date) {
return SDF_ISO_DATE.format(date) + " " + SDF_ISO_TIME.format(date);
}
public static String getTimeStampAsString(final long time) {
return getCurrentTimeStamp(new Date(time));
}

Java : given a list of object that has range of dates find two objects whose end month is closest to current date month

List of dates are as below (The list can be in any order):
3-Jan to 31-Mar, 2-Apr to 30-Jun, 1-Jul to 30-Sep, 4-Oct to 31-Dec
Current Date is: 19-Feb
Can someone please help me with the logic?
My approach is:
if(the given date should be greater than start date and less than end date){//this gives current quarter}else if(difference of the month of current date from the end date of each object should be less than or equal to 5)
i am hard coding the condition less than 5, which may break if in future the range of date will be of 4 months
Second approach is:
we can sort the list in ascending order and can get the current quarter index by comparing with current date and the next quarter will be of next index. But the complexity will be more.
I tried below code, but it gives only current quarter date. I am not able to get next quarter considering there would be only 3 objects and current date month is feb.
public static List getCurrentQtrOffr(List detail,Date currentDate) throws ParseException{
int currentQuarter = 9999, diff1;
int nextquarter = 9999, diff2;
Detail detail1;
Detail detail2;
Detail detail3 = null;
Detail detail4 = null;
Iterator<Detail> iterator = detail.iterator();
List<Detail> list = new ArrayList<Detail>();
while(iterator.hasNext()){
detail1 = iterator.next();
diff1 = getDiff(currentDate,detail1.startTime());
if(diff1>0){
if(iterator.hasNext()){
detail2 = iterator.next();
}else{
detail2 = null;
}
if(detail2 != null){
diff2 = getDiff(currentDate,detail2.startTime());
if(diff1 < diff2 ){
if(currentQuarter > diff1){
nextquarter = currentQuarter;
currentQuarter = diff1;
//how to assign detail3 before updating it with next minimum value, as if there will be only 3 object and flow comes in this if block then detail4 will be null
detail4=detail3;
detail3=detail1;
}else if(nextquarter > diff1){
nextquarter = diff1;
detail4=detail1;
}
}else{
if(currentQuarter > diff2){
nextquarter = currentQuarter;
currentQuarter = diff2;
detail4=detail3;
detail3=detail1;
}else if(nextquarter > diff2){
nextquarter = diff2;
detail4=detail1;
}
}
}else{
if(currentQuarter > diff1){
nextquarter = currentQuarter;
currentQuarter = diff1;
detail4=detail3;
detail3=detail1;
}else if(nextquarter > diff1){
nextquarter = diff1;
detail4=detail1;
}
}
}else{
System.out.println("skipped "+diff1);
}
}
list.add(detail3);
list.add(detail4);
return list;
}
If the periods are mutually exclusive (not overlapping) the you simply check for the first occurrence where:
The target is equal to or later than the start, and…
The target is before the stop.
This logic follows the Half-Open approach commonly used in date-time work where the beginning is inclusive while the ending is exclusive.
A shorter way of saying "the target is equal to or later than the start" is "not before start". The exclamation mark ! means not in Java syntax.
Boolean periodContainsTarget = ( ! target.isBefore( start ) ) && target.isBefore( stop ) ;
The above logic would be used with LocalDate if you meant date with a year. If you literally meant a month and day without a year, use the MonthDay class. The logic works for both.
Use Period class to represent the span of time between a pair of LocalDate objects. See Tutorial.
You might also find useful the Interval class in the ThreeTen-Extra project that supplements java.time.

Joda Time LocalTime of 24:00 end-of-day

We're creating a scheduling application and we need to represent someone's available schedule during the day, regardless of what time zone they are in. Taking a cue from Joda Time's Interval, which represents an interval in absolute time between two instances (start inclusive, end exclusive), we created a LocalInterval. The LocalInterval is made up of two LocalTimes (start inclusive, end exclusive), and we even made a handy class for persisting this in Hibernate.
For example, if someone is available from 1:00pm to 5:00pm, we would create:
new LocalInterval(new LocalTime(13, 0), new LocalTime(17, 0));
So far so good---until someone wants to be available from 11:00pm until midnight on some day. Since the end of an interval is exclusive, this should be easily represented as such:
new LocalInterval(new LocalTime(23, 0), new LocalTime(24, 0));
Ack! No go. This throws an exception, because LocalTime cannot hold any hour greater than 23.
This seems like a design flaw to me---Joda didn't consider that someone may want a LocalTime that represents a non-inclusive endpoint.
This is really frustrating, as it blows a hole in what was otherwise a very elegant model that we created.
What are my options---other than forking Joda and taking out the check for hour 24? (No, I don't like the option of using a dummy value---say 23:59:59---to represent 24:00.)
Update: To those who keep saying that there is no such thing as 24:00, here's a quote from ISO 8601-2004 4.2.3 Notes 2,3: "The end of one calendar day [24:00] coincides with [00:00] at the start of the next calendar day ..." and "Representations where [hh] has the value [24] are only preferred to represent the end of a time interval ...."
Well after 23:59:59 comes 00:00:00 on the next day. So maybe use a LocalTime of 0, 0 on the next calendar day?
Although since your start and end times are inclusive, 23:59:59 is really what you want anyways. That includes the 59th second of the 59th minute of the 23rd hour, and ends the range exactly on 00:00:00.
There is no such thing as 24:00 (when using LocalTime).
The solution we finally went with was to use 00:00 as a stand-in for 24:00, with logic throughout the class and the rest of the application to interpret this local value. This is a true kludge, but it's the least intrusive and most elegant thing I could come up with.
First, the LocalTimeInterval class keeps an internal flag of whether the interval endpoint is end-of-day midnight (24:00). This flag will only be true if the end time is 00:00 (equal to LocalTime.MIDNIGHT).
/**
* #return Whether the end of the day is {#link LocalTime#MIDNIGHT} and this should be considered midnight of the
* following day.
*/
public boolean isEndOfDay()
{
return isEndOfDay;
}
By default the constructor considers 00:00 to be beginning-of-day, but there is an alternate constructor for manually creating an interval that goes all day:
public LocalTimeInterval(final LocalTime start, final LocalTime end, final boolean considerMidnightEndOfDay)
{
...
this.isEndOfDay = considerMidnightEndOfDay && LocalTime.MIDNIGHT.equals(end);
}
There is a reason why this constructor doesn't just have a start time and an "is end-of-day" flag: when used with a UI with a drop-down list of times, we don't know if the user will choose 00:00 (which is rendered as 24:00), but we know that as the drop-down list is for the end of the range, in our use case it means 24:00. (Although LocalTimeInterval allows empty intervals, we don't allow them in our application.)
Overlap checking requires special logic to take care of 24:00:
public boolean overlaps(final LocalTimeInterval localInterval)
{
if (localInterval.isEndOfDay())
{
if (isEndOfDay())
{
return true;
}
return getEnd().isAfter(localInterval.getStart());
}
if (isEndOfDay())
{
return localInterval.getEnd().isAfter(getStart());
}
return localInterval.getEnd().isAfter(getStart()) && localInterval.getStart().isBefore(getEnd());
}
Similarly, converting to an absolute Interval requires adding another day to the result if isEndOfDay() returns true. It is important that application code never constructs an Interval manually from a LocalTimeInterval's start and end values, as the end time may indicate end-of-day:
public Interval toInterval(final ReadableInstant baseInstant)
{
final DateTime start = getStart().toDateTime(baseInstant);
DateTime end = getEnd().toDateTime(baseInstant);
if (isEndOfDay())
{
end = end.plusDays(1);
}
return new Interval(start, end);
}
When persisting LocalTimeInterval in the database, we were able to make the kludge totally transparent, as Hibernate and SQL have no 24:00 restriction (and indeed have no concept of LocalTime anyway). If isEndOfDay() returns true, our PersistentLocalTimeIntervalAsTime implementation stores and retrieves a true time value of 24:00:
...
final Time startTime = (Time) Hibernate.TIME.nullSafeGet(resultSet, names[0]);
final Time endTime = (Time) Hibernate.TIME.nullSafeGet(resultSet, names[1]);
...
final LocalTime start = new LocalTime(startTime, DateTimeZone.UTC);
if (endTime.equals(TIME_2400))
{
return new LocalTimeInterval(start, LocalTime.MIDNIGHT, true);
}
return new LocalTimeInterval(start, new LocalTime(endTime, DateTimeZone.UTC));
and
final Time startTime = asTime(localTimeInterval.getStart());
final Time endTime = localTimeInterval.isEndOfDay() ? TIME_2400 : asTime(localTimeInterval.getEnd());
Hibernate.TIME.nullSafeSet(statement, startTime, index);
Hibernate.TIME.nullSafeSet(statement, endTime, index + 1);
It's sad that we had to write a workaround in the first place; this is the best I could do.
It's not a design flaw. LocalDate doesn't handle (24,0) because there's no such thing as 24:00.
Also, what happens when you want to represent an interval between, say 9pm and 3am?
What's wrong with this:
new LocalInterval(new LocalTime(23, 0), new LocalTime(0, 0));
You just have to handle the possibility that the end time might be "before" the start time, and add a day when necessary, and just hope that noone wants to represent an interval longer than 24 hours.
Alternatively, represent the interval as a combination of a LocalDate and a Duration or Period. That removes the "longer than 24 hours" problem.
Your problem can be framed as defining an interval on a domain that wraps around. Your min is 00:00, and your max is 24:00 (not inclusive).
Suppose your interval is defined as (lower, upper). If you require that lower < upper, you can represent (21:00, 24:00), but you are still unable to represent (21:00, 02:00), an interval that wraps across the min/max boundary.
I don't know whether your scheduling application would involve wrap-around intervals, but if you are going to go to (21:00, 24:00) without involving days, I don't see what will stop you from requiring (21:00, 02:00) without involving days (thus leading to a wrap-around dimension).
If your design is amenable to a wrap-around implementation, the interval operators are quite trivial.
For example (in pseudo-code):
is x in (lower, upper)? :=
if (lower <= upper) return (lower <= x && x <= upper)
else return (lower <= x || x <= upper)
In this case, I have found that writing a wrapper around Joda-Time implementing the operators is simple enough, and reduces impedance between thought/math and API. Even if it is just for the inclusion of 24:00 as 00:00.
I do agree that the exclusion of 24:00 annoyed me at the start, and it'll be nice if someone offered a solution. Luckily for me, given that my use of time intervals is dominated by wrap-around semantics, I always end up with a wrapper, which incidentally solves the 24:00 exclusion.
The time 24:00 is a difficult one. While we humans can understand what is meant, coding up an API to represent that without negatively impacting everything else appears to me to be nigh on impossible.
The value 24 being invalid is deeply encoded in Joda-Time - trying to remove it would have negative implications in a lot of places. I wouldn't recommend trying to do that.
For your problem, the local interval should consist of either (LocalTime, LocalTime, Days) or (LocalTime, Period). The latter is slightly more flexible. This is needed to correctly support an interval from 23:00 to 03:00.
I find JodaStephen's proposal of (LocalTime, LocalTime, Days) acceptable.
Considering on 13 March 2011 and your availability on Sunday from 00:00-12:00 you would have (00:00, 12:00, 0) which were in fact 11 hours long because of DST.
An availability from say 15:00-24:00 you could then code as (15:00, 00:00, 1) which would expanded to 2011-03-13T15:00 - 2011-03-14T00:00 whereat the end would be desired 2011-03-13T24:00. That means you would use a LocalTime of 00:00 on the next calendar day like already aroth proposed.
Of course it would be nice to use a 24:00 LocalTime directly and ISO 8601 conform but this seems not possible without changing a lot inside JodaTime so this approach seems the lesser evil.
And last but not least you could even extend the barrier of a single day with something like (16:00, 05:00, 1)...
this is our implementation of TimeInterval, using null as end Date for end-of-day. It supports the overlaps() and contains() methods and is also based on joda-time. It supports intervals spanning multiple days.
/**
* Description: Immutable time interval<br>
* The start instant is inclusive but the end instant is exclusive.
* The end is always greater than or equal to the start.
* The interval is also restricted to just one chronology and time zone.
* Start can be null (infinite).
* End can be null and will stay null to let the interval last until end-of-day.
* It supports intervals spanning multiple days.
*/
public class TimeInterval {
public static final ReadableInstant INSTANT = null; // null means today
// public static final ReadableInstant INSTANT = new Instant(0); // this means 1st jan 1970
private final DateTime start;
private final DateTime end;
public TimeInterval() {
this((LocalTime) null, null);
}
/**
* #param from - null or a time (null = left unbounded == LocalTime.MIDNIGHT)
* #param to - null or a time (null = right unbounded)
* #throws IllegalArgumentException if invalid (to is before from)
*/
public TimeInterval(LocalTime from, LocalTime to) throws IllegalArgumentException {
this(from == null ? null : from.toDateTime(INSTANT),
to == null ? null : to.toDateTime(INSTANT));
}
/**
* create interval spanning multiple days possibly.
*
* #param start - start distinct time
* #param end - end distinct time
* #throws IllegalArgumentException - if start > end. start must be <= end
*/
public TimeInterval(DateTime start, DateTime end) throws IllegalArgumentException {
this.start = start;
this.end = end;
if (start != null && end != null && start.isAfter(end))
throw new IllegalArgumentException("start must be less or equal to end");
}
public DateTime getStart() {
return start;
}
public DateTime getEnd() {
return end;
}
public boolean isEndUndefined() {
return end == null;
}
public boolean isStartUndefined() {
return start == null;
}
public boolean isUndefined() {
return isEndUndefined() && isStartUndefined();
}
public boolean overlaps(TimeInterval other) {
return (start == null || (other.end == null || start.isBefore(other.end))) &&
(end == null || (other.start == null || other.start.isBefore(end)));
}
public boolean contains(TimeInterval other) {
return ((start != null && other.start != null && !start.isAfter(other.start)) || (start == null)) &&
((end != null && other.end != null && !other.end.isAfter(end)) || (end == null));
}
public boolean contains(LocalTime other) {
return contains(other == null ? null : other.toDateTime(INSTANT));
}
public boolean containsEnd(DateTime other) {
if (other == null) {
return end == null;
} else {
return (start == null || !other.isBefore(start)) &&
(end == null || !other.isAfter(end));
}
}
public boolean contains(DateTime other) {
if (other == null) {
return start == null;
} else {
return (start == null || !other.isBefore(start)) &&
(end == null || other.isBefore(end));
}
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("TimeInterval");
sb.append("{start=").append(start);
sb.append(", end=").append(end);
sb.append('}');
return sb.toString();
}
}
For the sake of completeness this test fails:
#Test()
public void testJoda() throws DGConstraintViolatedException {
DateTimeFormatter simpleTimeFormatter = DateTimeFormat.forPattern("HHmm");
LocalTime t1 = LocalTime.parse("0000", simpleTimeFormatter);
LocalTime t2 = LocalTime.MIDNIGHT;
Assert.assertTrue(t1.isBefore(t2));
}
This means the MIDNIGHT constant is not very usefull for the problem, as someone suggested.
This question is old, but many of these answers focus on Joda Time, and only partly address the true underlying problem:
The model in the OP's code doesn't match the reality it's modeling.
Unfortunately, since you do appear to care about the boundary condition between days, your "otherwise elegant model" isn't a good match for the problem you are modeling. You've used a pair of time values to represent intervals. Attempting to simplify the model down to a pair of times is simplifying below the complexity of the real world problem. Day boundaries actually do exist in reality and a pair of times looses that type of information. As always, over simplification results in subsequent complexity to restore or compensate for the missing information. Real complexity can only be pushed around from one part of the code to another.
The complexity of reality can only be eliminated with the magic of "unsupported use cases".
Your model would only make sense in a problem space where one didn't care how many days might exist between the start and end times. That problem space doesn't match most real world problems. Therefore, it's not surprising that Joda Time doesn't support it well. The use of 25 values for the hours place (0-24) is a code smell and usually points to a weakness in the design. There are only 24 hours in the day so 25 values should not be needed!
Note that since you aren't capturing the date on either end of LocalInterval, your class also does not capture sufficient information to account for daylight savings time. [00:30:00 TO 04:00:00) is usually 3.5 hours long but could also be 2.5, or 4.5 hours long.
You should either use a start date/time and duration, or a start date/time and an end date/time (inclusive start, exclusive end is a good default choice). Using a duration becomes tricky if you intend to display the end time because of things like daylight savings time, leap years and leap seconds. On the other hand using an end date becomes just as tricky if you expect to display the duration. Storing both of course is dangerous because it violates the DRY principle. If I were writing such a class I would store an end date/time and encapsulate the logic for obtaining the duration via a method on the object. That way clients of the class class do not all come up with their own code to calculate the duration.
I'd code up a example, but there's an even better option. Use the standard Interval Class from Joda time, which already accepts a start instant and either duration or end instant. It will also and happily calculate the duration or the end time for you. Sadly JSR-310 doesn't have an interval or similar class. (though one can use ThreeTenExtra to make up for that)
The relatively bright folks at Joda Time and Sun/Oracle (JSR-310) both thought very carefully about these problems. You might be smarter than them. It's possible. However, even if you are a brighter bulb, your 1 hour is probably not going to accomplish what they spent years on. Unless you are somewhere out in an esoteric edge case, it's usually waste of time and money to spend effort second guessing them. (of course at the time of the OP JSR-310 wasn't complete...)
Hopefully the above will help folks who find this question while designing or fixing similar issues.

Grouping objects by date: am I an idiot?

I have a list of objects called Activity:
class Activity {
public Date activityDate;
public double amount;
}
I want to iterate through List, group them by date and return a new list . Here's what I currently do:
private List<Activity> groupToList(List<Activity> activityList) {
SimpleDateFormatter sdf = new SimpleDateFormatter("YYYY-MM-DD");
Map<String,Activity> groupMap = new HashMap<String,Activity>();
for (Activity a in activityList) {
String key = sdf.format(a.getActivityDate());
Activity group = groupMap.get(key);
if (group == null) {
group = new Activity();
groupMap.add(key, group);
}
group.setAmount(group.getAmount() + a.getAmount());
}
return new ArrayList<Activity>(groupMap.values());
}
Is it a WTF to use the DateFormatter in this way?
I'm using the DateFormatter because each activityDate could have time information.
I would just use the date object itself as the key. If it it bothers you because the date object is mutable, then use its toString() value. No reason to go making formats.
If the issue is that you want to normalize the date by removing the time component, it would be much better to do that withing the Activity object and remove the time component. If the issue is still further that there are potential time zone issues, I would use JodaTime, but there is no object in the JDK currently that represents a pure date without time, so going with a string isn't outrageous, but it should be hidden behind a method in the Activity object and the fact that it is a date formatted string without a time component should be an implementation detail.
java.util.Date is a quite poor abstraction for your need; it is IMO fair to stick to strings if nothing better is around, HOWEVER Joda-time provides a good datatype for you: DateMidnight or alternatively LocalDate if Activity is strictly timezome-independant.
other than that, the code looks good to me, you might be able to shorten it a bit using an implementation of Multimap, to avoid messy null-checking code. to be honest, it doesn't get much shorter than your solution:
public List<Activity> groupedByDate(List<Activity> input) {
//group by day
final Multimap<DateMidnight, Activity> activityByDay
= Multimaps.index(input, new Function<Activity, DateMidnight>() {
#Override
public DateMidnight apply(Activity from) {
return new DateMidnight(from.activityDate);
}
});
//for each day, sum up amount
List<Activity> ret = Lists.newArrayList();
for (DateMidnight day : activityByDay.keySet()) {
Activity ins = new Activity();
ins.activityDate = day.toDate();
for (Activity activity : activityByDay.get(day)) {
ins.amount+=activity.amount;
}
}
return ret;
}
Why not simply create a HashMap<Date, Activity>() instead of the roundabout way with Strings?
Sorry, I didn't answer the question. The answer is: yes, unless I am an idiot ;)
You could do this using the Date as the key if you used a TreeMap and provided a Comparator that only compared the year, month and day and not the time.
As already mentioned the best solution is to represent your date with day precission. If this is not possible joda is nice library.
If you can ignore daylight saving time then grouping by date can be accomplished much easier. A unix time day is 86 400 s long. The timestamp does ignore leap seconds. (Your timer stops for one second or the leap second is distributed in some way.) All date values were day is equal are the same day:
int msPerDay = 86400 * 1000;
long day = new Date().getTime() / msPerDay
One minor point is to adjust the timezone. For my timezone CET (UTC/GMT +1 hour) the GMT day starts one our later:
new GregorianCalendar(2009, 10, 1, 1, 0).getTime().getTime() / msPerDay) ==
new GregorianCalendar(2009, 10, 2, 0, 59).getTime().getTime() / msPerDay) ==
new Date().getTime() / msPerDay
If the daylight saving time is significant the best way is to use joda. The rules are just to complicated and locale specific to implement.

Categories

Resources