Niedozwolony znak wzorca „T” podczas analizowania ciągu daty do java.util.Date

182

Mam ciąg daty i chcę go parsować do normalnej daty, używając interfejsu Java Date API, oto mój kod:

public static void main(String[] args) {
    String date="2010-10-02T12:23:23Z";
    String pattern="yyyy-MM-ddThh:mm:ssZ";
    SimpleDateFormat sdf=new SimpleDateFormat(pattern);
    try {
        Date d=sdf.parse(date);
        System.out.println(d.getYear());
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Mam jednak wyjątek: java.lang.IllegalArgumentException: Illegal pattern character 'T'

Zastanawiam się więc, czy muszę podzielić ciąg i przeanalizować go ręcznie?

BTW, próbowałem dodać pojedynczy znak cudzysłowu po obu stronach litery T:

String pattern="yyyy-MM-dd'T'hh:mm:ssZ";

To też nie działa.

hguser
źródło
1
Czy wiesz, co oznacza to „T”? Miarę Myślę, że cała data wraz z „T” powinien być przekształcony w terminie (nie pomijanie go przez „jednolitej Cytaty”, ale co nam brakuje to wzór parse.
coding_idiot
9
TStoi na czas i jest częścią ISO8601 normą data międzynarodowego czasie formatowania.
1
To dziwne. Zawinięcie Tpojedynczych cudzysłowów działało dla mnie (przynajmniej pod względem rozwiązania błędu analizy).
Agi Hammerthief,

Odpowiedzi:

203

Aktualizacja dla Java 8 i nowszych

Teraz możesz po prostu zrobić Instant.parse("2015-04-28T14:23:38.521Z")i uzyskać poprawną rzecz, zwłaszcza że powinieneś używać Instantzamiast zepsutego java.util.Datez najnowszymi wersjami Javy.

Powinieneś używać DateTimeFormatterzamiast SimpleDateFormatter.

Oryginalna odpowiedź:

Poniższe wyjaśnienie jest nadal aktualne, ponieważ reprezentuje ten format. Ale został napisany, zanim Java 8 była wszechobecna, więc używa starych klas, których nie powinieneś używać, jeśli używasz Java 8 lub wyższej.

Działa to z danymi wejściowymi ze znakiem końcowym, Zjak pokazano:

We wzorze Tucieka się z 'każdej strony.

Wzorzec Zna końcu jest tak XXXjak udokumentowany w JavaDoc SimpleDateFormat, po prostu nie jest bardzo jasne, w jaki sposób go używać, ponieważ Zjest również znacznikiem dla starych TimeZoneinformacji.

Q2597083.java

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

public class Q2597083
{
    /**
     * All Dates are normalized to UTC, it is up the client code to convert to the appropriate TimeZone.
     */
    public static final TimeZone UTC;

    /**
     * @see <a href="http://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations">Combined Date and Time Representations</a>
     */
    public static final String ISO_8601_24H_FULL_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";

    /**
     * 0001-01-01T00:00:00.000Z
     */
    public static final Date BEGINNING_OF_TIME;

    /**
     * 292278994-08-17T07:12:55.807Z
     */
    public static final Date END_OF_TIME;

    static
    {
        UTC = TimeZone.getTimeZone("UTC");
        TimeZone.setDefault(UTC);
        final Calendar c = new GregorianCalendar(UTC);
        c.set(1, 0, 1, 0, 0, 0);
        c.set(Calendar.MILLISECOND, 0);
        BEGINNING_OF_TIME = c.getTime();
        c.setTime(new Date(Long.MAX_VALUE));
        END_OF_TIME = c.getTime();
    }

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

        final SimpleDateFormat sdf = new SimpleDateFormat(ISO_8601_24H_FULL_FORMAT);
        sdf.setTimeZone(UTC);
        System.out.println("sdf.format(BEGINNING_OF_TIME) = " + sdf.format(BEGINNING_OF_TIME));
        System.out.println("sdf.format(END_OF_TIME) = " + sdf.format(END_OF_TIME));
        System.out.println("sdf.format(new Date()) = " + sdf.format(new Date()));
        System.out.println("sdf.parse(\"2015-04-28T14:23:38.521Z\") = " + sdf.parse("2015-04-28T14:23:38.521Z"));
        System.out.println("sdf.parse(\"0001-01-01T00:00:00.000Z\") = " + sdf.parse("0001-01-01T00:00:00.000Z"));
        System.out.println("sdf.parse(\"292278994-08-17T07:12:55.807Z\") = " + sdf.parse("292278994-08-17T07:12:55.807Z"));
    }
}

Daje następujące dane wyjściowe:

sdf.format(BEGINNING_OF_TIME) = 0001-01-01T00:00:00.000Z
sdf.format(END_OF_TIME) = 292278994-08-17T07:12:55.807Z
sdf.format(new Date()) = 2015-04-28T14:38:25.956Z
sdf.parse("2015-04-28T14:23:38.521Z") = Tue Apr 28 14:23:38 UTC 2015
sdf.parse("0001-01-01T00:00:00.000Z") = Sat Jan 01 00:00:00 UTC 1
sdf.parse("292278994-08-17T07:12:55.807Z") = Sun Aug 17 07:12:55 UTC 292278994

źródło
26

tl; dr

Użyj java.time.Instantklasy do parsowania tekstu w standardowym formacie ISO 8601, reprezentującym chwilę w UTC.

Instant.parse( "2010-10-02T12:23:23Z" )

ISO 8601

Ten format jest zdefiniowany przez normę ISO 8601 dla formatów ciągów daty i godziny.

Obie:

… Domyślnie używają formatów ISO 8601 do analizowania i generowania ciągów.

Ogólnie powinieneś unikać korzystania ze starych klas java.util.Date /.Calendar & java.text.SimpleDateFormat, ponieważ są one wyjątkowo kłopotliwe, mylące i wadliwe. Jeśli jest to wymagane do współpracy, możesz konwertować do iz powrotem.

java.time

Zbudowana w Javie 8 i nowszych jest nowa platforma java.time . Zainspirowany przez Joda-Time , zdefiniowany przez JSR 310 i rozszerzony przez projekt ThreeTen-Extra .

Instant instant = Instant.parse( "2010-10-02T12:23:23Z" );  // `Instant` is always in UTC.

Przejdź do starej klasy.

java.util.Date date = java.util.Date.from( instant );  // Pass an `Instant` to the `from` method.

Strefa czasowa

W razie potrzeby możesz przypisać strefę czasową.

ZoneId zoneId = ZoneId.of( "America/Montreal" ); // Define a time zone rather than rely implicitly on JVM’s current default time zone.
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );  // Assign a time zone adjustment from UTC.

Konwertować.

java.util.Date date = java.util.Date.from( zdt.toInstant() );  // Extract an `Instant` from the `ZonedDateTime` to pass to the `from` method.

Joda-Time

AKTUALIZACJA: Projekt Joda-Time jest teraz w trybie konserwacji. Zespół doradza migrację do klas java.time .

Oto przykładowy kod w Joda-Time 2.8.

org.joda.time.DateTime dateTime_Utc = new DateTime( "2010-10-02T12:23:23Z" , DateTimeZone.UTC );  // Specifying a time zone to apply, rather than implicitly assigning the JVM’s current default.

Konwertuj na starą klasę. Pamiętaj, że przypisana strefa czasowa jest tracona podczas konwersji, ponieważ juDate nie można przypisać strefy czasowej.

java.util.Date date = dateTime_Utc.toDate(); // The `toDate` method converts to old class.

Strefa czasowa

W razie potrzeby możesz przypisać strefę czasową.

DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime dateTime_Montreal = dateTime_Utc.withZone ( zone );

Tabela typów daty i godziny w Javie, zarówno nowoczesnych, jak i starszych.


O java.time

Środowisko java.time jest wbudowane w Javę 8 i nowsze wersje . Klasy te kłopotliwe zastąpić stary starszych klas Date-Time, takich jak java.util.Date, Calendar, i SimpleDateFormat.

Projekt Joda-Time , teraz w trybie konserwacji , zaleca migrację do klas java.time .

Aby dowiedzieć się więcej, zobacz samouczek Oracle . I przeszukaj stos przepełnienia dla wielu przykładów i wyjaśnień. Specyfikacja to JSR 310 .

Możesz wymieniać obiekty java.time bezpośrednio ze swoją bazą danych. Użyj sterownika JDBC zgodnego z JDBC 4.2 lub nowszym. Nie potrzeba ciągów, nie ma potrzeby java.sql.*klas.

Gdzie można uzyskać klasy java.time?

Tabela, z której biblioteki java.time należy korzystać, w której wersji Java lub Android.

Projekt ThreeTen-Extra rozszerza java.time o dodatkowe klasy. Ten projekt jest poligonem doświadczalnym dla ewentualnych przyszłych dodatków do java.time. Można znaleźć kilka przydatnych klas tutaj takie jak Interval, YearWeek, YearQuarter, i więcej .

Basil Bourque
źródło
1
@AalapPatel Należy pamiętać, że projekt Joda-Time jest teraz w trybie konserwacji. Zespół doradza migrację do klas java.time. Oba wysiłki są prowadzone przez tego samego mężczyznę, Stephena Colebourne.
Basil Bourque,
Dziękuję, używam go, aby uzyskać milisekundy od serwera zwróconego czasu UTC i / lub czasu lokalnego i działa dobrze dla mnie w tym przypadku ..
Aalap Patel
Instant.parse wymaga poziomu 26 interfejsu API w Androidzie
Naveed Ahmad
1
@NaveedAhmad Zobacz punkty na dole odpowiedzi na temat projektów ThreeTen-Backport i ThreeTenABP .
Basil Bourque,
0

Do tej pory istnieją dwie odpowiedzi i obie są długie (i tl; dr za krótkie IMHO), więc piszę podsumowanie z mojego doświadczenia, zaczynając korzystać z nowej biblioteki java.time (dotyczy to, jak wspomniano w innych odpowiedziach na wersję Java) 8+). ISO 8601 określa standardowy sposób zapisywania dat: YYYY-MM-DDwięc format daty i godziny jest tylko taki jak poniżej (może to być 0, 3, 6 lub 9 cyfr dla milisekund) i nie jest wymagany ciąg formatujący:

import java.time.Instant;
public static void main(String[] args) {
    String date="2010-10-02T12:23:23Z";
    try {
        Instant myDate = Instant.parse(date);
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Nie potrzebowałem tego, ale ponieważ otrzymywanie roku jest w kodzie od pytania, to:
jest trudniejsze, nie można tego zrobić Instantbezpośrednio, można to zrobić za Calendarpomocą pytań Uzyskaj całkowitą wartość bieżącego roku w Javie i konwertowaniu Java. czas do kalendarza, ale IMHO ze względu na ustalony format podciąg jest prostszy w użyciu:

myDate.toString().substring(0,4);
Aleksiej Martianow
źródło