Преобразование строки, соответствующей ISO 8601, в java.util.Date - PullRequest
602 голосов
/ 04 февраля 2010

Я пытаюсь преобразовать строку формата ISO 8601 в java.util.Date.

Я обнаружил, что шаблон yyyy-MM-dd'T'HH:mm:ssZ соответствует ISO8601, если используется с локалью (сравните образец).

Однако, используя java.text.SimpleDateFormat, я не могу преобразовать правильно отформатированную строку 2010-01-01T12:00:00+01:00. Я должен сначала преобразовать его в 2010-01-01T12:00:00+0100, без двоеточия.

Итак, текущее решение

SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));

что явно не так приятно. Я что-то упустил или есть лучшее решение?


Ответ

Благодаря комментарию JuanZe я обнаружил магию Joda-Time , также описанную здесь .

Итак, решение

DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));

Или, проще, используйте парсер по умолчанию через конструктор:

DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;

Мне это приятно.

Ответы [ 26 ]

445 голосов
/ 04 февраля 2010

К сожалению, форматы часовых поясов, доступные для SimpleDateFormat (Java 6 и более ранние версии), не ISO 8601 . SimpleDateFormat понимает строки часового пояса, такие как «GMT + 01: 00» или «+0100», последний в соответствии с RFC # 822 .

Даже если в Java 7 добавлена ​​поддержка дескрипторов часовых поясов в соответствии с ISO 8601, SimpleDateFormat по-прежнему не может правильно проанализировать полную строку даты, поскольку не поддерживает дополнительные части.

Переформатирование вашей входной строки с использованием regexp, безусловно, является одной из возможностей, но правила замены не так просты, как в вашем вопросе:

  • В некоторых часовых поясах не работают полные часы UTC , поэтому строка не обязательно заканчивается на ": 00".
  • ISO8601 позволяет включать в часовой пояс только количество часов, поэтому «+01» эквивалентно «+01: 00»
  • ISO8601 позволяет использовать «Z» для обозначения UTC вместо «+00: 00».

Возможно, более простым решением является использование преобразователя типов данных в JAXB, поскольку JAXB должен иметь возможность анализировать строку даты ISO8601 в соответствии со спецификацией схемы XML. javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z") даст вам Calendar объект, и вы можете просто использовать getTime (), если вам нужен Date объект.

Возможно, вы могли бы также использовать Joda-Time , но я не знаю, почему вы должны беспокоиться об этом.

198 голосов
/ 13 августа 2013

Способ, которым благословил документацию Java 7 :

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);

DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);

Вы можете найти больше примеров в разделе Примеры в SimpleDateFormat javadoc .

198 голосов
/ 16 мая 2012

Хорошо, на этот вопрос уже дан ответ, но я все равно оставлю свой ответ. Это может кому-то помочь.

Я искал решение для Android (API 7).

  • Йода не могла быть и речи - она ​​огромна и страдает от медленной инициализации. Для этой конкретной цели это также казалось чрезмерным излишеством.
  • Ответы, включающие javax.xml, не будут работать на Android API 7.

Заканчивается реализация этого простого класса. Он охватывает только наиболее распространенную форму строк ISO 8601, но в некоторых случаях этого должно быть достаточно (если вы абсолютно уверены, что ввод будет в этом формате).

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * Helper class for handling a most common subset of ISO 8601 strings
 * (in the following format: "2008-03-01T13:00:00+01:00"). It supports
 * parsing the "Z" timezone, but many other less-used features are
 * missing.
 */
public final class ISO8601 {
    /** Transform Calendar to ISO 8601 string. */
    public static String fromCalendar(final Calendar calendar) {
        Date date = calendar.getTime();
        String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .format(date);
        return formatted.substring(0, 22) + ":" + formatted.substring(22);
    }

    /** Get current date and time formatted as ISO 8601 string. */
    public static String now() {
        return fromCalendar(GregorianCalendar.getInstance());
    }

