Need help understanding below test code. Specifically I do not understand what the "11" and "12" represent in the calendar.set method? example "openCal.set(11, openHrs).
public static void main(String[] args) {
{
int openHrs = 07;
int openMins = 30;
int closedHrs = 23;
int closedMins = 00;
Calendar cal = Calendar.getInstance();
Calendar openCal = Calendar.getInstance();
openCal.set(11, openHrs);
openCal.set(12, openMins);
Calendar closeCal = Calendar.getInstance();
closeCal.set(11, closedHrs);
closeCal.set(12, closedMins);
if(openCal.before(cal) && closeCal.after(cal))
{
System.out.println("The Business is OPEN");
} else
{
System.out.println("The Business is CLOSED");
}
}
}
This is perfect example of why we should avoid magic numbers.
Instead of set(11 code should look like set(Calendar.HOUR_OF_DAY.
Instead of set(12 code should look like set(Calendar.MINUTE.
If you take a look at documentation of Calendar class you will find few examples of how to use set methods like
set(Calendar.MONTH, Calendar.SEPTEMBER)
set(Calendar.DAY_OF_MONTH, 30)
By looking at source code of Calendar class you will find many constants and their values. They can also be found at
http://docs.oracle.com/javase/8/docs/api/constant-values.html
so you see that
Calendar.HOUR_OF_DAY equals 11 -> (click here to check)
Calendar.MINUTE equals 12 -> (click here to check)
Just digging into Calendar source code those magic numbers are tied to the following fields
private static final String[] FIELD_NAME = {
"ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH", "DAY_OF_MONTH",
"DAY_OF_YEAR", "DAY_OF_WEEK", "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR",
"HOUR_OF_DAY", "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
"DST_OFFSET"
};
So in that case you can see that 11 is HOUR_OF_DAY and 12 is MINUTE
Where are you looking at this tutorial? In the set function for Calendar that has two parameters the first parameter is an index for where the data is and the second is the value to set. So from the code that would suggest that 11 is for Hours and 12 is for minutes. The documentation is at http://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html . The numbers should be replaced with constants from the Calendar class to make this code more readable and self answer your question.
Calendars get and set methods use integers as the first parameters, which indicate the field that should be retrieved respectively changed. This might seem strange, but Calendar is older than Enums in Java (and considering all the other stupidities in Java's date related classes, this one is a minor one).
As others have pointed out, the only acceptable practice is to use the constants defined in Calendar (HOUR_OF_DAY etc.), but syntactically, a [expletive removed] programmer can use numerical literals too (or even expressions that result in an int value).
tl;dr
LocalTime now = LocalTime.now( ZoneId.of( "America/Montreal" ) );
Boolean isOpenNow =
( ! now.isBefore( LocalTime.of( 7 , 30 ) ) )
&&
now.isBefore( LocalTime.of( 23 , 00 ) )
;
Details
The Answer by Pshemo is correct and should be accepted.
Avoid legacy date-time classes
The Question and answers have outmoded code using troublesome old date-time classes now supplanted by the java.time classes.
LocalTime
The LocalTime classe represents a time-of-day without a date and without a time zone.
LocalTime opening = LocalTime.of( 7 , 30 );
LocalTime closing = LocalTime.of( 23 , 00 );
Time zone
Determining the current time of day requires a time zone. For any given moment the time-of-day (and the date) vary around the globe by zone.
ZoneId z = ZoneId.of( "America/Montreal" );
LocalTime now = LocalTime.now( z );
Comparing
We can compare the LocalTime objects with compareTo, equals, isAfter, and isBefore.
In this example code we use the Half-Open approach to defining a span of time, where the beginning is inclusive while the ending is exclusive. This approach is sensible and commonly used. Using this approach throughout all your code makes your logic easier to comprehend and makes errors less likely. So we ask “is the current moment the same or later than the start but also earlier than the stop?”. A more compact way to say the same thing is:
Is now not earlier than start AND now is earlier than stop?
Boolean isOpenNow = ( ! now.isBefore( opening ) ) && now.isBefore( closing ) ;
Related
For the expiration date of credit/debit cards, I am using an array that contains all years since 2020 to 2030:
String[] expirationYearArray = { "2020", "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030" };
That is a bad approach because years are hard-coded. That means that after 3 years, in 2023, users will still have the option to choose 2020, 2021 and 2022 as the expiration date of credit cards. That is clearly wrong. What I want to do is to fill the array with Strings from current year to 10 more years from the current year. What I am thinking to do is to use a Java built-in function to get the current year. Then use a for loop to make 10 iterations and in each iteration convert from Int to String so that I convert 2020 to "2020" and then push that "2020" as the first element of the expirationYearArray array. The for or while loop would continue to do that until I reach the tenth iteration. Does this approach make sense to you? Please let me know if you see a different or more optimal option that may do the same with less code or if it makes sense to you the way I am envisioning it. Thank you.
It's a good idea to avoid hard-coding the list of years.
As for the range of years you generate, you might check with your card processor for the rules. For example, expired cards can be charged in some cases when they were setup for a recurring charge, like a subscription, but they probably shouldn't be accepted for new plans. Likewise, credit cards typically expire sooner than 10 years; your processor may reject cards with an unrealistically long expiration.
Again, processors may not be consistent in the enforcement of time zones. Some might consider a year to end when it ends in their local time zone, others might use UTC. The most lenient policy would be to use the "anywhere on earth" zone, with an offset of -12:00 from UTC.
To make your logic testable, pass it a Clock instance. Typically, the clock would be the system default clock, but in this case, you might set the zone to be "anywhere in the world". By passing a clock, you can remove the dependency on the actual time, and test how your calculation will behave at any time.
private static final ZoneOffset aoe = ZoneOffset.ofHours(-12);
private static final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuu");
public static void main(String[] args) {
Clock clock = Clock.system(aoe);
List<Year> years = nextYears(clock, 10L);
years.stream().map(fmt::format).forEach(System.out::println);
}
private List<Year> nextYears(Clock clock, long count) {
return Stream.iterate(Year.now(clock), year -> year.plusYears(1L))
.limit(count)
.collect(Collectors.toList());
}
tl;dr
In 2 lines, using streams and lambda syntax.
Year currentYear = Year.now( ZoneId.of( "Pacific/Auckland" ) ); // Determining the current year requires a time zone when near the ending/beginning of the year.
List < Year > years = // A list of objects of the class `Year`. Better to use a specific type than use a mere integer number.
IntStream // Generates a stream of `int` primitive numbers.
.range( // Get numbers from the specified beginning to the specified ending.
currentYear.getValue() , // Get `int` number of the current year.
currentYear.plusYears( 10 ).getValue() // Yet the `int` number of a later year.
) // Returns a `IntStream` object.
.boxed() // Converts the `int` primitive values into a stream of `Integer` objects.
.map( integer -> Year.of( integer ) ) // Uses each `Integer` object to instantiate a `Year` object. The `Integer` object is auto-boxed back into an `int` primitive`. Primitives cannot be mapped in a stream with a lambda. So we had to do this funky `int` -> `Integer` -> `int` conversion juggling.
.collect( Collectors.toList() ) // Gather the `Year` objects produced by the `map` method into a `List` collection.
;
In a few lines, using conventional syntax.
Year currentYear = Year.now( ZoneId.of( "Asia/Tokyo" ) ) ;
List< Year > years = new ArrayList<>( 10 ) ;
for( int i = 0 ; i < 10 ; i ++ )
{
years.add( currentYear.plusYears( i ) ) ;
}
See this code run live at IdeOne.com.
years.toString(): [2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029]
java.time.Year
Java offers a specific class to represent a year, appropriately named Year. I suggest using that class rather than a mere integer to make your code type-safe and more self-documenting.
Time zone is crucial
Getting the current year requires a time zone. For any given moment, the date various around the world by time zone. While "tomorrow" in Tokyo Japan, it is simultaneously "yesterday" in Montréal Québec.
Therefore, around the last day / first day of the year, it may be "next year" in Tokyo Japan while simultaneously "last year" in Montréal Québec. So when determining the current year, specify the desired/expected time zone.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
Year currentYear = Year.now( z ) ;
Get your next ten years by iterating.
List< Year > years = new ArrayList<>( 10 ) ;
Year year = currentYear ;
for( int i = 0 ; i < 10 ; i ++ )
{
// Remember this year.
years.add( year ) ;
// Set up the next loop.
year = currentYear.plusYears( i ) ;
}
Make an unmodifiable list from that ArrayList by calling List.copyOf.
List< Year > years = List.copyOf( years ) ; // Returns an unmodifiable `List`, with adding/removing elements disallowed.
java.time.Month
For expiration year-month, you also need the a widget for picking month. There you can use the Month class.
List< Month > months = List.copyOf( Month.values() )
The toString method of Month gives you the name of the month in English in all uppercase, based on the name of enum element. You might want to make a list of localized names for display to users.
List< String > monthsLocalized = new ArrayList<>( months.size() ) ;
Locale locale = Locale.CANADA_FRENCH ;
for( Month month : months )
{
monthsLocalized.add( month.getDisplayName( TextStyle.FULL , locale ) ;
}
monthsLocalized = List.copyOf( monthsLocalized ) ;
java.time.YearMonth
For your final result of a user picking a year and a month, use YearMonth. Oddly, the YearMonth factory methods do not take a Year object, only a mere integer. So call Year::getValue to get the number.
YearMonth yearMonth = YearMonth.of( selectedYear.getValue() , selectedMonth ) ;
Premature Optimization
You expressed concern about optimizing for performance. This is not the kind of code you need to optimize. Indeed, do not fall into the trap of premature optimization. Even the most experienced programmers have been shown to be notoriously bad at intuitively guessing where the bottlenecks live in their codebase. Optimize only after having empirically proven a significant slowdown that materially affects the performance of your code and the running of the business. Most often, writing simple straightforward short lines of Java code will result in highly optimized executing code after the Java compiler and runtime optimizer (HotSpot, OpenJ9) do their job.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
Here are several variants:
public static void main(String[] args) {
System.out.println(Arrays.toString(getExpirationYears(LocalDateTime.now().getYear())));
System.out.println(Arrays.toString(getExpirationYears(Calendar.getInstance().get(Calendar.YEAR))));
System.out.println(Arrays.toString(getExpirationYears(1900 + new Date().getYear())));
System.out.println(Arrays.toString(getExpirationYearByStreams(1900 + new Date().getYear())));
}
static String[] getExpirationYears(int year) {
String[] expirationYearArray = new String[10];
for (int i = 0; i < expirationYearArray.length; i++) {
expirationYearArray[i] = String.valueOf(year + i);
}
return expirationYearArray;
}
static String[] getExpirationYearByStreams(int year) {
return IntStream.range(year, year+10)
.boxed()
.map(String::valueOf)
.toArray(String[]::new);
}
This is how I achieved it in my code, following Nonika's advice (see his answer):
package com.myapp;
..........
public class Payment extends BaseActivity implements ResponseListener {
..........
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
String[] cardYearArray = new String[10];
..........
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
..........
cardYearArray=getExpirationYears(currentYear);
SpinnerAdapter creditCardYearAdapter = new SpinnerAdapter(Payment.this,
cardYearArray);
..........
}
static String[] getExpirationYears(int year) {
String[] expirationYearArray = new String[10];
for (int i = 0; i < expirationYearArray.length; i++) {
expirationYearArray[i] = String.valueOf(year + i);
}
return expirationYearArray;
}
..........
}
Now I see the years appearing correctly as I needed it:
In my code I need to iterate between a range of dates using Joda, and I already tried this:
for(LocalDate currentdate = startDate; currenDate.isBefore(endDate); currenDate= currenDate.plusDays(1)){
System.out.println(currentdate);
}
The above code is working, but the iteration stops when currenDate reaches the day before endDate. What I want to achieve is that the iteration stops when currentDate is exactly the same as endDate.
for(Date currentdate = startDate; currentdate <= endDate; currentdate++){
System.out.println(currentdate );
}
I know the code above is impossible, but I do it to make clear what I'd want.
Actually there's a simple way around to your original code you posted, see my implementation below, just modified your for loop implementation:
//test data
LocalDate startDate = LocalDate.now(); //get current date
LocalDate endDate = startDate.plusDays(5); //add 5 days to current date
System.out.println("startDate : " + startDate);
System.out.println("endDate : " + endDate);
for(LocalDate currentdate = startDate;
currentdate.isBefore(endDate) || currentdate.isEqual(endDate);
currentdate= currentdate.plusDays(1)){
System.out.println(currentdate);
}
Below is the Output (with respect to my localDate):
startDate : 2015-03-26
endDate : 2015-03-31
2015-03-26
2015-03-27
2015-03-28
2015-03-29
2015-03-30
2015-03-31
Hope this helps! Cheers. :)
If you want your loop to stop when the date your iterating is the same as todays date, you can use an equality check for that.
Have a look at .equals() on LocalDate
Here is a quick example:
public class DateIterator {
public static void main(String[] args) {
LocalDate lastMonth = LocalDate.now().minusMonths(1);
LocalDate lastWeek = LocalDate.now().minusWeeks(1);
LocalDate yesterday = LocalDate.now().minusDays(1);
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.now().plusDays(1);
List<LocalDate> dates = Arrays.asList(lastMonth, lastWeek, yesterday, today, tomorrow);
for (LocalDate date : dates) {
if (date.isEqual(today)) {
System.out.println("Date is equal to todays date! Break out, or do something else here");
} else if (date.isBefore(today)) {
System.out.println("The date " + date.toString() + " is in the past");
} else {
System.out.println("The date " + date.toString() + " is in the future");
}
}
}
}
Output is:
The date 2015-02-25 is in the past
The date 2015-03-18 is in the past
The date 2015-03-24 is in the past
Date is equal to todays date! Break out, or do something else here
The date 2015-03-26 is in the future
Obviously, if that equality check passes, you'll need to break out of the loop etc.
Heres another that uses a specific date and increments 1 day at a time, which I think is a bit more like what you want
public class DateIterator {
public static void main(String[] args) {
LocalDate specificDate = LocalDate.now().minusWeeks(1);
LocalDate today = LocalDate.now();
boolean matchFound = false;
while (!matchFound) {
if (!specificDate.isEqual(today)) {
System.out.println(specificDate.toString() + " is in the past, incrementing day and checking again...");
specificDate = specificDate.plusDays(1);
} else {
System.out.println("Date matches today!");
matchFound = true;
}
}
}
}
Output:
2015-03-18 is in the past, incrementing day and checking again...
2015-03-19 is in the past, incrementing day and checking again...
2015-03-20 is in the past, incrementing day and checking again...
2015-03-21 is in the past, incrementing day and checking again...
2015-03-22 is in the past, incrementing day and checking again...
2015-03-23 is in the past, incrementing day and checking again...
2015-03-24 is in the past, incrementing day and checking again...
Date matches today!
If you want the loop to be inclusive of the endDate you can use !currentDate.isAfter( endDate ). This is logically equivalent to currentDate.isBefore(endDate) || currentDate.equals(endDate).
The following example will print 6/1/2017 through 6/10/2017.
LocalDate startDate = new LocalDate( 2017, 6, 1 );
LocalDate endDate = new LocalDate( 2017, 6, 10 );
for ( LocalDate currentDate = startDate; !currentDate.isAfter( endDate ); currentDate = currentDate.plusDays( 1 ) )
{
System.out.println( currentDate );
}
Using java.time
The Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes. See Tutorial by Oracle.
The LocalDate class represents a date-only value without time-of-day and without time zone.
LocalDate start = LocalDate.of( 2017 , Month.JANUARY , 23 ) ;
LocalDate stop = LocalDate.of( 2017 , Month.FEBRUARY , 2 ) ;
By the way, you might want to add a sanity-check to verify that the ending is not before the beginning.
Not After
I believe the logic you are looking for, to include the ending date, is “not after“. The LocalDate class includes a isAfter method, to which you can add a logical “NOT” (!).
Also, a while loop seems more appropriate and self-explanatory in this situation than a for loop.
LocalDate ld = start ;
List<LocalDate> dates = new ArrayList<>() ;
while ( ! ld.isAfter( stop ) ) {
dates.add( ld ); // Collect this date.
ld = ld.plusDays( 1 ) ; // Setup the next loop.
}
See this code run live at IdeOne.com.
dates: [2017-01-23, 2017-01-24, 2017-01-25, 2017-01-26, 2017-01-27, 2017-01-28, 2017-01-29, 2017-01-30, 2017-01-31, 2017-02-01, 2017-02-02]
Half-Open
the iteration stops when currentDate reaches the day before endDate
This is actually desirable. Known as Half-Open, the common approach in date-time handling is to consider the beginning as inclusive while the ending is exclusive. So a lunch break starts at 12:00:00 (noon) and runs up to, but does not include, 13:00:00 (1 pm). The month of January starts on January 1 and runs up to, but does not include, February 1. A week starts on a Monday and runs up to, but does not include the following Monday. Most usefully, this approach avoids the problem of determining the last split second of a date-time where some systems use milliseconds (x.999), some (x.999999), same nanoseconds( x.999999999 ), and others use variations such as 5 decimal places (x.99999). Instead we go up to, but not include, the first moment of the next hour or day etc.
I find that using Half-Open approach consistently throughout my code makes the code easier to read, easier to comprehend, and much less likely to result in off-by-one bugs. I have been caught in countless financial mystery problems that turned out to be confusion or misunderstandings about a report covering date for with inclusive vs exclusive dates. So if possible, train your users to think the Half-Open way consistently. If not feasible, then adjust your code so your logic and loops are using Half-Open internally at least.
Here is code similar to above, but using isBefore rather than NOT isAfter, to use Half-Open approach. The ending is Feb. 3 instead of Feb. 2.
LocalDate start = LocalDate.of( 2017 , Month.JANUARY , 23 ) ;
LocalDate stop = LocalDate.of( 2017 , Month.FEBRUARY , 3 ) ; // Third instead of the Second of February, to be half-open.
LocalDate ld = start ;
List<LocalDate> dates = new ArrayList<>() ;
while ( ld.isBefore( stop ) ) { // Using "isBefore" for Half-Open approach.
dates.add( ld ); // Collect this date.
ld = ld.plusDays( 1 ) ; // Setup the next loop.
}
See this code run live at IdeOne.com.
start: 2017-01-23 | stop: 2017-02-03
dates: [2017-01-23, 2017-01-24, 2017-01-25, 2017-01-26, 2017-01-27, 2017-01-28, 2017-01-29, 2017-01-30, 2017-01-31, 2017-02-01, 2017-02-02]
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Not sure with joda-type but you could iterate at the interval (second, minute, hour, day, month, year) with Calendar API, here & here are examples
Evevntually went for this solution:
Range.inclusive(3, 0).map(i => LocalDate.now.minusDays(i)).foreach()
I'm currently trying to improve an aspect of a project of mine.
Users are allowed to do a specific task, but they must book a date in order to do it.
I'm trying to add some more realistic validation onto my date, so that the tasks can't be booked a year in advance, and only a few months.
Currently I'm only checking the year of the input and comparing it to the current year, so if they try to assign themselves a task on 31st of December, they will not be able to because any date they enter will roll over to the next year, and my validation prevents this.
How can I make it so it will check the amount of months, rather than the current year?
I am able to do this for the current year, I just get stuck when the year comes to december and the months roll into January again.
Edit:
Those looking for a way to fix this, go here: Calculating the difference between two Java date instances
Because the lengths of months are different, I would test the number of days. Here's a couple of utility methods that get the job done in one line:
// Tests if the end date is within so many days of the start date
public static boolean isWithinRange(int days, Date end, Date start) {
return TimeUnit.DAYS.convert(end.getTime() - start.getTime(), TimeUnit.MILLISECONDS) < days;
}
// Tests if the specified date is within so many days of today
public static boolean isWithinRange(int days, Date end) {
return isWithinRange(days, end, new Date());
}
Here I've used the TimeUnit class to do the calculation for me.
you can use your own method. Something like this
public boolean isLaterDay(Date date, Date reference) {
if (date.getYear () > reference.getYear ()) return true;
if (date.getYear () < reference.getYear ()) return false;
return (date.getMonth() > reference.getMonth());
}
Another way of doing this would be as follows.
boolean validDate(Calendar inputDate)
{
Calendar validationDate = Calendar.getInstance().add(Calendar.MONTH, numOfMonths);
return inputDate.before(validationDate);
}
You can do something like this to validate the time
private static final int MAX_MONTHS_IN_ADVANCE = 3;
public boolean isValidDate(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.MONTH, MAX_MONTHS_IN_ADVANCE);
return date.before(calendar.getTime());
}
Using the Joda-Time library:
If ( dateTimeInQuestion.isBefore( DateTime.now().plusMonths(3) )
java.time
The modern way to do this work is with the java.time classes. These classes supplant the troublesome old legacy date-time classes.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
ZoneId z = ZoneId.of( “America/Montreal” );
LocalDate today = LocalDate.now( z );
Construct the date desired by the user.
LocalDate ld = LocalDate.of( 2016 , 12 , 31 );
Determine the boundaries, say six months ago and six months from now.
LocalDate past = today.minusMonths( 6 );
LocalDate future = today.plusMonths( 6 );
You can compare LocalDate objects with isBefore, isAfter, equals, and compareTo.
Let's test by asking if the user's date is equal to or later than the before boundary (in other words, not before) AND the user's date is before the future boundary. This comparison uses the Half-Open approach commonly used with date-time work. The beginning is inclusive while the ending is exclusive.
Boolean validDate = ( ( ! ld.isBefore( past) ) && ( ld.isBefore( future) ) );
Interval
If you often work with the spans of time, consider using the Interval class found in the ThreeTen-Extra project that adds onto the java.time classes. That class has handy methods such as contains, abuts, overlaps, and more.
I have a date and a number and want to check if this date and this number occurs in a list of other dates within:
+-20 date intervall with the same number
so for example 1, 1.1.2013 and 1,3.1.2013 should reuturn false.
I tried to implement the method something like that:
private List<EventDate> dayIntervall(List<EventDate> eventList) throws Exception {
List<EventDate> resultList = new ArrayList<EventDate>();
for (int i = 0; i < eventList.size(); i++) {
String string = eventList.get(i).getDate();
Date equalDate = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN).parse(string);
for (int j = 0; j < eventList.size(); j++) {
String string1 = eventList.get(i).getDate();
Date otherDate = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN).parse(string1);
if (check number of i with number of j && check Date) {
//do magic
}
}
}
return resultList;
}
The construction of the iteration method is not that hard. What is hard for me is the date intervall checking part. I tried it like that:
boolean isWithinRange(Date testDate, Date days) {
return !(testDate.before(days) || testDate.after(days));
}
However that does not work because days are not takes as days. Any suggestions on how to fix that?
I really appreciate your answer!
You question is difficult to follow. But given its title, perhaps this will help…
Span Of Time In Joda-Time
The Joda-Time library provides a trio of classes to represent a span of time: Interval, Period, and Duration.
Interval
An Interval object has specific endpoints that lie on the timeline of the Universe. A handy contains method tells if a DateTime object occurs within those endpoints. The beginning endpoint in inclusive while the last endpoint is exclusive.
Time Zones
Note that time zones are important, for handling Daylight Saving Time and other anomalies, and for handling start-of-day. Keep in mind that while a java.util.Date seems like it has a time zone but does not, a DateTime truly does know its own time zone.
Sample Code
Some code off the top of my head (untested)…
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Berlin" );
DateTime dateTime = new DateTime( yourDateGoesHere, timeZone );
Interval interval = new Interval( dateTime.minusDays( 20 ), dateTime.plusDays( 20 ) );
boolean didEventOccurDuringInterval = interval.contains( someOtherDateTime );
Whole Days
If you want whole days, call the withTimeAtStartOfDay method to get first moment of the day. In this case, you probably need to add 21 rather than 20 days for the ending point. As I said above, the end point is exclusive. So if you want whole days, you need the first moment after the time period you care about. You need the moment after the stroke of midnight. If this does not make sense, see my answers to other questions here and here.
Note that Joda-Time includes some "midnight"-related methods and classes. Those are no longer recommended by the Joda team. The "withTimeAtStartOfDay" method takes their place.
DateTime start = dateTime.minusDays( 20 ).withTimeAtStartOfDay();
DateTime stop = dateTime.plusDays( 21 ).withTimeAtStartOfDay(); // 21, not 20, for whole days.
Interval interval = new Interval( start, stop );
You should avoid java.util.Date if at all possible. Using the backport of ThreeTen (the long awaited replacement date/time API coming in JDK8), you can get the number of days between two dates like so:
int daysBetween(LocalDate start, LocalDate end) {
return Math.abs(start.periodUntil(end).getDays());
}
Does that help?
You can get the number of dates in between the 2 dates and compare with your days parameter. Using Joda-Time API it is relatively an easy task: How do I calculate the difference between two dates?.
Code:
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);
Date startDate = format.parse("1.1.2013");
Date endDate = format.parse("3.1.2013");
Days d = Days.daysBetween(new DateTime(startDate), new DateTime(endDate));
System.out.println(d.getDays());
Gives,
2
This is possible using Calendar class as well:
Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
System.out.println(cal.fieldDifference(endDate, Calendar.DAY_OF_YEAR));
Gives,
2
This 2 can now be compared to your actual value (20).
I am working on an app where I store some information between each use, this data essentially boils down to counting the number of times an event has happened today, this week, this month and in the apps lifetime. I store this data in 4 distinct counters I can load/save using SharedPreferences.
Alongside the data I store the "last run time" of the app as a date, my plan was that during load time I will load in the counters then test the stored date against today's date to determine which counters need to be cleared.
Sounds simple right!
After pulling my hair out for a while and going backward and forwards through the Calendar documentation I think I understand them enough to come up with the following:
Calendar last = Calendar.getInstance();
last.setTimeInMillis(lastDate);
Calendar today = Calendar.getInstance();
today.add(Calendar.DATE, -1);
if ( !last.after(today) )
{
today = 0;
}
today.add(Calendar.WEEK_OF_MONTH, -1);
today.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
if ( !last.after(today) )
{
today = 0;
week = 0;
}
today = Calendar.getInstance();
today.add(Calendar.MONTH, -1);
today.set(Calendar.DATE, today.getActualMaximum(Calendar.DATE));
if ( !last.after(today) )
{
today = 0;
week = 0;
month = 0;
}
I think this should be fine, however the issue I have is testing, testing today is easy, however testing the month logic would require either waiting a month, or writing a test case which uses the Calendar API to simulate an old date, however I can't write the test case if my assumptions on how the API works was wrong in the first place!
Therefore, after a large wall of text my question is... does the above block of code look sane, or have I completely mis-understood working with dates in Java?
Thanks!
Edit:
Second pass at the code:
Does this look any more sensible? If I am understanding things correctly I am now attempting to compare the end of the date that was last saved with the very start of today, this week and this month.
Calendar last = Calendar.getInstance();
last.setTimeInMillis(lastDate);
last.set(Calendar.HOUR_OF_DAY, last.getActualMaximum(Calendar.HOUR_OF_DAY));
last.set(Calendar.MINUTE, last.getActualMaximum(Calendar.MINUTE));
last.set(Calendar.SECOND, last.getActualMaximum(Calendar.SECOND));
last.set(Calendar.MILLISECOND, last.getActualMaximum(Calendar.MILLISECOND));
Calendar todayStart = Calendar.getInstance();
todayStart.set(Calendar.HOUR_OF_DAY, todayStart.getActualMinimum(Calendar.HOUR_OF_DAY));
todayStart.set(Calendar.MINUTE, todayStart.getActualMinimum(Calendar.MINUTE));
todayStart.set(Calendar.SECOND, todayStart.getActualMinimum(Calendar.SECOND));
todayStart.set(Calendar.MILLISECOND, todayStart.getActualMinimum(Calendar.MILLISECOND));
// If the last recorded date was before the absolute minimum of today
if ( last.before(todayStart) )
{
todayCount = 0;
}
Calendar thisWeekStart = Calendar.getInstance();
thisWeekStart.set(Calendar.HOUR_OF_DAY, thisWeekStart.getActualMinimum(Calendar.HOUR_OF_DAY));
thisWeekStart.set(Calendar.MINUTE, thisWeekStart.getActualMinimum(Calendar.MINUTE));
thisWeekStart.set(Calendar.SECOND, thisWeekStart.getActualMinimum(Calendar.SECOND));
thisWeekStart.set(Calendar.DAY_OF_WEEK, thisWeekStart.getFirstDayOfWeek());
thisWeekStart.set(Calendar.MILLISECOND, thisWeekStart.getActualMinimum(Calendar.MILLISECOND));
// If the last date was before the absolute minimum of this week then clear
// this week (and today, just to be on the safe side)
if ( last.before(thisWeekStart) )
{
todayCount = 0;
weekCount = 0;
}
Calendar thisMonthStart = Calendar.getInstance();
thisMonthStart.set(Calendar.HOUR_OF_DAY, thisMonthStart.getActualMinimum(Calendar.HOUR_OF_DAY));
thisMonthStart.set(Calendar.MINUTE, thisMonthStart.getActualMinimum(Calendar.MINUTE));
thisMonthStart.set(Calendar.SECOND, thisMonthStart.getActualMinimum(Calendar.SECOND));
thisMonthStart.set(Calendar.DAY_OF_MONTH, thisMonthStart.getActualMinimum(Calendar.MONTH));
thisMonthStart.set(Calendar.MILLISECOND, thisMonthStart.getActualMinimum(Calendar.MILLISECOND));
// If the last date was before the absolute minimum of this month then clear month...
if ( !last.after(thisMonthStart) )
{
todayCount = 0;
weekCount = 0;
monthCount = 0;
}
Other than the readability challenges of using a variable called "today" and setting it to all manner of things that aren't "Today", you're not handling the time.
If it's now 3:20, and something happened at 5:00pm on Jan 31st, we probably want to still count that as happening in January? You should max out the time related fields to the end of the day as well.
For the week thing, that can be a real mess if someone executes in a locale where Sunday is considered the first day of the week. You may want to consider using the system's first day of week, rather than Sunday.
Also it is probably worth noting that this depends explicitly on the use of Calendar.add() to work properly. cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) -1); is NOT the same thing and would be broken.
You should just use Joda-Time. If you do your code becomes:
DateTime oneMonthAgo = new DateTime().minusMonths(1);
DateTime oneWeekAgo = new DateTime().minusWeeks(1);
And so on... It requires no further dependencies than the JDK itself and works on Android. Hope that helps.
Yes, you can use Joda-Time on Android. (From what I've read; I don't use Android)
Yes, you should be using Joda-Time. Far more advanced and useful that the notoriously troublesome java.util.Date and .Calendar classes bundled with Java.
Both your question and the other answers are ignoring the crucial issue of time zone. The time zone defines the meaning of "today" and the beginning/ending of other days.
You should define in plain declarative sentences exactly what you mean by "today", "this week", and "this month". For example, "today"…
Do you mean the last 24 hours?
Do you mean from 00:00:00 and up to but not including 00:00:00 tomorrow, in the UTC/GMT time zone (that is, no time zone offset)?
Do mean from the first moment of today in a given time zone (some offset from UTC) up to but not including the first moment of tomorrow in the same time zone? This may not be 24 hours because of Daylight Saving Time (DST) or other anomalies.
I'm too tired to parse your code. And I shouldn't have to. Before writing such date-time code, you should spell out in plain English what your goal is. Date-time work is surprisingly tricky, so you must be clear on your goals.
Here's some example code in Joda-Time 2.3.
Joda-Time uses the ISO 8601 standard for most defaults. This includes the definition of a week. Monday is first day, numbered 1, and Sunday is last day, numbered 7.
When focusing on a "day" with date-time objects, you may want to start with the first moment of the day. If so, call the withTimeAtStartOfDay method. To get end-of-day, don't. Use the Half-Open approach where you compare up to but not including the first moment of the next day. Explanation is found in other answers on StackOverflow.
Joda-Time offers 3 classes to handle spans of time: Period, Duration, and Interval. Check them all out. When doing comparisons, Joda-Time uses the "Half-Open" approach where the beginning is inclusive and the ending is exclusive. This makes sense when you ponder it. Search StackOverflow for more discussion.
Here's a bit of example code to get you going. I take a set of arbitrary date-time values. Then I define some spans of time as a day, week ago, and month ago. Then I count how many of the values fall into those spans.
String input = "2014-01-02T03:04:05Z";
DateTimeZone timeZone = DateTimeZone.forID( "America/Montreal" );
java.util.List<DateTime> dateTimes = new java.util.ArrayList<DateTime>();
DateTime dateTime1 = new DateTime( input, timeZone ); // Parse the string as being in Zulu time zone (UTC). Then adjust to Montréal time.
dateTimes.add( dateTime1 );
dateTimes.add( dateTime1.plusDays( 3 ) );
dateTimes.add( dateTime1.plusWeeks( 1 ) );
dateTimes.add( dateTime1.plusMonths( 1 ) );
DateTime now = new DateTime( timeZone );
dateTimes.add( now );
dateTimes.add( now.minusDays( 1 ) );
dateTimes.add( now.minusDays( 10 ) );
// Spans of time
Interval today = new Interval( now.withTimeAtStartOfDay(), now.plusDays( 1 ).withTimeAtStartOfDay() );
Interval pastWeek = new Interval( now.minusWeeks( 1 ).withTimeAtStartOfDay(), now.plusDays( 1 ).withTimeAtStartOfDay() );
Interval pastMonth = new Interval( now.minusMonths( 1 ).withTimeAtStartOfDay(), now.plusDays( 1 ).withTimeAtStartOfDay() );
int countTotal = dateTimes.size();
int countDay = 0;
int countWeek = 0;
int countMonth = 0;
for ( DateTime dateTime : dateTimes ) {
if ( today.contains( dateTime ) ) {
countDay++;
}
if ( pastWeek.contains( dateTime ) ) {
countWeek++;
}
if ( pastMonth.contains( dateTime ) ) {
countMonth++;
}
}
Dump to console…
System.out.println( "dateTimes: " + dateTimes );
System.out.println( "today: " + today );
System.out.println( "pastWeek: " + pastWeek );
System.out.println( "pastMonth: " + pastMonth );
System.out.println( "countTotal: " + countTotal );
System.out.println( "countDay: " + countDay );
System.out.println( "countWeek: " + countWeek );
System.out.println( "countMonth: " + countMonth );
When run…
dateTimes: [2014-01-01T22:04:05.000-05:00, 2014-01-04T22:04:05.000-05:00, 2014-01-08T22:04:05.000-05:00, 2014-02-01T22:04:05.000-05:00, 2014-03-05T07:40:25.508-05:00, 2014-03-04T07:40:25.508-05:00, 2014-02-23T07:40:25.508-05:00]
today: 2014-03-05T00:00:00.000-05:00/2014-03-06T00:00:00.000-05:00
pastWeek: 2014-02-26T00:00:00.000-05:00/2014-03-06T00:00:00.000-05:00
pastMonth: 2014-02-05T00:00:00.000-05:00/2014-03-06T00:00:00.000-05:00
countTotal: 7
countDay: 1
countWeek: 2
countMonth: 3