Czy strefa czasowa java.sql.Timestamp jest specyficzna?

85

Muszę przechowywać datę i godzinę UTC w DB.
Przekonwertowałem dateTime podaną w określonej strefie czasowej na UTC. w tym celu postępowałem zgodnie z poniższym kodem.
Moja data wejściowaGodzina to „20121225 10:00:00 Z” strefa czasowa to „Azja / Kalkuta”
Mój serwer / baza danych (Oracle) działa w tej samej strefie czasowej (IST) „Azja / Kalkuta”

Pobierz obiekt Date w tej określonej strefie czasowej

        String date = "20121225 10:00:00 Z";
        String timeZoneId = "Asia/Calcutta";
        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

        DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
                    //This date object is given time and given timezone
        java.util.Date parsedDate = dateFormatLocal.parse(date + " "  
                         + timeZone.getDisplayName(false, TimeZone.SHORT));

        if (timeZone.inDaylightTime(parsedDate)) {
            // We need to re-parse because we don't know if the date
            // is DST until it is parsed...
            parsedDate = dateFormatLocal.parse(date + " "
                    + timeZone.getDisplayName(true, TimeZone.SHORT));
        }

       //assigning to the java.sql.TimeStamp instace variable
        obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));

Przechowuj w DB

        if (tsSchedStartTime != null) {
            stmt.setTimestamp(11, tsSchedStartTime);
        } else {
            stmt.setNull(11, java.sql.Types.DATE);
        }

WYNIK

DB (oracle) przechowuje to samo, podane dateTime: "20121225 10:00:00nie w UTC.

Potwierdziłem z poniższego sql.

     select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable

Mój serwer DB działa również w tej samej strefie czasowej „Asia / Calcutta”

Daje mi poniższe wyglądy

  1. Date.getTime() nie jest w UTC
  2. Lub Znacznik czasu ma wpływ na strefę czasową podczas zapisywania w DB. Co ja tu robię źle?

Jeszcze jedno pytanie:

Czy będzie timeStamp.toString()drukować w lokalnej strefie czasowej, tak jak java.util.daterobi? Nie UTC?

Kanagavelu Sugumar
źródło
4
Odpowiadając na pytanie w tytule, nie, java.sql.Timestamp jest oparty na czasie UTC. Wyświetlanie sygnatury czasowej zależy od strefy czasowej.
Gilbert Le Blanc
@Gilbert Le Blanc Thanks !. Czy mógłbyś mi odpowiedzieć „new java.sql.Timestamp (parsedDate.getTime ())” zwróci obiekt znacznika czasu GMT dla moich danych wejściowych?
Kanagavelu Sugumar
1
@GilbertLeBlanc Chociaż java.sql.Timestampjest w czasie UTC, podczas przechowywania w polu TIMESTAMP bez informacji o strefie czasowej używa równoważnej daty / godziny w bieżącej strefie czasowej maszyny wirtualnej
Mark Rotteveel
@Kanagavelu Sugumar: Tak. metoda getTime klasy Date zwraca liczbę milisekund od 1 stycznia 1970 r. o 00:00:00 czasu GMT.
Gilbert Le Blanc,
@MarkRotteveel Mark Nie rozumiem, o co chodzi: „przechowywanie w polu TIMESTAMP bez informacji o strefie czasowej, używa równoważnej daty / godziny w bieżącej strefie czasowej maszyny wirtualnej”. Czy mógłbyś rozwinąć swoją odpowiedź. To będzie dla mnie bardziej pomocne.
Kanagavelu Sugumar

Odpowiedzi:

105

Chociaż nie jest to wyraźnie określone, setTimestamp(int parameterIndex, Timestamp x)kierowcy muszą przestrzegać zasad ustalonych przez setTimestamp(int parameterIndex, Timestamp x, Calendar cal)javadoc :

Ustawia wyznaczony parametr na podaną java.sql.Timestampwartość, używając podanego Calendarobiektu. Sterownik używa Calendarobiektu do skonstruowania TIMESTAMPwartości SQL , którą następnie wysyła do bazy danych. Za pomocą Calendarobiektu kierowca może obliczyć sygnaturę czasową z uwzględnieniem niestandardowej strefy czasowej. Jeśli nie Calendarokreślono żadnego obiektu, sterownik używa domyślnej strefy czasowej, czyli strefy czasowej maszyny wirtualnej, na której działa aplikacja.

