Múi giờ java.sql.Timestamp có cụ thể không?


81

Tôi phải lưu trữ dateTime UTC trong DB.
Tôi đã chuyển đổi dateTime trong múi giờ cụ thể thành UTC. cho rằng tôi đã làm theo mã dưới đây.
Ngày nhập của tôi Thời gian là "20121225 10:00:00 Z" múi giờ là "Châu Á / Calcutta"
Máy chủ / DB (oracle) của tôi đang chạy trong cùng múi giờ (IST) "Châu Á / Calcutta"

Lấy đối tượng Ngày trong Múi giờ cụ thể này

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

Lưu trữ vào DB

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

ĐẦU RA

DB (oracle) đã lưu trữ cùng một dateTime: "20121225 10:00:00không có trong UTC.

Tôi đã xác nhận từ sql dưới đây.

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

Máy chủ DB của tôi cũng chạy trên cùng múi giờ "Châu Á / Calcutta"

Nó cho tôi những lần xuất hiện dưới đây

  1. Date.getTime() không có trong UTC
  2. Hoặc Dấu thời gian có ảnh hưởng múi giờ khi lưu trữ vào DB Tôi đang làm gì sai ở đây?

Một câu hỏi nữa:

Sẽ timeStamp.toString()in ở múi giờ địa phương như thế java.util.datenào? Không phải UTC?


4
Để trả lời câu hỏi trong tiêu đề của bạn, không, java.sql.Timestamp dựa trên UTC. Hiển thị dấu thời gian là múi giờ cụ thể.
Gilbert Le Blanc

@Gilbert Le Blanc Cảm ơn !. Bạn có thể vui lòng trả lời tôi "new java.sql.Timestamp (parsedDate.getTime ())" sẽ trả về đối tượng dấu thời gian GMT cho đầu vào của tôi không?
Kanagavelu Sugumar

1
@GilbertLeBlanc Mặc dù java.sql.Timestamplà trong UTC, khi lưu trữ trong một lĩnh vực dấu thời gian không có thông tin múi giờ, nó sử dụng ngày tương đương / thời gian trong múi giờ hiện tại của máy ảo
Đánh dấu Rotteveel

@Kanagavelu Sugumar: Vâng. phương thức getTime của lớp Date trả về số mili giây kể từ ngày 1 tháng 1 năm 1970, 00:00:00 GMT.
Gilbert Le Blanc

@MarkRotteveel Đánh dấu Tôi không hiểu ý bạn "lưu trữ trong trường TIMESTAMP không có thông tin múi giờ, nó sử dụng ngày / giờ tương đương trong múi giờ hiện tại của máy ảo". Bạn có thể vui lòng giải thích trong câu trả lời của bạn. Nó sẽ hữu ích hơn cho tôi.
Kanagavelu Sugumar

Câu trả lời:


104

Mặc dù nó không được chỉ định rõ ràng cho setTimestamp(int parameterIndex, Timestamp x)các trình điều khiển phải tuân theo các quy tắc do setTimestamp(int parameterIndex, Timestamp x, Calendar cal)javadoc thiết lập :

Đặt tham số được chỉ định thành java.sql.Timestampgiá trị đã cho , sử dụng Calendarđối tượng đã cho . Trình điều khiển sử dụng Calendarđối tượng để xây dựng một TIMESTAMPgiá trị SQL , sau đó trình điều khiển sẽ gửi đến cơ sở dữ liệu. Với một Calendarđối tượng, người lái xe có thể tính toán dấu thời gian có tính đến múi giờ tùy chỉnh. Nếu không có Calendarđối tượng nào được chỉ định, trình điều khiển sử dụng múi giờ mặc định, là múi giờ của máy ảo đang chạy ứng dụng.