    /** Transform ISO 8601 string to Calendar. */
    public static Calendar toCalendar(final String iso8601string)
            throws ParseException {
        Calendar calendar = GregorianCalendar.getInstance();
        String s = iso8601string.replace("Z", "+00:00");
        try {
            s = s.substring(0, 22) + s.substring(23);  // to get rid of the ":"
        } catch (IndexOutOfBoundsException e) {
            throw new ParseException("Invalid length", 0);
        }
        Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
        calendar.setTime(date);
        return calendar;
    }
}

Примечание по производительности: Я создаю новый экземпляр SimpleDateFormat каждый раз, чтобы избежать ошибки в Android 2.1. Если вы удивлены так же, как и я, посмотрите эту загадку . Для других Java-движков вы можете кэшировать экземпляр в закрытом статическом поле (используя ThreadLocal, чтобы обеспечить безопасность потоков).

91 голосов
/ 15 декабря 2014

java.time

java.time API (встроенный в Java 8 и более поздние версии) делает это немного проще.

Если вы знаете, что ввод находится в UTC , например, Z (для зулу) на конце, класс Instant может анализировать.

java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));

Если ваш ввод может быть другим смещением от UTC значений вместо UTC , обозначенным Z (Zulu) на конце, используйте OffsetDateTime класс для разбора.

OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );

Затем извлеките Instant и преобразуйте в java.util.Date, позвонив по номеру from.

.
Instant instant = odt.toInstant();  // Instant is always in UTC.
java.util.Date date = java.util.Date.from( instant );
64 голосов
/ 05 июля 2013

Библиотека Jackson-databind также имеет ISO8601DateFormat class , который делает это (фактическая реализация в ISO8601Utils .

ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");
41 голосов
/ 14 декабря 2013

ТЛ; др

OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )

Использование java.time

Новый пакет java.time в Java 8 и более поздних версиях был вдохновлен Joda-Time.

Класс OffsetDateTime представляет момент на временной шкале с смещением от UTC , но не с часовым поясом.

OffsetDateTime odt = OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" );

При вызове toString генерируется строка в стандартном формате ISO 8601:

2010-01-01T12: 00 + 01: 00

Чтобы увидеть то же значение через объектив UTC, извлеките Instant или настройте смещение от +01:00 до 00:00.

Instant instant = odt.toInstant();  

... или ...

OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );

При необходимости настройте часовой пояс. часовой пояс - это история значений offset-from-UTC для региона с набором правил для обработки аномалий, таких как переход на летнее время (DST). Поэтому применяйте часовой пояс, а не просто смещение, когда это возможно.

ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );

О java.time

Фреймворк java.time встроен в Java 8 и более поздние версии. Эти классы заменяют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , теперь в режиме обслуживания , рекомендует перейти на классы java.time .

Чтобы узнать больше, см. Oracle Tutorial . И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310 .

Вы можете обмениваться java.time объектами напрямую с вашей базой данных. Используйте драйвер JDBC , совместимый с JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в java.sql.* классах.

Где взять классы java.time?

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и more .


27 голосов
/ 08 апреля 2014

Для Java версии 7

Вы можете следовать документации Oracle: http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

X - используется для часового пояса ISO 8601

TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());

System.out.println(nowAsISO);

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
//nowAsISO = "2013-05-31T00:00:00Z";
Date finalResult = df1.parse(nowAsISO);

System.out.println(finalResult);
20 голосов
/ 13 апреля 2011

Решение DatatypeConverter работает не на всех виртуальных машинах. У меня работает следующее:

javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()

Я обнаружил, что joda не работает "из коробки" (специально для приведенного выше примера с часовым поясом на дату, которая должна быть действительной)

14 голосов
/ 27 сентября 2012

Я думаю, что мы должны использовать

DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")

для даты 2010-01-01T12:00:00Z

9 голосов
/ 22 августа 2013

Еще один очень простой способ анализа меток времени ISO8601 - использовать org.apache.commons.lang.time.DateUtils:

import static org.junit.Assert.assertEquals;

import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;

public class ISO8601TimestampFormatTest {
  @Test
  public void parse() throws ParseException {
    Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
    assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
  }
}
...