Java SimpleDateFormat для часового пояса с разделителем двоеточия?

У меня дата в следующем формате: 2010-03-01T00:00:00-08:00

Я бросил на него следующие SimpleDateFormats, чтобы проанализировать его:

private static final SimpleDateFormat[] FORMATS = {
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"), //ISO8601 long RFC822 zone
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"), //ISO8601 long long form zone
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"), //ignore timezone
        new SimpleDateFormat("yyyyMMddHHmmssZ"), //ISO8601 short
        new SimpleDateFormat("yyyyMMddHHmm"),
        new SimpleDateFormat("yyyyMMdd"), //birthdate from NIST IHE C32 sample
        new SimpleDateFormat("yyyyMM"),
        new SimpleDateFormat("yyyy") //just the year
    };

У меня есть удобный метод, который использует эти форматы следующим образом:

public static Date figureOutTheDamnDate(String wtf) {
    if (wtf == null) {
        return null;
    }
    Date retval = null;
    for (SimpleDateFormat sdf : FORMATS) {
        try {
            sdf.setLenient(false)
            retval = sdf.parse(wtf);
            System.out.println("Date:" + wtf + " hit on pattern:" + sdf.toPattern());
            break;
        } catch (ParseException ex) {
            retval = null;
            continue;
        }
    }

    return retval;
}

Кажется, он попадает в шаблон yyyyMMddHHmm, но возвращает дату как Thu Dec 03 00:01:00 PST 2009.

Каков правильный образец для анализа этой даты?

ОБНОВЛЕНИЕ: мне не нужен синтаксический анализ часового пояса. Я не ожидаю, что у меня возникнут проблемы, связанные со временем при перемещении между зонами, но как мне получить формат зоны «-08: 00» для синтаксического анализа ????

Модульный тест:

@Test
public void test_date_parser() {
    System.out.println("\ntest_date_parser");
    //month is zero based, are you effing kidding me
    Calendar d = new GregorianCalendar(2000, 3, 6, 13, 00, 00);
    assertEquals(d.getTime(), MyClass.figureOutTheDamnDate("200004061300"));
    assertEquals(new GregorianCalendar(1950, 0, 1).getTime(), MyClass.figureOutTheDamnDate("1950"));
    assertEquals(new GregorianCalendar(1997, 0, 1).getTime(),  MyClass.figureOutTheDamnDate("199701"));
    assertEquals(new GregorianCalendar(2010, 1, 25, 15, 19, 44).getTime(),   MyClass.figureOutTheDamnDate("20100225151944-0800"));

    //my machine happens to be in GMT-0800
    assertEquals(new GregorianCalendar(2010, 1, 15, 13, 15, 00).getTime(),MyClass.figureOutTheDamnDate("2010-02-15T13:15:00-05:00"));
    assertEquals(new GregorianCalendar(2010, 1, 15, 18, 15, 00).getTime(), MyClass.figureOutTheDamnDate("2010-02-15T18:15:00-05:00"));

    assertEquals(new GregorianCalendar(2010, 2, 1).getTime(), MyClass.figureOutTheDamnDate("2010-03-01T00:00:00-08:00"));
    assertEquals(new GregorianCalendar(2010, 2, 1, 17, 0, 0).getTime(), MyClass.figureOutTheDamnDate("2010-03-01T17:00:00-05:00"));
}

Выход из модульного теста:

test_date_parser
Date:200004061300 hit on pattern:yyyyMMddHHmm
Date:1950 hit on pattern:yyyy
Date:199701 hit on pattern:yyyyMM
Date:20100225151944-0800 hit on pattern:yyyyMMddHHmmssZ
Date:2010-02-15T13:15:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-02-15T18:15:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-03-01T00:00:00-08:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-03-01T17:00:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss

person Freiheit    schedule 03.03.2010    source источник
comment
Просто хотел обратить ваше внимание на то, что JDK SimpleDateFormat не является потокобезопасным. Предварительное создание SimpleDateFormat объектов является анти-шаблоном, если они хранятся в статическом поле и, возможно, доступны для нескольких потоков. Только сами паттерны могут быть постоянными.   -  person mwhs    schedule 14.11.2013
comment
@mwhs Совершенно верно! Для получения дополнительной информации (и простого решения) обратитесь к моему сообщению в блоге по этой самой теме: Как текстовые форматы Java могут незаметно нарушить ваш код   -  person Stijn de Witt    schedule 23.10.2014


Ответы (11)


JodaTime _ 1_ для спасения:

String dateString = "2010-03-01T00:00:00-08:00";
String pattern = "yyyy-MM-dd'T'HH:mm:ssZ";
DateTimeFormatter dtf = DateTimeFormat.forPattern(pattern);
DateTime dateTime = dtf.parseDateTime(dateString);
System.out.println(dateTime); // 2010-03-01T04:00:00.000-04:00

(разница во времени и часовом поясе в toString() просто потому, что я нахожусь в GMT-4 и не установил языковой стандарт явно)

Если вы хотите получить java.util.Date, просто используйте DateTime#toDate():

Date date = dateTime.toDate();

Подождите JDK7 (JSR-310) JSR-310, Реализация ссылки называется ThreeTen (надеюсь, она войдет в Java 8), если вам нужен лучший форматтер в стандартном Java SE API. Текущий SimpleDateFormat действительно не использует двоеточие в обозначении часового пояса.

Обновление: согласно обновлению, вам явно не нужен часовой пояс. Это должно работать с SimpleDateFormat. Просто опустите его (Z) в шаблоне.

String dateString = "2010-03-01T00:00:00-08:00";
String pattern = "yyyy-MM-dd'T'HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date date = sdf.parse(dateString);
System.out.println(date); // Mon Mar 01 00:00:00 BOT 2010

(что правильно в соответствии с моим часовым поясом)

person BalusC    schedule 03.03.2010
comment
Чувак, если бы я мог обновиться до JDK7, я был бы в раю. Есть и другие вещи из 7, которые мне нужны. Я посмотрю, требуется ли анализ зоны. Я продолжаю слышать хорошие отзывы о Джоде и, наверное, мне стоит попробовать. - person Freiheit; 04.03.2010
comment
Попробуйте сами. Это стоит того. Особенно, если вы хотите сделать с датами / временем немного больше, чем просто их хранение, например, синтаксический анализ, форматирование, изменение, вычисление и т. Д. - person BalusC; 04.03.2010
comment
@BalusC У меня была та же проблема, что и у Freiheit, и отсутствие часового пояса помогло. Затем SimpleDateFormat использовать шаблон вроде «начинается с» вместо «равно» (чтобы все остальное, написанное в строке, игнорировалось)? - person RaphaelDDL; 29.02.2012
comment
С Joda-Time 2.3 (и, возможно, ранее) вам не нужны форматер и синтаксический анализ. Joda-Time имеет встроенную поддержку форматов ISO 8601 как для синтаксического анализа, так и для генерации строк. Просто передайте строку конструктору DateTime. : DateTime dateTime = new DateTime( "2010-03-01T00:00:00-08:00" );. Вы также можете указать часовой пояс, передав второй аргумент с DateTimeZone. - person Basil Bourque; 04.04.2014
comment
@BalusC В документах JodaTime говорится, что «ZZ» в конце использует двоеточие, а «Z» - нет. Ваш код выдает двоеточие, но этот код, использующий миллисекунды, не делает: final String pattern = "yyyy-MM-dd'T'HH:mm:ssZ"; final DateTimeFormatter dateFormatter = DateTimeFormat.forPattern(pattern); final String dateString = dateFormatter.print(1474068823000L); System.out.println(dateString); Только использование ZZ дает двоеточие. Разве это не кажется непоследовательным? - person rimsky; 17.09.2016
comment
Вам не нужно использовать JodaTime. Очевидно, если вы используете более старую версию Java, как в случае с Android, вы можете использовать ZZZZZ вместо XXX для достижения того же результата. Источник: stackoverflow.com/a/31830673/740474 - person alexgophermix; 02.11.2016

