I have a class Period that is represented by start and end dates, where end is after start. I need to write a function to check if periods overlap.
The straightforward approach is to check every period with every other period. Is there a way to introduce a data structure that will perform faster?
class Period {
LocalDateTime start;
LocalDateTime end;
}
boolean isOverlap(Set<Period> periods) {
// TODO put the code here
}
isOverlap should return true when at least two of the periods overlap.
Checking every period against every other period will have an O(n2) time complexity. Instead, I'd sort them by start and end times and then iterate over the list. This way, a period can only overlap the periods directly before and after it (or multiple subsequent ones before or after it - but that's inconsequential, since you're looking for a single overlap to return true). You can iterate over the list and check this. The total cost of this algorithm would be the cost of the sorting, O(nlog(n)):
boolean isOverlap(Set<Period> periods) {
List<Period> sorted =
periods.stream()
.sorted(Comparator.comparing((Period p) -> p.start)
.thenComparing(p -> p.end))
.collect(Collectors.toList());
for (int i = 0; i < sorted.size() - 1; ++i) {
if (sorted.get(i).end.compareTo(sorted.get(i + 1).start) > 0) {
return true;
}
}
return false;
}
for some reason while testing this method during a lesson in class we found an issue we couldn't understand. When writing System.out.println(); for some reason it passes?. Can someone explain why this is happening?
public class Zones {
public ZoneId getZoneId(String input) {
if (input.equalsIgnoreCase("Stockholm")) {
return ZoneId.of("Europe/Stockholm");
}
else if (input.equalsIgnoreCase("Shanghai")) {
return ZoneId.of("Asia/Shanghai");
} else if (input.equalsIgnoreCase("Toronto")) {
return ZoneId.of("America/Toronto");
}
else if (input.equalsIgnoreCase("Hamburg")) {
return ZoneId.of("Europe/Berlin");
}
else return null;
}
public LocalDateTime getZoneTime(ZoneId zoneId) {
LocalDateTime lt = LocalDateTime.now(zoneId);
return lt;
}
}
private Zones z = new Zones();
#Test
public void getZoneTimeTest () {
System.out.println(z.getZoneTime(zIDToronto).getNano() );
System.out.println(LocalDateTime.now(zIDToronto).getNano() );
assertTrue(z.getZoneTime(zIDToronto).getNano() == LocalDateTime.now(zIDToronto).getNano());
}
Finally had some time to investigate this deeper.
I started experimenting and found after a while, that is in fact not the presence of System.out.println that influenced the result, but the fact that you instantiate 2 LocalDateTime instances before it.
Digging deeper, into the code of LocalDateTime, and the SystemClock (to which it delegates), I found that the sub milli precision was achieved by a call to the native call jdk.internal.misc.VM#getNanoTimeAdjustment.
That last call is OS specific. I experimented a bit with it and found that it doesn't return values linearly, as it gets called in a loop (assuming my loop ran fairly regularly).
So I decided to run some code to map the returned nano values.
I made this sampling code :
Clock clock = Clock.systemDefaultZone();
int samples = 1_000;
LocalDateTime[] instants = new LocalDateTime[samples];
int k = 0;
for (int i = 0; i < samples; i++) {
instants[i] = LocalDateTime.now(clock);
for (int j = 0; j < 10000; j++) {
k = j % 2;
}
}
wrote the values to a file, and then mapped the nano differences vs the first value into a graph :
As you can see this graph (of 1000 values) makes intermittent leaps. This is obviously in part due to precision restriction of the underlying system. But wat struck me is that the first two values consistently were different. It's as if upon regular access the OS system starts caching the value for a while (possibly to avoid strain on the system resources).
But the result it seems is that you set yourself up for getting the same value on the 3rd and 4th call (unless enough time has passed).
That would explain why your test passes with, and fails without those prior instantiations.
As an aside, for unit tests you don't want to rely on a system clock. Make sure your business code gets it's time from an injected Clock instance. Then you can inject a custom clock for tests, and test whether your code will run on a DST changeover date or on a leap day without having to wait several months.
The test involves race condition and passes (sometimes) because of the timing and adding statements changes the timing and therefore test outcome.
The condition that is checked in the assertion basically is the equality of the nanoseconds parts of two date-times taken one after another in succession.
Given that by default System.currentTimeMillis() is used internally by LocalDateTime.now and it has at most millisecond precision the check will succeed if the second sequence of invocations to get the number of nanoseconds is quick enough (that is the actual sequence of calls leading to invocation of System.currentTimeMillis finished within the same millisecond as the first).
When you invoke the functions used to obtain the value of nanosecond before the actual assertion the corresponding classes are loaded, the code of corresponding methods get into the CPU caches etc. This makes the second pair of calls to get the number of nanoseconds run much quicker.
I have two tables Previous_Schedule and New_Schedule. Both the tables have 3 columns :
Objective_ID , START_DATE and END_DATE.
I need to create a NOT_PRESENT_IN_PREVIOUS_SCHEDULE Table having 3 columns :
Objective_ID , START_DATE and END_DATE.
If Previous Schedule is having sample data as:
Objective_id --Start_Date -- End_Date
1 -- 10-Jan-2014 -- 20-Jan-2014
If New_Schedule is having sample data as:
Objective_id -- Start_Date -- End_Date
1 -- 12-Jan-2014 -- 15-Jan-2014
My NOT_PRESENT_IN_PREVIOUS_SCHEDULE should have the following data based on the above scenario:
Objective_id -- Start_Date --End_Date
1 -- 10-Jan-2014 -- 11-Jan-2014
1 -- 16-Jan-2014 -- 20-Jan-2014
The logic having the NOT_PRESENT_IN_PREVIOUS_SCHEDULE output should be implemented in Java. It should be generic for any sort of PREVIOUS_SCHEDULE and NEW_SCHEDULE as an Input returning NOT_PRESENT_IN_PREVIOUS_SCHEDULE as an output.
Here’s my suggestion. The following method “subtracts” two lists of schedules by eliminating the date intervals from the first list that are in the second list. It uses a double loop where it first iterates over the schedules in the second list, those that should be subtracted. For each such schedule it subtracts it from each schedule from the first list, building a new list of the resulting schedules.
public static List<Schedule> scheduleListDiff(
List<Schedule> schedules, List<Schedule> schedulesToExclude) {
// eliminate dates from schedulesToExclude one schdule at a time
for (Schedule toExclude : schedulesToExclude) {
List<Schedule> result = new ArrayList<>();
for (Schedule originalSchedule : schedules) {
result.addAll(originalSchedule.notPresentIn(toExclude));
}
schedules = result;
}
return schedules;
}
You may call it this way
List<Schedule> notPresentInPreviousSchedule
= scheduleListDiff(previousSchedules, newSchedules);
With the lists from your question the result is the desired
1 -- 10-Jan-2014 -- 11-Jan-2014
1 -- 16-Jan-2014 -- 20-Jan-2014
I have fitted the Schedule class with an auxiliary method notPresentIn() to perform the actual comparison:
/** #return a list of 0, 1 or 2 schedules with the dates from this schedule that are not in other */
List<Schedule> notPresentIn(Schedule other) {
if (other.end.isBefore(start) || end.isBefore(other.start)) { // no overlap
return Collections.singletonList(this);
}
// now we know there is an overlap
List<Schedule> result = new ArrayList<>(2);
if (start.isBefore(other.start)) { // need to include day/s from the first part of this
// this bit must end the day before other.start
result.add(new Schedule(objectiveId, start, other.start.minusDays(1)));
}
if (end.isAfter(other.end)) { // need day/s from the last part
result.add(new Schedule(objectiveId, other.end.plusDays(1), end));
}
return result;
}
I have not tested thoroughly, there could easily be a bug somewhere, but I hope this gets you started.
I have not considered efficiency. If you have millions of schedules you may benefit from a more complicated algorithm that sorts the schedules chronologically first so you don’t need to compare every schedule from one list to every schedule of the other. With a few hundred schedules I heavily doubt that you need care.
I am using java.time.LocalDate for the dates in the Schedule class:
int objectiveId;
// dates are inclusive; end is on or after start
LocalDate start;
LocalDate end;
Edit: I ran my code on the sample data from the duplicate question find out cancelled period from given date. That sample has two new sample schedules within one previous schedule. So this previous schedule should be split up into three. The result was:
107 -- 10 May 2016 -- 11 May 2016
107 -- 14 May 2016 -- 15 May 2016
107 -- 19 May 2016 -- 20 May 2016
This works because each iteration in scheduleListDiff() uses the result from the previous iteration, so first the schedule is split into two, next iteration one of the two is further split.
To convert the date strings into Java date objects, you could use the SimpleDateFormat class. Solution for Java 7:
String string = "20-Jan-2014";
DateFormat format = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
Date date = format.parse(string);
For Java 8 you could find a solution here.
To calculate the difference between two dates, you could use this operation:
long diff = date2.getTime() - date1.getTime();
System.out.println ("Days: " + TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS));
Calculate the difference between Previous_Schedule and New_Schedule
An idea on how to solve this problem could be to transform the date period in Previous_Schedule into single dates, stored in a Set.
Set<String> dates = new HashSet<String>();
dates.add( "10-Jan-2014" );
dates.add( "11-Jan-2014" );
dates.add( "12-Jan-2014" );
...
dates.add( "20-Jan-2014" );
Then you remove the dates from the period in New_Schedule from the Set:
dates.remove( "12-Jan-2014" );
...
dates.remove( "15-Jan-2014" );
The remaining elements in the Set would provide the basis to create NOT_PRESENT_IN_PREVIOUS_SCHEDULE.
Instead of using Strings, you could add date objects to the Set as well:
Set<Date> dates = new HashSet<Date>();
dates.add( date1 );
How to split a date period like 10-Jan-2014 -- 20-Jan-2014 into single dates and how to do the reverse task to create NOT_PRESENT_IN_PREVIOUS_SCHEDULE should come from your own creativity. Hint: you might use a loop to solve that task.
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.
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.