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.
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.
Initial time=22:00:00,final time=23:59:59.
If the current time is, let's say 23:00:00 then I will get the success message otherwise error message will be shown. And I am comparing this time with the system time.
My code:
//retrieving the system time in string format
SimpleDateFormat sdfDate = new SimpleDateFormat("HH:mm:ss");
Date date = new Date();
String s=sdfDate.format(date);
//Initial and final predefined time
String ten ="22:00:00";
String twelve ="23:59:59";
//comparing with the system time
try{
if(s.compareTo("twelve")<0 && s.compareTo("ten")>0 ){
out.print("success");
}else{
out.print("failed");
}
}catch(Exception l){
System.out.println(l.toString());
}
I tried to check it when the system time was 23:45:00. I also confirmed the time by printing out.print(""+s);. But I got the failed message. I don't know my loop is working or not.
What can I do to fix this?
If you're comparing hours, don't use strings, use a proper type.
In Java you have:
Java >= 8: java.time.LocalTime
Java <= 7: org.threeten.bp.LocalTime (from external lib: http://www.threeten.org/threetenbp/)
In both you can do:
LocalTime ten = LocalTime.parse("22:00:00");
LocalTime twelve = LocalTime.parse("23:59:59");
// current time
LocalTime now = LocalTime.now();
// compare
if (now.isBefore(twelve) && now.isAfter(ten)) {
// succcess
}
To get the current time, you could also use the now method with a timezone (example: LocalTime.now(ZoneId.of("America/New_York"))), if you need the time at some specific place (now() without arguments will use the JVM default timezone).
Transforming the strings to a type that represents the data you're working with is much more reliable. Also note that this API makes the code much easier and more readable, with meaningful methods names such as isAfter and isBefore.
Comparing strings might work, but using the proper types works even better.
Try this:
if(s.compareTo(twelve) < 0 && s.compareTo(ten) > 0)
By putting quotes around twelve and ten, you're comparing to the strings "twelve" and "ten", and not referencing the variables twelve and ten that you defined ealier in the program.
What is an efficient and easy-to-read expression of testing if LocalDate dayX falls in a duration that is described with a starting day LocalDate day0 and a length Period length?
Currently I am doing like this:
boolean match = !day0.isAfter(dayX) && day0.plus(length).isAfter(dayX);
I just feel this looks a bit dumb and every time I read this it takes several seconds for my brain to tell if the boundaries are correct. So I am looking for a smarter way that maybe involves the Duration or Interval classes.
What you are doing is correct (except that you need to use before method for the first condition). Maybe you can wrap it into a method and call it to make it reusable and look more elegant, e.g.:
public boolean isInRange(Localdate start, Period period, Localdate target){
return !target.before(start) && !target.after(start.plus(period));
}
Somewhat similar approach: day0 < dayx < (day0 + period)
boolean match = (dayx.isAfter(day0) && dayx.isBefore(day0.plus(length));
As far I know, in Java I can get weekdays in normal (Friday) or short mode (Fri). But, there is any way to obtain only first letter?
I thought I can get first letter using "substring", but it won't be correct for all languages. For example, spanish weekdays are: Lunes, Martes, Miércoles, Jueves, Viernes, Sábado and Domingo, and first letter for "Miércoles" is X instead of M to difference it from "Martes".
In Android you can use SimpleDateFormat with "EEEEE". In the next example you can see it.
SimpleDateFormat formatLetterDay = new SimpleDateFormat("EEEEE",Locale.getDefault());
String letter = formatLetterDay.format(new Date());
EDIT: it's actually not entirely true. The result on Android could have more than a single letter (and also non-unique, if this matters), but this is what we have. Here's proof that you won't get these characteristics on Android, going over all locales. It's written in Kotlin, but should work for Java too, of course:
val charCountStats = SparseIntArray()
Locale.getAvailableLocales().forEach { locale ->
val sb = StringBuilder("$locale : ")
val formatLetterDay = SimpleDateFormat("EEEEE", locale)
for (day in 1..7) {
val cal = Calendar.getInstance()
cal.set(Calendar.DAY_OF_WEEK, day)
val letter: String = formatLetterDay.format(cal.time)
charCountStats.put(letter.length, charCountStats.get(letter.length, 0)+1)
sb.append(letter)
if (day != 7)
sb.append(',')
}
Log.d("AppLog", "$sb")
}
Log.d("AppLog", "stats:")
charCountStats.forEach { key, value ->
Log.d("AppLog", "formatted days with $key characters:$value")
}
And the result is that for most cases it's indeed a single letter, but for many it's more, and can even reaches 8 characters (though it might look as less letters, even one) :
formatted days with 1 characters:4889
formatted days with 2 characters:471
formatted days with 3 characters:99
formatted days with 4 characters:58
formatted days with 5 characters:3
formatted days with 8 characters:3
Example of a locale that it shows as 3 letters (and not just has 3 letters) is "wo" ("Wolof" language), as this is the result for each of its days of the week using the above formatting:
Dib,Alt,Tal,Àla,Alx,Àjj,Ase
As mentioned above there is no standard Java support for this. Using the formatting string "EEEEE" however is not guaranteed to work on all Android devices. The following code is guaranteed to work on any device:
public String firstLetterOfDayOfTheWeek(Date date) {
Locale locale = Locale.getDefault();
DateFormat weekdayNameFormat = new SimpleDateFormat("EEE", locale);
String weekday = weekdayNameFormat.format(date);
return weekday.charAt(0)+"";
}
There is no standard Java API support for doing that1.
Part of the reason is that many (maybe even most) languages don't have conventional unique one-letter weekday abbreviations. In English there isn't, for example (M T W T F S S).
A (hypothetical) formatting option that doesn't work2 in many / most locales would be an impediment to internationalization rather than a help.
It has been pointed out that:
SimpleDateFormat formatLetterDay =
new SimpleDateFormat("EEEEE", Locale.getDefault());
String letter = formatLetterDay.format(new Date());
gives one letter abbreviations for later versions of Android (18 and above), though the javadocs do not mention this. It appears that this "5 letter" format has been borrowed from DateTimeFormatter whose javadoc says:
The count of pattern letters determines the format.
Text: The text style is determined based on the number of pattern letters used. Less than 4 pattern letters will use the short form. Exactly 4 pattern letters will use the full form. Exactly 5 pattern letters will use the narrow form. ...
If you are targeting Android API 26 or later, you should consider using the java.time.* classes rather than the legacy classes.
But either way, this isn't guaranteed to give you unique day letters.
1 - By "that" I mean mapping to unique 1-letter abbreviations.
2 - I mean it doesn't work in the human sense. You could invent a convention, but typical people wouldn't understand what the abbreviations meant; e.g. they wouldn't know that "X" meant "Miércoles", or in English that (say) "R" meant "Thursday" (see https://stackoverflow.com/a/21049169/139985).
I realize the OP was asking for standards across languages, and this does not address it. But there is/was a standard for using single character Day of Week abbreviation.
Back in mainframe days, using a 1-character abbreviation for Day of Week was common, either to store day of week in 1 character field (to save precious space), or have a report heading for single-character column. The "standard" was to use MTWRFSU, where R was for Thursday, and U for Sunday.
I could not find any definitive references to this (which is why I quoted "standard", but here are a couple of examples:
http://eventguide.com/topics/one_digit_day_abbreviations.html
http://www.registrar.ucla.edu/soc/definitions.htm#Anchor-Days-3800
I think there's no direct java function to get the first letter and no standard way to do it.
You can refer to this link to obtain the first letter of the string day using substring() java method
Given a string in Java, just take the first X letters
DateFormatSymbols.getWeekdays with a width of NARROW will give you the first letter of each week day. It works for every language. However, it requires API 24.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String[] weekDays = DateFormatSymbols.getInstance(Locale.getDefault())
.getWeekdays(DateFormatSymbols.STANDALONE, DateFormatSymbols.NARROW);
}
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.