если бы вы использовали java 7, вы могли бы использовать следующий шаблон даты и времени. Похоже, этот шаблон не поддерживается в более ранней версии java.

String dateTimeString  = "2010-03-01T00:00:00-08:00";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date date = df.parse(dateTimeString);

Для получения дополнительной информации см. SimpleDateFormat документацию.

person YouEyeK    schedule 03.04.2014
comment
Ах, я бы хотел, чтобы это сработало, но если ваш часовой пояс GMT, он отформатирует Z вместо этого. Это Z является проблемой, поскольку мы должны анализировать результирующую строку даты в IE, а IE не любит Z - person Sa'ad; 03.02.2015

Вот фрагмент, который я использовал - с простым SimpleDateFormat. Надеюсь, кому-то это пригодится:

public static void main(String[] args) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") {
        public StringBuffer format(Date date, StringBuffer toAppendTo, java.text.FieldPosition pos) {
            StringBuffer toFix = super.format(date, toAppendTo, pos);
            return toFix.insert(toFix.length()-2, ':');
        };
    };
    // Usage:
    System.out.println(dateFormat.format(new Date()));
}

Выход:

- Usual Output.........: 2013-06-14T10:54:07-0200
- This snippet's Output: 2013-06-14T10:54:07-02:00

Или ... лучше использовать более простой, другой шаблон:

SimpleDateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
// Usage:
System.out.println(dateFormat2.format(new Date()));

Выход:

- This pattern's output: 2013-06-14T10:54:07-02:00

См. документацию по этому поводу.

person acdcjunior    schedule 20.01.2013
comment
Это ужасно. Прочтите документы и используйте формат тройной X yyyy-MM-dd'T'HH:mm:ssXXX - person Petr Újezdský; 12.02.2019
comment
@ PetrÚjezdský Вы правы, спасибо! Я обновил ответ для использования в будущем. - person acdcjunior; 22.03.2019

Попробуйте это, это работает для меня:

Date date = javax.xml.bind.DatatypeConverter.parseDateTime("2013-06-01T12:45:01+04:00").getTime();

В Java 8:

