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.
Related
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 am using Time4j to parse recurring intervals like this:
IsoRecurrence.parseTimestampIntervals("R/2019-01-01T00:00:00/P1D")
This will give me an iterator with infinite number of daily recurring instances starting from the beginning of 2019.
Is it possible to only iterate the instances between a start date and end date, let's say e.g. for June, without changing the original rule?
Basically I would like to be able to define schedules with the ISO 8601 recurrence format but only need to generate instances for a given period.
Yes, it is possible but you have to introduce your own customizable condition to stop the infinite loop or stream. Example:
#Test
public void parseInfiniteTimestampIntervals() throws ParseException {
IsoRecurrence<TimestampInterval> intervals =
IsoRecurrence.parseTimestampIntervals("R/2019-01-01T00:00:00/P1D");
PlainDate start = PlainDate.of(2019, 6, 11);
PlainDate end = PlainDate.of(2019, 6, 15);
for (TimestampInterval interval : intervals) {
PlainDate current = interval.getStartAsTimestamp().getCalendarDate();
if (current.isAfterOrEqual(start)) {
if (current.isBeforeOrEqual(end)) {
System.out.println(interval); // or do your own stuff with the current interval
} else {
break; // end of infinite loop
}
}
}
}
Output:
[2019-06-11T00/2019-06-12T00)
[2019-06-12T00/2019-06-13T00)
[2019-06-13T00/2019-06-14T00)
[2019-06-14T00/2019-06-15T00)
[2019-06-15T00/2019-06-16T00)
However, infinite iterating requires special care how to model the stop condition and only exist in the class IsoRecurrence because the ISO-8601-standard has explicitly allowed this option. I hope that your ISO-expression (which is to be parsed) is not too wide in range because excessive iterating over many intervals should be avoided for sake of performance.
In case you only have daily intervals when the time of day is irrelevant, I recommend to use the type DateInterval.
I am trying to write a high data rate UDP streaming interface simulator/tester in Java 8 to a realtime machine that has a very accurate time processor card. Every message has a time field in it and this field is in microseconds resolution. The interface relies on the high resolution time processor for packet ordering. The interface relies on the high precision time card which I don't have and need to simulate out of the equation. I figured I could get away with using something like this:
TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
It does work but after running for extended periods of time I found UDP bites me because I send a couple hundred packets out of order with the same exact time stamp and the other side of the interface can't tell that the packets it received were out of order. The interface is tolerant of this to an extent but this isn't really an issue on the real system with the high precision clocks.
To mitigate this I have added a sense of synthetic microseconds to my currentTimeMillis() as follows:
class TimeFactory {
private long prev;
private long incr;
public long now() {
final long now = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
long synthNow = now;
if(now == prev) {
if(incr < 999) {
incr += 1;
}
synthNow += incr;
} else {
incr = 0;
}
prev = now;
return synthNow;
}
}
Has anyone ever dealt with synthetic time like this? Is there any other way to tighten this code up or even a better way to handle this (using nanoTime somehow)? If I ever did send more then 999 packets would it be safe to increment into the milliseconds range (ie: increment + 1000 or more)? It looks like I am getting around ~10-15ms difference between currentTimeMillis() calls but I'm sure this is very system dependent.
In case anyone is interested here is what I ended up with to work around the lack of a high resolution system clock. It will give me a synthetic microseconds counter that increments until either System.currentTimeMillis() returns an updated value or you have called this 999 times. In practice I have only seen a maximum of ~500 increments. It doesn't look like I will have worry about spilling into the millisecond range.
I'm still open to other more realistic result alternatives.
public class SyntheticMicrosClock extends Clock {
private final ZoneId zone;
private long prev;
private long incr;
SyntheticMicrosClock (ZoneId zone) {
this.zone = zone;
}
#Override
public ZoneId getZone() {
return zone;
}
#Override
public Clock withZone(ZoneId zone) {
if (zone.equals(this.zone)) { // intentional NPE
return this;
}
return new SyntheticMicrosClock(zone);
}
public long micros() {
final long now = TimeUnit.MILLISECONDS.toMicros(millis());
long synthNow = now;
if(now == prev) {
if(incr < 999) {
incr += 1;
}
synthNow += incr;
} else {
incr = 0;
}
prev = now;
return synthNow;
}
#Override
public long millis() {
return System.currentTimeMillis();
}
#Override
public Instant instant() {
return Instant.ofEpochSecond(0, micros());
}
}
To use it I inject my synthetic Clock where I need it. Ex:
Clock synthClock = Inject or new SynthClock(ZoneOffset.UTC);
Instant.now(synthClock);
Do you need a timestamp or just a high resolution increasing number that it time based? If so, you might be able to use System.nanoTime.
There were issues with this call in early JVM's/OS' but they seem to have been addressed (see first answer here).
Of course there's that odd chance that it might loop around on you. Don't know what kind of flexibility you have with your protocol, but there should be ways to deal with that.
Building on what #Bill suggested, you have 200+ years of resolution with nanoTime, so why not store nanoTime on init, currentTimeMillis on init, then add the difference of nanoTime and initNanoTime to initCurrentTimeMillis to get an augmented, high-precision timestamp? Once you detect clock skew between this augmented clock and the real one over 100ms, or so, you can reinit.
I am trying to get a date range using Guava's new Range functionality, via
Range<Date> dateRange = Ranges.range(start, BoundType.CLOSED, end, BoundType.CLOSED);
My goal is to get the hours in this date range. So I have created a DiscreteDomain like such:
private static final DiscreteDomain<Date> HOURS = new DiscreteDomain<Date>() {
public Date next(Date value) {
return addHours(value, 1);
}
private Date addHours(Date value, int i) {
Calendar cal = Calendar.getInstance();
cal.setTime(value);
cal.add(Calendar.HOUR_OF_DAY, i);
return cal.getTime();
}
public Date previous(Date value) {
return addHours(value, -1);
}
public long distance(Date start, Date end) {
Calendar cal1 = Calendar.getInstance();
cal1.setTime(start);
Calendar cal2 = Calendar.getInstance();
cal2.setTime(end);
return cal2.getTimeInMillis() - cal1.getTimeInMillis();
}
public Date minValue() {
return new Date(Long.MIN_VALUE);
}
public Date maxValue() {
return new Date(Long.MAX_VALUE);
}
};
If I merely sysout the output, I get the closed set
[Thu Feb 24 00:00:00 EST 2011..Thu Feb 24 00:02:00 EST 2011]
I really want to see each hour in the range, however, so I try a for loop:
for (Date hour : hours) {
System.out.println(hour);
}
When running this block, I seem to get an infinite set, beginning at the left side of the range, but not stopping at the right side, making me kill the IDE. What am I doing wrong?
I think this might be due to the behavior of the Iterator returned by the ContiguousSet (returned by Range.asSet()):
#Override public UnmodifiableIterator<C> iterator() {
return new AbstractLinkedIterator<C>(first()) {
final C last = last();
#Override
protected C computeNext(C previous) {
return equalsOrThrow(previous, last) ? null : domain.next(previous);
}
};
}
private static boolean equalsOrThrow(Comparable<?> left,
#Nullable Comparable<?> right) {
return right != null && compareOrThrow(left, right) == 0;
}
private static int compareOrThrow(Comparable left, Comparable right) {
return left.compareTo(right);
}
It only stops when the next computed value is equal to the right bound of the range.
In your case, have you tried calling it using Thu Feb 24 02:00:00 instead of Thu Feb 24 00:02:00 for the right bound of your range?
I think this behavior is problematic, and it might be worth asking if equalsOrThrow() could be changed to check for left <= right instead of left == right
Also, your distance() method is incorrect. It should return the distance in hours, not in milliseconds, according to the method contract.
EDIT
All this being said, I believe the real problem is that, according to the DiscreteDomain's javadoc:
A discrete domain always represents
the entire set of values of its type;
it cannot represent partial domains
such as "prime integers" or "strings
of length 5."
In your case, you are attempting to create a discrete domain over hourly dates, which is a partial domain of all dates. This is, I think, the root cause of the problem. When you have a partial domain, the equalsOrThrow method becomes unreliable, and it can "miss" the right bound of your range.
I just tried this and it worked fine for me. #eneveu already pointed out the issue with your distance method as well. I'm also guessing that there's some minor difference at the millisecond level between start and end which means that you'll never actually get a Date equal to end by adding hours to start.
However, that's all just symptoms of using the classes in a way they aren't designed to work. The Javadoc for DiscreteDomain states:
A discrete domain always represents the entire set of values of its type; it cannot represent partial domains such as "prime integers" or "strings of length 5."
A DiscreteDomain of "hours" does not represent the domain of all possible Date objects and as such breaks its contract.
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.