Podczas rozmowy ze setTimestamp(int parameterIndex, Timestamp x)sterownikiem JDBC używa strefy czasowej maszyny wirtualnej do obliczenia daty i godziny znacznika czasu w tej strefie czasowej. Ta data i godzina jest tym, co jest przechowywane w bazie danych, a jeśli kolumna bazy danych nie przechowuje informacji o strefie czasowej, wszelkie informacje o strefie są tracone (co oznacza, że ​​jest to zależne od aplikacji używającej bazy danych do korzystania z tę samą strefę czasową konsekwentnie lub wymyśl inny sposób rozróżniania strefy czasowej (tj. zapisz ją w osobnej kolumnie).

Na przykład: Twoja lokalna strefa czasowa to GMT + 2. Przechowujesz „2012-12-25 10:00:00 UTC”. Rzeczywista wartość przechowywana w bazie danych to „2012-12-25 12:00:00”. Pobierasz go ponownie: odzyskujesz go jako „2012-12-25 10:00:00 UTC” (ale tylko jeśli pobierasz go przy użyciu getTimestamp(..)), ale gdy inna aplikacja uzyskuje dostęp do bazy danych w strefie czasowej GMT + 0, pobierz znacznik czasu jako „2012-12-25 12:00:00 UTC”.

Jeśli chcesz przechowywać go w innej strefie czasowej, musisz użyć setTimestamp(int parameterIndex, Timestamp x, Calendar cal)instancji Kalendarza w wymaganej strefie czasowej. Po prostu upewnij się, że podczas pobierania wartości używasz również równoważnego gettera z tą samą strefą czasową (jeśli używasz TIMESTAMPw bazie danych bez informacji o strefie czasowej).

Więc zakładając, że chcesz zapisać aktualną strefę czasową GMT, musisz użyć:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
stmt.setTimestamp(11, tsSchedStartTime, cal);

Z JDBC 4.2 zgodny sterownik powinien obsługiwać java.time.LocalDateTime(i java.time.LocalTime) for TIMESTAMP(i TIME) through get/set/updateObject. Te java.time.Local*zajęcia są bez stref czasowych, więc konwersja nie musi być stosowane (choć może otworzyć nowy zestaw problemów, jeśli kod nie przybierały określoną strefę czasową).

Mark Rotteveel
źródło
Chcę przechowywać swój czas w GMT. Wierzę, że mój znacznik czasu jest w GMT. Więc co powinienem zrobić? czy to jest setTimestamp (int parameterIndex, Timestamp tzGmtObj, Calendar calGmtObj) ??
Kanagavelu Sugumar
jeśli „setTimestamp (int parameterIndex, Timestamp x)” używa strefy czasowej maszyny wirtualnej. jaka jest rola x w kodzie?
Kanagavelu Sugumar
1
@KanagaveluSugumar To jest rzeczywista wartość znacznika czasu do ustawienia. Sterownik używa obiektu Calendar tylko do informacji o strefie czasowej, a nie do jego wartości!
Mark Rotteveel
1
Świetnie, to działa :) Spędzam dużo czasu, aby udowodnić, że (Date / Timestamp) .getTime () przechowuje określony czas strefy czasowej w milisekundach. Teraz zrozumiałem, że tak nie jest. Zawsze podaje milisekundy UTC, niezależnie od strefy czasowej. Ale (Date / Timestamp) .toString () jest specyficzne dla strefy czasowej. Wielkie dzięki.
Kanagavelu Sugumar
4
toString()Na java.lang.Date(i java.lang.Timestamp) będą zawsze przedstawić go w lokalnej strefy czasowej.
Mark Rotteveel
35

Myślę, że poprawną odpowiedzią powinno być java.sql.Timestamp NIE jest specyficzne dla strefy czasowej. Sygnatura czasowa jest złożeniem wartości java.util.Date i oddzielnej wartości w nanosekundach. W tych zajęciach nie ma informacji o strefie czasowej. Tak więc, podobnie jak Date, ta klasa po prostu przechowuje liczbę milisekund od 1 stycznia 1970, 00:00:00 GMT + nanos.

