考虑到夏令时的变化,此代码是否适用于 Java 的工作时间?
Is this code realiable for working out hours in Java, considering summer time changes?
有人可以帮我解决这个困惑吗?我需要显示每个工人的总工作时间。有不同的工作班次。在这种情况下,最复杂的是黎明班,当这个人从一天的 21:00 工作到第二天的 7:00 时,考虑到日期偏移,即在 10 月 31 日有一个时间瑞士的变化。我打算只在国内应用这个逻辑。以下代码是否是这种情况的可靠解决方案?我试图理解 java.time 和许多针对这个特定问题的帖子,但它们都变得更加混乱。
public String saveWorkTime(@Valid @ModelAttribute("workCalc") WorkCalculator workCalculator,
BindingResult bindingResult, ModelMap modelMap) throws ParseException {
(...)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
Date startDT = sdf.parse(workCalculator.getStartDate() + " " + workCalculator.getStartTime());
Date endDT = sdf.parse(workCalculator.getEndDate() + " " + workCalculator.getEndTime());
long timeDiffMilli = endDT.getTime() - startDT.getTime();
long timeDiffMin = TimeUnit.MILLISECONDS.toMinutes(timeDiffMilli) % 60;
long timeDiffH = TimeUnit.MILLISECONDS.toHours(timeDiffMilli) % 24;
workCalculator.setTotalWorkH(timeDiffH);
workCalculator.setTotalWorkMin(timeDiffMin);
workCalculatorService.save(workCalculator);
return "redirect:/index";
}
我的实体模型如下所示:
(...)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@Column(name = "start_date")
private String startDate;
@Column(name = "end_date")
private String endDate;
@Column(name = "total_work_h")
private long totalWorkH;
@Column(name = "total_work_min")
private long totalWorkMin;
@Column(name = "end_time")
private String endTime;
@Column(name = "start_time")
private String startTime;
(...)
我的数据库架构如下所示:
(...)
CREATE TABLE `work_calc` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`start_date` varchar(30) DEFAULT NULL,
`end_date` varchar(30) DEFAULT NULL,
`start_time` varchar(20) DEFAULT NULL,
`end_time` varchar(20) DEFAULT NULL,
`total_work_h` int(10) DEFAULT NULL,
`total_work_min` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
提前感谢大家的努力和时间。
java.time
很简单。如果你愿意的话。我建议您使用 java.time,现代 Java 日期和时间 API,用于所有日期和时间工作。
// This should be your entity class
public class Work {
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm", Locale.ROOT);
private OffsetDateTime start;
private OffsetDateTime end;
public Work(OffsetDateTime start, OffsetDateTime end) {
this.start = start;
this.end = end;
}
public Duration getTotalWorkTime() {
return Duration.between(start, end);
}
@Override
public String toString() {
return start.format(FORMATTER) + " - " + end.format(FORMATTER);
}
}
我们来试试吧。为了构建一些 Work
对象,我编写了以下辅助方法。它接受日期和时间的字符串。
/** Switzerland time */
private static final ZoneId ZONE = ZoneId.of("Europe/Zurich");
private static Work buildWork(String startDate, String startTime,
String endDate, String endTime) {
ZonedDateTime start = ZonedDateTime.of(
LocalDate.parse(startDate), LocalTime.parse(startTime), ZONE);
ZonedDateTime end = ZonedDateTime.of(
LocalDate.parse(endDate), LocalTime.parse(endTime), ZONE);
if (start.isAfter(end)) {
throw new IllegalArgumentException("Start must not be after end");
}
return new Work(start.toOffsetDateTime(), end.toOffsetDateTime());
}
示范:
Work[] exampleEntities = {
buildWork("2021-10-13", "07:00", "2021-10-14", "17:45"),
buildWork("2021-10-15", "07:00", "2021-10-15", "21:00"),
buildWork("2021-10-30", "21:00", "2021-10-31", "07:00")
};
for (Work work : exampleEntities) {
Duration totalWork = work.getTotalWorkTime();
System.out.format("%s: %-8s or %2d hours %2d minutes%n", work,
totalWork, totalWork.toHours(), totalWork.toMinutesPart());
}
输出:
2021-10-13 07:00 - 2021-10-14 17:45: PT34H45M or 34 hours 45 minutes
2021-10-15 07:00 - 2021-10-15 21:00: PT14H or 14 hours 0 minutes
2021-10-30 21:00 - 2021-10-31 07:00: PT11H or 11 hours 0 minutes
在正常的夜晚,我们预计从 21:00 到 7:00 的时间为 10 小时。您可以在上面的输出中看到,在 10 月 30 日至 31 日的晚上,欧洲在夏令时 (DST) 之后将时钟调回 11 小时。编辑:如果有疑问,可以通过以下方式查看:工人首先工作 21 到 3 = 6 小时。然后时钟调回 2。然后工人继续工作 2 到 7 = 5 小时。所以总工作时间 i 6 + 5 小时 = 11 小时。所以代码计算实际经过的时间(而不是时钟时间之间的差异)。
我想进一步说明
- 使用适当的日期时间对象来存储日期和时间。你不应该在字符串中存储数字和布尔值(我当然希望),你也不应该在字符串中存储日期和时间。我真的很想将
ZonedDateTime
用于带时区的日期和时间(如 Europe/Zurich),但许多 SQL 数据库无法存储 ZonedDateTime
的值实时时区,所以我们可能必须像上面的代码一样使用 OffsetDateTime
。 OffsetDateTime
非常适合常见 SQL 数据库的 timestamp with time zone
列,尽管供应商之间的差异很大。在一段时间内使用 Java 中的 Duration
class(不是单独的数字)。
- 不要在您的实体中存储冗余信息。某些数据库更新导致未检测到的数据不一致的风险太大。总工作时间可以从开始和结束计算,所以我们只是按需计算它们(这可能不经常,但你知道得更多)。
- 正如 MadProgrammer 在评论中所说,留给标准库方法来执行日期和时间数学运算。它通常比你想象的要复杂,所以不要像在你自己的代码中那样手工完成。
你的代码可靠吗?
仅部分回答您的问题。 Java 1.0 和 1.1 中的旧日期和时间 classes 没有太多可靠的信息。即使你得到了与他们一起工作的代码,也往往是不必要的复杂代码,一些程序员后来引入错误的风险不容忽视。
我没有使用回退(例如 2021 年 10 月 30 日至 31 日)和 spring 前进(预计 2022 年 3 月 26 日至 27 日)之夜的数据测试您的代码。你可以自己做。
您的工作班次很长,从 2021-10-13 07:00 到 2021-10-14 17:45。可能是数据有误,但应该正确计算工作时间,这样也有助于用户发现是否确实有误。正确的持续时间是 34 小时 45 分钟。从您的屏幕截图看来,您的代码计算出的时间仅为 10 小时 45 分钟。
Link
Oracle tutorial: Date Time 解释如何使用 java.time.
有人可以帮我解决这个困惑吗?我需要显示每个工人的总工作时间。有不同的工作班次。在这种情况下,最复杂的是黎明班,当这个人从一天的 21:00 工作到第二天的 7:00 时,考虑到日期偏移,即在 10 月 31 日有一个时间瑞士的变化。我打算只在国内应用这个逻辑。以下代码是否是这种情况的可靠解决方案?我试图理解 java.time 和许多针对这个特定问题的帖子,但它们都变得更加混乱。
public String saveWorkTime(@Valid @ModelAttribute("workCalc") WorkCalculator workCalculator,
BindingResult bindingResult, ModelMap modelMap) throws ParseException {
(...)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
Date startDT = sdf.parse(workCalculator.getStartDate() + " " + workCalculator.getStartTime());
Date endDT = sdf.parse(workCalculator.getEndDate() + " " + workCalculator.getEndTime());
long timeDiffMilli = endDT.getTime() - startDT.getTime();
long timeDiffMin = TimeUnit.MILLISECONDS.toMinutes(timeDiffMilli) % 60;
long timeDiffH = TimeUnit.MILLISECONDS.toHours(timeDiffMilli) % 24;
workCalculator.setTotalWorkH(timeDiffH);
workCalculator.setTotalWorkMin(timeDiffMin);
workCalculatorService.save(workCalculator);
return "redirect:/index";
}
我的实体模型如下所示:
(...)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@Column(name = "start_date")
private String startDate;
@Column(name = "end_date")
private String endDate;
@Column(name = "total_work_h")
private long totalWorkH;
@Column(name = "total_work_min")
private long totalWorkMin;
@Column(name = "end_time")
private String endTime;
@Column(name = "start_time")
private String startTime;
(...)
我的数据库架构如下所示:
(...)
CREATE TABLE `work_calc` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`start_date` varchar(30) DEFAULT NULL,
`end_date` varchar(30) DEFAULT NULL,
`start_time` varchar(20) DEFAULT NULL,
`end_time` varchar(20) DEFAULT NULL,
`total_work_h` int(10) DEFAULT NULL,
`total_work_min` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
提前感谢大家的努力和时间。
java.time
很简单。如果你愿意的话。我建议您使用 java.time,现代 Java 日期和时间 API,用于所有日期和时间工作。
// This should be your entity class
public class Work {
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm", Locale.ROOT);
private OffsetDateTime start;
private OffsetDateTime end;
public Work(OffsetDateTime start, OffsetDateTime end) {
this.start = start;
this.end = end;
}
public Duration getTotalWorkTime() {
return Duration.between(start, end);
}
@Override
public String toString() {
return start.format(FORMATTER) + " - " + end.format(FORMATTER);
}
}
我们来试试吧。为了构建一些 Work
对象,我编写了以下辅助方法。它接受日期和时间的字符串。
/** Switzerland time */
private static final ZoneId ZONE = ZoneId.of("Europe/Zurich");
private static Work buildWork(String startDate, String startTime,
String endDate, String endTime) {
ZonedDateTime start = ZonedDateTime.of(
LocalDate.parse(startDate), LocalTime.parse(startTime), ZONE);
ZonedDateTime end = ZonedDateTime.of(
LocalDate.parse(endDate), LocalTime.parse(endTime), ZONE);
if (start.isAfter(end)) {
throw new IllegalArgumentException("Start must not be after end");
}
return new Work(start.toOffsetDateTime(), end.toOffsetDateTime());
}
示范:
Work[] exampleEntities = {
buildWork("2021-10-13", "07:00", "2021-10-14", "17:45"),
buildWork("2021-10-15", "07:00", "2021-10-15", "21:00"),
buildWork("2021-10-30", "21:00", "2021-10-31", "07:00")
};
for (Work work : exampleEntities) {
Duration totalWork = work.getTotalWorkTime();
System.out.format("%s: %-8s or %2d hours %2d minutes%n", work,
totalWork, totalWork.toHours(), totalWork.toMinutesPart());
}
输出:
2021-10-13 07:00 - 2021-10-14 17:45: PT34H45M or 34 hours 45 minutes 2021-10-15 07:00 - 2021-10-15 21:00: PT14H or 14 hours 0 minutes 2021-10-30 21:00 - 2021-10-31 07:00: PT11H or 11 hours 0 minutes
在正常的夜晚,我们预计从 21:00 到 7:00 的时间为 10 小时。您可以在上面的输出中看到,在 10 月 30 日至 31 日的晚上,欧洲在夏令时 (DST) 之后将时钟调回 11 小时。编辑:如果有疑问,可以通过以下方式查看:工人首先工作 21 到 3 = 6 小时。然后时钟调回 2。然后工人继续工作 2 到 7 = 5 小时。所以总工作时间 i 6 + 5 小时 = 11 小时。所以代码计算实际经过的时间(而不是时钟时间之间的差异)。
我想进一步说明
- 使用适当的日期时间对象来存储日期和时间。你不应该在字符串中存储数字和布尔值(我当然希望),你也不应该在字符串中存储日期和时间。我真的很想将
ZonedDateTime
用于带时区的日期和时间(如 Europe/Zurich),但许多 SQL 数据库无法存储ZonedDateTime
的值实时时区,所以我们可能必须像上面的代码一样使用OffsetDateTime
。OffsetDateTime
非常适合常见 SQL 数据库的timestamp with time zone
列,尽管供应商之间的差异很大。在一段时间内使用 Java 中的Duration
class(不是单独的数字)。 - 不要在您的实体中存储冗余信息。某些数据库更新导致未检测到的数据不一致的风险太大。总工作时间可以从开始和结束计算,所以我们只是按需计算它们(这可能不经常,但你知道得更多)。
- 正如 MadProgrammer 在评论中所说,留给标准库方法来执行日期和时间数学运算。它通常比你想象的要复杂,所以不要像在你自己的代码中那样手工完成。
你的代码可靠吗?
仅部分回答您的问题。 Java 1.0 和 1.1 中的旧日期和时间 classes 没有太多可靠的信息。即使你得到了与他们一起工作的代码,也往往是不必要的复杂代码,一些程序员后来引入错误的风险不容忽视。
我没有使用回退(例如 2021 年 10 月 30 日至 31 日)和 spring 前进(预计 2022 年 3 月 26 日至 27 日)之夜的数据测试您的代码。你可以自己做。
您的工作班次很长,从 2021-10-13 07:00 到 2021-10-14 17:45。可能是数据有误,但应该正确计算工作时间,这样也有助于用户发现是否确实有误。正确的持续时间是 34 小时 45 分钟。从您的屏幕截图看来,您的代码计算出的时间仅为 10 小时 45 分钟。
Link
Oracle tutorial: Date Time 解释如何使用 java.time.