I request a Date in a Controller with
#GetMapping(path = "/{terminal}/{date}",consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ServiceResponse appointmentsListDate(#PathVariable Long terminal, #PathVariable #DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date) {
Then I need to compare that Date with a stored date in database (type = timestamp without time zone).
I´m using the methods before() and after() but it fails when comparing the time part (the date comparison is good, but it doesn´t compare the time).
public Collection<EntrySlot> getAppointment( Date date) {
Collection<EntrySlot> entrySlotList=new ArrayList<>();
for(int i=0; i<appointmentList.size();i++){
Appointment appointment = ((List<Appointment>)appointmentList).get(i);
EntrySlot entrySlot = appointment.getIdEntrySlot();
if (date.before(entrySlot.getStartDate())){
entrySlotList.add(entrySlot);
}
return entrySlotList
}
At example of the entrySlot.getStartDate stored in db is 2021-05-01 16:00:00. My date is 2021-05-01 17:00:00 and when using the date.before(entrySlot.getStartDate) the result is true.
I don´t know if the problem is related to the date formatting.
Thanks in advance!
PD: I have solved the problem. The hour stored in the database has GMT+2 hours. I had to substract those hour and now I can calculate the difference even using the deprecated date.util.
java.util.Date is a lie; it does not represent dates. It presents moments in time.
'timestamp without timezone' is something completely different. You're asking the system: Hey, I have this apple. Is it better than this pear?
It'd have been better all around if the code had failed to compile but for complicated reasons, it does compile. Nevertheless, it is gobbledygook.
First, compare all the times you have into the data type that properly represents the concept of 'timestamp without timezone', which is java.time.LocalDateTime.
Most DB engines can give you this directly; at the JDBC level, call .getObject(idxOrColName, LocalDateTime.class). A few rusty old JDBC drivers can't do this, in which case you're forced to call e.g. .getTimestamp, which will convert a timestamp-without-timezone in-flight to a moment-in-time-devoid-of-context, and you then have to unconvert this messed up conversion, preferably ASAP.
Once you have that, put your target in terms of LocalDateTime as well, and now compare the two. If it fails now, you can just debug each process individually, because then one of the two processes that end in you having an instance of LDT, is broken and it is instantly obvious which one: It's the one where printing that LDT does not show what you expected.
NB: All date/time related classes in java.util, java.sql, and the (Gregorian)Calendar class are obsolete and bad API which lead to exactly the problems you have now. Don't use those, or if you're forced into it, convert them to a java.time type immediately and debug this conversion on its own before continuing with the program.
Related
This question already has answers here:
Jackson deserialize elasticsearch long as LocalDateTime with Java 8
(2 answers)
Closed 1 year ago.
I receive this error when trying to get response from Api Call:
I receive this field like this from the object:
"createdAt":1620133356550
My Dto has this field, so i can get the value with object mapper:
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createdAt;
And i receive this error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: raw timestamp (1620133356550) not allowed for `java.time.LocalDateTime`: need additional information such as an offset or time-zone (see class Javadocs)
Possible setter of the object:
public void setCreatedAt(long createdAt) {
Instant instant = Instant.ofEpochMilli(createdAt);
LocalDateTime localMark =
instant.atZone(ZoneId.of("Europe/Amsterdam"))
.toLocalDateTime();
this.createdAt = localMark;
}
That's because you're asking jackson to silently convert an apple into a pear and it won't do that. There is no way to do this without additional information.
This: 1620133356550 looks like a milliseconds-since-epoch value.
This represents an instant in time. Instants in time and LocalDateTime are a real guns and grandmas situation: They are almost entirely unrelated, and one cannot be transformed into the other; at least, not without parameters that are simply not present here.
First you need to 'localize' the timestamp: That's milliseconds since some instant in time. Which instant in time? A common format is 'milliseconds since midnight, new years, 1970, in the UTC timezone'. If that's indeed what it is, all you need to do is to say Instant.ofEpochMilli(1620133356550L), and you now have that properly represented (namely, as an Instant).
This still isn't a LocalDateTime though. The problem is: Local to whom?
UTC isn't actually used by anybody except space flight, aviation in general, and computers talking to each other. Unless you're writing software specifically intended to run on the Dragon space capsule and nowhere else, by definition then this isn't yet ready to be put in terms of local date and time.
First you need to transform your instant to a locale. THEN you can have a LocalDateTime.
For example, if you want to know: Okay, so if I walk around Amsterdam at the exact instant as represented by this java.time.Instant I made, and I ask somebody the date and time, what would they say? Then you do:
Instant instant = Instant.ofEpochMilli(1620133356550L);
LocalDateTime localMark =
instant.atZone(ZoneId.of("Europe/Amsterdam"))
.toLocalDateTime();
In case this is some bizarro format where a LocalDateTime is serialized by way of: Take the local date time, turn that into an instant by assuming you're asking the current date and time at that exact instant by asking someone zipping about the international space station, and then turn that into epochmillis and put that on the wire, okay, then, write that. Use ZoneOffset.UTC instead of ZoneId.of("Europe/Amsterdam") in the above code.
I have a MySQL database which is storing a datetime value, let's say 2020-10-11 12:00:00. (yyyy-mm-dd hh:mm:ss format)
The type of this date (in mysql) is DATETIME
When I retrieve this data in my controller, it has the java 7 type "Date". But it adds a timezone CEST due to my locale I suspect. Here I already find confusing that when displaying this date which is not supposed to have a timezone attached it actually has... and the debugger says it is "2020-10-11 12:00:00 CEST".
My problem is that date was not stored with the CEST timezone. It was stored with the America/New_York one, for example. EDIT: What I mean with this line, is that the date was stored from new york using the timezone of new york. So, it was really 12:00:00 AM there, but here in Madrid it was 18:00:00 PM. I need that 18:00:00.
So in New York, someone did an insert at that time. Which means that the time in Europe was different. I need to calculate which time was in Europe when in America was 12AM. But my computer keeps setting that date to CEST when I retrieve it so all my parsing attempts are failing... This was my idea:
Date testingDate // This date is initialized fetching the "2020-10-11 12:00:00" from mySql
Calendar calendar = new GregorianCalendar()
calendar.setTime(testingDate)
calendar.setTimeZone(TimeZone.getTimeZone("America/New_York")
SimpleDateFormat localDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
TimeZone localTimeZone = TimeZone.getTimeZone("Europe/Madrid")
localDateFormatter.setTimeZone(localTimeZone)
String localStringDate = localDateFormatter.format(calendar.getTime())
Date newDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(localStringDate)
Here my idea is that: I create a brand new calendar, put on it the time that I had on America and I also say hey this calendar should have the America Timezone. So when I get the time of it using a formatter from Europe it should add the corresponding hours to it. It makes a lot of sense in my head but it is just not working in the code D: And I really don't want to calculate the time difference by myself and adding or substracting the hours because that would look extremely hardcoded in my opinion.
Can any one give me some ideas of what I'm interpreting wrong or how should I tackle this problem in a better way?
Important: I'm using java 7 and grails 2.3.6.
My problem is that date was not stored with the CEST timezone. It was stored with the America/New_York one, for example.
From what I know of MySQL, this is impossible.
Calendar calendar = new GregorianCalendar()
No, don't. The calendar API is a disaster. Use java.time, the only time API in java that actually works and isn't completely broken / very badly designed. If you can't (java 7 is extremely out of date and insecure, you must upgrade!), there's the jsr310 backport. Add that dependency and use it.
Let me try to explain how to understand time first, because otherwise, any answer to this question cannot be properly understood:
About time!
There are 3 completely different concepts and they are all often simplified to mean 'time', but you shouldn't simplify them - these 3 different ideas are not really related and if you ever confuse one for another, problems always occur. You cannot convert between these 3 concepts unless you do so deliberately!
"solarflares time": These describe the moment in time as a universal global concept that something occurred or will occur. "That solar flare was observed at X" is a 'solarflares' time. Best way to store this is millis since epoch.
"appointment time": These describe a specific moment in time as it was or will be in some localized place, but stated in a globally understandable way. "We have a zoom meeting next tuesday at 5" is one of these. It's not actually constant, because locales can decide to adopt new timezones or e.g. move the 'switch date' for daylight savings. For example, if you have an appointment at your dentist on 'november 5th, at 17:00, 2021', and you want to know how many hours are left until your appointment starts, then the value should not change just because you flew to another timezone and are looking at this number from there. However, it should change if the country where you made the appointment in decided to abolish daylight savings time. That's the difference between this one and the 'solarflares' one. This one can still change due to political decisions.
"wake-up-alarm time": These describe a more mutable concept: Some way humans refer to time which doesn't refer to any specific instant or is even trying to. Think "I like to wake up at 8", and thus the amount of time until your alarm will go off next is continually in flux if you are travelling across timezones.
Now, on to your question:
I have a MySQL database which is storing a datetime value, let's say 2020-10-11 12:00:00. (yyyy-mm-dd hh:mm:ss format)
Not so fast. What exact type does that column have? What is in your CREATE TABLE statement? The key thing to figure out here is what is actually stored on disk? Is it solarflare, appointment, or wakeup-alarm? There's DATE, DATETIME and TIMESTAMP, and over the years, mysql has significantly changed how these things are stored.
I believe that, assuming you are using the modern takes on storage (So, newish mysql and no settings to explicitly emulate old behaviour), e.g. a DATETIME stores sign, year, day, hour, minute, and second under the hood, which means it is wakeup alarm style: There is no timezone info in this, therefore, the actual moment in time is not set at all and depends on who is asking.
Contrast to TIMEZONE which is stored as UTC epoch seconds, so it's solarflares time, and it doesn't include any timezone at all. You'd have to store that separately. As far as I know, the most useful of the 3 time representations (appointment time) is not a thing in mysql. That's very annoying; mysql tends to be, so perhaps par for the course.
In java, all 3 concepts exist:
solarflares time is java.time.Instant. java.util.Date, java.sql.Timestamp, System.currentTimeMillis() are also solarflares time. That 'Date' is solarflares timestamp is insane, but then there is a reason that API was replaced.
appointment time is java.time.ZonedDateTime
wakeup-alarm time is java.time.LocalDateTime.
When I retrieve this data in my controller, it has the java 7 type "Date".
Right. So, solarflares time.
Here's the crucial thing:
If the type of time stored in MySQL does not match the type of time on the java side, pain happens.
It sure sounds like you have wakeup-alarm time on disk, and it ends up on java side as solarflares time. That means somebody involved a timezone conversion. Could have happened internally in mysql, could have happened in flight between mysql and the jdbc driver (mysql puts it 'on the wire' converted), or the jdbc driver did it to match java.sql.Timestamp.
The best solution is not to convert at all, and the only real way to do that is to either change your mysql table def to match java's, so, make that CREATE TABLE (foo TIMESTAMP), as TIMESTAMP is also solarflares time, or, to use, at the JDBC level, not:
someResultSet.getTimestamp(col);
as that returns solarflares time, but:
someResultSet.getObject(col, LocalDateTime.class);
The problem is: Your JDBC driver may not support this. If it doesn't, it's a crappy JDBC driver, but that happens sometimes.
This is still the superior plan - plan A. So do not proceed to the crappy plan B alternative unless there is no other way.
Plan B:
Acknowledge that conversion happens and that this is extremely annoying and errorprone. So, make sure you manage it, carefully and explicitly: Make sure the proper SET call is set up so that mysql's sense of which timezone we are at matched. Consider adding storing the timezone as a column in your table if you really need appointment time. etcetera.
Thanks to #rzwitserloot I was able to find out a solution.
First I'll get the data from the database. I'll get rid of any timezone added by the driver / mysql by converting it to a LocalDateTime. Then, I'll create a new ZonedDateTime using the Timezone that was used when storing the data in the database.
Once I have a ZonedDateTime, it is time to convert it using my current timezone. I'll get a new ZonedDateTime object with the proper time.
Then I just add a few more lines to convert it back to my main "Date" class:
I've used the ThreeTen backport as suggested.
Date dateMySQL //Initialized with the date from mysql
Calendar calendar = new GregorianCalendar()
calendar.setTime(dateMySQL)
org.threeten.bp.LocalDateTime localDateTime = org.threeten.bp.LocalDateTime.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1,
calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND))
String timezone //Initialized with the timezone from mysql (Ex: "America/New_York")
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of(timezone))
ZonedDateTime utcDate = zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/Madrid"))
calendar.setTimeInMillis(utcDate.toInstant().toEpochMilli())
Date desiredDate = calendar.time
dateMySQL: "2020-10-11 10:00:00" // CEST due to my driver
timezone: "America/New_York"
desiredDate: "2020-10-11 19:00:00" // CEST Yay!
Using JodaTime library (although I am a bit flexible). I realized some of the inputs coming in are breaking Joda time because the days of the month are above 31 or below 1 (because of client-side code).
I am using the LocalDate object for calendar manipulation. Is there a library or method to easily sanitize the dates so the input doesn't start throwing exceptions?
Some Scala code I am using now: EDIT: Fixed code
val now = new LocalDate();
val workingDate = now.withYear(y).withMonthOfYear(m).withDayOfMonth(d).withDayOfWeek(DateTimeConstants.SUNDAY)
ymdStart = toTimestampAtStart( workingDate )
For clarification, the goal here is to convert the date to a proper date, so if a user submitted July 38, it would convert to August 7. There's an incoming URL structure causing a lot of this and it looks like /timeline/2012/07/30.
For reasons of pure exercise (I agree normalization seems to be bad practice) I'm now just purely curious if there are libraries that deal with such a problem.
Thanks!
Final Update:
Like the answer points out, normalization was a poor idea. I did a lot of re-factoring on the client side to fix the incoming variables. This is the code I ended up using:
ymdStart = new Timestamp( toTimestampAtStart( new LocalDate(y,m,d).withDayOfWeek(1) ).getTime - 86400000 )
ymdEnd = new Timestamp( ymdStart.getTime + 691200000 )
First of all, a LocalDate is immutable, so each chained with...() is creating a new date.
Second, it is a well-known antipattern to update pieces of a date one at a time. The end result will depend on the current value of the date, the order in which you update the pieces, and whether or not the implementation "normalizes" dates.
In other words NEVER update a date/time piecemeal.
Assume for a minute that the implementation "normalizes" (i.e. corrects for overflow) invalid dates. Given your code, if today's date was 31-Jan-2011 and you did
now.setMonth(FEBRUARY);
now.setDayOfMonth(12);
the result will be 12-March-2011. The first statement sets the date to 31-February, which gets normalized to 03-March, then the day gets set to 12. Ah, you say, you can just set the day-of-month first. But that doesn't work for different starting points (construction of which is left as an exercise).
And from your question I surmise that JodaTime throws exceptions rather than normalize, which is anothe reason for not doing it this way.
So me and my partner have been working on this project for a while now. We work with dates A LOT in this project, and we recently noticed an issue, and we are rather deep in at this point.
We store our times in SQLlite (Android project) as a formatted string, since a lot of the time they are directly bound to listviews and such.
The problem we noticed, which i found kind of odd, is that that SimpleDateTimeFormat object, when used to format to 24h time (its a medical based project, so 24h time is the convention here) 12:00am-12:59am are formatted to 24:00-24:59, instead of 00:00-00:59...
This isn't too much of an issue until we query the database and order the results by the dates, any data that is between 12:00am and 12:59am will show up at the end of the list, but it should show up at the beginning...
Anyone else encountered this problem? or know a way around it? The best thing possible would be a way to store the data as 00:00 not 24:00.
Cheers
I strongly suspect you're using the wrong pattern. We've got to guess as you haven't posted any code (hint, hint), but I suspect you're using a pattern such as
kk:mm:ss
instead of
HH:mm:ss
Sample code:
import java.util.*;
import java.text.*;
public class Test {
public static void main(String[] args) throws Exception {
SimpleDateFormat broken = new SimpleDateFormat("kk:mm:ss");
broken.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
SimpleDateFormat working = new SimpleDateFormat("HH:mm:ss");
working.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
Date epoch = new Date(0);
System.out.println(broken.format(epoch));
System.out.println(working.format(epoch));
}
}
Additionally, as others have pointed out, you shouldn't be storing your values in string format to start with... avoid string conversions wherever you can, as each conversion is a potential pain point.
Please read this and this about how SQLite stores dates (or doesn't store dates). SQLite doesn't have a "Date" type, so it is stored as a string. You should store your date as an integer (milliseconds), and then you can use date and time functions to pull them out (from the first link).
From the documentation
1.2 Date and Time Datatype
SQLite does not have a storage class set aside for storing dates
and/or times. Instead, the built-in Date And Time Functions of SQLite
are capable of storing dates and times as TEXT, REAL, or INTEGER
values:
TEXT as ISO8601 strings ("YYYY-MM-DD HH:MM:SS.SSS"). REAL as Julian
day numbers, the number of days since noon in Greenwich on November
24, 4714 B.C. according to the proleptic Gregorian calendar. INTEGER
as Unix Time, the number of seconds since 1970-01-01 00:00:00 UTC.
Applications can chose to store dates and times in any of these
formats and freely convert between formats using the built-in date and
time functions.
I prefer INTEGER / Unix time storage, then use the built in date and time functions to format when pulling from DB.
EDIT: Also, this will take care of sorting. I'm guessing your current "sorting" of the dates in SQLite is string based, which is bad mmmmkay.
What is the format string you are passing to your SimpleDateFormat? According to the docs, using 'H' for the hours should get you 0-23, using 'k' should get you 1-24.
Is there any way to remove the timezone component from a Java Date object that is being returned from a web service?
For example I have a start Date of 12AM. I want that to be used as 12AM local time of the clients.
I think that if the soap message doesn't have a timezone component then the local timezone is used. There are 2 other options that I have weighed which would be either doing arithmetic on the date on the client side (which I really really do not want to do) or creating a new date class that holds the day, month and year as integers(I don't need the time information). The latter option would require substantial refactoring so if there is a way to just chop off the timezone info in the soap message that would be preferable.
The client is written in .NET so if there is a way to change the how the date is interpreted on the client side that would achieve the same goal i think.
I believe I have found a solution to my problem. I used #XmlJavaTypeAdapter to change the way the date class is marshaled to Xml.
Here is the DateAdapter I used.
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date> {
// the desired format
private String pattern = "yyyy-MM-dd";
public String marshal(Date date) throws Exception {
return new SimpleDateFormat(pattern).format(date);
}
public Date unmarshal(String dateString) throws Exception {
return new SimpleDateFormat(pattern).parse(dateString);
}
}
And I used the following annotation on all of my getters that I wanted to use the "yyyy-MM-dd" date format.
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
Java Date objects don't have time zone components. They have no concept of time zones at all: they're always just milliseconds since midnight January 1st 1970, UTC.
I don't know much about the SOAP representation of dates and times... if you can persuade it that a date is all you need (rather than a date and time) you may well be fine. Otherwise, I suggest you do everything in UTC but consider it to be the local time on the client. If you're only dealing with dates, this shouldn't be too bad - although you do need to consider that midnight doesn't always occur on every date in every time zone, or it may occur twice...
Basically it's a pity that neither .NET nor Java has a decent built-in date and time API. On Java there's Joda Time where you'd want to use LocalDate by the sounds of it, to represent exactly what you're interested in (a date in whatever the local time zone is). On .NET there will (eventually) be Noda Time but that isn't ready yet. Even if both of these existed in the respective base platforms, you'd still need to persuade SOAP to serialize them appropriately :(