2015-07-01 26 views
7

Tôi có thử nghiệm sau:Tôi có tìm thấy lỗi java.util.Calendar không?

import static org.junit.Assert.assertEquals; 

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

import org.junit.Test; 
public class CalendarBug { 
    private static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC");// +0 hours 
    private static final TimeZone IST_ZONE = TimeZone.getTimeZone("IST");// +5 hours 30 minutes 

    @Test 
    public void calendarBug() { 
     Calendar utcCalendar = Calendar.getInstance(UTC_ZONE); 
     utcCalendar.set(Calendar.YEAR, 2015); 
     utcCalendar.set(Calendar.MONTH, 3); 
     utcCalendar.set(Calendar.DAY_OF_MONTH, 12); 
     utcCalendar.set(Calendar.HOUR_OF_DAY, 10); 
     utcCalendar.set(Calendar.MINUTE, 0); 
     utcCalendar.set(Calendar.SECOND, 0); 
     SimpleDateFormat utcFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); 
     utcFormatter.setTimeZone(UTC_ZONE); 
     System.out.println("If I have this line commented out, the test fails: " + utcFormatter.format(utcCalendar.getTime())); 
     Calendar istCalendar = (Calendar) utcCalendar.clone(); 
     assertEquals(UTC_ZONE, istCalendar.getTimeZone()); 
     istCalendar.setTimeZone(IST_ZONE); 
     assertEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()); 
    } 
} 

Nếu bạn chạy thử nghiệm, nó hoạt động. Tuy nhiên nếu bạn nhận xét ra dòng System.out.println với định dạng bên trong, nó không thành công:

java.lang.AssertionError: expected:<1428813000979> but was:<1428832800979> 
    at org.junit.Assert.fail(Assert.java:93) 
    at org.junit.Assert.failNotEquals(Assert.java:647) 
    at org.junit.Assert.assertEquals(Assert.java:128) 
    at org.junit.Assert.assertEquals(Assert.java:472) 
    at org.junit.Assert.assertEquals(Assert.java:456) 
    at xxx.yyy.CalendarBug.calendarBug(CalendarBug.java:29) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:606) 
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) 
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300) 
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) 
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) 

Dường như định dạng thay đổi trạng thái nội bộ của utcCalendar và nó gây ra các thử nghiệm để vượt qua.

Tôi đã lạm dụng bất cứ điều gì ở đây? Có một lỗi mở trong JDK cho điều này?

phiên bản Java:

[email protected]:~$ java -version 
java version "1.7.0_60" 
Java(TM) SE Runtime Environment (build 1.7.0_60-b19) 
Java HotSpot(TM) 64-Bit Server VM (build 24.60-b09, mixed mode) 
+0

Tôi không thể sao chép giống nhau trong Java 1.7.0_21 – Sighil

Trả lời

1

EDIT: Tôi đã đăng một câu trả lời không chính xác trong sự vội vàng, do đọc chỉ là một phần của mã của bạn.

Dưới đây là một câu trả lời hoàn toàn mới:

Trước hết, Javadoc của các quốc gia Lịch khi thời gian của lịch thực sự được tính:

Bắt và Thiết Lịch Dòng Values ​​

Giá trị trường lịch có thể được đặt bằng cách gọi phương thức đặt. Bất kỳ giá trị trường nào được đặt trong Lịch sẽ không được diễn giải cho đến khi cần tính giá trị thời gian thời gian (mili giây từ Epoch) hoặc giá trị của lịch trường. Calling get, getTimeInMillis, getTime, thêm và cuộn liên quan đến việc tính toán như vậy.

Hành vi bạn gặp phải không phải là lỗi.

Bạn tạo một phiên bản Calendar và sau đó tạo bản sao của cá thể đó. Sau đó, bạn thay đổi múi giờ của thể hiện nhân bản.

Tuy nhiên, có hai kịch bản:

  1. Thời gian của đối tượng Lịch gốc được tính trước khi tạo clone, như là kết quả của việc gọi utcCalendar.getTime() trong bản Tuyên Bố in của bạn. Lịch nhân bản có chứa thời gian này, vì vậy việc thay đổi múi giờ không thay đổi thời gian đó (vì getTimeInMillis() trả lại UTC milliseconds from the epoch, do đó, nó không phụ thuộc vào múi giờ nào được sử dụng). Thời gian không được tính toán lại khi bạn gọi số assertEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()), vì vậy bài kiểm tra sẽ trôi qua.

  2. Thời gian của đối tượng Lịch ban đầu không được tính trước khi tạo bản sao. Sau đó, bạn thay đổi múi giờ của thể hiện nhân bản. Bây giờ, khi bạn gọi assertEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()), thời gian trong cả hai trường hợp Lịch được tính toán và chúng khác nhau, vì 10AM IST và 10AM UTC không cùng thời gian (cách nhau 5,5 giờ, chính xác là sự khác biệt giữa 14288130009791428832800979).

+1

Vậy tại sao nó không phải là lỗi? – immibis

+0

Tôi vẫn mong đợi rằng các giá trị trường được nhân bản và cuộc gọi getTime trong assertEquals sẽ tính toán cùng một giá trị cho cả hai lịch. –

+0

Sau đó, câu hỏi thực sự là: trạng thái nội bộ của lịch là gì? Giá trị trường hoặc thời gian UTC tính bằng milis? Điều gì cần được nhân bản thực sự? –

0

Tôi nghĩ rằng đây là một hành vi mong đợi hơn kể từ khi bạn nhân bản.

Trước hết, giá trị thời gian trên hai lịch đó không thể giống như IST sẽ có bộ tắt 19800000 và được phản ánh ở trên AssertionError. Khẳng định đúng sẽ là:

assertNotEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()); 

Khi bạn gọi utcCalendar.getTime(); để in ra định dạng bạn đang thực sự thay đổi trạng thái nội bộ của đối tượng lịch để có isTimeComputed để true. Điều này là để tránh việc tính toán lại thời gian khi getTime() hoặc getTime Millis() được gọi lại. Cùng một cờ mang theo như true đến nhân bản đối tượng istCalendar và do đó thời gian không được tính lại trên istCalendar khi bạn gọi getTimeInMillis.

Điều này thực sự khiến cả hai lịch đều in sai một mili giây.

Đây không phải là lỗi, tôi sẽ nói. Đây là một hiệu ứng phụ phụ của bản sao.

Các vấn đề liên quan