-1

I'm trying to convert my String in Date + Timezone. I get my String from a DateTime Variable (here: xyz). My code:

String abc = xyz.toString("yyyy-MM-ddZZ");
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-ddXXX");
java.util.Date date = sdf.parse(abc);
System.out.println("Date: " + sdf.format(date));

Error:

Invalid format: "2017-01-03+01:00" is malformed at "+01:00"

If I try SimpleDateFormat("yyyy-MM-dd"); it works but without the Timezone ("+01:00")

Alexa666
  • 9
  • 3
  • 4
    Possible duplicate of [Java string to date conversion](https://stackoverflow.com/questions/4216745/java-string-to-date-conversion) – Flika205 Jan 24 '18 at 09:32
  • The timezone had to be placed after a time, which is a thing you don't have in your String – DamCx Jan 24 '18 at 09:34
  • @DamCx If I print out my String, I have the Timezone in it. It's from a Datetime Format. – Alexa666 Jan 24 '18 at 09:40
  • Yes, with time, but in the format you want to parse (`yyyy-MM-ddXXX`), you don't have the Time and `SimpleDateFormat` will take it for a `Date` – DamCx Jan 24 '18 at 09:43
  • 2
    Any reason why you are still trying to use the long outdated and notoriously troublesome `SimpleDateFormat` class and its equally outdated friend `Date`? [`java.time`, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) is so much nicer to work with. – Ole V.V. Jan 24 '18 at 09:45
  • 1
    A `Date` cannot contain a time-zone, but you can have the offset from UTC information separately. – Ole V.V. Jan 24 '18 at 09:46
  • @OleV.V. How can I do it? It's so new for me. – Alexa666 Jan 24 '18 at 09:58
  • @OleV.V. Thank you! – Alexa666 Jan 24 '18 at 10:04
  • See this answer: https://stackoverflow.com/a/41796443/5207900 to a question similar to yours. – Marteng Jan 24 '18 at 10:29
  • A date-only with an offset-from-UTC makes no sense by itself. – Basil Bourque Jan 26 '18 at 00:36

3 Answers3

1

The input has a date - year, month, day - and an offset - the difference from UTC - but to build a java.util.Date, you also need the time: hour, minutes, seconds, fraction of seconds.

SimpleDateFormat is terrible because it does some "magic", setting the missing fields to default values. Another problem is that the X pattern doesn't work for all Java versions, and the documentation sucks.

You can use the new Java 8 classes, as explained. With them, you can parse the input, choose the default values to be used for the time fields and convert to java.util.Date, if that's what you need:

DateTimeFormatter fmt = new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_OFFSET_DATE)
    // set hour to midnight
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0).toFormatter();

OffsetDateTime odt = OffsetDateTime.parse("2017-01-03+01:00", fmt); // 2017-01-03T00:00+01:00

The OffsetDateTime will have the time set to midnight, but you can change it to whatever values you need, while with SimpleDateFormat it's not possible, because it uses internal default values and you can't control it.

And the date and offset were correctly set to the values in the input string. You can then convert to java.util.Date if you want:

Date date = Date.from(odt.toInstant());

You can also get the individual "pieces" of the date if you want:

// get just the date
LocalDate localDate = odt.toLocalDate(); // 2017-01-03
// get just the offset
ZoneOffset offset = odt.getOffset(); // +01:00

PS: the offset +01:00 is not the same thing as a timezone. See the difference here

wss
  • 70
  • 4
  • Very elegant solution to get close to a “date with time zone”, as the title asked for. Upvoted. – Ole V.V. Jan 31 '18 at 15:23
0
    String abc = "2017-01-03+01:00";
    TemporalAccessor parsed = DateTimeFormatter.ISO_OFFSET_DATE.parse(abc);
    LocalDate date = LocalDate.from(parsed);
    ZoneOffset offset = ZoneOffset.from(parsed);
    System.out.println("Date: " + date + "; offset: " + offset + '.');

This prints:

Date: 2017-01-03; offset: +01:00.

I am using java.time, the modern Java date and time API, and recommend you do the same. The Date class is long outdated (sorry, no pun intended) and SimpleDateFormat in particular notoriously troublesome. Don’t use them. The modern API is so much nicer to work with. Only if you need a java.util.Date and/or a java.util.TimeZone for a legacy API that you cannot change, convert like this:

    Date oldfashionedDate = Date.from(date.atStartOfDay(offset).toInstant());
    TimeZone oldfashionedTimeZone = TimeZone.getTimeZone(offset);
    System.out.println("Old-fashioned date: " + oldfashionedDate
            + "; old-fashioned time-zone: " + oldfashionedTimeZone.getDisplayName() + '.');

On my computer this prints:

