Преобразование строки, совместимой с ISO 8601, в java.util.Date

Я пытаюсь преобразовать отформатированную строку 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" ) ;

Для меня это приятно.

Ответ 1

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

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

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

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

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

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

Ответ 2

Способ, который благословлен документацией по 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.

Ответ 3

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

Я искал решение для 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, чтобы быть потокобезопасным).

Ответ 4

java.time

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

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

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

Если ваш ввод может быть другим offset-from-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 );

Ответ 5

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

ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");

Ответ 6

ТЛ; др

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 );

При необходимости настройте часовой пояс. часовой пояс - это история значений смещения от 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. И поищите в Кару множество примеров и объяснений. Спецификация: JSR 310.

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

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

  • Java SE 8, Java SE 9, Java SE 10 и более поздние версии
    • Встроенный.
    • Часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые мелкие функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функциональности java.time перенесена на Java 6 & 7 в ThreeTen-Backport.
  • Android
    • Более поздние версии Android связывают реализации классов java.time.
    • Для более ранних версий Android (& lt; 26) проект ThreeTenABP адаптирует ThreeTen-Backport (упомянуто выше). Смотрите Как использовать ThreeTenABP....

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


Ответ 7

Для 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);

Ответ 8

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

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

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

Ответ 9

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

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

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

Ответ 10

Еще один очень простой способ разбора временных меток 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());
  }
}

Ответ 11

java.time

Обратите внимание, что в Java 8 вы можете использовать класс java.time.ZonedDateTime и его статический метод parse(CharSequence text).

Ответ 12

Обходной путь для Java 7+ использует SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);

Этот код может анализировать формат ISO8601, например:

  • 2017-05-17T06:01:43.785Z
  • 2017-05-13T02:58:21.391+01:00

Но на Java6, SimpleDateFormat не понимает символ X и будет выбрасывать IllegalArgumentException: Unknown pattern character 'X'
Нам нужно нормализовать дату ISO8601 в формате, читаемом на Java 6, с помощью SimpleDateFormat.

public static Date iso8601Format(String formattedDate) throws ParseException {
    try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
        return df.parse(formattedDate);
    } catch (IllegalArgumentException ex) {
        // error happen in Java 6: Unknown pattern character 'X'
        if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
        else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
        DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
        return df1.parse(formattedDate);
    }
}

Метод выше, чтобы заменить [ Z на +0000] или [+01:00 на +0100], когда ошибка возникает в Java 6 (вы можете обнаружить версию Java и заменить try/catch с помощью инструкции if).

Ответ 14

Я столкнулся с проблемой той же и решил ее следующим кодом.

 public static Calendar getCalendarFromISO(String datestring) {
    Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
    try {
        Date date = dateformat.parse(datestring);
        date.setHours(date.getHours() - 1);
        calendar.setTime(date);

        String test = dateformat.format(calendar.getTime());
        Log.e("TEST_TIME", test);

    } catch (ParseException e) {
        e.printStackTrace();
    }

    return calendar;
}

Раньше я использовал SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());

Но позже я обнаружил, что основной причиной исключения была yyyy-MM-dd'T'HH:mm:ss.SSSZ,

Итак, я использовал

SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());

Это сработало для меня.

Ответ 16

Как уже упоминалось, Android не имеет хорошего способа поддержки синтаксического анализа/форматирования дат ISO 8601 с использованием классов, включенных в SDK. Я написал этот код несколько раз, поэтому я, наконец, создал Gist, который включает в себя класс DateUtils, который поддерживает форматирование и синтаксический анализ дат ISO 8601 и RFC 1123. В Gist также есть тестовый пример, показывающий, что он поддерживает.

https://gist.github.com/mraccola/702330625fad8eebe7d3

Ответ 17

SimpleDateFormat для JAVA 1.7 имеет классный образец для формата ISO 8601.

Класс SimpleDateFormat

Вот что я сделал:

Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
         Locale.ENGLISH).format(System.currentTimeMillis());