OffsetDateTime dt = OffsetDateTime.parse("2010-03-01T00:00:00-08:00");
person Rustam    schedule 14.11.2013
comment
Отсутствует на Android :( - person Mooing Duck; 17.10.2015
comment
@MooingDuck Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport проект. И далее адаптирован под Android в проекте ThreeTenABP. - person Basil Bourque; 19.04.2016
comment
@Rustam Рад видеть пример Java 8, но я полагаю, что OffsetDateTime было бы лучшим выбором, чем ZonedDateTime, поскольку на входе отсутствует полный часовой пояс. Я показал это в моем ответе. - person Basil Bourque; 19.04.2016

Если вы можете использовать JDK 1.7 или выше, попробуйте следующее:

public class DateUtil {
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

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

    public static Date parse(String dateString) throws AquariusException {
        try {
            return dateFormat.parse(dateString);
        } catch (ParseException e) {
            throw new AquariusException(e);
        }
    }
}

документ: https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html, который поддерживает новый формат часового пояса «XXX» (например, -3: 00).

В то время как JDK 1.6 поддерживает только другие форматы для часовых поясов, а именно «z» (например, NZST), «zzzz» (например, новозеландское стандартное время), «Z» (например, +1200) и т. Д.

person Willie Z    schedule 19.04.2016

tl;dr

OffsetDateTime.parse( "2010-03-01T00:00:00-08:00" )

Подробности

ответ BalusC правильный, но теперь он устарел в Java 8.

java.time

Фреймворк java.time является преемник как библиотеки Joda-Time, так и старых проблемных классов даты и времени, связанных с самыми ранними версиями Java (java.util.Date/.Calendar & java.text.SimpleDateFormat).

ISO 8601

Ваша строка входных данных соответствует стандарту ISO 8601.

Классы java.time по умолчанию используют форматы ISO 8601 при синтаксическом анализе / генерации текстовых представлений значений даты и времени. Поэтому нет необходимости определять шаблон форматирования.

OffsetDateTime

Класс OffsetDateTime представляет собой момент на временная шкала скорректирована с учетом определенного смещения от UTC. В вашем вводе смещение на 8 часов отстает от UTC, обычно используемого на большей части западного побережья Северной Америки.

OffsetDateTime odt = OffsetDateTime.parse( "2010-03-01T00:00:00-08:00" );

Кажется, вам нужна только дата, и в этом случае используйте LocalDate класс. Но имейте в виду, что вы отбрасываете данные, (а) время дня и (б) часовой пояс. На самом деле дата не имеет значения без контекста часового пояса. В любой момент дата меняется по всему миру. Например, сразу после полуночи в Париже все еще «вчера» в Монреале. Поэтому, хотя я предлагаю придерживаться значений даты и времени, вы можете легко преобразовать их в LocalDate, если будете настаивать.

LocalDate localDate = odt.toLocalDate();

Часовой пояс

Если вы знаете предполагаемый часовой пояс, примените его. Часовой пояс - это смещение плюс правил, используемых для обработки аномалий, таких как Дневной свет Экономия времени (DST). Применение ZoneId дает нам _ 9_ объект.

ZoneId zoneId = ZoneId.of( "America/Los_Angeles" );
ZonedDateTime zdt = odt.atZoneSameInstant( zoneId );

Генерация строк

Чтобы сгенерировать строку в формате ISO 8601, вызовите toString.

String output = odt.toString();

Если вам нужны строки в других форматах, выполните поиск в Stack Overflow, чтобы найти пакет java.util.format.

Преобразование в java.util.Date

Лучше избегать java.util.Date, но если вы должен, вы можете конвертировать. Вызовите новые методы, добавленные к старым классам, например _ 15_, где вы передаете Instant. Instant - это момент на временной шкале в UTC. Мы можем извлечь Instant из нашего OffsetDateTime.

java.util.Date utilDate = java.util.Date( odt.toInstant() );

О java.time

java.time фреймворк встроен в Java 8 и новее. Эти классы заменяют неудобные старые устаревшие классы даты и времени, такие как _ 21_, _ 22_, & _ 23_.

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

Чтобы узнать больше, см. Учебник Oracle . И поищите в Stack Overflow множество примеров и объяснений. Спецификация - JSR 310.

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

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

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

person Basil Bourque    schedule 19.04.2016

Благодарим acdcjunior за ваше решение. Вот немного оптимизированная версия для форматирования и анализа:

public static final SimpleDateFormat XML_SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.FRANCE)
{
    private static final long serialVersionUID = -8275126788734707527L;

    public StringBuffer format(Date date, StringBuffer toAppendTo, java.text.FieldPosition pos)
    {            
        final StringBuffer buf = super.format(date, toAppendTo, pos);
        buf.insert(buf.length() - 2, ':');
        return buf;
    };

    public Date parse(String source) throws java.text.ParseException {
        final int split = source.length() - 2;
        return super.parse(source.substring(0, split - 1) + source.substring(split)); // replace ":" du TimeZone
    };
};
person Bludwarf    schedule 24.05.2013
comment
Просто примечание: вы можете добавить условие для parse, чтобы, если source имеет значение null или меньше 3 символов, вы делаете что-то еще, а не пытаетесь использовать substring с отрицательным индексом ... - person BrainStorm.exe; 07.09.2013

Вы можете использовать X в Java 7.

https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

static final SimpleDateFormat DATE_TIME_FORMAT = 
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

static final SimpleDateFormat JSON_DATE_TIME_FORMAT = 
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

private String stringDate = "2016-12-01 22:05:30";
private String requiredDate = "2016-12-01T22:05:30+03:00";

@Test
public void parseDateToBinBankFormat() throws ParseException {
    Date date = DATE_TIME_FORMAT.parse(stringDate);
    String jsonDate = JSON_DATE_TIME_FORMAT.format(date);

    System.out.println(jsonDate);
    Assert.assertEquals(jsonDate, requiredDate);
}
person bigspawn    schedule 17.11.2017