W PreparedStatement.setTimestamp (int parameterIndex, Timestamp x, Calendar cal) Kalendarz jest używany przez sterownik do zmiany domyślnej strefy czasowej. Ale Timestamp nadal zawiera milisekundy w GMT.

API nie jest jasne, jak dokładnie sterownik JDBC ma używać Kalendarza. Wydaje się, że dostawcy czują się swobodnie, jeśli chodzi o jego interpretację, np. Kiedy ostatnio pracowałem z kalendarzem MySQL 5.5, sterownik po prostu zignorował Kalendarz w obu PreparedStatement.setTimestamp i ResultSet.getTimestamp.

Evgeniy Dorofeev
źródło
Zainteresowany komentarz na temat MySQL 5.5
Alex,
2
Uważam, że to błąd w Javie 8+. Wewnątrz znacznika czasu (ponieważ pochodzi od java.util.Date) znajduje się atrybut kalendarza, który ma atrybut zoneinfo, który w rzeczywistości zawiera strefę czasową. Nadal możesz uzyskać czas UTC za pomocą .getTime (), ponieważ Timestamp również go przechowuje, ale MA informacje o strefie czasowej.
Jorge.V
1
masz na myśli, że przechowuje liczbę milisekund od 1 stycznia 1970, 00:00:00 UTC zamiast GMT. GMT ma czas letni. Epoka UNIX to milisekundy od 1 stycznia 1970, 00:00 UTC.
Kirby
6

W przypadku MySQL mamy ograniczenie. W sterowniku Mysql doc mamy:

Poniżej przedstawiono niektóre znane problemy i ograniczenia dotyczące MySQL Connector / J: Gdy Connector / J pobiera sygnatury czasowe dla dnia zmiany czasu letniego (DST) przy użyciu metody getTimeStamp () w zestawie wyników, niektóre zwracane wartości mogą być nieprawidłowe. Błędy można uniknąć, korzystając z następujących opcji połączenia podczas łączenia się z bazą danych:

useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC

Tak więc, gdy nie używamy tych parametrów i dzwonimy setTimestamp or getTimestampz kalendarzem lub bez kalendarza, mamy znacznik czasu w strefie czasowej jvm.

Przykład:

Strefa czasowa jvm to GMT + 2. W bazie danych mamy sygnaturę czasową: 1461100256 = 19/04/16 21: 10: 56,000000000 GMT

Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "");
props.setProperty("useTimezone", "true");
props.setProperty("useLegacyDatetimeCode", "false");
props.setProperty("serverTimezone", "UTC");
Connection con = DriverManager.getConnection(conString, props);
......
Calendar nowGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar nowGMTPlus4 = Calendar.getInstance(TimeZone.getTimeZone("GMT+4"));
......
rs.getTimestamp("timestampColumn");//Oracle driver convert date to jvm timezone and Mysql convert date to GMT (specified in the parameter)
rs.getTimestamp("timestampColumn", nowGMT);//convert date to GMT 
rs.getTimestamp("timestampColumn", nowGMTPlus4);//convert date to GMT+4 timezone

Pierwsza metoda zwraca: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Druga metoda zwraca: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Trzecia metoda zwraca: 1461085856000 = 19/04/2016 - 17:10:56 GMT

Zamiast Oracle, gdy używamy tych samych wywołań, mamy:

Pierwsza metoda zwraca: 1461093056000 = 19/04/2016 - 19:10:56 GMT

Druga metoda zwraca: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Trzecia metoda zwraca: 1461085856000 = 19/04/2016 - 17:10:56 GMT

Uwaga: nie ma potrzeby określania parametrów Oracle.

Abdelhafid
źródło
1
Wydaje mi się, że w przypadku Oracle można ustawić strefę czasową sesji na UTC, aby uzyskać taką samą konwersję, jak w przypadku MySQL w przypadku przypadku innego niż kalendarz. „ ALTER SESSION SET TIME_ZONE = 'UTC'”.
eckes
5

