2013-05-01 44 views
5

Khi thử nghiệm dịch vụ web ánh xạ các loại ngày giờ giữa các hệ thống, tôi nhận thấy rằng việc gửi bất kỳ ngày nào trước thời gian bắt đầu của lịch Gregorian dẫn đến mất độ chính xác khi truyền sang loại cuối cùng kết quả cuối cùng luôn đi trước một chút trong khoảng thời gian vài ngày.Thay đổi ngày khi chuyển đổi từ XMLGregorianCalendar sang Lịch

tôi thu hẹp vấn đề vào dòng chính xác, nhưng tôi vẫn không thể tìm ra tại sao nó được đúc như vậy, từ documentation nó khẳng định rằng lịch Julian được sử dụng cho datetimes trước khi bắt đầu lịch Gregorian: 15 tháng 10, 1582.

dòng vấn đề là tại các diễn viên từ XMLGregorianCalendar để GregorianCalendar, dòng 78: calendarDate = argCal.toGregorianCalendar(); Khi thời gian được lấy từ calendarDate trên dòng 86: cal.setTime(calendarDate.getTime()); Hiện trở lại 2 ngày trước những gì nó phải được , Ngày 03 tháng 1 thay vì ngày 01 tháng 1, như bạn sẽ thấy từ đầu ra trong chương trình bên dưới.

Dưới đây là một chương trình mẫu tôi đã thực hiện để hiển thị các quá trình đúc kết thúc để kết thúc:

import java.sql.Date; 
import java.util.Calendar; 
import java.util.GregorianCalendar; 

import javax.xml.datatype.DatatypeConfigurationException; 
import javax.xml.datatype.DatatypeFactory; 
import javax.xml.datatype.XMLGregorianCalendar; 



public class TestDateConversions { 

    public static void main(String[] args) 
    { 
     TestDateConversions testDates = new TestDateConversions(); 
     try 
     { 
      XMLGregorianCalendar testDate1 = DatatypeFactory.newInstance().newXMLGregorianCalendar(); 
      testDate1.setYear(0001); 
      testDate1.setMonth(01); 
      testDate1.setDay(01); 
      System.out.println("Start date: "+testDate1.toString() +"\n**********************"); 

      testDates.setXMLGregorianCalendar(testDate1); 
      System.out.println("\nNull given \n"+ "**********"); 
      testDates.setXMLGregorianCalendar(null); 
     } 
     catch(Exception e) 
     { 
      System.out.println(e); 
     } 
    } 


    public void setXMLGregorianCalendar(XMLGregorianCalendar argCal) 
    { 
     GregorianCalendar calendarDate; 
     if (argCal != null) 
     { 
      calendarDate = argCal.toGregorianCalendar(); 
      System.out.println("XMLGregorianCalendar time: " + argCal.getHour() + ":"+argCal.getMinute()+":"+argCal.getSecond()); 
      System.out.println("XMLGregorianCalendar time(ms): "+argCal.getMillisecond()); 
      System.out.println("XMLGregorianCalendar -> GregorianCalendar: "+calendarDate.get(GregorianCalendar.YEAR) + "-"+(calendarDate.get(GregorianCalendar.MONTH)+1) + "-"+calendarDate.get(GregorianCalendar.DAY_OF_MONTH)); 
      System.out.println("!!!!PROBLEM AREA!!!!"); 
      Calendar cal = Calendar.getInstance(); 
      System.out.println("-- New Calendar instance: "+cal.get(Calendar.YEAR) + "-"+(cal.get(Calendar.MONTH)+1)+"-"+cal.get(Calendar.DAY_OF_MONTH)); 
      System.out.println("-- Calling Calendar.setTime(GregorianCalendar.getTime())"); 
      cal.setTime(calendarDate.getTime()); 
      System.out.println("-- calendarDate.getTime() = " + calendarDate.getTime() + " <-- time is incorrect"); 
      System.out.println("-- Calendar with time set from GregorianCalendar: "+cal.get(Calendar.YEAR) + "-"+(cal.get(Calendar.MONTH)+1)+"-"+cal.get(Calendar.DAY_OF_MONTH) + " <-- day is increased here"); 
      setCalendar(cal); 
     } 
     else 
     { 
      setCalendar(null); 
     } 
    } 