Ответ 18

В Java есть десяток различных способов анализа даты-времени, как показывают превосходные ответы. Но несколько удивительно, ни один из Java классов времени не полностью выполняет ISO 8601!

С Java 8 я бы рекомендовал:

ZonedDateTime zp = ZonedDateTime.parse(string);
Date date = Date.from(zp.toInstant());

Это будет обрабатывать примеры как в формате UTC, так и со смещением, например "2017-09-13T10: 36: 40Z" или "2017-09-13T10: 36: 40 + 01: 00". Это будет сделано для большинства случаев использования.

Но он не будет обрабатывать такие примеры, как "2017-09-13T10: 36: 40 + 01", который является действительным датой даты ISO 8601.
Он также не будет обрабатывать только дату, например. "2017-09-13".

Если вам нужно обработать их, я бы предложил сначала использовать регулярное выражение, чтобы обнюхать синтаксис.

Там есть хороший список примеров ISO 8601 с большим количеством угловых случаев: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ Я не знаю ни одного Java-класса которые могли бы справиться со всеми из них.

Ответ 19

Сделайте это так:

public static void main(String[] args) throws ParseException {

    String dateStr = "2016-10-19T14:15:36+08:00";
    Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();

    System.out.println(date);

}

Вот результат:

Wed Oct 19 15:15:36 CST 2016

Ответ 20

Использовать строку как LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)

Ответ 21

У меня была аналогичная необходимость: мне нужно было разобрать любую дату, совместимую с ISO8601, не зная точного формата заранее, и я хотел бы облегченное решение, которое также будет работать на Android.

Когда я искал свои потребности, я наткнулся на этот вопрос и заметил, что AFAIU не отвечает полностью моим потребностям. Поэтому я разработал jISO8601 и нажал на центральную часть maven.

Просто добавьте в себя pom.xml:

<dependency>
  <groupId>fr.turri</groupId>
  <artifactId>jISO8601</artifactId>
  <version>0.2</version>
</dependency>

и тогда вам будет хорошо:

import fr.turri.jiso8601.*;
...
Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");

Надеется, что это поможет.

Ответ 22

Чтобы просто форматировать дату, подобную этой, мне работало в приложении на основе Java 6. В проекте тимелеафа есть класс DateFormat JacksonThymeleafISO8601DateFormat, который вставляет отсутствующий двоеточие:

https://github.com/thymeleaf/thymeleaf/blob/40d27f44df7b52eda47d1bc6f1b3012add6098b3/src/main/java/org/thymeleaf/standard/serializer/StandardJavaScriptSerializer.java

Я использовал его для совместимости формата даты ECMAScript.

Ответ 23

Я удивлен, что ни одна библиотека java не поддерживает все форматы даты ISO 8601 согласно https://en.wikipedia.org/wiki/ISO_8601. Joda DateTime поддерживала большинство из них, но не все, и поэтому я добавил собственную логику для обработки всех из них. Вот моя реализация.

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;

public class ISO8601DateUtils {
	
	/**
	 * It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.
	 * Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.
	 * @param dateTimeString ISO 8601 date time string
	 * @return
	 */
	public static DateTime parse(String dateTimeString) {
		try {
			return new DateTime( dateTimeString );
		} catch(Exception e) {
			try {
				Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);
				return new DateTime(dateTime.getTime());
			} catch (ParseException e1) {
				throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));
			}
		}
	}
  
  	private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {
			// upto millis
			"yyyyMMdd'T'HHmmssSSS'Z'",
			"yyyyMMdd'T'HHmmssSSSZ",
			"yyyyMMdd'T'HHmmssSSSXXX",
			
			"yyyy-MM-dd'T'HHmmssSSS'Z'",
			"yyyy-MM-dd'T'HHmmssSSSZ",
			"yyyy-MM-dd'T'HHmmssSSSXXX",
			
			// upto seconds
			"yyyyMMdd'T'HHmmss'Z'",
			"yyyyMMdd'T'HHmmssZ",
			"yyyyMMdd'T'HHmmssXXX",
			
			"yyyy-MM-dd'T'HHmmss'Z'", 
			"yyyy-MM-dd'T'HHmmssZ",
			"yyyy-MM-dd'T'HHmmssXXX",
			
			// upto minutes
			"yyyyMMdd'T'HHmm'Z'",
			"yyyyMMdd'T'HHmmZ",
			"yyyyMMdd'T'HHmmXXX",

			"yyyy-MM-dd'T'HHmm'Z'",
			"yyyy-MM-dd'T'HHmmZ",
			"yyyy-MM-dd'T'HHmmXXX",
			
			//upto hours is already supported by Joda DateTime
	};
}

