Convert java.util.Date with custom time zone to UTC [duplicate] - java

This question already has answers here:
How to set time zone of a java.util.Date?
(12 answers)
Closed 2 years ago.
I have a java.util.Date object. When I format it to String using:
String timeString = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss-Z").format(time);
I get:
2020-03-26-14:40:55-+0200
So, I somehow have the correct time zone (but I can have any time zone, not only +2), this is good.
I want to convert this date to UTC, and then convert it to String. If I send an object with a date to the client, then Jackson will automatically convert it to UTC. But I want to do this manually.
How can I do this?

It is not recommended anymore to use java.util for date-time operations, especially not for time-zone or offset conversions...
Instead, use java.time:
public static void main(String[] args) {
// the String (with a strange formatting) to be parsed
String datetime = "2020-03-26-14:40:55-+0200";
// parse it to an OffsetDateTime
OffsetDateTime odt = OffsetDateTime.parse(datetime,
// using a formatter for this specific pattern
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH:mm:ss-x"));
// print the parsing result
System.out.println(odt);
// then convert it to UTC (keeping the moment & adjusting the offset)
OffsetDateTime odtUtc = odt.withOffsetSameInstant(ZoneOffset.UTC);
// print the conversion result
System.out.println(odtUtc);
}
gives you the following output
2020-03-26T14:40:55+02:00
2020-03-26T12:40:55Z

java.time
I understand that you have got a field of type java.util.Date that you cannot afford to change just now. For any operation on that Date you should still convert it to a modern Instant first and then do your further work from there. For converting it to a string in UTC you may use the very simple:
String timeString = yourJavaUtilDate.toInstant().toString();
System.out.println(timeString);
Example output:
2020-03-26T12:40:55Z
While an Instant is a point in time without time zone or offset, just like a Date is, Instant.toString() produces a string in UTC in ISO 8601 format, which I find quite nice. If you wanted the peculiar format mentioned in your question (you must have very special reasons for that):
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("yyyy-MM-dd-HH:mm:ss-Z");
String timeString = yourJavaUtilDate.toInstant()
.atOffset(ZoneOffset.UTC)
.format(formatter);
2020-03-26-12:40:55-+0000
Your format pattern string from SimpleDateFormat works with the modern DateTimeFormatter too. That is not always the case, there are differences between the sets of format pattern letters for the two, only many of the letters have the same or similar meaning.
I am myself working with a very old code base that over the years has acquired a messy mixture of outdated and modern date and time classes. So we’re constantly converting back and forth between Date, LocalDate, OffsetDateTime, XMLGregorianCalendar and many other classes, which is far from ideal, and it will take some years still to get to the sweet spot where we will be using the modern classes only.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601

Related

Format a XMLGregorianCalendar with Italian date format(dd/mm/yyyy) with no time [duplicate]

This question already has answers here:
want current date and time in "dd/MM/yyyy HH:mm:ss.SS" format
(11 answers)
Closed 4 years ago.
I am trying to convert a java.util.Date to XMLGregorianCalendar in Italian format (dd/mm/yyyy) with no time. Whatever I try the output always prints yyyy-mm-dd.
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Rome"));
XMLGregorianCalendar xmlDate = DatatypeFactory.newInstance().newXMLGregorianCalendarDate(cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH),
DatatypeConstants.FIELD_UNDEFINED);
System.out.println(xmlDate);
I am a consumer of SOAP web-service, and the date attribute is defined as XMLGregorianCalendar.
Please advise how can I change the code to get the output with format (dd/mm/yyyy).
You don’t need an XMLGregorianCalender. It will not, cannot give you what you ask for. Instead you need a LocalDate and a DateTimeFormatter:
DateTimeFormatter italianDateFormatter
= DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.ITALIAN);
LocalDate date = LocalDate.now(ZoneId.of("Europe/Rome"));
String formattedDate = date.format(italianDateFormatter);
System.out.println(formattedDate);
When I ran this code today, the output was:
21/01/19
The difference from what you ask for is the two digit year, 19. If instead of FormatStyle.SHORT we use FormatStyle.MEDIUM, we get four digit year, but the month as a three letter abbreviaiton:
21-gen-2019
The advantage is that the code lends itself very well to internationalization: you just need to change the locale to get proper code for some other language and country. If you do need 21/01/2019 (with four digit year), specify the format explicitly using a pattern:
DateTimeFormatter italianDateFormatter
= DateTimeFormatter.ofPattern("dd/MM/uuuu");
21/01/2019
What’s wrong with using XMLGregorianCalendar?
When you call System.out.println(xmlDate), you are implicitly calling toString on your XMLGregorianCalendar. When it hasn’t got time and zone offset, toString always generates yyyy-MM-dd format, there is no way you can change that. On the other hand you can have any format you like in a String. Next obstacle is, there is no formatter that can format an XMLGregorianCalendar directly. You would need to convert to a different type, like ZonedDateTime, for example, first. Since you only want the date, neither the time of day nor the time zone, it’s simpler and easier to start out from LocalDate from the start. Not least for those reading and maintaining your code after you.
Your question mentions java.util.Date and your code uses GregorianCalendar too. Both of those classes are poorly designed and long outdated, fully replaced by java.time, the modern Java date and time API. So I suggest you don’t use them.
Link
Oracle tutorial: Date Time explaining how to use java.time, the modern Java date and time API.