    public void setCalendar(Calendar argCal) 
    { 
     if (argCal != null) 
     { 
      Date date = new Date(argCal.getTimeInMillis()); 
      System.out.println("Calendar to Date: "+date); 
      setDate(date); 
     } 
     else 
     { 
      setDate(null); 
     } 

    } 

    public void setDate(Date argDate) 
    { 
     try 
     { 
      if (argDate == null) 
      { 
       Calendar cal = new GregorianCalendar(1,0,1); 
       Date nullDate = new Date(cal.getTimeInMillis()); 
       System.out.println("Null Calendar created: "+cal.get(Calendar.YEAR) + "-"+(cal.get(Calendar.MONTH)+1)+"-"+cal.get(Calendar.DAY_OF_MONTH)); 
       System.out.println("Null Date created: "+nullDate); 
      } 
      else 
      { 
       System.out.println("Final date type: "+argDate); 
      } 
     } 
     catch (Exception ex) 
     { 
      System.out.println(ex); 
     } 
    } 
} 
+0

FWIW: Lịch Julian không biến mất ngay lập tức vào năm 1582. Có thể phần mềm này thực sự làm điều đó (một ý tưởng tồi IMO), nhưng mã hệ thống cơ bản có thể không. Xin vui lòng cho chúng tôi một ví dụ chính xác (ngày cũ, ngày dự kiến, ngày kết quả). –

+0

@jimmcnamara Tôi không nói bất cứ điều gì đã biến mất, tôi chỉ ra rằng bất kỳ ngày nào * trước * thời gian Gregorian đang được thay đổi. Ví dụ chính xác của tôi là khá rõ ràng có trong mã chương trình, ngày '0001-01-01' đang được thay đổi thành' 0001-01-03', và nó không nên. – JWiley

+0

Được rồi - nói thật, điều này có vẻ như là một vấn đề trong thư viện. Như một giả thuyết, giả sử nó đang cố gắng sử dụng lịch Proleptic. Nó vẫn còn từ Julian bởi số tiền sai. Bạn hoàn toàn chính xác. http://calendars.wikia.com/wiki/Proleptic_Gregorian_calendar - –

Trả lời

9

Trích từ XMLGregorianCalendar.toGregorianCalendar() JavaDoc về cách họ tạo ra các ví dụ GregorianCalendar:

Lấy một Lịch Gregorian tinh khiết bằng cách gọi GregorianCalendar.setGregorianChange (new Date (Long.MIN_VALUE)).

Điều này có nghĩa là lịch đã tạo sẽ phát sinh và sẽ không chuyển sang lịch Julian theo mặc định cho các ngày cũ.Sau đó, vấn đề là ở đây:

  • argCal.toGregorianCalendar() - chuyển đổi từ XMLGregorianCalendar-GregorianCalendarsử dụng đại diện lĩnh vực (hệ thống Julian không được sử dụng - xem ở trên)
  • cal.setTime(calendarDate.getTime());
    • này là thực sự chuyển đổi biểu diễn trường để hiển thị biểu thị dấu thời gian và khởi tạo lịch mới với dấu thời gian này
    • lịch mới đang sử dụng hệ thống Julian để đại diện cho ngày vì nó là lớn hơn 1582

Có vài cách làm thế nào để giải quyết này:

  • sử dụng JodaTime và LocalDate#fromCalendarFiels nếu bạn là chỉ quan tâm đến ngày
  • chuyển đổi lịch sử dụng truy cập trường và không phải phương thức #getTime
  • lực lịch Gregorian sử dụng hệ thống proleptic (trong cùng một cách như XMLGregorianCalendar được làm việc đó)

CẬP NHẬT Xin lưu ý rằng Java ngày và Lịch API không được thiết kế rất tốt và có thể (và) đôi khi khá khó hiểu. Đây cũng là lý do tại sao Java 8 chứa thư viện ngày giờ hoàn toàn được làm lại JSR-310 (dựa trên JodaTime bằng cách này).

