1. 夏令时
时区 Asia/Shanghai 存在夏令时 进入夏令时 时钟快一个小时,离开夏令时 时钟慢一个小时
GMT 标准时,不存在夏令时
Java中 默认,和系统有关
TimeZone.getDefault()
sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null]
mysql中 查询时区,设置时区
show variables like '%time_zone%'
set global time_zone = '+8:00';
2. 找出中国的夏令时时间段
网上说的夏令时,和代码实现查找的不太一样
1935年至1951年,每年5月1日至9月30日
1952年3月1日至10月31日
1953年至1954年,每年4月1日至10月31日
1955年至1956年,每年5月1日至9月30日
1957年至1959年,每年4月1日至9月30日
1960年至1961年,每年6月1日至9月30日
1974年至1975年,每年4月1日至10月31日
1979年7月1日至9月30日
1986年至1991年,每年4月中旬的第一个星期日2时起至9月中旬的第一个星期日2时止。具体如下:
1986年5月4日至9月14日,
1987年4月12日至9月13日,
1988年4月10日至9月11日,
1989年4月16日至9月17日,
1990年4月15日至9月16日,
1991年4月14日至9月15日。
代码实现
关键 timeZone.inDaylightTime(date) // 判断 date对象是否为夏令时
/**
* 计算中国夏令时时间段,从夏令时到不是夏令时的结束夏令时
* @param year
* @param monthCDT
* @param dayCDT
* @param hourCDT
* @return
* @throws ParseException
*/
public static String cdtRange(int year, int monthCDT, int dayCDT, int hourCDT) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone timeZone = TimeZone.getDefault();
// 获取到的日期+时间
String dataStr = "";
for(int month = monthCDT; month <= 12; month++){
// 日计算过的 从当天算起
int day = 1;
if(month == monthCDT){
day = dayCDT;
}
for(; day <= 31; day++){
if(month % 2 == 0){
//偶数月
if(day <= 30){
// 2月
if(month == 2){
if(isLeapYear(year)) {
if(day <= 29){
dataStr = year + "-" + getMonth(month) + "-"+ getDay(day);
}
}else {
if(day <= 28){
dataStr = year + "-" + getMonth(month) + "-"+ getDay(day);
}
}
}else {
dataStr = year + "-" + getMonth(month) + "-" + getDay(day);
}
}
}else{
dataStr = year + "-" + getMonth(month) + "-"+ getDay(day);
}
// 时计算过的 从当天当时算起
int hour = 0;
if(month == monthCDT && day == dayCDT){
hour = hourCDT;
}
for(; hour <= 23; hour++){
String dateTime = dataStr + " " + getHour(hour) + ":00:00";
Date date = simpleDateFormat.parse(dateTime);
if(!timeZone.inDaylightTime(date)){
//夏令时结束的时间
return simpleDateFormat.format(new Date(date.getTime() ));
// return dateTime;
}
}
}
}
return "";
}
/**
* 测试夏令时
* @throws Exception
*/
@Test
public void test5() throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone timeZone = TimeZone.getDefault();
// sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null]
System.out.println(timeZone);
List<String> list = new ArrayList<>();
label:
for(int year = 1900; year <= 2023; year++){
// 获取到的日期+时间
String dataStr = "";
for(int month = 1; month <= 12; month++){
for(int day = 1; day <= 31; day++){
if(month % 2 == 0){
if(day <= 30){
if(month == 2){
if(isLeapYear(year)) {
if(day <= 29){
dataStr = year + "-" + getMonth(month) + "-"+ getDay(day);
}
}else {
if(day <= 28){
dataStr = year + "-" + getMonth(month) + "-"+ getDay(day);
}
}
}else {
dataStr = year + "-" + getMonth(month) + "-" + getDay(day);
}
}
}else{
dataStr = year + "-" + getMonth(month) + "-"+ getDay(day);
}
for(int hour = 0; hour <= 23; hour++){
String dateTimeStart = dataStr + " " + getHour(hour) + ":00:00";
Date date = simpleDateFormat.parse(dateTimeStart);
// 是夏令时
if(timeZone.inDaylightTime(date)){
String dateTimeEnd = cdtRange(year, month, day, hour);
list.add(dateTimeStart + "\t\t" + dateTimeEnd);
continue label;
}
}
}
}
}
list.forEach(System.out::println);
}
运行结果
开始夏令时 - 结束夏令时(1940-10-12 23:00:00 不是夏令时, 1940-10-12 22:59:59 是夏令时)
1940-06-01 00:00:00 1940-10-12 23:00:00
1941-03-15 00:00:00 1941-11-01 23:00:00
1942-01-31 00:00:00
1943-01-01 00:00:00
1944-01-01 00:00:00
1945-01-01 00:00:00 1945-09-01 23:00:00
1946-05-15 00:00:00 1946-09-30 23:00:00
1947-04-15 00:00:00 1947-11-01 00:00:00
1948-05-01 00:00:00 1948-09-30 23:00:00
1949-05-01 00:00:00 1949-05-27 23:00:00
1986-05-04 02:00:00 1986-09-14 01:00:00
1987-04-12 02:00:00 1987-09-13 01:00:00
1988-04-17 02:00:00 1988-09-11 01:00:00
1989-04-16 02:00:00 1989-09-17 01:00:00
1990-04-15 02:00:00 1990-09-16 01:00:00
1991-04-14 02:00:00 1991-09-15 01:00:00
3. 测试
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// Sun Apr 14 03:00:00 CDT 1991 快一个小时 没有 真正的 2时
System.out.println(simpleDateFormat.parse("1991-04-14 02:00:00").toString());
// 1991-09-15 01:00:00 结束夏令时 慢 一个小时 1991-09-15 01:00:00
System.out.println(simpleDateFormat.format(simpleDateFormat.parse("1991-09-15 00:00:00").getTime() + 2 * 60 * 60 * 1000L));
// 1991-09-15 02:00:00
System.out.println(simpleDateFormat.format(simpleDateFormat.parse("1991-09-15 00:00:00").getTime() + 3 * 60 * 60 * 1000L));
4. springboot 中夏令时存在问题
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.TransientDataAccessResourceException:
Error attempting to get column 'create_time' from result set. Cause: java.sql.SQLException: HOUR_OF_DAY: 2 -> 3; HOUR_OF_DAY: 2 -> 3; nested exception is java.sql.SQLException: HOUR_OF_DAY: 2 -> 3] with root cause
解决
Date类型时
@Data
public class Box {
private String id;
private String title;
private String content;
private String price;
private Date createTime;
private Date updateTime;
}
yml中 jdbc配置
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8
LocalDateTime时
@Data
public class Box {
private String id;
private String title;
private String content;
private String price;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
yml中 jdbc配置
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=Asia/Shanghai
添加配置类
/**
* localDateTime 序列化/反序列化配置,并且兼容 Date类型的序列化/反序列化
*
* @author liuxb
*/
@Configuration
public class LocalDateTimeConfig {
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
private String localDateTimeFormat;
private String localDateFormat = "yyyy-MM-dd";
private String localTimeFormat = "HH:mm:ss";
@Value("${spring.jackson.time-zone:GMT+8}")
private TimeZone timeZone;
@Bean
public ObjectMapper objectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
//序列化
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(localDateTimeFormat)));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(localDateFormat)));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(localTimeFormat)));
//反序列化
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(localDateTimeFormat)));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(localDateFormat)));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(localTimeFormat)));
// 不能直接使用 new ObjectMapper(),会导致 Date 类型序列化失效变为毫秒数
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
.json()
.modules(javaTimeModule)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
//不格式化未知的属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//不返回时间戳格式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.setDateFormat(new SimpleDateFormat(localDateTimeFormat));
objectMapper.setTimeZone(timeZone);
return objectMapper;
}
}