Jest to specyficzne dla twojego kierowcy. Musisz podać parametr w programie Java, aby określić strefę czasową, której chcesz użyć.

java -Duser.timezone="America/New_York" GetCurrentDateTimeZone

Dalej to:

to_char(new_time(sched_start_time, 'CURRENT_TIMEZONE', 'NEW_TIMEZONE'), 'MM/DD/YY HH:MI AM')

Może również mieć znaczenie przy prawidłowym przeprowadzaniu konwersji. Zaczerpnięte stąd

Woot4Moo
źródło
czy chodziło mi o to, żebym śledził stackoverflow.com/questions/2858182/ ... czy też chcesz ustawić moją aplikację JVM na działanie w GMT?
Kanagavelu Sugumar
@KanagaveluSugumar, to zależy. Jeśli chcesz zmodyfikować sygnaturę czasową KAŻDEGO zapytania, ustaw go przy starcie, jeśli chcesz zmodyfikować tylko NIEKTÓRE (czyli mniej niż KAŻDY), zmodyfikuj każde zapytanie w sposób określony w poście, do którego się odnosisz.
Woot4Moo
5

Odpowiedź brzmi: java.sql.Timestampto bałagan i należy go unikać. Użyj java.time.LocalDateTimezamiast tego.

Więc dlaczego to bałagan? Z java.sql.TimestampJavaDoc, a java.sql.Timestampjest „cienkim opakowaniem, java.util.Datektóre umożliwia interfejsowi API JDBC zidentyfikowanie tego jako wartości TIMESTAMP języka SQL”. W java.util.DateJavaDoc „ Dateklasa ma odzwierciedlać uniwersalny czas koordynowany (UTC)”. Ze specyfikacji ISO SQL TIMESTAMP BEZ STREFY CZASOWEJ „jest typem danych, który jest datą i godziną bez strefy czasowej”. TIMESTAMP to krótka nazwa TIMESTAMP BEZ STREFY CZASOWEJ. Zatem java.sql.Timestamp„odzwierciedla” UTC, podczas gdy SQL TIMESTAMP jest „bez strefy czasowej”.

Ponieważ java.sql.Timestampodzwierciedla UTC, jego metody stosują konwersje. To powoduje niekończące się zamieszanie. Z punktu widzenia języka SQL nie ma sensu przekształcanie wartości TIMESTAMP języka SQL na inną strefę czasową, ponieważ TIMESTAMP nie ma strefy czasowej, z której można by dokonać konwersji. Co to znaczy przeliczyć 42 stopnie Fahrenheita? To nic nie znaczy, ponieważ 42 nie ma jednostek temperatury. To tylko pusta liczba. Podobnie nie możesz przekonwertować TIMESTAMP z 2020-07-22T10: 38: 00 na Ameryki / Los Angeles, ponieważ 2020-07-22T10: 30: 00 nie znajduje się w żadnej strefie czasowej. To nie jest UTC, GMT ani nic innego. To nagi czas na randkę.

java.time.LocalDateTimejest również samą datą. Nie ma strefy czasowej, dokładnie tak jak SQL TIMESTAMP. Żadna z jego metod nie stosuje żadnego rodzaju konwersji stref czasowych, co znacznie ułatwia przewidywanie i zrozumienie jego zachowania. Więc nie używaj java.sql.Timestamp. Posługiwać sięjava.time.LocalDateTime .

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);
Douglas Surber
źródło
Tak dobrze powiedziane. Dziękuję Ci.
Ole VV
2

Możesz użyć poniższej metody, aby zapisać sygnaturę czasową w bazie danych specyficzną dla żądanej strefy / identyfikatora strefy.

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());

Częstym błędem jest używanie LocaleDateTime uzyskanie sygnatury czasowej tej chwili, która odrzuca wszelkie informacje dotyczące Twojej strefy, nawet jeśli spróbujesz je później przekonwertować. Nie rozumie Strefy.

Należy pamiętać, że Timestampjest z klasy java.sql.Timestamp.

Ayush Kumar
źródło
Dzięki za rozwiązanie. Zadziałało !!!
Karthik SA