I have Grid which will render a calendar, and I'm provided with an ArrayList<CalendarEventEntity> which contains events. Those events have to be highlighted in the grid.
As I have to fill the grid by my self I have something like this:
for( loop through the days of the month ){
Calendar eventDate = event.getDate();
// look for the events in the calendar that matchs this day
for(CalendarEventEntity event : events) {
// if there are events in this specific day
if( eventDate.get(Calendar.YEAR) == calendarMonth.get(Calendar.YEAR) &&
eventDate.get(Calendar.MONTH) == calendarMonth.get(Calendar.MONTH) &&
eventDate.get(Calendar.DAY_OF_MONTH) == dayIndex ) {
// highlight it!!!
}
}
}
This works fine, but it's too slow. So I want to speed it up! I added this before the inner for:
// ignore dates which does not make part of this month or year
if( eventDate.get(Calendar.YEAR) < calendarMonth.get(Calendar.YEAR) ||
eventDate.get(Calendar.MONTH) < calendarMonth.get(Calendar.MONTH) ||
eventDate.get(Calendar.DAY_OF_MONTH) != DateIdx ) {
continue;
}
// stop when processing dates which are higher than this month or year
if( eventDate.get(Calendar.YEAR) > calendarMonth.get(Calendar.YEAR) ||
eventDate.get(Calendar.MONTH) > calendarMonth.get(Calendar.MONTH)
|| eventDate.get(Calendar.DAY_OF_MONTH) != DateIdx ) {
break;
}
and that made if faster, but it's still too slow. How can I improve this algorithm?
The problem is that each day you have to search every event looking for events on that date. You need to find a way to only be searching through events on that day, or to know if events are on that day at all.
You should consider using a HashMap to store your events indexed by date. Then you can just check to see if there's a HashMap entry for the day in question. You'll have to pick a way to represent a day that would be universal enough to be used as a key.
This will also be handy when you have to drill down into the details of one specific day and show only the events on that day. You shouldn't have to search through all events every time you want to find events for one specific day.
This is a classic example of a problem that would benefit from using a (sorted) tree. Java provides one in TreeMap. You can get events that begin on a day using the subMap method. Calendar implements Comparable, so it should just work. (Use the calendar entries as keys; use subMap from the last second of the preceding day to the first second of the following day to get all events that are on the day in question.)
If you have many multi-day events, then you'd need an interval tree, but it might just be easier to split the single event "Five Day Workshop" into five entries "Workshop, Day One of Five" etc. so that no events spill over from one day to another.
Related
Problem situation: I have an incredibly high number of records all marked with a timestamp. I'm looping through all of them to do this and that but I need to detect when the day has changed.
Right now for each loop I'm doing:
cal.setTimeInMillis(record.time);
int currentDay = cal.get(Calendar.DAY_OF_WEEK);
Is this as slow as I imagine it is when it's running hundreds of thousands of times?
I imagine I'm missing a really simple modulo answer or something.
Edit: Time zone does not matter, the information I'm collecting more resolves around a consumable report for someone. 24 hours per report is more accurate, so realistically I don't have to worry about whether or not that's 5am - 5am or 3pm - 3pm, just that I was able to gather 24H worth of info.
Thanks all
After Andy Turner’s time test I am not necessarily convinved that you need any optimized solution. In any case, timsmelik’s suggestion is pretty straightforward: convert the time when the day changes to a count of milliseconds since the epoch so you only need to compare long values. I don’t find that it hurts readability very badly. So here it is in code. I am using and warmly recommending java.time, the modern Java date and time API, if only for the conversion from hours to milliseconds and for printing the results. Even when such a conversion seems trivial, it’s always best to leave to the standard library to do it. It’s more self-explanatory and less error-prone, and it’s easier for the reader to convince oneself that it’s correct.
final long twentyfourHoursAsMillis = Duration.ofHours(24).toMillis();
// Times are already sorted descending (from newest to oldest)
long[] times = { 1_611_718_370_000L, 1_611_632_000_000L,
1_611_631_970_000L, 1_611_459_150_000L };
List<List<Long>> chunks = new ArrayList<>();
List<Long> currentChunk = new ArrayList<>();
// Process first time separately to get started
currentChunk.add(times[0]);
long timeOfNextChunk = times[0] - twentyfourHoursAsMillis;
// Process remaining times
for (int i = 1; i < times.length; i++) {
long currentTime = times[i];
if (currentTime <= timeOfNextChunk) {
chunks.add(currentChunk);
currentChunk = new ArrayList<>();
do {
timeOfNextChunk -= twentyfourHoursAsMillis;
} while (currentTime <= timeOfNextChunk);
}
currentChunk.add(currentTime);
}
// Save last chunk, why not?
chunks.add(currentChunk);
// Print result
for (List<Long> chunk : chunks) {
String chunkAsString = chunk.stream()
.map(Instant::ofEpochMilli)
.map(Instant::toString)
.collect(Collectors.joining(", "));
System.out.println(chunkAsString);
}
Output is:
2021-01-27T03:32:50Z, 2021-01-26T03:33:20Z
2021-01-26T03:32:50Z
2021-01-24T03:32:30Z
I am printing Instant objects. They always print in UTC. For your situation you may want to do otherwise if you need to print the times at all.
You should add a check of your assumption that the times come in sorted order.
I have taken your word for it and broken into chunks at 24 hours. 24 hours may not even mean 5am - 5am but could mean for instance from 5 AM EST on March 13 to 6 AM EDT on March 14 because summer time (DST) has begun in the meantime. If you prefer to split at the same clock hour, the code can be modified to do that.
I looked at this question and my problem is similar but not exactly identical. I have many timestamps in problem and they are all in the form "yyyyMMddHHmmssSSS", so I am parsing the string as follow:
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").parse("20180301050630663")
which will output 2018-03-01T05:06:30.663 (I do not know what the "T" in the middle stands for and do not know how to get rid of it)
Problem is I only care about the timestamps that lies within the range of [09:15:00.000am -12:00:00.000pm] and [15:15:00.000pm -18:00:00.000pm] across all different dates (inclusive too, meaning that if the timestamp is exactly at 09:15:00.000, then it should return true too).
However, how should I tackle this problem because sometimes the dates could be different, i.e. it could be across different dates 2018-03-01T05:06:30.663, 2018-03-02T10:36:30.596,2018-03-11T05:06:30.663? Since the date and times always come together, shall I extract the time from the timestamp ? What is the best way to deal with this in this situation ? I am not too familiar with the datetime libraries in Java.
You were on the right track with DateTimeFormatter. Rather than use DateTimeFormatter.parse, you can pass this formatter to LocalTime.parse which will effectively discard the date portion of the timestamp. You can then use Comparable.compareTo to see whether it's in the ranges you've given.
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
LocalTime time = LocalTime.parse("20180301050630663", formatter);
if (time.compareTo(LocalTime.of(9, 15)) >= 0
&& time.compareTo(LocalTime.of(12, 0)) <= 0)
{
System.out.println("First period");
}
else if (time.compareTo(LocalTime.of(15, 15)) >= 0
&& time.compareTo(LocalTime.of(18, 0 )) <= 0)
{
System.out.println("Second period");
}
If your ranges were exclusive rather than inclusive, you could have used LocalTime.isAfter and LocalTime.isBefore which would have resulted in slightly nicer looking code.
I know this is a quite discussed topic but I think I have a very specific problem.
I'm storing opening and closing time of stores on a database as well as GPS coordinates. I'd like to be able to show the stores on a map with a green marker if the store is open and red if close (wrt current time).
My problem is that I'm using the method string.compareTo(string) to know if the store is closed or open. Let's say we are on monday, the store close at 02:00 in the morning (tuesday morning), current time is 23:00. How can I tell the store is still open since 02:00 < 23:00 ?
Thank you for any answer,
Arnaud
edit: I'm editing my answer to give a better idea of what I meant. Also, try to back up a second and re-think the way you store your data structure. This is a very important part of programming and in most cases can be the difference between a bad design and a good design implementation.
Storing the time as a string is not a good idea, but in your case what you should do (I think) is this: (This code assumes that the hours for the store opening and closing time refer to the same day as now)
// Get the current time.
Calendar now = Calendar.getInstance();
// Create the opening time.
Calendar openingTime = Calendar.getInstance();
// Set the opening hours. (IMPORTANT: It will be very useful to know the day also).
openingTime.set(Calendar.HOUR_OF_DAY, storeOpeningTime); // (storeOpeningTime is in 24-hours format so for 10PM use 22).
// Check that the store "was opened" today.
if (now.before(openingTime)) {
return CLOSED;
}
// If the store "was opened" today check that it's not closed already ("tricky" part).
// Create the closing time and closing time for the store.
Calendar closingTime = Calendar.getInstance();
// Check if we are in the AM part.
if (storeClosingTime < 12 // Closing time is in the AM part.
&& storeClosingTime < openingTime // Closing time is before opening time, meaning next day.
// now and closingTime is the same day (edge case for if we passed midnight when getting closingTime)
&& closingTime.get(Calendar.DAY_OF_WEEK) == now.get(Calendar.DAY_OF_WEEK)) {
// Closing time is next day.
closingTime.add(Calendar.DAY_OF_WEEK, 1);
}
// Set the closing hours.
closingTime.set(Calendar.HOUR_OF_DAY, storeClosingTime); // (storeClosingTime is in 24-hours format so for 10PM use 22).
// Check if the store is not closed yet.
if (now.before(closingTime)) {
return OPEN;
}
// Store is closed.
return CLOSED;
I haven't tested this, but I think according to your questions and how to save the opening and closing times, this should work.
Important: There are more edge cases and tweaks you can make to the code to improve it, but I don't have enough time to do it now. I wanted to give a general guiding hand to solve your problem. Handling time and dates is never fun, you need to keep in mind a lot of elements like time zones and clock shifting in winter and summer in different countries. This is not a finished implementation in any way, and getting it to a finished state is not simple.
I have this assignment due by the end of the month and I'm already halfway done. We had to use Zeller's congruence to calculate the day of the week that the 1st landed on. From that point (and accounting for leap years), you have to print off a calendar of the corresponding year using loops. The teacher said that this should be a really short program and we should not try to brute force the the logic of the program (basically, don' t use a bunch of if else and loops for each month). I already have the math calculations and leap year part down, but I can't wrap my head around how to make this for loop work for numerous reasons:
How would I go about factoring in whitespaces for each month? Like how many nested loops will I actually need?
Would I need to write different loops for Leap years, months with 31 days, and months with 30 days, etc or can all this be handled with 1 giant nested loop?
For example, I just started off writing a for loop just to see if I could print off a typical 31 day calendar without any special formatting (whitespaces, Month name, days etc). Here was my first test at using a for loop:
for(int i = 1; i < 31; i++)
{
for(int j = 0; j < 7; j++)
{
System.out.print(i + "\t");
i++;
if(j == 6){
i--;}
if(i > 31)
{
break;
}
}
System.out.print("\n");
}
What is the logic behind using it for multiple months and whitespaces. I welcome all help, but please DO NOT GIVE ME SOURCE CODE. I want the satisfaction of getting this done with code I've written, and like I said, this is also an assignment and I won't really learn from copying and pasting code. Just throw me a bone about the logic I need to use. I can work in all other conditionals for special cases. Thanks guys.
Ok, you need to manage something which varies between a fixed set of values, so you basically need a way to access these variable data from within a loop.
The best tool to fullfil this problem is probably an array.
If you store inside an array, for example the duration of the months you will be able to access them in a straightforward way (through an index) so that you won't need any conditional chain, you will just need the index of the current month, eg monthDurations[i] == 31.
If you want to structure everything better you could define your own Month class or Day class or whatever you need to keep track of printing issues (eg white spaces or names) so that everything will be easily encapsulated and maintainable.
I would write a function to display one month with parameters: dayOfWeek, numberOfDays, [name, firstDayOfWeek]
dayOfWeek - what day of week is 1st day of the month
numberOfDays - number of days in this month
optional parameters You may implement if You like
name - name of the month
firstDayOfWeek - some calendars use Sundays other use Mondays as first day, if You like to have this flexibility You can implement this as well.
And the logic is simple You can do with just one loop that would iterate numberOfDays+dayOfWeek-1 times.
Inside You just need to check for end of week to make new lines and display empty spaces dayOfWeek-1 times in first week.
We have a requirement to present two p:calendar components to the user, representing a start and end date each. Both datetimes have dates, hours and minutes.
PrimeFaces has perfect mindate, maxdate, minHour, maxHour, minMinute, and minMinute attributes available.
The requirement now is:
It is impossible to set the start datetime to anything greater than or equal to the end datetime.
It is impossible to set the end datetime to anything less than or equal to the end datetime.
The following equation should hold true:
begin datetime < end datetime
Now we tried the following JSF:
<p:calendar id="begin-date"
value="#{debugManager.selectedBeginDate}"
mindate="#{debugManager.minBeginDate}"
maxdate="#{debugManager.maxBeginDate}"
maxHour="#{debugManager.maxBeginHour}"
maxMinute="#{debugManager.maxBeginMinute}"
pattern="yyyy-MM-dd HH:mm"
showButtonPanel="true"
readonlyInput="true"
navigator="true"
showOn="button"
required="true">
<p:ajax event="dateSelect" update="end-date" />
</p:calendar>
<p:calendar id="end-date"
value="#{debugManager.selectedEndDate}"
mindate="#{debugManager.minEndDate}"
minHour="#{debugManager.minEndHour}"
minMinute="#{debugManager.minEndMinute}"
pattern="yyyy-MM-dd HH:mm"
showButtonPanel="true"
readonlyInput="true"
navigator="true"
showOn="button">
<p:ajax event="dateSelect" update="begin-date" />
</p:calendar>
Here's an examplary min/max method (mindate of end-date):
public Date getMinEndDate()
{
return this.getSelectedBeginDate();
}
As you can see, the minimum end date is the currently AJAX-selected begin date. Setting an end date correctly disallows setting the begin date past the end date.
The problems start when involving the time into the equation...
Since the interface of p:calendar has separate methods, the bean has to provide the logic:
public int getMinEndHour()
{
Date selectedBeginDate = this.getSelectedBeginDate();
Date selectedEndDate = this.getSelectedEndDate();
if ( selectedBeginDate != null && DateUtil.isSameDay( selectedBeginDate, selectedEndDate ) )
{
return DateUtil.getHourOf( selectedBeginDate );
}
return ComplianceConstants.DEFAULT_COMPLIANCE_CASE_MIN_END_HOUR;
}
This basically only says if a begin date has been set and it the begin and end dates are currently the same, restrict the selectable end hour (minHour of end-date) to the begin hour.
Operations:
Set the begin datetime to 2013-04-20 12:34 (legit)
Set the end datetime to 2013-04-22 00:00 (legit)
Now the time for end date sits on 00:00 and selecting a calendar date 2013-04-20 should be allowed as long as the end time is somehow adjusted to at least 12:35.
The p:calendar component however cannot know this and now
sets the end datetime to 2013-04-20 00:00 (legit, but false)
...
The problem now is that when the user presses a certain new end date in the calendar, the mindate/maxdate attributes cannot restrict the user to hit the the same as the begin date. If the end date time now happens to be before the same begin date's time there's nothing we can do about it (which is wrong).
The followup problem now is that the user is able to close the calendar and just press the submit button to insert false data into the DB. Of course, a validator could/should be run, but we have to somehow achieve this without a validator.
What we were trying next was to patch the setSelectedBeginDate( Date selectedBeginDate ) and setSelectedEndDate( Date selectedEndDate ) methods to adjust the set java.util.Date time portions if the dates were on the same day. Something like this:
public void adjustSelectedEndDate()
{
if ( this.selectedEndDate != null )
{
this.log.infov( "adjustSelectedEndDate: b-hour = {0}, e-hour = {1}", DateUtil.getHourOf( this.selectedBeginDate ), DateUtil.getHourOf( this.selectedEndDate ) );
if ( DateUtil.isSameDay( this.selectedBeginDate, this.selectedEndDate ) &&
( DateUtil.getHourOf( this.selectedEndDate ) < DateUtil.getHourOf( this.selectedBeginDate ) ) ||
DateUtil.getHourOf( this.selectedEndDate ) == DateUtil.getHourOf( this.selectedBeginDate ) && DateUtil.getMinuteOf( this.selectedEndDate ) <= DateUtil.getMinuteOf( this.selectedBeginDate ) )
{
this.log.info( "Adjusting selected end date!" );
this.selectedEndDate = DateUtil.addOneMinuteTo( DateUtil.copyTime( this.selectedBeginDate, this.selectedEndDate ) );
}
}
}
This required us to add #this to the update attribute of each p:calendar so that the respective getters (getSelectedBeginDate() and getSelectedEndDate + the min/max limiters) will be called during update.
Placing an #this on the update however confuses the p:calendar components, making the time sliders only slidable once. Subsequent slider events are simply ignored, behaving broken.
Q's
How do you generally approach solving this?
Is using p:remoteCommand the way to achieve what we want?
Optional Q:
Why hasn't the PrimeFaces p:calendar been implemented to provide a single minDateTime and maxDateTime, which could potentially solve the problems at hand?
I bet this scenario I described has already been solved before. I'd very much appreciate if you could describe the approach you managed to solve this (or even share a partly solution).
Preface:
I don't work with JSF, but there are a couple of things that might steer you back to where you want to be:
a) when working with just the date portion of a dateTime in a standard calendar, consider using:
someCalendar.set(Calendar.MILLISECOND, 0)
b) consider using joda-time, as it seems to be frequently recommended (here, here , and many other places) over the standard library for correctness, performance, and ease of use in many situations.
c) Make sure your bean scope is surviving each ajax call (not redirecting, only sending standard post-backs, etc) and each event handler is getting the faces context (eg. FacesContext facesContext = FacesContext.getCurrentInstance();)
d) mindate and the like probably don't work like you expect , and I don't expect that automatic behavior can be quite so easily interjected.
When those options aren't available, and you have to do it all yourself with what you have:
Philisophical / UX:
The first thing I would do is remove the expectation of arrangement or perspective from the pair of dates. Don't treat the pair as a vector that exposes or expects a direction on the timeline.
In other words, is a start or from date always less than or earlier than an end or to date? No, as can be seen for a query of historical data, or for applying corrections to events that have either yet to happen or have already happened?
This connotation can easily confuse a user as to whether they are going 'back to' or 'forward from' (and can easily confuse yourself). Instead I would treat a pair of dates with a time-period between them as just and simply that a pair of dates or a range or a period that declares an interval, and infer their relative position on the timeline depending on the any consequently chosen values. In this way you can honor the respective and inherent requirements that the dates never be equal, and the left is always to the left, the right always to the right.
We can't infer what 'start' or 'from' means, but we can infer some meaning and relative relationship: a right, a left, and a between on a chronological timeline. Note: Always resolve dates to UTC before doing any calculation or comparison.
long oneDateValue = oneDate.toUtc().toMilliseconds();
long anotherDateValue = anotherDate.toUtc().toMilliseconds();
long right = max (oneDateValue, anotherDateValue);
long left = min (oneDateValue, anotherDateValue);
Evaluating Precision:
The second thing I would look at when working with a range of dates in any language is similar to how you might deal with floating point numbers. For comparisons, do not compare for equality, but instead compare the delta to an "acceptable error level". In other words, the application is really only concerned with a certain degree of precision, so make sure that only that precision is captured and considered:
const int dateTimeResolutionInMs = 86400000; // milliseconds per day
public bool areEssentiallySame(long left, long right) {
// the difference between right and left is less than our precision
// requires, thus dates are effectively the same
return (right - left < dateTimeResolutionInMs);
}
Coercing Precision:
Thirdly, how do we resolve the difference in values even if within the range of the resolution? (Out application was given more precision than it can handle or expect or needs).
long diff = value % dateTimeResolutionInMs;
Truncate: return value - diff;
Nearest (w/bias): return value + (diff < dateTimeResolutionInMs/ 2) ? -1 * diff : dateTimeResolutionInMs - diff;
Others: there are lots of other strategies for either shrinking or expanding a value to a preferred resolution or precision
Addendum:
As far as getting post-backs/Ajax calls to return a view with the values you expect for the events fired by a calendar element, you may want to separate that concern off to a new question if the note in the preface didn't get you anywhere, and you know for certain your bean is properly registered and recognized. You may have some browser/browser-version specific issues that contribute to the undesired behavior, and like anything else, there are issues, both known and unknown.