Single class to parse any Date Format in Java

I have been parsing dates in the below formats. I maintain an array of these formats and parse every date string in all these formats.
The code I used was -
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
simpleDateFormat.setTimeZone(timeZone); //timeZone is a java.util.TimeZone object
Date date = simpleDateFormat.parse(dateString);
Now I want to parse yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX format as well but using SimpleDateFormat the 6 digit microseconds are not considered. So I looked into java.time package.
To parse yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX formats I will be needing OffsetDateTime class and for other formats, I need ZonedDateTime class. The format will be set in DateTimeFormatter class.
Is there a way to use a single class like SimpleDateFormat to pass all the formats?
Since your Java 8 doesn’t behave as would be reasonably expected, I suggest that a workaround is trying to parse without zone first. If a zone or an offset is parsed from the string, this will be used. If the parsing without zone fails, try with a zone. The following method does that:
private static void parseAndPrint(String formatPattern, String dateTimeString) {
// Try parsing without zone first
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern);
Instant parsedInstant;
try {
parsedInstant = formatter.parse(dateTimeString, Instant::from);
} catch (DateTimeParseException dtpe) {
// Try parsing with zone
ZoneId defaultZone = ZoneId.of("Asia/Calcutta");
formatter = formatter.withZone(defaultZone);
parsedInstant = formatter.parse(dateTimeString, Instant::from);
}
System.out.println("Parsed instant: " + parsedInstant);
}
Let’s try it:
parseAndPrint("yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", "2018-10-22T02:17:58.717853Z");
parseAndPrint("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", "2018-10-22T02:17:58.717853");
parseAndPrint("EEE MMM d HH:mm:ss zzz yyyy", "Mon Oct 22 02:17:58 CEST 2018");
Output on Java 8 is:
Parsed instant: 2018-10-22T02:17:58.717853Z
Parsed instant: 2018-10-21T20:47:58.717853Z
Parsed instant: 2018-10-22T00:17:58Z
The first example has an offset in the string and the last a time zone abbreviation in the string, and in both cases are these respected: the instant printed has adjusted the time into UTC (since an Instant always prints in UTC, its toString method makes sure). The middle example has got neither offset nor time zone in the string, so uses the default time zone of Asia/Calcutta specified in the method.
That said, parsing a three or four letter time zone abbreviation like CEST is a dangerous and discouraged practice since the abbreviations are often ambiguous. I included the example for demonstration only.
Is there a way to use a single class…?
I have used Instant for all cases, so yes there is a way to use just one class. The limitation is that you do not know afterward whether any time zone or offset was in the string nor what it was. You didn’t know when you were using SimpleDateFormat and Date either, so I figured it was OK?
A bug in Java 8?
The results from your demonstration on REX tester are disappointing and wrong and do not agree with the results I got on Java 11. It seems to me that you have been hit by a bug in Java 8, possibly this one: Parsing with DateTimeFormatter.withZone does not behave as described in javadocs.

DateTime Manipulation in C# vs Java