Khi bạn gọi bằng setTimestamp(int parameterIndex, Timestamp x)trình điều khiển JDBC sử dụng múi giờ của máy ảo để tính ngày và giờ của dấu thời gian trong múi giờ đó. Ngày và giờ này là những gì được lưu trữ trong cơ sở dữ liệu và nếu cột cơ sở dữ liệu không lưu trữ thông tin múi giờ, thì bất kỳ thông tin nào về múi giờ sẽ bị mất (có nghĩa là tùy thuộc vào (các) ứng dụng sử dụng cơ sở dữ liệu để sử dụng cùng một múi giờ nhất quán hoặc đưa ra một lược đồ khác để phân biệt múi giờ (tức là lưu trữ trong một cột riêng biệt).

Ví dụ: Múi giờ địa phương của bạn là GMT + 2. Bạn lưu trữ "2012-12-25 10:00:00 UTC". Giá trị thực được lưu trữ trong cơ sở dữ liệu là "2012-12-25 12:00:00". Bạn truy xuất lại: bạn lấy lại nó là "2012-12-25 10:00:00 UTC" (nhưng chỉ khi bạn truy xuất nó bằng cách sử dụng getTimestamp(..)), nhưng khi một ứng dụng khác truy cập cơ sở dữ liệu theo múi giờ GMT + 0, nó sẽ truy xuất dấu thời gian là "2012-12-25 12:00:00 UTC".

Nếu bạn muốn lưu trữ nó ở một múi giờ khác, thì bạn cần sử dụng phiên bản setTimestamp(int parameterIndex, Timestamp x, Calendar cal)with a Calendar trong múi giờ bắt buộc. Chỉ cần đảm bảo rằng bạn cũng sử dụng getter tương đương với cùng múi giờ khi truy xuất giá trị (nếu bạn sử dụng TIMESTAMPthông tin không có múi giờ trong cơ sở dữ liệu của mình).

Vì vậy, giả sử bạn muốn lưu trữ múi giờ GMT thực tế, bạn cần sử dụng:

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

Với JDBC 4.2, một trình điều khiển tuân thủ sẽ hỗ trợ java.time.LocalDateTime(và java.time.LocalTime) cho TIMESTAMP(và TIME) thông qua get/set/updateObject. Các java.time.Local*lớp không có múi giờ, vì vậy không cần áp dụng chuyển đổi (mặc dù điều đó có thể mở ra một loạt vấn đề mới nếu mã của bạn đã giả sử một múi giờ cụ thể).


Tôi muốn lưu trữ thời gian của mình theo GMT. Tôi tin rằng timeStamp của tôi tính theo GMT. Vậy tôi nên làm gì? nó là setTimestamp (int tham sốIndex, Timestamp tzGmtObj, Lịch calGmtObj) ??
Kanagavelu Sugumar

nếu "setTimestamp (int parameterIndex, Timestamp x)" sử dụng múi giờ của máy ảo. vai trò của x trong mã là gì?
Kanagavelu Sugumar

1
@KanagaveluSugumar Đó là giá trị dấu thời gian thực tế cần đặt. Người lái xe chỉ sử dụng đối tượng Lịch cho thông tin múi giờ, không sử dụng cho giá trị của nó!
Mark Rotteveel

1
Tuyệt vời, Nó đang hoạt động :) Tôi dành rất nhiều thời gian để chứng minh (Date / Timestamp) .getTime () đang nắm giữ múi giờ cụ thể theo mili giây. Bây giờ tôi hiểu rằng nó không phải là. Nó luôn cung cấp phần nghìn giây UTC bất kể múi giờ. Nhưng (Date / Timestamp) .toString () là múi giờ cụ thể. Cảm ơn rất nhiều.
Kanagavelu Sugumar

4
Các toString()trên java.lang.Date(và java.lang.Timestamp) sẽ luôn luôn trình bày nó trong múi giờ địa phương của bạn.
Đánh dấu Rotteveel

35