Old-fashioned date: Tue Jan 03 00:00:00 CET 2017; old-fashioned time-zone: GMT+01:00.

I happen to be in a time zone that agrees with your offset from UTC, so it’s fairly obvious that the conversion has given the correct result. In other time zones the output will be more confusing because Date.toString() uses the JVM’s time zone setting for generating the string, but the Date will still be correct.

A date with a time zone? Neither a LocalDate nor a Date can hold a time zone in them, so you need to have the offset information separately. Interestingly your string seems to follow a “ISO-8601-like” format for an offset date that is even represented by a built-in formatter that has ISO in its name. If Java had contained an OffsetDate or a ZonedDate class, I would have expected such a class to parse your string into just one object and even without an explicit formatter. Unfortunately no such class exists, not even in the ThreeTen-Extra project, as far as I can tell at a glance.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Thank you for your detailed answer. I get an error at LocalDate-line: Instance method 'from' cannot be invoked in static context. Can you help me? – Alexa666 Jan 24 '18 at 13:18
  • Klingt merkwürdig, @Alexa666. `LocalDate.from` is a static method (not an instance method; see [the documentation](https://docs.oracle.com/javase/9/docs/api/java/time/LocalDate.html#from-java.time.temporal.TemporalAccessor-)). Which `LocalDate` class have you imported? (Look for something like `import xxxx.xxxx.LocalDate;` near the top of your Java file.) It should be `java.time.LocalDate` (or if using the ThreeTen Backport on Java 6 or 7, `org.threeten.bp.LocalDate`). – Ole V.V. Jan 24 '18 at 13:26
  • Hmm actually it is `import java.time.LocalDate;` – Alexa666 Jan 24 '18 at 13:34
  • I wish I could help, @Alexa666. At the bottom of my answer I have added a link to where you can see my code run. Maybe you can compare, or copy my entire program and see whether you can make it run on your computer. If all else fails, you may post a new question with an [MCVE](https://stackoverflow.com/help/mcve). – Ole V.V. Jan 24 '18 at 13:42
  • The class `OffsetDate` had once existed in jsr310-project but was finally thrown out by Oracle because it was considered as not so useful (its main purpose is just to build a bridge to xml-schema type xsd:date with offset). – Meno Hochschild Jan 24 '18 at 15:53
  • About your claim that an offset date is ISO-8601, I think it is not (have not found any mention in the original paper of ISO which instead speaks about offsets only in context of local time components). – Meno Hochschild Jan 24 '18 at 16:07
  • Thanks, @Meno, for you (as always) knowledgeable comments. I have edited. To me, conceptually an “offset date” and a “zoned date” still make perfect sense, and this SO question is not the first about an “offset date”; see for example [Java 8 Offset Date Parsing](https://stackoverflow.com/questions/34810324/java-8-offset-date-parsing). – Ole V.V. Jan 24 '18 at 16:48
  • Just discovered that the formatter is built in. My code became one line shorter. – Ole V.V. Jan 29 '18 at 21:41
0

"2017-01-03+01:00"

I thought it a similar ISO 8601 format date string, but actually not ISO 8601. Thanks @Meno Hochschild and @Basil Bourque's indication.

It is so luck that this method works for such format's string: javax.xml.bind.DatatypeConverter.parseDateTime, it will return a Calendar:

System.out.println(DatatypeConverter.parseDate("2017-01-03+01:00").getTime());

Output:

Tue Jan 03 07:00:00 CST 2017

From the method javadoc:

public static Calendar parseDate(String lexicalXSDDate)
Converts the string argument into a Calendar value.

Parameters: lexicalXSDDate - A string containing lexical representation of xsd:Date.

Returns: A Calendar value represented by the string argument.

Throws: IllegalArgumentException - if string parameter does not conform to lexical value space defined in XML Schema Part 2: Datatypes for xsd:Date.

wss
  • 70
  • 4
Shen Yudong
  • 1,190
  • 7
  • 14
  • 2
    It is not ISO-8601 (see the original paper - offsets require a time, too) but derived from XML-schema where it is an extra feature and was seemingly introduced for obscure symmetry reasons. – Meno Hochschild Jan 24 '18 at 16:05
  • 1
    No, that String is *not* ISO 8601 compliant, as noted by Meno Hochschild. The ISO 8601 formats are sensible and practical, whereas that string is neither. Please correct your Answer, or provide documentation to back your claim. – Basil Bourque Jan 24 '18 at 22:51
  • The solution looks elegant, and though the `Calendar` class returned by `parseDate` is capable of holding both the date and the “time zone” (which in this example isn’t a true time zone), `Calendar` is also poorly designed and long outdated. And its `getTime` method throws away the time zone information (as your example output shows). – Ole V.V. Jan 25 '18 at 08:22