I'm new to Java. I have a time I am getting from a web-page, this is in the "hh:mm" format (not 24 hour). This comes to me as a string. I then want to combine this string with todays date in order to make a Java Date I can use.
In C#:
string s = "5:45 PM";
DateTime d;
DateTime.TryParse(s, out d);
in Java I have attempted:
String s = "5:45 PM";
Date d = new Date(); // Which instantiates with the current date/time.
String[] arr = s.split(" ");
boolean isPm = arr[1].compareToIgnoreCase("PM") == 0;
arr = arr[0].split(":");
int hours = Integer.parseInt(arr[0]);
d.setHours(isPm ? hours + 12 : hours);
d.setMinutes(Integer.parseInt(arr[1]));
d.setSeconds(0);
Is there a better way to achieve what I want?
Is there a better way to achieve what I want?
Absolutely - in both .NET and in Java, in fact. In .NET I'd (in a biased way) recommend using Noda Time so you can represent just a time of day as a LocalTime, parsing precisely the pattern you expect.
In Java 8 you can do the same thing with java.time.LocalTime:
import java.time.*;
import java.time.format.*;
public class Test {
public static void main(String[] args) {
String text = "5:45 PM";
DateTimeFormatter format = DateTimeFormatter.ofPattern("h:mm a");
LocalTime time = LocalTime.parse(text, format);
System.out.println(time);
}
}
Once you've parsed the text you've got into an appropriate type, you can combine it with other types. For example, to get a ZonedDateTime in the system time zone, using today's date and the specified time of day, you might use:
ZonedDateTime zoned = ZonedDateTime.now().with(time);
That uses the system time zone and clock by default, making it hard to test - I'd recommend passing in a Clock for testability.
(The same sort of thing is available in Noda Time, but slightly differently. Let me know if you need details.)
I would strongly recommend against using java.util.Date, which just represents an instant in time and has an awful API.
The key points here are:
Parse the text with a well-specified format
Parse the text into a type that represents the information it conveys: a time of day
Combine that value with another value which should also be carefully specified (in terms of clock and time zone)
All of these will lead to clear, reliable, testable code. (And the existing .NET code doesn't meet any of those bullet points, IMO.)
To parse the time, you can do as explained in #Jon Skeet's answer:
String input = "5:45 PM";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("h:mm a", Locale.ENGLISH);
LocalTime time = LocalTime.parse(input, parser);
Note that I also used a java.util.Locale because if you don't specify it, it'll use the system's default locale - and some locales can use different symbols for AM/PM field. Using an explicit locale avoids this corner-case (and the default locale can also be changed, even at runtime, so it's better to use an explicit one).
To combine with the today's date, you'll need a java.time.LocalDate (to get the date) and combine with the LocalTime, to get a LocalDateTime:
// combine with today's date
LocalDateTime combined = LocalDate.now().atTime(time);
Then you can format the LocalDateTime using another formatter:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
System.out.println(combined.format(fmt));
The output is:
16/08/2017 17:45
If you want to convert the LocalDateTime to a java.util.Date, you must take care of some details.
A java.util.Date represents the number of milliseconds since 1970-01-01T00:00Z (aka Unix Epoch). It's an instant (a specific point in time). Check this article for more info.
So, the same Date object can represent different dates or times, depending on where you are: think that, right now, at this moment, everybody in the world are in the same instant (the same number of milliseconds since 1970-01-01T00:00Z), but the local date and time is different in each part of the world.
A LocalDateTime represents this concept of "local": it's a date (day, month and year) and a time (hour, minute, second and nanosecond), but without any relation to a specific timezone.
The same LocalDateTime object can represent different instants in time in different timezones. So, to convert it to a Date, you must define in what timezone you want it.
One option is to use the system's default timezone:
// convert to system's default timezone
ZonedDateTime atDefaultTimezone = combined.atZone(ZoneId.systemDefault());
// convert to java.util.Date
Date date = Date.from(atDefaultTimezone.toInstant());
But the default can vary from system/environment, and can also be changed, even at runtime. To not depend on that and have more control over it, you can use an explicit zone:
// convert to a specific timezone
ZonedDateTime zdt = combined.atZone(ZoneId.of("Europe/London"));
// convert to java.util.Date
Date date = Date.from(zdt.toInstant());
Note that I used Europe/London. The API uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin).
Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().
And there's also the corner cases of Daylight Saving Time (when a LocalDateTime can exist twice or can't exist due to overlaps and gaps). In this case, Jon's solution using ZonedDateTime avoids this problem).

Java DateFormat for UTC [duplicate]