Tôi nghĩ câu trả lời chính xác phải là java.sql. Dấu kim KHÔNG phải là múi giờ cụ thể. Dấu thời gian là kết hợp của java.util.Date và một giá trị nano giây riêng biệt. Không có thông tin múi giờ trong lớp này. Do đó, giống như Date, lớp này chỉ đơn giản là giữ số mili giây kể từ ngày 1 tháng 1 năm 1970, 00:00:00 GMT + nano.

Trong PreparedStatement.setTimestamp (int parameterIndex, Timestamp x, Calendar cal) Lịch được trình điều khiển sử dụng để thay đổi múi giờ mặc định. Nhưng Dấu thời gian vẫn giữ phần nghìn giây theo GMT.

API không rõ ràng về cách chính xác trình điều khiển JDBC được sử dụng Lịch. Các nhà cung cấp dường như không cảm thấy thoải mái về cách diễn giải nó, ví dụ lần trước tôi làm việc với MySQL 5.5 Calendar, trình điều khiển chỉ đơn giản là bỏ qua Lịch trong cả PreparedStatement.setTimestamp và ResultSet.getTimestamp.


Nhận xét quan tâm về MySQL 5.5
Alex

1
Tôi tin rằng điều này là sai đối với java 8+. Bên trong Dấu thời gian (vì nó mở rộng từ java.util.Date) có một thuộc tính lịch, có một thuộc tính múi giờ trên thực tế chứa múi giờ. Bạn vẫn có thể lấy thời gian UTC với .getTime () vì Dấu thời gian cũng lưu trữ nó, nhưng nó CÓ thông tin múi giờ.
Jorge.V

1
ý bạn là nó chứa số mili giây kể từ ngày 1 tháng 1 năm 1970, 00:00:00 UTC chứ không phải GMT. GMT có giờ tiết kiệm ánh sáng ban ngày. Kỷ nguyên UNIX là mili giây kể từ ngày 1 tháng 1 năm 1970, 00:00 giờ UTC.
Kirby ngày

6

Đối với Mysql, chúng tôi có một giới hạn. Trong trình điều khiển Mysql doc , chúng tôi có:

Sau đây là một số vấn đề và hạn chế đã biết đối với MySQL Connector / J: Khi Connector / J truy xuất dấu thời gian cho ngày chuyển đổi thời gian tiết kiệm ánh sáng ban ngày (DST) bằng cách sử dụng phương thức getTimeStamp () trên tập kết quả, một số giá trị trả về có thể bị sai. Có thể tránh được lỗi bằng cách sử dụng các tùy chọn kết nối sau khi kết nối với cơ sở dữ liệu:

useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC

Vì vậy, khi chúng tôi không sử dụng các tham số này và chúng tôi gọi setTimestamp or getTimestamp có lịch hoặc không có lịch, chúng tôi có dấu thời gian trong múi giờ jvm.

Thí dụ :

Múi giờ jvm là GMT + 2. Trong cơ sở dữ liệu, chúng tôi có một dấu thời gian: 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

Phương thức đầu tiên trả về: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Phương thức thứ hai trả về: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Phương thức thứ ba trả về: 1461085856000 = 19/04/2016 - 17:10:56 GMT

Thay vì Oracle, khi chúng ta sử dụng các lệnh gọi giống nhau, chúng ta có:

Phương thức đầu tiên trả về: 1461093056000 = 19/04/2016 - 19:10:56 GMT

Phương thức thứ hai trả về: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Phương thức thứ ba trả về: 1461085856000 = 19/04/2016 - 17:10:56 GMT

NB: Không cần thiết phải chỉ định các tham số cho Oracle.


1
Tôi đoán bạn có thể cho oracle đặt múi giờ phiên thành UTC để đạt được chuyển đổi tương tự như MySQL thực hiện cho trường hợp không lịch. " ALTER SESSION SET TIME_ZONE = 'UTC'".
eckes

5

Nó là cụ thể từ trình điều khiển của bạn. Bạn cần cung cấp một tham số trong chương trình Java của mình để cho nó biết múi giờ bạn muốn sử dụng.

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