Bây giờ, bạn phải nhận ra, bạn có thể lưu trữ và làm việc với một (từ khóa độc lập lịch) tức cụ thể thông qua hai cách tiếp cận rất khác nhau:

  • lưu trữ một offset (ví dụ trong mili giây) từ một được xác định rõ tức gọi là kỷ nguyên (ví dụ unix epoch 1970-01-01)
  • ngày lưu trữ bởi các lĩnh vực lịch của nó (ví dụ 01 tháng 1 năm 1970)

Cách tiếp cận đầu tiên là những gì đang được sử dụng dưới mui xe trong java.util.Date. Tuy nhiên đại diện này thường không thân thiện với con người. Con người làm việc với ngày theo lịch, chứ không phải dấu thời gian. Chuyển đổi dấu thời gian thành các trường ngày là nơi mà các bước được nhập. Ngoài ra, nơi phần bắt đầu vui nhộn ... nếu bạn muốn đại diện cho ngày theo các trường của nó, bạn cần nhận ra rằng luôn có nhiều cách để làm điều đó. Một số quốc gia có thể quyết định sử dụng những tháng âm lịch, những người khác có thể nói rằng năm 0 chỉ cách đây 10 năm. Và lịch gregorian chỉ là một cách để chuyển đổi tức thời thực tế thành các trường ngày tháng thực tế.

Một chút về XMLGregorianCalendar vs GregorianCalendar:

  • đặc tả XML một cách rõ ràng nói rằng ngày con người có thể đọc được là một lịch Gregorian ngày
  • Java GregorianCalendar chứa "kỳ diệu này ", chuyển sang hệ thống Julian dưới mui xe, nếu khoảnh khắc cũ hơn ngày chuyển đổi được xác định
  • đó là lý do tại sao XMLGregorianCalendar đổi GregorianCalendar trong quá trình khởi của nó để vô hiệu hóa chuyển đổi kỳ diệu này (xem đoạn trích từ javadoc ở trên)

Bây giờ là phần thú vị:

Nếu công tắc Julian thắng' t bị vô hiệu hóa, GregorianCalendar giả định rằng các trường lịch là từ hệ thống Julian và nó sẽ thay đổi chúng sau 3 ngày. Bạn nghĩ rằng ngày đã được thay đổi 3 ngày và một cái gì đó chắc chắn đã đi sai, phải không? Không, ngày thực sự chính xác là và nó chứa đúng dấu thời gian dưới mui xe! Chỉ có lịch đã trình bày cho bạn các trường Julian thay vì các trường Gregorian. Và điều này là khá khó hiểu tôi sẽ nói :) [JSR-310 cười trong nền].

Vì vậy, nếu bạn muốn làm việc với lịch Gregorian tinh khiết (tức là để sử dụng cái gọi là proleptic Gregorian cho những ngày cũ), bạn cần phải khởi tạo lịch như thế này:

Calendar calendar = Calendar.getInstance(); 
((GregorianCalendar) calendar).setGregorianChange(new Date(Long.MIN_VALUE)); 

Bạn có thể nói: calendar.getTime() là vẫn cho tôi ngày không chính xác. Vâng, đó là vì java.util.Date.toString() (được gọi là System.out.println) đang sử dụng mặc định Calendar, sẽ chuyển sang hệ thống Julian cho các ngày cũ hơn. Bối rối? Có lẽ tức giận (tôi biết tôi là :))?


CẬP NHẬT 2

// Get XML gregorian calendar 
XMLGregorianCalendar xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(); 
xmlCalendar.setYear(1); // Watch for octal number representations (you had there 0001) 
xmlCalendar.setMonth(1); 
xmlCalendar.setDay(1); 

// Convert to Calendar as it is easier to work with it 
Calendar calendar = xmlCalendar.toGregorianCalendar(); // Proleptic for old dates 