Ответ 24

Небольшой тест, который показывает, как анализировать дату в ISO8601 и что LocalDateTime не обрабатывает DST.

 @Test
    public void shouldHandleDaylightSavingTimes() throws ParseException {

        //ISO8601 UTC date format
        SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

        // 1 hour of difference between 2 dates in UTC happening at the Daylight Saving Time
        Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
        Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");

        //Date 2 is before date 2
        Assert.assertTrue(d1.getTime() < d2.getTime());
        // And there is 1 hour difference between the 2 dates
        Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());

        //Print the dates in local time
        SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));
        localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));

        //Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)
        Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));
        Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));

        //Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)
        LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));
        LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));

        //Note that a localdatetime does not handle DST, therefore the 2 dates are the same
        Assert.assertEquals(ld1, ld2);

        //They both have the following local values
        Assert.assertEquals(2019, ld1.getYear());
        Assert.assertEquals(27, ld1.getDayOfMonth());
        Assert.assertEquals(10, ld1.getMonthValue());
        Assert.assertEquals(2, ld1.getHour());
        Assert.assertEquals(30, ld1.getMinute());
        Assert.assertEquals(0, ld1.getSecond());

    }

Ответ 25

Базовая функция Предоставлено: @wrygiel.

Эта функция может преобразовать формат ISO8601 в Java Date, которая может обрабатывать значения смещения. В соответствии с определение ISO 8601 смещение может упоминаться в разных форматах.

±[hh]:[mm]
±[hh][mm]
±[hh]

Eg:  "18:30Z", "22:30+04", "1130-0700", and "15:00-03:30" all mean the same time. - 06:30PM UTC

У этого класса есть статические методы для преобразования

  • Объект ISO8601 для объекта Date (Local TimeZone)
  • Дата по строке ISO8601
  • Летнее время автоматически вычисляется

Примеры строк ISO8601

/*       "2013-06-25T14:00:00Z";
         "2013-06-25T140000Z";
         "2013-06-25T14:00:00+04";
         "2013-06-25T14:00:00+0400";
         "2013-06-25T140000+0400";
         "2013-06-25T14:00:00-04";
         "2013-06-25T14:00:00-0400";
         "2013-06-25T140000-0400";*/


public class ISO8601DateFormatter {

private static final DateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
private static final DateFormat DATE_FORMAT_2 = new SimpleDateFormat("yyyy-MM-dd'T'HHmmssZ");
private static final String UTC_PLUS = "+";
private static final String UTC_MINUS = "-";

public static Date toDate(String iso8601string) throws ParseException {
    iso8601string = iso8601string.trim();
    if(iso8601string.toUpperCase().indexOf("Z")>0){
        iso8601string = iso8601string.toUpperCase().replace("Z", "+0000");
    }else if(((iso8601string.indexOf(UTC_PLUS))>0)){
        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_PLUS));
        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_PLUS), UTC_PLUS);
    }else if(((iso8601string.indexOf(UTC_MINUS))>0)){
        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_MINUS));
        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_MINUS), UTC_MINUS);
    }

    Date date = null;
    if(iso8601string.contains(":"))
        date = DATE_FORMAT_1.parse(iso8601string);
    else{
        date = DATE_FORMAT_2.parse(iso8601string);
    }
    return date;
}

