May I know what is the most efficient way to construct a date object using a specific day, month, and year.
Date(int year, int month, int day)
This construct is depreciated. Hence, what I usually do is:
Calendar calendar = Calendar.getInstance();
Date date = calendar.set(year, month, date).getTime();
However, my understanding is that Calendar.getInstance() is rather expensive. What is the most efficient way to construct a Date object? Or should I just use Date(int year, int month, int day) quietly without telling the rest?
Please don't suggest using any third-party library.
With this you can avoid the innecesary "now time" instance creation.
Date coolDate = new GregorianCalendar(year, month, day).getTime();
You can check GregorianCalendar javadoc for other constructors. You have date+time, and timezone.
Anyway I agree with Jon Skeet that it's not so expensive. I agree with you that code doesn't need a default "now" initialization.
"Rather expensive" is somewhat vague. Have you actually tried using the code you've supplied, measured it and found it to be too expensive? Do you have a concrete idea of how cheap you need this operation to be?
Also, you haven't specified which time zone you want the value in the Date to represent. UTC? The default time zone? What time of day do you want it to be - midnight, or the current time of day? Your current code will keep the existing time of day - is that really what you want?
(As I mentioned in a comment, I would strongly suggest you move to Joda Time - but even if you don't, you should still check whether or not you've actually got a problem with your existing code before looking for a solution.)
I would simply do this:
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
Date thisDate = formatter.parse("2010-03-04");
It's pretty efficient from a lines of code standpoint; I can't speak to its runtime efficiency vis a vis Calendar.
Ten years later in 2020: the only right answer is to use classes in java.util.time package.
LocalDate localDate = LocalDate.now();
LocalDate.of(2020, 3, 7);
LocalDate.parse("2020-03-07");
Whatever you use, as long as it's in the Java Standard API, it will involve the use of Calendar (both the Date constructor and SimpleDateFormat use it internally), so there's no point fretting about that class's supposed inefficiency.
Related
I should preface this with I use Apache Spark, which uses java.sql.Date, in case anyone suggests I should use dates from java.time. The example below is in Scala.
The API that I use (which is deprecated) to get the month for a date is as follows:
val date: java.sql.Date = ???
val month = date.getMonth()
However if I look at how it appears I should do this based on the deprecation, the above code would be re-written as follows:
val date: java.sql.Date = ???
val cal = Calendar.getInstance()
cal.setTime(date)
cal.get(Calendar.MONTH)
The simplicity and readability of the code is significantly different, and the date being a side effect on the calendar is not terribly nice from a functional programming point of view. Can someone explain why they think this change was made?
Prior to JDK 1.1, the class Date had two additional functions. It
allowed the interpretation of dates as year, month, day, hour, minute,
and second values. It also allowed the formatting and parsing of date
strings. Unfortunately, the API for these functions was not amenable
to internationalization. As of JDK 1.1, the Calendar class should be
used to convert between dates and time fields and the DateFormat class
should be used to format and parse date strings. The corresponding
methods in Date are deprecated.
The JavaDoc explains. Internationalization.
"in case anyone suggests I should use dates from java.time"
There is nothing to stop you from converting to java.time classes as soon as possible, performing whatever calculations/modifications you need and, if you need to re-insert, converting back to java.sql.Date again.
val date: java.sql.Date = ???
val month = date.toLocalDate().getMonthValue()
You said it yourself, and I still think: You should use java.time, the modern Java date and time API. When you get an old-fashioned java.sql.Date from a legacy API not yet upgraded to java.time, convert it to a modern LocalDate and enjoy the natural code writing with java.time.
Why were getMonth() and the other getXxx methods deprecated?
While Michael has already answered the question with respect to java.util.Date, I have something to add when it comes to java.sql.Date. For this class the situation is quite a bit worse than what Michael reported.
What is left undeprecated (apprecated?) of java.util.Date after the deprecations is that a Date is a point in time. java.sql.Date on the other hand was never meant to be a point in time. One way to illustrate this fact is that its toInstant method — which should convert it to an Instant, a point in time — unconditionally throws an UnsupportedOperationException. A java.sql.Date was meant to be a calendar date to be used with an SQL database and its date datatype, which in most cases is also a date, defined by year, month and day of month. Since a Date is no longer year, month and day of month, they have virtually deprecated everything that a java.sql.Date was supposed to be. And they didn’t give us a replacement until with JDBC 4.2 we can exchange LocalDate objects with SQL databases.
The observations that lead to deprecation have got very practical consequences. Let’s try this (in Java because it is what I can write):
void foo(java.sql.Date sqlDate) {
System.out.println(sqlDate);
TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("Pacific/Samoa")));
System.out.println(sqlDate.getMonth());
}
In one call the method printed:
2020-11-02
9
So we had the 2nd day of the 11th month, and month prints as 9? There are two things going on:
Confusingly the month number that getMonth() returns is 0-based, so 9 means October.
The Date is internally represented as a count of milliseconds since the epoch to the start of the day in the default time zone of the JVM. 2020-11-02 00:00:00 in my original time zone (set to Pacific/Kiritimati for this demonstration) is the same point in time as 2020-10-31 23:00:00 in Samoa. Therefore we get October.
You don’t have to change the time zone yourself for this to happen. Situations where it can happen include:
The default time zone of the JVM can be changed from any part of your program and from any other program running in the same JVM.
The date may be serialized in a program running in one JVM and deserialized in a different JVM with a different time zone setting.
BTW the first snippet I presented at the top often won’t help against unexpected results in these situations. If things go off track before you convert from java.sql.Date to LocalDate, the conversion too will give you the wrong date. If you can make it, convert to LocalDate before anyone messes with the JVM time zone setting and be on the safe side.
I am working on a project in my CIS 163 class that is effectively a campsite reservation system. The bulk of the code was provided and I just have to add certain functionalities to it. Currently I need to be able to determine how much time has passed between 2 different GregorianCalendar instances (one being the current date, the other being a predetermined "check out") represented by days. I haven't been able to figure out quite how to do this, and was hoping someone here might be able to help me out.
The GregorianCalendar is old and you shouldn't really use it anymore. It was cumbersome and was replaced by the "new" java.time module since Java 8.
Still, if you need to compare using GC instances, you could easily calculate time using milliseconds between dates, like this:
GregorianCalendar date1 = new GregorianCalendar();
GregorianCalendar date2 = new GregorianCalendar();
// Adding 15 days after the first date
date2.add(GregorianCalendar.DAY_OF_MONTH, 15);
long duration = (date2.getTimeInMillis() - date1.getTimeInMillis() )
/ ( 1000 * 60 * 60 * 24) ;
System.out.println(duration);
If you want to use the new Time API, the following code would work.
LocalDate date1 = LocalDate.now();
LocalDate date2 = date1.plusDays(15);
Period period = Period.between(date1, date2);
int diff = period.getDays();
System.out.println(diff);
If you need to convert between the types (e.g. you're working with legacy code), you can do it like this:
LocalDate date3 = gcDate1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate date4 = gcDate2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
Also I'm pretty sure this question must've been asked over and over again, so make sure you search properly before asking.
Since you have been forced to use the old and poorly designed GregorianCalendar class, the first thing you should do is convert each of the two GregorianCalendar objects to a modern type. Since Java 8 GregorianCalendar has a method that converts it to ZonedDateTime. Check the documentation, I include a link below.
Now that you’ve got two ZonedDateTime objects, there are different paths depending on your exact requirements. Often one will use Duration.between() for finding the duration, the amount of time between them in hours, minutes, seconds and fraction of second. If you know that you will always need just one of those time units, you may instead use for example ChronoUnit.HOURS.between() or ChronoUnit.MILLISECONDS.between(). If you need to count days, use ChronoUnit.DAYS.between().
If instead you need the time in months and days, you should instead use Period.between().
Links
Documentation:
GregorianCalendar (long outdated, don’t use unless forced to)
Duration
ChronoUnit
Period
Oracle tutorial: Date Time explaining how to use java.time, the modern Java date and time API to which ZonedDateTIme, Duration, ChronoUnit and Period belong.
I mentioned that one of the method in the production project work wrong with dates, but i can't just replace it, because it is in production for a long time. I've created a new method, that works correct, but i can't figure out why the first method work wrong.
Old method (that works wrong):
public static Integer getNumberOfDays(Date startDate, Date endDate) {
TimeZone.setDefault((TimeZone.getTimeZone("Europe/Moscow")));
startDate.setHours(00);
startDate.setMinutes(00);
startDate.setSeconds(00);
endDate.setHours(23);
endDate.setMinutes(59);
endDate.setSeconds(59);
Calendar cal1 = Calendar.getInstance();
cal1.setTime(startDate);
Calendar cal2 = Calendar.getInstance();
cal2.setTime(endDate);
Calendar date = (Calendar) cal1.clone();
int daysBetween = 0;
while (date.before(cal2)){
date.add(Calendar.DAY_OF_MONTH, 1);
daysBetween++;
}
return daysBetween;
}
New method:
public static Integer getNumberOfDaysSecondVersion(Date startDate, Date endDate) {
long difference = startDate.getTime() - endDate.getTime();
float daysBetween = (difference / (1000*60*60*24));
return (int) daysBetween > 0 ? (int) daysBetween : 0;
}
Here is how i call both:
DateFormat formated = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(Calculation.getNumberOfDays(
formated.parse("2018-06-14"),
formated.parse("2018-06-06")
));
System.out.println(Calculation.getNumberOfDaysSecondVersion(
format.parse("2018-06-14"),
format.parse("2018-06-06"))
);
Output:
0
8
Please help.
Your old method is the correct one. When start date is after end date, it returns 0. This is the case in your call.
Your new method subtracts end date from start date, which is wrong, is should be the other way around. I also suspect that it will give surprises across transitions from and to summer time (DST). While Moscow currently doesn’t use summer time, it has done historically, at least until 2010, and may do again if politicians decide so.
That said, you should try if you can avoid the old and long outdated date and time classes DateFormat, SimpleDateFormat, Calendar, Date and TimeZone. Today we have so much better in java.time, the modern Java date and time API. Of course, in legacy code you have old-fashioned Date objects. When writing a new method, I recommend you convert those to the modern LocalDate and use ChronoUnit.DAYS.between().
ChronoUnit.DAYS.between(
LocalDate.parse( "2018-06-14" ) ,
LocalDate.parse( "2018-06-06" )
)
-8
Be aware that when the old method sets the default time zone, it affects all programs running in your JVM and may come as a nasty surprise to other parts of your program and to other programs.
You used a very different algorithm for the two versions.
The old version keeps adding days to the start date until it is after the end date.
The new version subtracts the end date from the start date and divides it by the number of milliseconds there are in a day.
This means that for the first version to work, the start date must be before the end date, and for the second version to work, the start date must be after the end date. The parameters you gave the the first version has the start date after the end date, making it return 0.
To fix this, you can just reverse the two arguments:
System.out.println(getNumberOfDays(
formated.parse("2018-06-06"),
formated.parse("2018-06-14")
));
Or, check which date comes first before calculating the difference between them.
By the way, your first version seems to output one more than your second version. You seem to want a result of 8 days. This means that your first version has an off-by-1 error. You can fix this by subtracting 1 from the counted result.
Remember to always work with java.time whenever you can!
Probably because startDate and endDate's timezones aren't affected by setting the default timezone, so that when you set Calendar times (in Moscow time) based on them, you're converting timezones, possibly turning 00:00:00 into the previous day 21:00:00 or something.
EDIT
Seeing your outputs, it became obvious... you're passing in a start date that is in the future compared to end date. The original method uses a loop that can only count up, while your new method takes the absolute value of the difference.
Is there any way to update only a Date's time path?
I tried Date.setTime() but it replaces the date path too. I there any java method or the only way is to set hour, minute, second and milisecond?
Thank you
A Java Date is just a wrapper around a long that counts time from the epoch (January 1, 1970). Much more flexible is Calendar. You can create a Calendar from a Date:
Date date = . . .;
Calendar cal = new GregorianCalendar();
cal.setTime(date);
Then you can set various fields of the Calendar:
cal.set(Calendar.HOUR_OF_DAY, 8);
// etc.
I would start by moving away from java.util.Date entirely. Ideally, use Joda Time as it's a far more capable date/time library.
Otherwise, you should use java.util.Calendar. A java.util.Date doesn't have a particular date/time until you decide what time zone you're interested in - it just represents an instant in time, which different people around the world will consider to be a different date and time of day.
You'll want to take a look at java.util.Calender.
It will allow you to change the individual parts of the date/time.
Calendar cal = Calender.getInstance();
cal.setTime(date);
cal.set(Calender.HOUR, hour);
Alternatively, as has already being suggested, I'd take a look at Joda Time
This question already has answers here:
How can I get the current date and time in UTC or GMT in Java?
(33 answers)
Closed 6 years ago.
Since java.util.Date is mostly deprecated, what's the right way to get a timestamp for a given date, UTC time? The one that could be compared against System.currentTimeMillis().
Using the Calendar class you can create a time stamp at a specific time in a specific timezone. Once you have that, you can then get the millisecond time stamp to compare with:
Calendar cal = new GregorianCalendar();
cal.set(Calendar.DAY_OF_MONTH, 10);
// etc...
if (System.currentTimeMillis() < cal.getTimeInMillis()) {
// do your stuff
}
edit: changed to use more direct method to get time in milliseconds from Calendar instance. Thanks Outlaw Programmer
The "official" replacement for many things Date was used for is Calendar. Unfortunately it is rather clumsy and over-engineered. Your problem can be solved like this:
long currentMillis = System.currentTimeMillis();
Date date = new Date(currentMillis);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
long calendarMillis = calendar.getTimeInMillis();
assert currentMillis == calendarMillis;
Calendars can also be initialized in different ways, even one field at a time (hour, minute, second, etc.). Have a look at the Javadoc.
Although I didn't try it myself, I believe you should take a look at the JODA-Time project (open source) if your project allows external libs.
AFAIK, JODA time has contributed a lot to a new JSR (normally in Java7) on date/time.
Many people claim JODA time is the solution to all java.util.Date/Calendar problems;-)
Definitely worth a try.
Take a look at the Calendar class.
You can make a Data with the current time (not deprecated) and then make Calendar from that (there are other ways too).