Why was getMonth deprecated on java.sql.Date and java.util.Date - java

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.

Related

Reset Timestamp in Java 8

What is the best way to write the following code snippet in Java 8?
private Timestamp resetTime(Timestamp ts) {
ts.setHours(0);
ts.setMinutes(0);
ts.setSeconds(0);
return ts;
}
I was going to use the Calendar class but then read that it is advisable not to do so in Java 8. Any help would be greatly appreciated. Thanks.
Your code seems to try to adjust the Timestamp object to the start of the day in the default time zone (it doesn’t in all cases do that to perfection).
In the old days we used Timestamp to transfer a value to an SQL timestamp with or without time zone. The latter is somewhat self-contradictory: a time stamp is supposed to define a point in time, but a date and time of day without time zone or UTC offset doesn’t do that. So let’s first assume that you want a value to transfer to an SQL database that needs a timestamp with time zone. The type to use for that in Java 8 (assuming JDBC 4.2 or later driver) is OffsetDateTime (some JDBC drivers also accept Instant). Since the databases I know of always use UTC as time zone, I find it most natural and least confusing to transfer an OffsetDateTime in UTC.
private OffsetDateTime resetTime(LocalDate date) {
return date.atStartOfDay(ZoneId.systemDefault())
.toOffsetDateTime()
.withOffsetSameInstant(ZoneOffset.UTC);
}
Example use:
OffsetDateTime ts = resetTime(LocalDate.of(2019, Month.NOVEMBER, 30));
System.out.println(ts);
Output when running in the Africa/Blantyre time zone (just to pick a time zone at random):
2019-11-29T22:00Z
My method accepts a LocalDate argument. A LocalDate is a date without time of day and all that the method needs since it is setting the time of day to 00:00:00 anyway.
Should your database require a timestamp without time zone (not recommended), you will need a LocalDateTime instead:
private LocalDateTime resetTime(LocalDate date) {
return date.atStartOfDay();
}
I was going to use the Calendar class but then read that it is
advisable not to do so in Java 8.
Your are completely correct. Before the advent of java.time in 2014 the Timestamp class was used with SQL databases and the Calendar class would have been the correct means for you task (with the Joda-Time library as a probably better alternative). Even though both Timestamp and Calendar were poorly designed. Now they are long outdated, we should not use any of them anymore.
Link: Oracle tutorial: Date Time explaining how to use java.time.
You can user java.time.ZonedDateTime and java.sql.Timestamp together
Timestamp.valueOf(ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toLocalDateTime());

Is there any different behavior between Calendar#add(Calendar.MONTH, months) and LocalDate#plusMonth(months)?

I'm working on some legacy code where java.util.Calendar is used for date related calculations (basically adding months). Now I want to replace java.util.Calendar by java.time.LocalDate in the code but I don't want to change the behavior. So, I've been looking for a source which clarifies that they yield same result for the same calculation for any case but I can't find any.
Especially I want to know if is there a date that makes them yield a different result between:
Calendar#add(Calendar.MONTH, months)
and
LocalDate#plusMonth(months)
I've tested some corner cases (e.g. a leap year related dates) and they seem to yield the same result but I can't be 100% sure with that. Isn't there any official information about that or some known difference between them?
TL;DR
If:
You are sure that your Calendar is really a GregorianCalendar (by far the most commonly used subclass), and…
Your dates don’t go more than 100 years back, then…
…you can safely use LocalDate#plusMonth(months) instead of Calendar#add(Calendar.MONTH, months).
Details
Congratulations on the decision to migrate from the old and poorly designed Calendar class to LocalDate from java.time, the modern Java date and time API. This will be an improvement to your code base.
You are correct, the methods you mention are used for the same purpose and generally work the same. So when you migrate from Calendar to java.time, if you find that LocalDate is the right new class to use, then you will use LocalDate#plusMonth(months) where you used Calendar#add(Calendar.MONTH, months) before.
Differences include:
The Calendar class is an abstract superclass for classes representing dates (and times) in many different calendar systems (Gregorian, Buddhist and more), a LocalDate is always in the proleptic Gregorian calendar. Since each calendar system has its own definition of what a month is, adding a number of months in a calendar other than the Gregorian calendar can give quite different results from what LocalDate.plusMonths gives you.
If your dates go back in history it will also make a minor difference that LocalDate uses the proleptic Gregorian calendar. This means that it doesn’t use the Julian calendar for dates where it was in use centuries ago.
While Calendar.add modifies the Calendar object that you call it on, LocalDate.plusMonths returns a new LocalDate object with the new date.
While for going backward in the calendar you need to pass a negative number of months to Calendar::add, LocalDate has a convenient minusMonths method that you will typically want to use instead of plusMonths (both work, though).
The range of dates that each class can represent is different. I don’t readily remember the minimum and maximum date for each. On Calendar/GregorianCalendar, see their various methods such as getGreatestMinimum​ & getLeastMaximum​. For LocalDate, see the constants: MAX & MIN.

