#java #date #datetime
#java #Дата #дата и время
Вопрос:
Итак, у меня такая странная проблема с датой Java.
Точкой входа для этой проблемы являются 2 строковые даты StartDate («30 января 2016 года») и endate «»29 января 2017 года».
Проблема немного сложнее, но я подвел ее только к этому случаю.
Что мне нужно сделать, так это получить расписание на 1 год, точно такое же, как показано ниже, начиная с этих данных.
Таблица содержит для каждого месяца: первый день, последний день и дни между этими 2 днями.
Ожидаемые результаты:
/**
-
Сб 30 января 00:00:00 GMT 2016 — Вс 28 февраля 23:59:59 GMT 2016 — 30
-
Пн, 29 февраля, 00:00:00 по Гринвичу 2016 — Вт, 29 марта, 23:59:59 по британскому времени 2016 — 30……….. по британскому летнему времени
-
Ср. 30 марта 00:00:00 BST 2016 — Пт. 29 апр. 23:59:59 BST 2016 — 31. По британскому летнему времени по британскому летнему времени
-
Сб, 30 апреля, 00:00:00 BST 2016 — Вс, 29 мая, 23:59:59 BST, 2016 — 30……….. по британскому летнему времени по британскому летнему времени
-
Пн, 30 мая, 00:00:00 по британскому летнему времени 2016 — Ср, 29 июня, 23:59:59 по британскому летнему времени 2016 — 31
-
Чт, 30 июня, 00:00:00 по британскому летнему времени 2016 — Пт, 29 июля, 23:59:59 по британскому летнему времени 2016 — 30
-
Сб, 30 июля, 00:00:00 BST 2016 — Пн, 29 августа, 23:59:59 BST, 2016 — 31……….. по британскому летнему времени по британскому летнему времени
-
Вт, 30 августа, 00:00:00 BST 2016 — Чт, 29 сентября, 23:59:59 BST, 2016 — 31……….. по британскому летнему времени по британскому летнему времени.
-
Пт, 30 сентября, 00:00:00 BST 2016 — Сб, 29 октября, 23:59:59 BST, 2016 — 30……….. по британскому летнему времени по британскому летнему времени.
-
ВС окт 30 00:00:00 по британскому летнему времени 2016 — Вт Ноя 29 23:59:59 по Гринвичу 2016 — 31
-
Ср. 30 ноября 00:00:00 GMT 2016 — Чт. 29 дек. 23:59:59 GMT 2016 — 30
-
Пт Дек 30 00:00:00 GMT 2016 — Вс Янв 29 23:59:59 GMT 2017 — 31
*/
Мои результаты
Сб 30 января 00:00:00 EET 2016 ::::: Вс 28 февраля 23:59:59 EET 2016 ::::: 29
Пн 29 февраля 00:00:00 EET 2016 ::::: Пн 28 марта 23:59:59 EEST 2016 ::::: 28
Tue Mar 29 00:00:00 EEST 2016 ::::: Thu Apr 28 23:59:59 EEST 2016 ::::: 30
Пт 29 апреля 00:00:00 EEST 2016 ::::: Сб 28 мая 23:59:59 EEST 2016 ::::: 29
Вс 29 мая 00:00:00 EEST 2016 ::::: Вт 28 июня 23:59:59 EEST 2016 ::::: 30
Ср. 29 июня 00:00:00 EEST 2016 ::::: Чт. 28 июля 23:59:59 EEST 2016 ::::: 29
Пт 29 июля 00:00:00 EEST 2016 ::::: Вс 28 августа 23:59:59 EEST 2016 ::::: 30
Пн Авг 29 00:00:00 EEST 2016 ::::: Ср 28 сентября 23:59:59 EEST 2016 ::::: 30
Чт 29 сентября 00:00:00 EEST 2016 ::::: Пт 28 октября 23:59:59 EEST 2016 ::::: 29
Sat Oct 29 00:00:00 EEST 2016 ::::: Mon Nov 28 23:59:59 EET 2016 ::::: 31
Tue Nov 29 00:00:00 EET 2016 ::::: Wed Dec 28 23:59:59 EET 2016 ::::: 29
Thu Dec 29 00:00:00 EET 2016 ::::: Sat Jan 28 23:59:59 EET 2017 ::::: 30
So I’m not sure if this happens because of the February month or because 2016 was a leap year that included 29th of February.
What am I missing? I have the same problem run multiple test cases and all others are OK, but this.
Я также пытался сделать это с помощью Javas 8 LocalDate и LocalDateTime, и я получаю точно такие же результаты.
Вот мой код
import org.apache.commons.lang.time.DateUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Main {
public static void main(String[] args) throws ParseException {
String startDate = "30 Jan 2016";
String endDate = "29 Jan 2017"; // current not using this ?!
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
Date start = formatter.parse(startDate);
List<Item> items = new ArrayList<>();
for (int i = 0; i < 12; i ) {
Date end = DateUtils.addMonths(start, 1);
end = DateUtils.addSeconds(end, -1);
items.add(new Item(start, end));
start = DateUtils.addMonths(start, 1);
}
items.forEach(item -> {
System.out.println(item.getStart() " ::::: " item.getEnd() " ::::: " getDifferenceDays(item.getStart(), item.getEnd()));
});
}
public static long getDifferenceDays(Date d1, Date d2) {
return ChronoUnit.DAYS.between(d1.toInstant(), d2.toInstant());
}
}
- Класс элемента
import java.util.Date;
public class Item {
Date start;
Date end;
public Item(Date start, Date end) {
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public void setStart(Date start) {
this.start = start;
}
public Date getEnd() {
return end;
}
public void setEnd(Date end) {
this.end = end;
}
@Override
public String toString() {
return "Item{"
"start=" start
", end=" end
'}';
}
}
Комментарии:
1. Ну, для начала, вы не должны использовать
Date
,Calendar
иSimpleDateFormat
классы. Они доставляют хлопоты. Вместо этого используйте классы изjava.time
.2. Как я уже сказал… Я использовал Javas 8 LocalDate и LocalDateTime, и я получаю такое же поведение.
3. Вы пробовали docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html ; установка зоны. О, я вижу, что DateUtils — это внешняя библиотека, вспомните мой комментарий по этому поводу.
4. DateUtils взят из import org.apache.commons.lang.time. DateUtils; Я не понимаю, что именно вы подразумеваете под классом ZonedDateTime. Тем не менее O изменил дату моего компьютера на GTM, и я не получаю те же часовые пояса, но поведение даты остается прежним.
5. PeterMmm правильно: вы неправильно используете DateUtils. Но что более важно, если вы используете Java 8 или выше, вам ГОРАЗДО лучше использовать вместо этого новое Java «time». ПРЕДЛОЖЕНИЕ: Пожалуйста, попробуйте совет Ole V.V.В. Если это работает, пожалуйста, «Проголосуйте» и «Примите» его.
Ответ №1:
java.time
Поскольку вы можете использовать java.time, современный Java date и time API, я рекомендую вам придерживаться этого и оставить старые классы SimpleDateFormat
и Date
в покое. Тогда вам также не нужен Apache DateUtils
. ChronoUnit
Перечисление взято из java.time.
DateTimeFormatter dateFormatter
= DateTimeFormatter.ofPattern("d MMM u", Locale.ENGLISH);
String startDateString = "30 Jan 2016";
LocalDate originalStartDate
= LocalDate.parse(startDateString, dateFormatter);
for (int i = 0; i < 12; i ) {
LocalDate startDate = originalStartDate.plusMonths(i);
LocalDate nextStartDate = originalStartDate.plusMonths(i 1);
LocalDate endDate = nextStartDate.minusDays(1);
long differenceDays = ChronoUnit.DAYS.between(startDate, nextStartDate);
System.out.format("%s - %s : %d%n", startDate, endDate, differenceDays);
}
Вывод:
2016-01-30 - 2016-02-28 : 30 2016-02-29 - 2016-03-29 : 30 2016-03-30 - 2016-04-29 : 31 2016-04-30 - 2016-05-29 : 30 2016-05-30 - 2016-06-29 : 31 2016-06-30 - 2016-07-29 : 30 2016-07-30 - 2016-08-29 : 31 2016-08-30 - 2016-09-29 : 31 2016-09-30 - 2016-10-29 : 30 2016-10-30 - 2016-11-29 : 31 2016-11-30 - 2016-12-29 : 30 2016-12-30 - 2017-01-29 : 31
Что пошло не так в вашем коде?
За вашими наблюдаемыми неожиданными результатами есть несколько причин.
- Когда вы добавляете месяц к 30 января, вы получаете 29 февраля, как и ожидали. В невисокосный год вы получили бы 28 февраля. Когда вы добавляете еще один месяц к 29 февраля, вы получаете 29 марта. Удивительно ли это, когда вы думаете об этом? В невисокосный год вы получили бы 28 марта, поэтому 2016 год, являющийся високосным годом, фактически помог вам приблизиться к желаемому результату. В моем коде я решаю эту проблему, добавляя правильное количество месяцев к исходной дате начала, а не добавляя один месяц к предыдущей дате начала.
- Как уже сказал PeterMmm,
ChronoUnit.DAYS.Between()
считается полных 24 часа в сутки. Любой неполный день отбрасывается. Даже 23 часа 59 минут 59 секунд. С Сб 30 января 00:00:00 EET 2016 по Вс 28 февраля 23:59: 59 EET 2016 составляет 30 дней 23 часа 59 минут 59 секунд, поэтому результат, который вы получаете, составляет 30 дней. В моем коде я решаю проблему, считая дни до начала следующего элемента.
В качестве отступления: с понедельника 29 февраля 00:00:00 EET 2016 по понедельник 28 марта 23:59: 59 EEST 2016 из-за перехода на летнее время (летнее время) составляет всего 28 дней 22 часа 59 минут 59 секунд. Таким образом, воздержание от вычитания секунды не решит вашу проблему в этом случае.
Комментарии:
1. Спасибо!!! Я не смотрел на это так. Дата / Время / Високосные годы — это нечто сложное в программировании. Рад, что Java 8 с пакетом java.time решила многие из этих проблем.
Ответ №2:
Это
end = DateUtils.addSeconds(end, -1);
не требуется. Или сделать
end = DateUtils.addSeconds(end, 0);
взгляните на документацию between().
Второй параметр является эксклюзивным. Это означает, что ваш второй параметр не достигает конца дня, и поэтому учитывается на день меньше (учитываются только полные 24-часовые дни).
Ответ №3:
Вот немного другой подход:
LocalDate startDate = LocalDate.of(2016, 1, 30);
LocalDate endDate = LocalDate.of(2017, 1, 30);
Function<YearMonth, LocalDate> addMonthFunction = ym -> ym
.atDay(Math.min(startDate.getDayOfMonth(), ym.lengthOfMonth()));
long months = ChronoUnit.MONTHS.between(startDate, endDate);
YearMonth yearMonth = YearMonth.from(startDate);
Stream.iterate(yearMonth, ym -> ym.plusMonths(1))
.limit(months)
.map(addMonthFunction)
.map(LocalDate::atStartOfDay)
.map(date -> {
LocalDateTime end = addMonthFunction.apply(YearMonth.from(date.plusMonths(1)))
.atStartOfDay()
.minusSeconds(1);
long between = ChronoUnit.DAYS.between(date, end) 1;
return String.format("%s to %s (%s days)", date, end, between);
})
.forEach(System.out::println);
Идея заключается в том, что каждая следующая дата всегда приходится на 30-й день месяца (или, точнее, на тот же день месяца, что и дата начала), за исключением случаев, когда это будет недопустимая дата.
Я использовал LocalDate
здесь s, но вы могли бы сделать его чувствительным к часовому поясу, используя ZonedDateTime
.
Комментарии:
1. Очень интересно! Спасибо.