Subtracting one day off Calendar doesn't work - java

I am trying to build a simple program with java.util.Calendar. When trying to get the weekday before, my output always stays the same. Code and what I tried below:
Calendar c = Calendar.getInstance();
c.set(Calendar.MONTH, month);
c.set(Calendar.DAY_OF_MONTH, day);
c.set(Calendar.YEAR, year);
int date = c.get(Calendar.DAY_OF_WEEK);
return new SimpleDateFormat("EEEE").format(date).toUpperCase();
This was my code at the start. I have tried subtracting day by one:
day = day-1;
I have tried adding minus one to both the Day of Month and Day of week field:
c.add(Calendar.DAY_OF_WEEK, -1);
c.add(Calendar.DAY_OF_MONTH, -1);
I think that it has something to do with my SimpleDateFormat, but I am not sure.

I am trying to build a simple program with java.util.Calendar.
That is impossible. Specifically, the 'simple program' part. Nothing that uses Calendar is simple.
The calendar API is horrible; it makes no sense (the first month of the year is... 0, to change values, you have to use int constants, which isn't idiomatic java), and is confused about what it is trying to represent (it's not a calendar, it's a date/time value, or, is it? Is it solarflares time, appointment time, or alarmclock time? It's confused and doesn't know). That's why there is a new API: java.time. Java does not remove stuff even if it is obsolete, because that would break old code. So, the fact that Calendar is still around doesn't mean much.
Use java.time.
I think that it has something to do with my SimpleDateFormat, but I am not sure.
It doesn't. But let's forget about this silly API and use java time instead!
// note that in calendar, january is 0, and that is insane.
// in localdate, it is 1, which is sane.
// thus, assuming you have `month = month - 1;` someplace in your code...
// remove that.
LocalDate date = LocalDate.of(year, month, day);
DayOfWeek day = date.getDayOfWeek();
System.out.println(day);
Wanna go back a day? Okay.
DayOfWeek day = date.minusDays(1).getDayOfWeek();
System.out.println(day);

The reason this fails is that you aren't invoking the method you think you are. There is no SimpleDateFormat#format(int). But SimpleDateFormat extends Format, which declares Format#format(Object). Your int is boxed to Integer and then the relevant code in Format#format(Object) is
if (obj instanceof Date)
return format( (Date)obj, toAppendTo, fieldPosition );
else if (obj instanceof Number)
return format( new Date(((Number)obj).longValue()),
toAppendTo, fieldPosition );
else
throw new IllegalArgumentException("Cannot format given Object as a Date");
At this point, since the value you passed is boxed to Integer which extends Number, the second branch of the if-else is taken and your day-of-week value is converted to a date via the constructor. The number is interpreted as a millisecond value and when you subtracted 1 you changed the time by 1 millisecond, not 1 day.
All that said, you should NOT be using Calendar, use the new date/time API in the java.time package.

Related

Calendar behavior is not compatible with LocalDate?

Hi buddies I'm in a trouble trying to migrate a behavior from calendar to localdate.
payDate.set(Calendar.DAY_OF_MONTH,payDay)
Lets imagine that payDate had the current date, 2020-01-29
for business reasons payDay can had the value of 0, so, when the previous code line is executed with the previous scenario, the result is that payDate update the date to 2019-12-31,
that is to say the the date back to the last day of the past month.
I'm not sure, the technical reason of this, if someone can explain to me this I'll be so thankful, I tried checking the java doc but it was not helpful.
So I need to replicate that behavior with LocalDate java library. From my point of view; the similar of set method from Calendar with the value of DAY_OF_MONTH in LocalDate is:
payDate.withDayOfMonth(payDay)
But when the below scenario is presented and payDay is equal to 0 I get an error:
java.time.DateTimeException: Invalid value for DayOfMonth (valid values 1 - 28/31): 0
Also I had some ideas about how can I get the same result of calendar in localDate when the rule comes on (if payDay is 0, return to the last day of previous month), but are too verbose.
If you know a similar behavior on LocalDate please help me. Thanks.
TL;DR: Use payDate = payDate.plusDays(payDay - payDate.getDayOfMonth());
The behavior of Calendar you're describing is documented in the javadoc:
Leniency
Calendar has two modes for interpreting the calendar fields, lenient and non-lenient. When a Calendar is in lenient mode, it accepts a wider range of calendar field values than it produces. When a Calendar recomputes calendar field values for return by get(), all of the calendar fields are normalized. For example, a lenient GregorianCalendar interprets MONTH == JANUARY, DAY_OF_MONTH == 32 as February 1.
When a Calendar is in non-lenient mode, it throws an exception if there is any inconsistency in its calendar fields. For example, a GregorianCalendar always produces DAY_OF_MONTH values between 1 and the length of the month. A non-lenient GregorianCalendar throws an exception upon calculating its time or calendar field values if any out-of-range field value has been set.
To show the effect of this, try setting the date of a Calendar to January 70, 2020:
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2020, Calendar.JANUARY, 70);
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime()));
Output
2020-03-10
You would get the same result if you did:
cal.set(2020, Calendar.JANUARY, 1);
cal.add(Calendar.DAY_OF_MONTH, 69);
LocalDate is always non-lenient, so you can't set the day-of-month value to a value that is out-of-range. You can however get the same result as what Calendar does, by changing the operation to "add" instead of "set".
So, if you have a particular date, e.g. the 2020-01-29 date mentioned in the question, and you want to "set" the day-of-month value to 70 or 0, with same lenient overflow logic as Calendar has, do this:
LocalDate date = LocalDate.parse("2020-01-29");
date = date.plusDays(70 - date.getDayOfMonth());
System.out.println(date);
LocalDate date = LocalDate.parse("2020-01-29");
date = date.plusDays(0 - date.getDayOfMonth());
System.out.println(date);
Output
2020-03-10
2019-12-31
As you can see, date.plusDays(dayToSet - date.getDayOfMonth()) will give you the desired result.
Here’s how I would go about it:
LocalDate payDate = LocalDate.now(); // or whatever
int payDay = 0;
if (payDay == 0) {
// simulate `GregorianCalendar` behaviour: day 0 is the day before day 1
payDate = payDate.withDayOfMonth(1).minusDays(1);
} else {
payDate = payDate.withDayOfMonth(payDay);
}
System.out.println(payDate);
When I ran the snippet just now, the output was the date you already mentioned:
2019-12-31
If we wanted it shorter, we could use payDate.withDayOfMonth(1).minusDays(1).plusDays(payDay) or the trick from Andreas’ answer, and we would not need the if statement. I would not, though. (1) It’s harder to read. (2) It doesn’t give the validation of payDay that comes for free in the snippet above.
The confusing behaviour of Calendar comes from not range checking the argument to set(). So day 0 of the month is the day before day 1 of the month. Day -1 would be the day before that, and so forth. It’s in this snippet from the documentation (or was supposed to be, at least):
When a Calendar is in lenient mode, it accepts a wider range of
calendar field values than it produces. When a Calendar recomputes
calendar field values for return by get(), all of the calendar
fields are normalized. For example, a lenient GregorianCalendar
interprets MONTH == JANUARY, DAY_OF_MONTH == 32 as February 1.
You may read it with this snippet from the documentation of the setLenient method:
The default is lenient.
Links
Documentation of Calendar
Documentation of Calendar.setLenient()
You're not going to be able to just invoke one method to achieve the same results. If you're sure that setting DAY_OF_MONTH to 0 should cause it to roll back one month (this is the type of thing I'd run past a business analyst or product owner for a sanity check) then you're going to have to do something like this:
int payDay = 0;
LocalDate payDate = LocalDate.of(2020, Month.JANUARY, 29);
if(payDay == 0) {
payDate = payDate.minusMonths(1);
payDay = payDate.lengthOfMonth();
}
payDate = payDate.withDayOfMonth(payDay);
Another approach:
int payDay = 0;
LocalDate payDate = LocalDate.of(2020, Month.JANUARY, 29);
if(payDay == 0) {
payDate = payDate.withDayOfMonth(1).minusDays(1);
} else {
payDate = payDate.withDayOfMonth(payDay);
}

How many instances of a partial date (month & day) appear in a range, and its day of the week

In Java, how would I go about constructing a utility that would take a range of dates (start and end date) and then would see how many times a given partial date ( the month and day-of-month) appears in that range, and will add an entry to a list for each match.
In my instance, I want to give it a range of say 5 years - starting Jan 1st 2014 and going to Dec 31st 2019. My check date is the 2nd August. I want the method to return the full information about each match of any August 2 of any year in the range. So for 2014 is will return Saturday 2nd August 2014, then Sunday 2nd August 2015 etc and so on.
I've been trying to get something working so far with Joda Time and the default date/calendar classes in Java and I'm just getting myself in a mess.
Thanks,
S
Edit: How silly of me, apologies for not adding my code :(
public static List<Date> getDaysInRange(Date startdate,
Date enddate,
Date checkDate) {
SimpleDateFormat sdf = new SimpleDateFormat("MMdd");
List<Date> dates = new ArrayList<>();
Calendar cal = new GregorianCalendar();
cal.setTime(startdate);
while (cal.getTime().before(enddate)) {
if (sdf.format(cal.getTime()).equals(sdf.format(checkDate))) {
Date result = cal.getTime();
dates.add(result);
}
cal.add(Calendar.DATE, 1);
}
return dates;
}
Date-Only
Since you want only a date without time-of-day and without time zone, use a date-only class. The old java.util.Date/.Calendar classes lack such a class. And those old classes are notoriously troublesome and flawed.
Instead use either:
Joda-Time
java.time, built into Java 8, inspired by Joda-Time.
Joda-Time
Here is some untested code using Joda-Time 2.6.
The main idea is to focus on the small set of possible year numbers rather than test every day of year. In the example below, that means six date-time values to compare rather than thousands. Besides efficiency, the purpose of the code becomes more apparent.
The arguments to your routine should be a month number and a day-of-month number, a pair of ints or Integers, rather than a Date. As seen in this examples two int variables, month and day.
LocalDate start = new LocalDate( 2011, 2, 3 );
LocalDate stop = new LocalDate( 2016, 4, 5 );
int yearStart = start.getYear();
int yearStop = stop.getYear();
int month = 11;
int day = 22;
for ( i = yearStart, i <= yearStop, i++ )
{
LocalDate x = new LocalDate( i, month, day );
boolean matchStart = ( x.isEqual( start ) || x.isAfter( start ) );
boolean matchStop = x.isBefore( stop ); // Half-Open approach where beginning of range is inclusive while ending is exclusive.
if ( matchStart && matchStop )
{
// Add to collection of LocalDate objects.
// Later you can ask each LocalDate object for its day-of-week.
{
}
java.time
The java.time package also offers a LocalDate class. The code would be similar to the above Joda-Time example.
I think using SimpleDateFormat is a bad idea. Use Calendar for comparison directly, like this
cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) && cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH)

Java - Check whether two Dates are Equal

I am trying to compare dates in two different formats:
Tue Jul 01 00:12:14 EST 2014
which is created using the function:
private Date getDate (int day, int month, int year){
Calendar calendar = Calendar.getInstance();
calendar.setLenient(false);
calendar.set(year, month-1, day);
Date date = calendar.getTime();
return date;
}
and
2014-07-01
After comparing these two dates, I would like the output to show that they are equal. However I BELIEVE, because of the timestamp in the 1st Date, they are not being determined as equal.
Is my assumption correct?
If so, is there a way that I could convert the first date into the second? The second Date is being retrieved from an SQL database where the variable is DATE.
Thank you for your help.
It sounds like you are comparing a java.util.Date (an instant in time) with a java.sql.Date (an instant in time whose time of day is midnight).
Arithmetic rounding must deal with the local timezones, making it more complex than you might first think.
The simplest way to compare the two would be to use a data formatter and compare the output:
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
if (f.format(date1).equals(f.format(date2))) {
// the two dates are on the same "day"
}
java.sql.Date Has Zero Time
The documentation explains that a java.sql.Date has its time portion set to zero (UTC), meaning midnight.
So when comparing to a java.util.Date with a non-zero time-of-day, the two will not be equal.
LocalDate
So much easier using Joda-Time of the new java.time package in Java 8. Both offer a LocalDate class that ignores time-of-day.
LocalDate x = new LocalDate( 2014, 5, 6 );
LocalDate y = new LocalDate( 2014, 5, 6 );
boolean same = x.equals( y );
To convert your java.sql.Date to a Joda-Time LocalDate, pass it to the constructor of New LocalDate. You may need to also pass DateTimeZone.UTC to be sure it is not interpreted by your JVM's default time zone.
Is my assumption correct?
Yes, your assumption is correct. Two Date instances are correct if both their getTime() results are the same
from Date.java
public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
Converting just assumes you need to set the hours,minutes and seconds to 0:
calendar.set(Calendar.HOUR_OF_DAY,0);
calendar.set(Calendar.MINUTE,0);
calendar.set(Calendar.SECOND,0);
Assuming that both dates are in the same timezone and also assuming that the date equality criteria here is the day of the year, I believe you can just compare the date as strings.
To do that, you can use SimpleDateFormat to ensure both are in the same format.
I would suggest you convert both of them to one particular format and compare them using a Comparator.
If you need to check whether two dates are equal, the best way is to use compareTo method.
if(yesterday.compareTo(today) == 0) {
System.out.println("Given dates are same");
} else {
System.out.println("Given dates are different ");
}
Read more: https://www.java67.com/2016/09/how-to-compare-two-dates-in-java.html#ixzz6uH5r1xE2

What is the proper way to remove the time part from java.util.Date? [duplicate]

This question already has answers here:
Java Date cut off time information
(20 answers)
Closed 8 years ago.
I want to implement a thread-safe function to remove the time part from java.util.Date.
I tried this way
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
public static Date removeTimeFromDate(Date date) {
Date returnDate = date;
if (date == null) {
return returnDate;
}
//just have the date remove the time
String targetDateStr = df.format(date);
try {
returnDate = df.parse(targetDateStr);
} catch (ParseException e) {
}
return returnDate;
}
and use synchronized or threadLocal to make it thread-safe.
But it there any better way to implement it in Java. It seems this way is a bit verbose.
I am not satisfied with it.
A Date object holds a variable wich represents the time as the number of milliseconds since epoch. So, you can't "remove" the time part. What you can do is set the time of that day to zero, which means it will be 00:00:00 000 of that day. This is done by using a GregorianCalendar:
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(date);
gc.set(Calendar.HOUR_OF_DAY, 0);
gc.set(Calendar.MINUTE, 0);
gc.set(Calendar.SECOND, 0);
gc.set(Calendar.MILLISECOND, 0);
Date returnDate = gc.getTime();
A Date holds an instant in time - that means it doesn't unambiguously specify a particular date. So you need to specify a time zone as well, in order to work out what date something falls on. You then need to work out how you want to represent the result - as a Date with a value of "midnight on that date in UTC" for example?
You should also note that midnight itself doesn't occur on all days in all time zones, due to DST transitions which can occur at midnight. (Brazil is a common example of this.)
Unless you're really wedded to Date and Calendar, I'd recommend that you start using Joda Time instead, as that allows you to have a value of type LocalDate which gets rid of most of these problems.

Getting Day from a Date Object

I wanna see if someDate has any day in it. Am I checking it right?
Calendar cal = Calendar.getInstance();
cal.setTime(someDate); // someDate is a Date
int day = cal.get(Calendar.DAY_OF_MONTH);
if(day == 0){
// code //
}
I'm not sure what you mean by "has any day in it" - all Dates will have a day in them... :-)
Other than that, you probably want the following:
Calendar cal = Calendar.getInstance();
cal.setTime(someDate); // someDate is a Date
int day = cal.get(Calendar.DAY_OF_WEEK);
if(day == Calendar.SUNDAY){
// code //
}
The big change is that you want to get the DAY_OF_WEEK field; what your example does is gets the day within the month (e.g. September 15th would return "15"). Secondly, comparing with Calendar.SUNDAY (or equivalent) is clearer and less error-prone that directly comparing with e.g. 0, even if the code is equivalent.
Every date object will have a day. The day of the month is never going to be 0 though, it will be in the range 1-31. Meaning that your check will always fail.
If I understand correctly you want Calendar.DAY_OF_WEEK.
The answer by Andrzej Doyle is correct.
Just for the heck of it, here is that same kind of code but using the Joda-Time 2.3 library and Java 7.
Unlike java.util.Calendar, Joda-Time sensibly uses 1-based counting. So the days of the week are numbered 1 to 7. Furthermore, Joda-Time uses the standard (ISO 8601) approach where Monday is the first day of the week (1).
// © 2013 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
// import org.joda.time.*;
// import org.joda.time.format.*;
// Specify time zone rather than rely on default.
// Time Zone list… http://joda-time.sourceforge.net/timezones.html (not quite up-to-date, read page for details)
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );
DateTime now = new DateTime( timeZone );
if( now.dayOfWeek().get() == DateTimeConstants.MONDAY ) {
System.out.println( "Today is a Monday." );
} else {
System.out.println( "Nope, today is some other day of week." );
}

Categories

Resources