Java Date and Calendar

I'm aware that Java 8 has a much improved date and time library based on Joda Time, but I'm very curious about the decisions made in the old libraries. I haven't found any good explanation about the java.util.Date constructor deprecation rationale (the closest question I've found is this: Difference between new Date() and Calendar date but it doesn't ask about deprecated methods/constructors and doesn't have an accepted answer).
The constructor java.util.Date(year, month, day) is considered deprecated and we should use new GregorianCalendar(year + 1900, month, date). If we call getTime() (which returns a Date...) on the Calendar instance, what have we gained, other than avoiding a deprecated constructor? Even java.sql.Date.toLocalDate uses some deprecated methods internally.
I have a codebase littered with this pattern (new GregorianCalendar followed by getTime) just to avoid the deprecated methods of java.util.Date (I need java.util.Date and java.sql.Date in JPA and JDBC), but I'm not sure what's the point or what was the point back then (*).
(*) Nowadays I can finally change them all to LocalDate because that's what I really needed anyway — saving what the user typed in without any timezone conversions.
See second paragraph in the javadoc of java.util.Date:
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.
So, to answer your question "What have we gained?", the answer is "support for internationalization":
Ability to specify time zone (using Calendar).
Ability to use non-Gregorian calendar (using Calendar).
Ability to use localized formatting and parsing of date strings (using DateFormat).
The old libraries permitted the construction of java.util.Date items from entries in a Gregorian calendar, by passing in the year, month, day, etc items.
This was problematic for a number of reasons. First, the Gregorian calendar system is a hybrid calendar system that transitioned from the Julian calendar system. This transition included the need to "skip" dates to realign the Julian calendar system with the seasons. So, there are missing days. Surprisingly, the java.util.Date does a decent job of capturing this behavior, except:
The dates to be skipped are dependent on when the transition was adopted, which mostly maps out to be Locale dependent.
The strong binding to the Gregorian Calendar of the core java.util.Date object means that implementing other calendar systems is problematic, as you need to implement them on top of a Gregorian System.
The date being tied to Locale and TimeZone also meant that you had to adjust the platform's Locale and TimeZone to get the appropriate Date behavior you wished, often adjusting it back for out-of local date computations.
The new calendar system attempts to avoid this by:
Having an API that passes in a field to set with the value, preventing direct binding of the calendar fields to the API methods.
Having an API that permits subclassing a Calendar such that one could implement calendars with vastly different definitions of months, days, and years (think lunar calendars, Jewish calendars, Arabic Calendars, Chinese Calendars, etc).
Going forward, one should use java.util.Date only as a thin wrapper around a timestamp, and that's more to have compatibility with the older APIs. All Date manipulations should be done in the appropriate Calendar instance.

Calendars getTime method modifies the timezone [duplicate]