public static String toISO8601String(Date date){
    return DATE_FORMAT_1.format(date);
}

private static String replaceColon(String sourceStr, int offsetIndex){
    if(sourceStr.substring(offsetIndex).contains(":"))
        return sourceStr.substring(0, offsetIndex) + sourceStr.substring(offsetIndex).replace(":", "");
    return sourceStr;
}

private static String appendZeros(String sourceStr, int offsetIndex, String offsetChar){
    if((sourceStr.length()-1)-sourceStr.indexOf(offsetChar,offsetIndex)<=2)
        return sourceStr + "00";
    return sourceStr;
}

}

Ответ 26

Мне показалось, что это лучше всего работает:

public static Date fromISO8601_( String string ) {

    try {
            return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ssXXX").parse ( string );
    } catch ( ParseException e ) {
        return Exceptions.handle (Date.class, "Not a valid ISO8601", e);
    }


}

Мне нужно было преобразовать строки строки даты JavaScript в Java. Я нашел вышеупомянутые работы с рекомендацией. Были некоторые примеры использования SimpleDateFormat, которые были близки, но они, похоже, не были подмножеством, как рекомендовано:

http://www.w3.org/TR/NOTE-datetime

и поддерживается строками PLIST и JavaScript, и это то, что мне нужно.

Это, по-видимому, самая распространенная форма строки ISO8601 и хорошее подмножество.

Приведем примеры:

1994-11-05T08:15:30-05:00 corresponds 
November 5, 1994, 8:15:30 am, US Eastern Standard Time.

 1994-11-05T13:15:30Z corresponds to the same instant.

У меня также есть быстрая версия:

final static int SHORT_ISO_8601_TIME_LENGTH =  "1994-11-05T08:15:30Z".length ();
                                            // 01234567890123456789012
final static int LONG_ISO_8601_TIME_LENGTH = "1994-11-05T08:15:30-05:00".length ();


public static Date fromISO8601( String string ) {
    if (isISO8601 ( string )) {
        char [] charArray = Reflection.toCharArray ( string );//uses unsafe or string.toCharArray if unsafe is not available
        int year = CharScanner.parseIntFromTo ( charArray, 0, 4 );
        int month = CharScanner.parseIntFromTo ( charArray, 5, 7 );
        int day = CharScanner.parseIntFromTo ( charArray, 8, 10 );
        int hour = CharScanner.parseIntFromTo ( charArray, 11, 13 );

        int minute = CharScanner.parseIntFromTo ( charArray, 14, 16 );

        int second = CharScanner.parseIntFromTo ( charArray, 17, 19 );

        TimeZone tz ;

         if (charArray[19] == 'Z') {

             tz = TimeZone.getTimeZone ( "GMT" );
         } else {

             StringBuilder builder = new StringBuilder ( 9 );
             builder.append ( "GMT" );
             builder.append( charArray, 19, LONG_ISO_8601_TIME_LENGTH - 19);
             String tzStr = builder.toString ();
             tz = TimeZone.getTimeZone ( tzStr ) ;

         }
         return toDate ( tz, year, month, day, hour, minute, second );

    }   else {
        return null;
    }

}

...

