2012-05-09 7 views
6

Tôi có một đối tượng java.util.Date và tôi cần chèn nó vào trường ngày giờ trong MySQL ở định dạng UTC.Các vấn đề về múi giờ của Java MySQL Dấu thời gian

java.util.Date date = myDateFromSomewhereElse; 
PreparedStatement prep = con.prepareStatement(
    "INSERT INTO table (t1, t2) VALUES (?,?)"); 

java.sql.Timestamp t = new Timestamp(date.getTime()); 
prep.setTimestamp(1, t, Calendar.getInstance(TimeZone.getTimeZone("PST")); 
prep.setTimestamp(2, t, Calendar.getInstance(TimeZone.getTimeZone("UTC")); 
System.out.println(prep.toString()); 

nào mang lại cho tôi sự chuẩn bị chuỗi câu lệnh SQL:

INSERT INTO table (t1, t2) VALUES ('2012-05-09 11:37:08','2012-05-09 11:37:08'); 

Các dấu thời gian quay trở lại là dấu thời gian cùng không phụ thuộc vào múi giờ tôi chỉ định. Nó bỏ qua đối tượng Lịch với múi giờ mà tôi chỉ định. Điều gì đang xảy ra và tôi đang làm gì sai?

+2

Múi giờ không thay đổi dấu thời gian. Họ chỉ thay đổi những gì được hiển thị khi ngày được hiển thị. – Jeremy

+0

Có, tôi hiểu rằng dấu thời gian chỉ là một số mili giây kể từ epoch GMT, bất kể múi giờ. Vấn đề của tôi là thậm chí bằng cách chỉ định múi giờ, nó sẽ hiển thị cùng ngày hiển thị giống nhau (xem mặc dù tôi đã chỉ định các múi giờ khác nhau, ngày được hiển thị chính xác như nhau trong lệnh SQL cuối cùng). – Jordan

+0

Tôi nghĩ rằng đây có thể là những gì bạn đang tìm kiếm: http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/ – Chris

Trả lời

2

Múi giờ chỉ là các cách khác nhau để xem ngày (là một điểm cố định đúng lúc). Tôi đã viết một ví dụ nhỏ ở đây (chú ý tới sự khẳng định):

// timezone independent date (usually interpreted by the timezone of 
// the default locale of the user machine) 
Date now = new Date(); 

// now lets get explicit with how we wish to interpret the date 
Calendar london = Calendar.getInstance(TimeZone.getTimeZone("Europe/London")); 
Calendar paris = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris")); 

// now set the same date on two different calendar instance 
london.setTime(now); 
paris.setTime(now); 

// the time is the same 
assert london.getTimeInMillis() == paris.getTimeInMillis(); 

// London is interpreted one hour earlier than Paris (as of post date of 9th May 2012) 
String londonTime = london.get(Calendar.HOUR) + ":" + london.get(Calendar.MINUTE); 
String londonTZ = london.getTimeZone().getDisplayName(london.getTimeZone().inDaylightTime(london.getTime()), TimeZone.SHORT); 
System.out.println(londonTime + " " + londonTZ); 

// Paris is interpreted one hour later than Paris (as of post date of 9th May 2012) 
String parisTime = paris.get(Calendar.HOUR) + ":" + paris.get(Calendar.MINUTE); 
String parisTZ = paris.getTimeZone().getDisplayName(paris.getTimeZone().inDaylightTime(paris.getTime()), TimeZone.SHORT); 
System.out.println(parisTime + " " + parisTZ); 

Sản lượng đến đoạn này là (kết quả sẽ khác nhau tùy thuộc vào ngày thi/giờ):

8:18 BST 
9:18 CEST 

của bạn đoạn trích trong câu hỏi đơn giản là không làm bất cứ điều gì liên quan đến ngày được lưu trữ. Thông thường các cơ sở dữ liệu được cấu hình cho một TimeZone riêng. Tôi khuyên bạn nên lưu trữ một trường bổ sung đại diện cho TimeZone được sử dụng khi diễn giải ngày tháng. Nó không phải là (nói chung) một ý tưởng tốt để sửa đổi ngày (mà về cơ bản chỉ là mili giây trước/sau một thời điểm cố định) vì đây sẽ là một sửa đổi mất dữ liệu sẽ được diễn giải khác nhau tại các điểm khác nhau trong năm (do thời gian tiết kiệm ánh sáng ban ngày).

Hoặc này: http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/

+0

Thực ra anh ấy có thể nhận được các giá trị khác nhau trong cơ sở dữ liệu, nếu cơ sở dữ liệu của ông là Oracle và ông đã sử dụng loại cột 'TIMESTAMP WITH TIME ZONE'. Nhưng không hỗ trợ múi giờ cho các ngày trong MySQL. –

+0

Có, tôi đặt một liên kết ở dưới cùng để triển khai Oracle cụ thể để lưu trữ/truy xuất TimeZones cùng với ngày tháng. Tôi nghĩ rằng nó chung chung hơn để lưu trữ múi giờ một cách riêng biệt vì nó làm cho nó dễ dàng hơn cho cơ sở dữ liệu không phải của Oracle. Điều tuyệt vời về các ngày thậm chí không có bối cảnh TimeZone là chúng có thể được so sánh với nhau mà không có vấn đề gì. – Chris

+0

Nếu bạn thực sự cần phải duy trì thông tin múi giờ thì tốt hơn nên sử dụng tính năng cơ sở dữ liệu cụ thể (như trong Oracle và PostgreSQL) và quên đi tính di động. Nếu không, bạn sẽ có vấn đề với so sánh và phân loại. –

2

Kiểm tra this link cho lời giải thích cho MySQL (và bạn không nên cố gắng áp dụng lời khuyên về Oracle MySQL).

Loại dữ liệu TIMESTAMP được sử dụng cho các giá trị chứa cả phần ngày và giờ. TIMESTAMP có phạm vi '1970-01-01 00:00:01' UTC đến '2038-01-19 03:14:07' UTC.

MySQL chuyển đổi giá trị TIMESTAMP từ múi giờ hiện tại sang UTC để lưu trữ và ngược lại từ UTC thành múi giờ hiện tại để truy xuất. (Điều này không xảy ra đối với các kiểu khác như DATETIME.) Theo mặc định, múi giờ hiện tại cho mỗi kết nối là thời gian của máy chủ.

9

Jordan, thực ra bạn đã có ý tưởng đúng. Vấn đề là có một lỗi trong trình điều khiển MySQL JDBC và đối số Lịch hoàn toàn bị bỏ qua theo mặc định. Nhìn vào mã nguồn cho PreparedStatement để thực sự thấy những gì đang xảy ra.

Lưu ý định dạng đó là Dấu thời gian sử dụng múi giờ của JVM. Điều này sẽ chỉ hoạt động nếu JVM của bạn đang sử dụng múi giờ UTC. Đối tượng Lịch bị bỏ qua hoàn toàn.

this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''", Locale.US); 
timestampString = this.tsdf.format(x); 

Để cho MySQL sử dụng lập luận Lịch, bạn phải vô hiệu hóa ngày di sản/mã thời gian với các tùy chọn kết nối sau:

useLegacyDatetimeCode=false 

Vì vậy, bạn có thể sử dụng nó khi kết nối với cơ sở dữ liệu như thế này:

String url = "jdbc:mysql://localhost/tz?useLegacyDatetimeCode=false" 

Nếu bạn vô hiệu hóa các mã di sản datetime sử dụng dòng ở trên, sau đó nó sẽ làm cho bạn Timestamp theo múi giờ mục tiêu của Lịch:

if (targetCalendar != null) { 
    targetCalendar.setTime(x); 
    this.tsdf.setTimeZone(targetCalendar.getTimeZone()); 

    timestampString = this.tsdf.format(x); 
} else { 
    this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ()); 
    timestampString = this.tsdf.format(x); 
} 

Thật dễ dàng để xem những gì đang diễn ra ở đây. Nếu bạn vượt qua trong một đối tượng Lịch, nó sẽ sử dụng nó khi định dạng dữ liệu. Nếu không, nó sẽ sử dụng múi giờ của cơ sở dữ liệu để định dạng dữ liệu. Kỳ lạ thay, nếu bạn chuyển vào một Lịch, nó cũng sẽ đặt thời gian cho giá trị Dấu thời gian đã cho (dường như là vô nghĩa).