This question already has answers here:
How to set time zone of a java.util.Date?
(12 answers)
Closed 5 years ago.
I have a problem whereby the Calendar.getTime() method changes the timezone (probably to be in line with the JVM).
Calendar cal = javax.xml.bind.DatatypeConverter.parseDateTime("2017-10-20T07:10:08.123458Z");
Date datrFromCal = cal.getTime();//Adds two hours(GMT+2:00)
Is there any way to stop this movement from GMT+0 to GMT+2 when calling cal.getTime()?
P.s. We use Java 7 at my company.
Another thing related to this is the support for microseconds. I have read a lot about Java 7 and below not supporting microSeconds (when parsing a String), but is there any suggestions to get around this?
It's not the Date that has the timezone. Dates are simply a number of milliseconds since the "epoch" (1 Jan 1970 GMT). They do not contain timezone information. You only see time zones when you format a date for display. By default, it uses the timezone for your locale. You can use SimpleDateFormat to print a date with a different Locale or TimeZone.
Make sure your Calendar instance has the right TimeZone (e.g. you might have to explicitly set it: cal.setTimeZone(TimeZone.getTimeZone("GMT"));).
If I were you, I'd switch to the Joda time library or the ThreeTen Backport and use the far better designed java.time. The built-in java.util.Date/Calendar classes are a nightmare to work with. They are poorly designed and mutable, which means you can't trust them as value objects to be passed around.
Though i agree with #ELEVATE, i have to add that the iso 8601 date format uses a time zone.
We can see that you are effectively setting the timezone with the 'Z' indicator but not specify it. Therefore, the calendar is built using the your local time zone but with the time defined as UTC (or GMT if you prefer, therefore as GMT+2h00 in your case).
If, however, you remove the 'Z' indicator, you'll the time set with your timezone as it is.
See https://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators for instance

Why does conversion between GregorianCalendar and OffsetDateTime fail when evaluating the ZoneInfo part?

I am converting an OffsetDateTime to a GregorianCalendar as part of populating values for an outbound web service. I just thought I'd test the actual conversion.
It is failing and i don't understand why - here is the test method:
#Test
public void testOffsetDateTimeToGregorianCalendar() {
// given the current gregorian utc time
GregorianCalendar expectedGregorianUtcNow = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
OffsetDateTime expectedUtcOffsetDateTime = OffsetDateTime.from(expectedGregorianUtcNow.toZonedDateTime());
// when converting to a GregorianCalendar
GregorianCalendar actualGregorianUtcNow = GregorianCalendar.from(expectedUtcOffsetDateTime.toZonedDateTime());
// then make sure we got the correct value
assertThat(actualGregorianUtcNow, equalTo(expectedGregorianUtcNow));
}
within the equalto, the comparison fails in the evaluation of the gregorianCutover. Why? Is this a bug?
it looks like actual has:
gregorianCutover = -9223372036854775808
and expected has:
gregorianCutover = -12219292800000
Everything else is correct as can be seen in the unit test output (gregorianCutover does not appear there):
java.lang.AssertionError:
Expected:<java.util.GregorianCalendar[time=1458667828375,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2016,MONTH=2,WEEK_OF_YEAR=12,WEEK_OF_MONTH=4,DAY_OF_MONTH=22,DAY_OF_YEAR=82,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=5,HOUR_OF_DAY=17,MINUTE=30,SECOND=28,MILLISECOND=375,ZONE_OFFSET=0,DST_OFFSET=0]>
but: was <java.util.GregorianCalendar[time=1458667828375,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2016,MONTH=2,WEEK_OF_YEAR=12,WEEK_OF_MONTH=4,DAY_OF_MONTH=22,DAY_OF_YEAR=82,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=5,HOUR_OF_DAY=17,MINUTE=30,SECOND=28,MILLISECOND=375,ZONE_OFFSET=0,DST_OFFSET=0]>
Did i do something wrong?
Basically, as far as I'm aware, java.time doesn't try to model a Gregorian cutover - so when an OffsetDateTime is converted back to a GregorianCalendar, that's done by modeling it as having a Gregorian calendar with a cutover back at the start of time. Hence the documentation for GregorianCalendar.from(ZoneDateTime):
Since ZonedDateTime does not support a Julian-Gregorian cutover date and uses ISO calendar system, the return GregorianCalendar is a pure Gregorian calendar and uses ISO 8601 standard for week definitions, which has MONDAY as the FirstDayOfWeek and 4 as the value of the MinimalDaysInFirstWeek.
Ideally, I'd suggest that you avoid using java.util.* in terms of date/time API... stick with java.time everywhere. But if you really need to, I'd probably suggest just not using GregorianCalendar.equals for testing equality - check the instant in time and the time zone separately, if those are the things you're interested in.

Categories

Resources