I've tried almost everything about this snippet, and I still get IllegalInstentException.
public int getDateDay() {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
DateTime dt;
try {
dt = formatter.parseDateTime(date);
} catch (IllegalInstantException e) {
dt = formatter.parseLocalDateTime(date).toDateTime();
}
return dt.getDayOfMonth();
}
Fatal Exception: org.joda.time.IllegalInstantException Illegal instant
due to time zone offset transition (daylight savings time 'gap'):
2018-10-21T00:00:00.000 (America/Sao_Paulo) keyboard_arrow_up
arrow_right org.joda.time.chrono.ZonedChronology.localToUTC
(ZonedChronology.java:157)
org.joda.time.chrono.ZonedChronology.getDateTimeMillis
(ZonedChronology.java:122)
org.joda.time.chrono.AssembledChronology.getDateTimeMillis
(AssembledChronology.java:133) org.joda.time.base.BaseDateTime.
(BaseDateTime.java:257) org.joda.time.DateTime. (DateTime.java:532)
org.joda.time.LocalDateTime.toDateTime (LocalDateTime.java:750)
org.joda.time.LocalDateTime.toDateTime (LocalDateTime.java:731)
Seems the input is not a valid date. The problem has been discussed in this page.
Reason:
Joda-Time only allows the key classes to store valid date-times. For
example, 31st February is not a valid date so it can't be stored
(except in Partial).
The same principle of valid date-times applies to daylight savings
time (DST). In many places DST is used, where the local clock moves
forward by an hour in spring and back by an hour in autumn/fall. This
means that in spring, there is a "gap" where a local time does not
exist.
The error "Illegal instant due to time zone offset transition" refers
to this gap. It means that your application tried to create a
date-time inside the gap - a time that did not exist. Since Joda-Time
objects must be valid, this is not allowed.
Possible solutions may be as follows:
Use LocalDateTime, as all local date-times are valid.
When converting a LocalDate to a DateTime, then use toDateTimeAsStartOfDay() as this handles and manages any gaps.
When parsing, use parseLocalDateTime() if the string being parsed has no time-zone.
Related
I have a Calendar object that corresponds to 2021-07-05T18:00:00.000-04:00 (Eastern Daylight Time). Yet Calendar.get(DST_OFFSET) and Calendar.getTimeZone().getDSTSavings() both give 0. It should be 1 hour. What am I missing or what am I going wrong? All the other methods I play with are returning the expected values.
I am creating the Calendar using setTimeInMillis() and TimeZone using offsets. Is that the reason it is not working? The displayed civil times are always right...
As much as I would like to use the new Java time I am using Java for Android. Last I checked only the most recent versions of Android support the new Java time. They may eventually add support to their older versions.
One problem is that the input defines an offset from UTC, but not a real time zone with specific rules (like if DST is applied at all and if it is, when will DST be applied).
Calendar is clearly not capable of handling those rules, the class (and probably the entire API) was not designed to be.
That's one of the reasons for java.time having been introduced in Java 8.
Here's some example use of java.time in a situation like yours:
public static void main(String[] args) {
// example String in ISO format
String dateString = "2021-07-05T18:00:00.000-04:00";
// define your time zone
ZoneId americaNewYork = ZoneId.of("America/New_York");
// parse the (zone-less) String and add the time zone
ZonedDateTime odt = OffsetDateTime.parse(dateString)
.atZoneSameInstant(americaNewYork);
// then get the rules of that zone
long hours = americaNewYork.getRules()
// then get the daylight savings of the datetime
.getDaylightSavings(odt.toInstant())
// and get the full hours of the dst offset
.toHoursPart();
// use a formatter to format the output (nearly) as desired
System.out.println(odt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)
+ " has a daylight saving offset of "
+ hours);
}
This prints
2021-07-05T18:00:00-04:00[America/New_York] has a daylight saving offset of 1
EDIT:
Your comment made me provide a similar version that uses a long as input:
public static void main(String[] args) {
// example String in ISO format
long input = 1625522400000L;
// create an Instant from the input
Instant instant = Instant.ofEpochMilli(input);
// define your time zone
ZoneId americaNewYork = ZoneId.of("America/New_York");
// then get the rules of that zone
long hours = americaNewYork.getRules()
// then get the daylight savings of the Instant
.getDaylightSavings(instant)
// and get the full hours of the dst offset
.toHoursPart();
// use a formatter to format the output (nearly) as desired
System.out.println(ZonedDateTime.ofInstant(instant, americaNewYork)
.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)
+ " has a daylight saving offset of "
+ hours);
}
The output is just the same as in the above example.
in a java bug tracker you will find your problem.
During the "fall-back" period, Calendar doesn't support disambiguation and the given local time is interpreted as standard time.
To avoid the unexpected DST to standard time change, call add() to reset the value.
you can resolve it by replacing set() with
cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));
A server is storing the date/time of a client action with System.currentTimeMillis(). Let's say this server is in the EST time zone.
A client in France wants to see what time that action was made. That long value stored via System.currentTimeMillis() (on the US server) is returned to the client in France.
I'm having problems understanding how to make that conversion on the client side to accurately describe their action time. My understanding is that on the server the System.currentTimeMillis() is a zoneless UTC time. Things seem strange when I instantiate a Calendar object with a "UTC" timezone, so is the server storing a time zoned epoch time when it saves with currentTimeMillis()
First attempt:
String timezone = clientTimezone;//Lets say france
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time); //This time var is the server stored value (currentTimeMillis)
if(!timezone.equals("")) {
long timezoneAlteredTime = time + TimeZone.getTimeZone(timezone).getRawOffset();
cal = Calendar.getInstance(TimeZone.getTimeZone(timezone));
cal.setTimeInMillis(timezoneAlteredTime);
}
Other attempt:
String timezone = clientTimezone;//Lets say france
TimeZone utc = TimeZone.getTimeZone("UTC");
Calendar cal = Calendar.getInstance(utc);
cal.setTimeInMillis(time); //This time var is the server stored value
if(!timezone.equals("")) {
long timezoneAlteredTime = time + TimeZone.getTimeZone(timezone).getRawOffset();
cal = Calendar.getInstance(TimeZone.getTimeZone(timezone));
cal.setTimeInMillis(timezoneAlteredTime);
}
Am I misinterpreting this issue altogether? How would you go about this conversion
The Answer by Ole V.V. is correct: Call Instant::atZone to adjust a moment from UTC to a particular time zone. Same point on the timeline, different wall-clock time.
Here are a few more thoughts.
Let's say this server is in the EST time zone.
FYI, generally best to set UTC as the time zone on servers.
the System.currentTimeMillis() is a zoneless UTC time
Not zoneless. That method returns the number of milliseconds since the epoch reference of the first moment of 1970 as seen in UTC. (An offset-from-UTC of zero hours-minutes-seconds)
Without the context of an offset-from-UTC or a time zone, you cannot represent a moment, a specific point on the timeline.
Things seem strange when I instantiate a Calendar object
Everything about the Calendar class is strange.
That is why we stopped using that terrible class years ago. Use only the java.time classes.
A server is storing the date/time of a client action with System.currentTimeMillis().
We have a class for that: Instant. So no need to use a mere integer number, now you can use a type-safe and handy class.
The Instant class represents a moment as seen in UTC. Its objects use a resolution of nanoseconds, though most computer clocks nowadays can capture a moment as microseconds.
Instant instant = Instant.now() ;
You can convert between your count-of-milliseconds and Instant.
Instant instant = Instant.ofEpochMilli( yourCountOfMillis ) ;
An Instant is in UTC, by definition. To see the same moment from an on other offset, use OffsetDateTime. To see the same moment in a particular time zone, use ZonedDateTime.
Understand that a time zone is a history of past, present, and future changes to the offset used by the people of a particular region. Politicians frequently change the offset of their jurisdiction(s) for various reasons.
Search Stack Overflow to learn more. These issues have been covered many many times already.
java.time
This is pretty straightforward when you know how. And I recommend that you leave the work to java.time, the modern Java date and time API.
String clientTimezone = "Europe/Paris";
long time = 1_607_708_578_154L;
ZonedDateTime timeInClientTimeZone = Instant.ofEpochMilli(time)
.atZone(ZoneId.of(clientTimezone));
System.out.println(timeInClientTimeZone);
Output from this example code snippet is:
2020-12-11T18:42:58.154+01:00[Europe/Paris]
What went wrong in your code?
My understanding is that on the server the System.currentTimeMillis()
is a zoneless UTC time.
This far your understanding is correct.
Even though the epoch is usually defined in UTC, the clue here is zoneless. So you don’t need any date-time object in UTC. You also don’t need to make any adjustments to the millisecond count, so by doing so, you are turning a correct time into an incorrect one.
Link
Oracle tutorial: Date Time explaining how to use java.time.
An external API returns an object with a date.
According to their API specification, all dates are always reported in GMT.
However, the generated client classes (which I can't edit) doesn't set the timezone correctly. Instead, it uses the local timezone without converting the date to that timezone.
So, long story short, I have an object with a date that I know to be GMT but it says CET. How can I adjust for this mistake withouth changing my local timezone on the computer or doing something like this:
LocalDateTime.ofInstant(someObject.getDate().toInstant().plus(1, ChronoUnit.HOURS),
ZoneId.of("CET"));
Thank you.
tl;dr ⇒ use ZonedDateTime for conversion
public static void main(String[] args) {
// use your date here, this is just "now"
Date date = new Date();
// parse it to an object that is aware of the (currently wrong) time zone
ZonedDateTime wrongZoneZdt = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("CET"));
// print it to see the result
System.out.println(wrongZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the information that should stay (only date and time, NOT zone or offset)
LocalDateTime ldt = wrongZoneZdt.toLocalDateTime();
// print it, too
System.out.println(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// then take the object without zone information and simply add a zone
ZonedDateTime correctZoneZdt = ldt.atZone(ZoneId.of("GMT"));
// print the result
System.out.println(correctZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
Output:
2020-01-24T09:21:37.167+01:00[CET]
2020-01-24T09:21:37.167
2020-01-24T09:21:37.167Z[GMT]
Explanation:
The reason why your approach did not just correct the zone but also adjusted the time accordingly (which is good when desired) is your use of a LocalDateTime created from an Instant. An Instant represents a moment in time which could have different representations in different zones but it stays the same moment. If you create a LocalDateTime from it and put another zone, the date and time are getting converted to the target zone's. This is not just replacing the zone while keeping the date and time as they are.
If you use a LocalDateTime from a ZonedDateTime, you extract the date and time representation ignoring the zone, which enables you to add a different zone afterwards and keep the date and time as it was.
Edit: If the code is running in the same JVM as the faulty code, you can use ZoneId.systemDefault() to get the same time zone as the faulty code is using. And depending on taste you may use ZoneOffset.UTC instead of ZoneId.of("GMT").
I am afraid you will not get around some calculations here. I'd strongly suggest to follow an approach based on java.time classes, but alternatively you might use the java.util.Calendar class and myCalendar.get(Calendar.ZONE_OFFSET) for those calculations:
https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#ZONE_OFFSET
I have time 12:00:00 in format HH:mm:ss.
I know that this time comes from server witch is setup with +3 offset.
If i use SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");, it parses time with regard to device, which can be in a different timezone.
Is there another way to parse it with regard to +3 offset except adding it to the original string?
First, should your server rather send the time in UTC? If clients are everywhere, this would seem more time zone neutral and standardized. However, the way to handle it in code wouldn’t be much different. In any case the server offset form UTC could be constant:
private static final ZoneOffset serverOffset = ZoneOffset.ofHours(3);
In real code you will probably want to make it configurable somehow, though. To parse:
OffsetTime serverTime = LocalTime.parse("12:00:00").atOffset(serverOffset);
System.out.println(serverTime);
This prints
12:00+03:00
Since your time format agrees with LocalTime’s default (ISO 8601), we need no explicit formatter. If a representation of the time with offset is all you need, we’re done. If you need to convert to the user’s local time, to do that reliably you need to decide both a time zone and a date:
LocalTime clientTime = serverTime.atDate(LocalDate.of(2018, Month.JANUARY, 25))
.atZoneSameInstant(ZoneId.of("Indian/Maldives"))
.toLocalTime();
System.out.println(clientTime);
With the chosen day and zone we get
14:00
Please substitute your desired time zone and date.
Just hypothetically, if you knew the user’s offset from UTC, you could use just that:
LocalTime clientTime = serverTime.withOffsetSameInstant(ZoneOffset.of("-08:45"))
.toLocalTime();
The example yields 00:15. However, no one knows when the politicians introduce summer time (DST) or other anomalies in the user’s time zone, so I discourage relying on an offset alone.
And yes, I too am using java.time. SimpleDateFormat is not only long outdated, it is also notoriously troublesome, so java.time is what I warmly recommend.
Set the timezone on your SimpleDateFormat object:
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("GMT+03:00"));
I recommend you use the Java 8 date and time API (package java.time) instead of the old API, of which SimpleDateFormat is a part.
Using the Java 8 DateTime API:
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("HH:mm:ss");
LocalTime clientLocalTime = LocalTime
.parse("12:00:00", formatter)
// Create an OffsetTime object set to the server's +3 offset zone
.atOffset(ZoneOffset.ofHours(3))
// Convert the time from the server timezone to the client's local timezone.
// This expects the time value to be from the same day,
// otherwise the local timezone offset may be incorrect.
.withOffsetSameInstant(ZoneId.systemDefault().getRules().getOffset(Instant.now()))
// Drop the timezone info - not necessary
.toLocalTime();
I have a time value stored in my database in HH:mm:ss format (using MySQL's time type). This time is to be considered as a value of IST timezone. The server on which my Java code runs follows the UTC timezone.
How can I get a formatted datetime in yyyy-MM-dd HH:mm:ss in IST (or in UTC millis)? Following is what I've tried till now:
// ... Code truncated for brevity
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalTime time = resultSet.getTime("send_time").toLocalTime();
LocalDateTime datetime = time.atDate(LocalDate.now());
System.out.println(datetime.format(formatter));
The above correctly prints the datetime on my local machine, which is on IST, but I'm concerned about how it will behave on the remote server.
Your approach is fine and should work regardless of your computer's time zone since there is no time zone information in either LocalTime or LocalDateTime. One possible issue is with LocalDate.now() which returns today's date in the computer's local time zone, not in IST. You may want to replace it with LocalDate.now(ZoneId.of("Asia/Calcutta")).
Or as commented by #OleV.V. you could use the new driver facilities to derive a LocalTime directly:
LocalTime time = resultSet.getObject("send_time", LocalTime.class);
Note possible caveats with your approach:
if the time zone you use introduces DST, you may end up with two identical times in your DB that were actually different instants - using UTC to store times is probably more robust
time in mysql can store values smaller than 00:00 and larger than 23:59:59.999999, in which case you may experience unexpected behaviours on the Java side.