public static int parseIntFromTo ( char[] digitChars, int offset, int to ) {
    int num = digitChars[ offset ] - '0';
    if ( ++offset < to ) {
        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
        if ( ++offset < to ) {
            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
            if ( ++offset < to ) {
                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                if ( ++offset < to ) {
                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                    if ( ++offset < to ) {
                        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                        if ( ++offset < to ) {
                            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                            if ( ++offset < to ) {
                                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                if ( ++offset < to ) {
                                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return num;
}


public static boolean isISO8601( String string ) {
      boolean valid = true;

      if (string.length () == SHORT_ISO_8601_TIME_LENGTH) {
          valid &=  (string.charAt ( 19 )  == 'Z');

      } else if (string.length () == LONG_ISO_8601_TIME_LENGTH) {
          valid &=  (string.charAt ( 19 )  == '-' || string.charAt ( 19 )  == '+');
          valid &=  (string.charAt ( 22 )  == ':');

      } else {
          return false;
      }

    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
    // "1 9 9 4 - 1 1 - 0 5 T 0 8 : 1 5 : 3 0 - 0 5 : 0 0

    valid &=  (string.charAt ( 4 )  == '-') &&
                (string.charAt ( 7 )  == '-') &&
                (string.charAt ( 10 ) == 'T') &&
                (string.charAt ( 13 ) == ':') &&
                (string.charAt ( 16 ) == ':');

    return valid;
}

Я не тестировал это, но думаю, это будет довольно быстро. Кажется, это работает.:)

@Test
public void testIsoShortDate() {
    String test =  "1994-11-05T08:15:30Z";

    Date date = Dates.fromISO8601 ( test );
    Date date2 = Dates.fromISO8601_ ( test );

    assertEquals(date2.toString (), date.toString ());

    puts (date);
}

@Test
public void testIsoLongDate() {
    String test =  "1994-11-05T08:11:22-05:00";

    Date date = Dates.fromISO8601 ( test );
    Date date2 = Dates.fromISO8601_ ( test );

    assertEquals(date2.toString (), date.toString ());

    puts (date);
}

Ответ 27

Я думаю, что многие хотят сделать, это проанализировать строки даты JSON. Есть хороший шанс, если вы перейдете на эту страницу, чтобы вы могли конвертировать дату JSON JavaScript в дату Java.

Чтобы показать, как выглядит строка даты JSON:

    var d=new Date();
    var s = JSON.stringify(d);

    document.write(s);
    document.write("<br />"+d);


    "2013-12-14T01:55:33.412Z"
    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)

Строка даты JSON - 2013-12-14T01: 55: 33.412Z.

Даты не покрываются спецификацией JSON для одного слова, но выше это очень специфический формат ISO 8601, тогда как ISO_8601 намного больше, и это просто подмножество, хотя и очень важное.

См. http://www.json.org См. http://en.wikipedia.org/wiki/ISO_8601 См. http://www.w3.org/TR/NOTE-datetime

Как это бывает, я написал парсер JSON и парсер PLIST, оба из которых используют ISO-8601, но не одни и те же.

/*
    var d=new Date();
    var s = JSON.stringify(d);

    document.write(s);
    document.write("<br />"+d);


    "2013-12-14T01:55:33.412Z"
    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)


 */
@Test
public void jsonJavaScriptDate() {
    String test =  "2013-12-14T01:55:33.412Z";

    Date date = Dates.fromJsonDate ( test );
    Date date2 = Dates.fromJsonDate_ ( test );

    assertEquals(date2.toString (), "" + date);

    puts (date);
}

Я написал два способа сделать это для своего проекта. Один стандарт, один быстрый.

Опять же, строка даты JSON - это очень специфическая реализация ISO 8601....

(Я отправил другой в другом ответе, который должен работать для дат PLIST, которые отличаются от ISO 8601).

Дата JSON выглядит следующим образом:

public static Date fromJsonDate_( String string ) {

    try {

        return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse ( string );
    } catch ( ParseException e ) {
        return Exceptions.handle (Date.class, "Not a valid JSON date", e);
    }


}

Файлы PLIST (ASCII non GNUNext) также используют ISO 8601, но без милисекунд, поэтому... не все даты ISO-8601 одинаковы. (По крайней мере, я еще не нашел тот, который использует milis еще, и синтаксический анализатор, который я видел, пропускает часовой пояс вообще OMG).

Теперь для быстрой версии (вы можете найти ее в Boon).

public static Date fromJsonDate( String string ) {

    return fromJsonDate ( Reflection.toCharArray ( string ), 0, string.length () );

}

Обратите внимание, что Reflection.toCharArray использует небезопасные, если они доступны, но по умолчанию используется string.toCharArray, если нет.

(Вы можете извлечь его из примера, заменив Reflection.toCharArray(string) на string.toCharArray()).

public static Date fromJsonDate( char[] charArray, int from, int to ) {

    if (isJsonDate ( charArray, from, to )) {
        int year = CharScanner.parseIntFromTo ( charArray, from + 0, from + 4 );
        int month = CharScanner.parseIntFromTo ( charArray,  from +5,  from +7 );
        int day = CharScanner.parseIntFromTo ( charArray,  from +8,  from +10 );
        int hour = CharScanner.parseIntFromTo ( charArray,  from +11,  from +13 );

        int minute = CharScanner.parseIntFromTo ( charArray,  from +14,  from +16 );

        int second = CharScanner.parseIntFromTo ( charArray,  from +17,  from +19 );

        int miliseconds = CharScanner.parseIntFromTo ( charArray,  from +20,  from +23 );

        TimeZone tz = TimeZone.getTimeZone ( "GMT" );


        return toDate ( tz, year, month, day, hour, minute, second, miliseconds );

    }   else {
        return null;
    }

}

isJsonDate реализуется следующим образом:

public static boolean isJsonDate( char[] charArray, int start, int to ) {
    boolean valid = true;
    final int length = to -start;

    if (length != JSON_TIME_LENGTH) {
        return false;
    }

    valid &=  (charArray [ start + 19 ]  == '.');

    if (!valid) {
        return false;
    }


    valid &=  (charArray[  start +4 ]  == '-') &&
            (charArray[  start +7 ]  == '-') &&
            (charArray[  start +10 ] == 'T') &&
            (charArray[  start +13 ] == ':') &&
            (charArray[  start +16 ] == ':');

    return valid;
}

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

public static int parseIntFromTo ( char[] digitChars, int offset, int to ) {
    int num = digitChars[ offset ] - '0';
    if ( ++offset < to ) {
        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
        if ( ++offset < to ) {
            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
            if ( ++offset < to ) {
                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                if ( ++offset < to ) {
                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                    if ( ++offset < to ) {
                        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                        if ( ++offset < to ) {
                            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                            if ( ++offset < to ) {
                                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                if ( ++offset < to ) {
                                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return num;
}

См. https://github.com/RichardHightower/boon Boon имеет парсер PLIST (ASCII) и парсер JSON.

Парсер JSON является самым быстрым парсером Java JSON, о котором я знаю.

Независимо проверяется партизанами из Gatling.

https://github.com/gatling/json-parsers-benchmark

Benchmark                               Mode Thr     Count  Sec         Mean   Mean error        Units
BoonCharArrayBenchmark.roundRobin      thrpt  16        10    1   724815,875    54339,825    ops/s
JacksonObjectBenchmark.roundRobin      thrpt  16        10    1   580014,875   145097,700    ops/s
JsonSmartBytesBenchmark.roundRobin     thrpt  16        10    1   575548,435    64202,618    ops/s
JsonSmartStringBenchmark.roundRobin    thrpt  16        10    1   541212,220    45144,815    ops/s
GSONStringBenchmark.roundRobin         thrpt  16        10    1   522947,175    65572,427    ops/s
BoonDirectBytesBenchmark.roundRobin    thrpt  16        10    1   521528,912    41366,197    ops/s
JacksonASTBenchmark.roundRobin         thrpt  16        10    1   512564,205   300704,545    ops/s
GSONReaderBenchmark.roundRobin         thrpt  16        10    1   446322,220    41327,496    ops/s
JsonSmartStreamBenchmark.roundRobin    thrpt  16        10    1   276399,298   130055,340    ops/s
JsonSmartReaderBenchmark.roundRobin    thrpt  16        10    1    86789,825    17690,031    ops/s

Он имеет самый быстрый парсер JSON для потоков, считывателей, байтов [], char [], CharSequence (StringBuilder, CharacterBuffer) и String.

См. больше тестов по адресу:

https://github.com/RichardHightower/json-parsers-benchmark