Jeśli dopiero parsowania wejście String
, to straighforward:
LocalDate d1 = LocalDate.parse("1893-04-01");
System.out.println(d1); // 1893-04-01
LocalDate d2 = LocalDate.parse("1400-04-01");
System.out.println(d2); // 1400-04-01
Wyjście jest:
1893-04-01
1400-04-01
Ale jeśli masz java.util.Date
obiekt i trzeba go przekonwertować, to trochę bardziej skomplikowane.
A java.util.Date
contains the number of milliseconds from unix epoch (1970-01-01T00:00Z
). Możesz więc powiedzieć "jest w UTC", ale kiedy drukujesz, wartość jest "konwertowana" na domyślną strefę czasową systemu (w twoim przypadku jest to CET
). I SimpleDateFormat
używa również wewnętrznej strefy czasowej wewnętrznie (w sposób niejasny muszę przyznać, że nie w pełni rozumiem).
W powyższym przykładzie wartość millis wynosząca -2422054800000
jest równoważna wartości czasu UTC 1893-03-31T23:00:00Z
. Sprawdzanie to wartość w Europe/Berlin
czasowej:
System.out.println(Instant.ofEpochMilli(-2422054800000L).atZone(ZoneId.of("Europe/Berlin")));
wyjście jest:
1893-03-31T23: 53: 28 + 00: 53: 28 [Europa/Berlin]
Tak, jest to bardzo dziwne, ale wszystkie miejsca używały dziwnych przesunięć przed rokiem 1900 - każde miasto miało swój lokalny czas, zanim nastąpiła norma UTC. To wyjaśnia, dlaczego otrzymujesz 1893-03-31
. Obiekt Date
drukuje prawdopodobnie dlatego, że stary interfejs API (java.util.TimeZone
) nie ma wszystkich historii przesunięć, więc zakłada, że jest to +01:00
.
Alternatywą do tej pracy jest, aby zawsze używać UTC jako strefy czasowej:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // set UTC to the format
Date date = sdf.parse("1893-04-01");
LocalDate d = date.toInstant().atZone(ZoneOffset.UTC).toLocalDate();
System.out.println(d); // 1893-04-01
Pozwoli to uzyskać prawidłową datę lokalne: 1893-04-01
.
Ale dla dat przed 1582-10-15
, powyższy kod nie działa. To data wprowadzenia kalendarza gregoriańskiego. Wcześniej używano kalendarza juliańskiego i datowano przed nim: need an adjustment.
Mogłem to zrobić z ThreeTen Extra project (rozszerzenie java.time
klas, stworzonych przez tego samego faceta BTW). W pakiecie org.threeten.extra.chrono
istnieją klasy JulianChronology
i JulianDate
:
// using the same SimpleDateFormat as above (with UTC set)
date = sdf.parse("1400-04-01");
// get julian date from date
JulianDate julianDate = JulianChronology.INSTANCE.date(date.toInstant().atZone(ZoneOffset.UTC));
System.out.println(julianDate); // Julian AD 1400-04-01
wyjście będzie:
Julian AD 1400-04-01
Teraz musimy przekształcić JulianDate
do LocalDate
. Jeśli wykonam LocalDate.from(julianDate)
, konwertuje się do kalendarza gregoriańskiego (a wynikiem jest 1400-04-10
).
Ale jeśli chcesz stworzyć LocalDate
z dokładnie 1400-04-01
, musisz to zrobić:
LocalDate converted = LocalDate.of(julianDate.get(ChronoField.YEAR_OF_ERA),
julianDate.get(ChronoField.MONTH_OF_YEAR),
julianDate.get(ChronoField.DAY_OF_MONTH));
System.out.println(converted); // 1400-04-01
wyjście będzie:
1400-04-01
Należy pamiętać, że daty przed 1582-10-15
mają tę regulację i SimpleDateFormat
nie może obsłużyć tych przypadków p roperly. Jeśli potrzebujesz pracować tylko z wartościami 1400-04-01
(rok/miesiąc/dzień), użyj LocalDate
. Ale jeśli chcesz przekonwertować go na java.util.Date
, pamiętaj, że może to nie być ta sama data (z powodu korekt Gregoriańskiego/Juliańskiego).
Jeśli nie chcesz dodawać kolejnej zależności, możesz również wykonywać wszystkie czynności matematyczne ręcznie. Mam dostosowany kod z ThreeTen, ale IMO idealnym jest użycie samego API (co może obejmować przypadki rogu i inne rzeczy jestem prawdopodobnie brakuje po prostu kopiując kawałek kodu):
// auxiliary method
public LocalDate ofYearDay(int prolepticYear, int dayOfYear) {
boolean leap = (prolepticYear % 4) == 0;
if (dayOfYear == 366 && leap == false) {
throw new DateTimeException("Invalid date 'DayOfYear 366' as '" + prolepticYear + "' is not a leap year");
}
Month moy = Month.of((dayOfYear - 1)/31 + 1);
int monthEnd = moy.firstDayOfYear(leap) + moy.length(leap) - 1;
if (dayOfYear > monthEnd) {
moy = moy.plus(1);
}
int dom = dayOfYear - moy.firstDayOfYear(leap) + 1;
return LocalDate.of(prolepticYear, moy.getValue(), dom);
}
// sdf with UTC set, as above
Date date = sdf.parse("1400-04-01");
ZonedDateTime z = date.toInstant().atZone(ZoneOffset.UTC);
LocalDate d;
// difference between the ISO and Julian epoch day count
long julianToIso = 719164;
int daysPerCicle = (365 * 4) + 1;
long julianEpochDay = z.toLocalDate().toEpochDay() + julianToIso;
long cycle = Math.floorDiv(julianEpochDay, daysPerCicle);
long daysInCycle = Math.floorMod(julianEpochDay, daysPerCicle);
if (daysInCycle == daysPerCicle - 1) {
int year = (int) ((cycle * 4 + 3) + 1);
d = ofYearDay(year, 366);
} else {
int year = (int) ((cycle * 4 + daysInCycle/365) + 1);
int doy = (int) ((daysInCycle % 365) + 1);
d = ofYearDay(year, doy);
}
System.out.println(d); // 1400-04-01
wyjście będzie:
1400-04-01
Wystarczy przypomnieć, że cała ta matematyka nie jest potrzebna dla dat po 1582-10-15
.
W każdym razie, jeśli masz wejście String
i chcesz go analizować, nie używaj SimpleDateFormat
- można użyć LocalDate.parse()
zamiast. Lub LocalDate.of(year, month, day)
, jeśli znasz już wartości.
Konwersja tych lokalnych dat z/na java.util.Date
jest bardziej skomplikowana, ponieważ Date
reprezentuje pełną sygnaturę czasową millis, a daty mogą się różnić w zależności od używanego systemu kalendarza.
Czy możesz podać dane wyjściowe pliku 'System.out.println (date.getTime())'? –
A także 'System.out.println (TimeZone.getDefault(). ToZoneId());' –
Pamiętaj, że żądana konwersja z 'Date' na' LocalDate' zależy od strefy czasowej. Jeśli masz pewność, że obiekt 'Date' miał zostać zinterpretowany w domyślnej strefie czasowej maszyny JVM oraz w (powszednim) kalendarzu gregoriańskim, twoja metoda konwersji powinna być poprawna. –