Попробуйте setLenient(false).

Приложение. Похоже, вы распознаете Date строки различного формата. Если вам нужно сделать запись, вам может понравиться этот пример что расширяет InputVerifier.

person trashgod    schedule 03.03.2010
comment
Хммм. Ближе, это, по крайней мере, имеет смысл для шаблонов, на которые он обращает внимание. Немедленно отредактирую сообщение, чтобы отразить эти изменения. - person Freiheit; 04.03.2010
comment
Обработка XML-данных. Spec говорит, что предполагается использовать ISO8601 для каждого поля даты, но это не то, что приходит на ум. Остальная часть системы - лучшая попытка, поэтому мне нужно получить любой разумный формат для синтаксического анализа. - person Freiheit; 04.03.2010
comment
@Freiheit: «Другой путь». -08:00 - правильный путь для ISO8601, но в Java 6 нет ничего, что правильно анализирует ISO8601. - person Mooing Duck; 17.10.2015

Так как пример Apache FastDateFormat (щелкните для просмотра документации по версиям: 2.6 и 3.5) здесь отсутствует, я добавляю его для тех, кому он может понадобиться. Ключевым моментом здесь является узор ZZ (2 заглавных Zs).

import java.text.ParseException
import java.util.Date;
import org.apache.commons.lang3.time.FastDateFormat;
public class DateFormatTest throws ParseException {
    public static void main(String[] args) {
        String stringDateFormat = "yyyy-MM-dd'T'HH:mm:ssZZ";
        FastDateFormat fastDateFormat = FastDateFormat.getInstance(stringDateFormat);
        System.out.println("Date formatted into String:");
        System.out.println(fastDateFormat.format(new Date()));
        String stringFormattedDate = "2016-11-22T14:30:14+05:30";
        System.out.println("String parsed into Date:");
        System.out.println(fastDateFormat.parse(stringFormattedDate));
    }
}

Вот вывод кода:

Date formatted into String:
2016-11-22T14:52:17+05:30
String parsed into Date:
Tue Nov 22 14:30:14 IST 2016

Примечание. Приведенный выше код принадлежит Apache Commons lang3. Класс org.apache.commons.lang.time.FastDateFormat не поддерживает синтаксический анализ, а поддерживает только форматирование. Например, вывод следующего кода:

import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang.time.FastDateFormat;
public class DateFormatTest {
    public static void main(String[] args) throws ParseException {
        String stringDateFormat = "yyyy-MM-dd'T'HH:mm:ssZZ";
        FastDateFormat fastDateFormat = FastDateFormat.getInstance(stringDateFormat);
        System.out.println("Date formatted into String:");
        System.out.println(fastDateFormat.format(new Date()));
        String stringFormattedDate = "2016-11-22T14:30:14+05:30";
        System.out.println("String parsed into Date:");
        System.out.println(fastDateFormat.parseObject(stringFormattedDate));
    }
}

будет это:

Date formatted into String:
2016-11-22T14:55:56+05:30
String parsed into Date:
Exception in thread "main" java.text.ParseException: Format.parseObject(String) failed
    at java.text.Format.parseObject(Format.java:228)
    at DateFormatTest.main(DateFormatTest.java:12)
person Abhishek Oza    schedule 22.11.2016

Если строка даты имеет вид 2018-07-20T12: 18: 29.802Z, используйте это

SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
person HimalayanCoder    schedule 20.07.2018
comment
К вашему сведению, ужасно неприятные старые классы даты и времени, такие как java.util.Date, java.util.Calendar, и java.text.SimpleDateFormat теперь являются устаревшими и заменены java.time классы, встроенные в Java 8 и новее . См. Руководство от Oracle. - person Basil Bourque; 20.07.2018
comment
Этот ответ не касается вопроса. Вопрос явно касается числового смещения от UTC (-08:00), а не символа Z Zulu, который означает UTC. - person Basil Bourque; 20.07.2018