This question already has answers here:
Converting ISO 8601-compliant String to java.util.Date
(31 answers)
Closed 5 years ago.
I have a DateFormat like
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
I have a date like this:
2017-02-23T11:00:04.072625
This "date" is a UTC date (Z is null). This parses fine. However, it seems to be interpreted as the timezone of the machine. So, my machine is EST, this ends up as
2017-02-23T11:00:04.072625-0500
Which is wrong. I can explicitly set the timezone with like
format.setTimeZone(TimeZone.getTimeZone("UTC"));
But if the time came in a different zone in the future, that would not be right either.
If I add a "Z" or "z" to the SimpleDateFormat string, this fails to parse.
Any ideas on how to handle this correctly?
First, since you are using Java 8, start by discarding the old classes Date, DateFormat, SimpleDateFormat and TimeZone. They will more likely than not give you trouble you don’t want. Instead use the classes in java.time introduces in Java 8. Here you go:
String yourDateTime = "2017-02-23T11:00:04.072625";
LocalDateTime ldt = LocalDateTime.parse(yourDateTime);
Instant i = ldt.atOffset(ZoneOffset.UTC).toInstant();
Instant is the new class that best corresponds to the old Date. In this case you get
2017-02-23T11:00:04.072625Z
(Z means UTC)
If in the future you get the date in a different time zone, you may do something like
ZoneId zi = ZoneId.of("Europe/Paris");
Instant i = ldt.atZone(zi).toInstant();
In this case you get instead
2017-02-23T10:00:04.072625Z
Should you for some reason require an oldfashioned Date instance, for example for use with old code, it’s easy:
Date oldfashionedDate = Date.from(i);

How do I convert an ISO 8601 date time String to java.time.LocalDateTime?

I am reading data from Wikidata. They represent their point in time property, P585 using ISO 8601 spec. However, the same beings with a +.
If I were using Joda then converting the String to joda dateTime would have been very simple.
new DateTime(dateTime, DateTimeZone.UTC);
However, when I do LocalDateTime.parse("+2017-02-26T00:00:00Z") I get an error saying can't parse the character at index 0. Is there a reason for this in Java 8. Joda does it pretty easily without any errors.
I also tried LocalDateTime.parse("+2017-02-26T00:00:00Z", DateTimeFormatter.ISO_DATE_TIME) but in vain.
How do we get around the Plus sign without having to remove it by string manipulation?
Java's DateTimeFormatter class may be of help. Here is some sample code that I believe addressses your problem (or at least gives you something to think about):
class DateTimeTester {
---
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("+yyyy-MM-dd'T'HH:mm:ss'Z'")
.withZone(ZoneId.of("UTC"));
LocalDateTime date = LocalDateTime.parse("+2017-02-26T01:02:03Z", formatter);
System.out.println(date);
}
}
Refer to the section called Patterns for Formatting and Parsing in the javadoc for DateTimeFormatter for details about the format-string passed to DateTimeFormatter.ofPattern().
Something to note:
Wikidata's list of available data types says about the time data type:
"explicit value for point in time, represented as a timestamp resembling ISO 8601, e.g. +2013-01-01T00:00:00Z. The year is always signed and padded to have between 4 and 16 digits.".
So Wikidata's time data type only resembles ISO 8601.
If your code needs to handle negative years (before year 0), you will need to adjust my suggested solution accordingly.
Unfortunately, the accepted answer is wrong for three reasons:
The given date-time string +2017-02-26T00:00:00Z does not represent a LocalDateTime. It represents an OffsetDateTime.
It uses a + sign with the pattern which means the pattern will fail to parse a date-time with a negative year.
'Z' is not the same as Z.
You can create the required DateTimeFormatter using the DateTimeFormatterBuilder as shown below:
class Main {
public static void main(String[] args) {
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4, 4, SignStyle.ALWAYS)
.appendPattern("-MM-dd'T'HH:mm:ssXXX")
.toFormatter(Locale.ENGLISH);
OffsetDateTime odt = OffsetDateTime.parse("+2017-02-26T00:00:00Z", parser);
System.out.println(odt);
// If you want a LocalDateTime, you can get it from `odt`
LocalDateTime ldt = odt.toLocalDateTime();
System.out.println(ldt);
}
}
Output:
2017-02-26T00:00Z
2017-02-26T00:00
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.
A quick solution can be :
LocalDateTime.parse(yourDate, DateTimeFormatter.ISO_DATE_TIME);
Don't forget to manage the DateTimeParseException in the case of a non supported date format 😉

Categories