Hơn nữa điều này:

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

Cũng có thể có giá trị trong việc xử lý chuyển đổi đúng cách. Lấy từ đây


ý bạn là tôi làm theo stackoverflow.com/questions/2858182/… hay Bạn muốn đặt ứng dụng JVM của tôi chạy theo GMT?
Kanagavelu Sugumar 28/12/12

@KanagaveluSugumar cũng tùy. Nếu bạn muốn sửa đổi dấu thời gian cho MỌI truy vấn, hãy đặt nó khi khởi động, nếu bạn chỉ muốn sửa đổi MỘT SỐ (có nghĩa là bất kỳ thứ gì nhỏ hơn MỌI) thì hãy sửa đổi từng truy vấn theo cách được trình bày trong bài đăng bạn đã tham chiếu.
Woot4Moo

3

Câu trả lời là đó java.sql.Timestamplà một mớ hỗn độn và nên tránh. Sử dụngjava.time.LocalDateTime thay thế.

Vậy tại sao lại là một mớ hỗn độn? Từ java.sql.TimestampJavaDoc, a java.sql.Timestamplà "một trình bao bọc mỏng xung quanh java.util.Datecho phép API JDBC xác định đây là giá trị TIMESTAMP của SQL". Từ java.util.DateJavaDoc, " Datelớp nhằm phản ánh giờ phối hợp toàn cầu (UTC)". Từ thông số ISO SQL, TIMESTAMP KHÔNG CÓ Múi giờ "là kiểu dữ liệu là ngày giờ không có múi giờ". TIMESTAMP là tên viết tắt của TIMESTAMP KHÔNG CÓ KHU VỰC THỜI GIAN. Vì vậy, mộtjava.sql.Timestamp UTC "phản ánh" trong khi SQL TIMESTAMP là "không có múi giờ".

java.sql.Timestampphản ánh UTC nên các phương pháp của nó áp dụng chuyển đổi. Điều này không gây ra sự nhầm lẫn. Từ quan điểm SQL, không có ý nghĩa gì khi chuyển đổi giá trị TIMESTAMP của SQL sang một số múi giờ khác vì TIMESTAMP không có múi giờ để chuyển đổi. Chuyển đổi từ 42 sang Fahrenheit có nghĩa là gì? Nó không có nghĩa gì vì 42 không có đơn vị nhiệt độ. Nó chỉ là một con số trần. Tương tự, bạn không thể chuyển đổi TIMESTAMP 2020-07-22T10: 38: 00 sang Châu Mỹ / Los Angeles vì ​​2020-07-22T10: 30: 00 không ở bất kỳ múi giờ nào. Nó không theo UTC hoặc GMT hay bất cứ thứ gì khác. Đó là thời gian hẹn hò trần trụi.

java.time.LocalDateTimecũng là một ngày giờ trần. Nó không có múi giờ, chính xác như SQL TIMESTAMP. Không có phương pháp nào của nó áp dụng bất kỳ loại chuyển đổi múi giờ nào khiến hành vi của nó dễ dự đoán và dễ hiểu hơn nhiều. Vì vậy, không sử dụng java.sql.Timestamp. Sử dụng java.time.LocalDateTime.

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);

Vì vậy, rất tốt nói. Cảm ơn bạn.
Ole VV

0

Bạn có thể sử dụng phương pháp dưới đây để lưu trữ dấu thời gian trong cơ sở dữ liệu cụ thể cho Id vùng / vùng mong muốn của bạn.

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

Một sai lầm phổ biến của mọi người là sử dụng LocaleDateTime lấy dấu thời gian của thời điểm đó để loại bỏ bất kỳ thông tin cụ thể nào đối với vùng của bạn ngay cả khi bạn cố gắng chuyển đổi nó sau đó. Nó không hiểu Zone.

Xin lưu ý Timestamplà của lớp java.sql.Timestamp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.