// Convert to default calendar (will misinterpret proleptic for Julian, but it is a workaround) 
Calendar result = Calendar.getInstance(); 
result.setTimeZone(calendar.getTimeZone()); 
result.set(Calendar.YEAR, calendar.get(Calendar.YEAR)); 
result.set(Calendar.MONTH, calendar.get(Calendar.MONTH)); 
result.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH)); 
result.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY)); 
result.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE)); 
result.set(Calendar.SECOND, calendar.get(Calendar.SECOND)); 
result.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND)); 

System.out.println(result.getTime()); 

Disclamer: mã này là sai (kết quả ngay lập tức là không giống như một trong file XML), nhưng OP hiểu được vấn đề và nó hậu quả (xem phần thảo luận theo câu trả lời này).

+0

Cảm ơn Pavel. Đáng tiếc là tôi không thể sử dụng Joda trong tình huống này, bạn có thể hiển thị viên đạn 2 và 3 không? – JWiley

+0

Tôi đã cập nhật câu trả lời với một số thông tin cơ bản về tương tác Ngày và Lịch. Ngoài ra tôi đã xóa viên đạn thứ hai vì đó không phải là lời khuyên chính xác và sẽ dẫn bạn vào nhiều vấn đề hơn. –

+0

Phần cuối cùng đó khiến nó khó hiểu hơn. Làm thế nào tôi sẽ áp dụng giải pháp của bạn cho những gì tôi có? Nói cách khác, tôi sẽ kết hợp lịch ((GregorianCalendar)) .setGregorianChange (ngày mới (Long.MIN_VALUE)); 'trong phương thức' setXMLGregorianCalendar' của tôi và sử dụng nó để lấy ngày từ biến 'GregorianCalendar'? – JWiley

0

Có vẻ như bạn đang vô tình chuyển đổi từ lịch Gregorian vào lịch Julian. Ngày mà (lịch ngoại suy hoặc proleptic) Lịch Gregorian đề cập đến là 0001-01-01 là, thực sự, 0001-01-03 trong Lịch Julian. Nếu bạn thay đổi ngày thành 1001-01-01, bạn sẽ thấy kết quả cuối cùng của mình là 1000-12-26. Điều này phù hợp với các chuyển đổi ngày từ Gregorian sang Julian, khi thay đổi bù đắp theo thời gian. Nó đã thực sự tích cực trước thế kỷ thứ 4, nhưng đã âm (và trở nên tiêu cực hơn theo thời gian) kể từ đó.

Các thể hiện của XMLGregorianCalendar tôi nhận được thực sự là một com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpltoGregorianCalendar() phương pháp bao gồm những dòng này:

result = new GregorianCalendar(tz, locale); 
    result.clear(); 
    result.setGregorianChange(PURE_GREGORIAN_CHANGE); 

Điều này làm cho lịch một GregorianCalendar thuần túy proleptic, vì vậy mà ngày trước 1583 vẫn đang chuyển đổi từ thời kỷ nguyên (mili giây từ năm 1970) bởi thuật toán Gregorian. Điều này có nghĩa là sự cố có thể xảy ra ở GregorianCalendar ...

Trường hợp thực sự là như vậy. Hãy thử chương trình mẫu này và bạn sẽ thấy:

public static void main(String...args) { 
    GregorianCalendar gcal = new GregorianCalendar(); 
    gcal.clear(); 
    gcal.setGregorianChange(new Date(Long.MIN_VALUE)); 
    gcal.set(Calendar.YEAR, 1); // or any other year before 1582 
    gcal.set(Calendar.MONTH, Calendar.JANUARY); 
    gcal.set(Calendar.DATE, 1); 

    Date d = gcal.getTime(); 
    System.out.println(d); 
} 

Các giá trị lĩnh vực đi vào được hiểu là Gregorian do giá trị của getGregorianChange() ... nhưng sắp ra (chuyển đổi từ mili giây kỷ nguyên) lĩnh vực mà dường như được không vâng lời . Các Date mà đi ra khỏi getTime() không có khái niệm về những gì giá trị chuyển đổi Gregorian được. Điều này có thể có hoặc không có thể là một lỗi, nhưng đó chắc chắn là hành vi bất ngờ.

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