Если вы просто анализируете ввод String
, это просто:
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
Результат:
1893-04-01
1400-04-01
Но если у вас есть объект java.util.Date
и вам нужно его преобразовать, это немного сложнее.
java.util.Date
содержит количество миллисекунд от эпохи Unix а> (1970-01-01T00:00Z
). Таким образом, вы можете сказать «это в формате UTC», но когда вы его печатаете, значение «конвертируется» в системный часовой пояс по умолчанию (в вашем случае это CET
). И SimpleDateFormat
также использует внутренний часовой пояс по умолчанию (непонятными способами, которые, я должен признать, я не совсем понимаю).
В вашем примере значение миллисекунды -2422054800000
эквивалентно моменту UTC 1893-03-31T23:00:00Z
. Проверка этого значения в часовом поясе Europe/Berlin
:
System.out.println(Instant.ofEpochMilli(-2422054800000L).atZone(ZoneId.of("Europe/Berlin")));
Результат:
1893-03-31T23:53:28+00:53:28[Европа/Берлин]
Да, это очень странно, но до 1900 года все места использовали странные смещения - в каждом городе было свое местное время, до того, как появился стандарт UTC. Это объясняет, почему вы получаете 1893-03-31
. Объект Date
печатает April 1st
, вероятно, потому, что старый API (java.util.TimeZone
) не имеет всей истории смещений, поэтому предполагается, что это +01:00
.
Один из способов заставить это работать — всегда использовать UTC в качестве часового пояса:
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
Это позволит получить правильную местную дату: 1893-04-01
.
Но для дат до 1582-10-15
приведенный выше код не работает. Это дата введения григорианского календаря. До этого использовался юлианский календарь, и даты до него требуют корректировки.
Я мог бы сделать это с помощью проекта ThreeTen Extra (расширение java.time
классов, созданное тем же парень кстати). В пакете org.threeten.extra.chrono
есть классы JulianChronology
и 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
Вывод будет:
Юлиан н.э. 1400-04-01
Теперь нам нужно преобразовать JulianDate
в LocalDate
. Если я делаю LocalDate.from(julianDate)
, он преобразуется в григорианский календарь (и результат 1400-04-10
).
Но если вы хотите создать LocalDate
ровно с 1400-04-01
, вам придется сделать это:
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
Вывод будет:
1400-04-01
Просто имейте в виду, что даты до 1582-10-15
имеют эту корректировку, и SimpleDateFormat
не может правильно обрабатывать такие случаи. Если вам нужно работать только с 1400-04-01
(значения года/месяца/дня), используйте файл LocalDate
. Но если вам нужно преобразовать его в java.util.Date
, имейте в виду, что это может быть не та же дата (из-за корректировок по григорианскому/юлианскому календарю).
Если вы не хотите добавлять еще одну зависимость, вы также можете выполнить всю математику вручную. Я адаптировал код из ThreeTen, но IMO идеально использовать сам API (поскольку он может охватывать крайние случаи и другие вещи, которые я, вероятно, упустил, просто скопировав фрагмент кода):
// 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
Вывод будет:
1400-04-01
Просто напомню, что вся эта математика не нужна для дат после 1582-10-15
.
В любом случае, если у вас есть ввод String
и вы хотите его разобрать, не используйте SimpleDateFormat
— вместо этого вы можете использовать LocalDate.parse()
. Или LocalDate.of(year, month, day)
, если вы уже знаете значения.
Но преобразовать эти локальные даты из/в java.util.Date
сложнее, потому что Date
представляет собой полную метку времени в миллисекундах, а даты могут варьироваться в зависимости от используемой календарной системы.
person
Community
schedule
28.06.2017
System.out.println(date.getTime())
? - person   schedule 28.06.2017System.out.println(TimeZone.getDefault().toZoneId());
- person   schedule 28.06.2017Date
вLocalDate
зависит от часового пояса. Если вы уверены, что ваш объектDate
должен интерпретироваться в часовом поясе вашей JVM по умолчанию и в (пролептическом) григорианском календаре, ваш метод преобразования должен быть правильным. - person Ole V.V.   schedule 28.06.2017