前言
项目主打海外业务,不可避免碰到时区的问题。目前的解决方案是,服务器、数据库统一设置为0时区,前端提交日期格式的字符串参数,请求头传递时区的参数(如:东八区【+8】,西一区【-1】),后台根据偏移量将日期字符串转换成0时区的Date
切面
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
/**
* @author
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class TimeZoneAspect {
private final ThreadLocal<CtlHeaderTrackAnno> localTrackAnno = new ThreadLocal<>();
private static final String BEGIN_TIME = "beginTime";
private static final String END_TIME = "endTime";
private static final String START_DATE = "startDate";
private static final String END_DATE = "endDate";
@Pointcut("@annotation(com.test.test.service.core.security.annotation.TimeZone)")
public void timeZone() {
// For Aspect
}
@Before("timeZone()")
public void runBefore(JoinPoint joinPoint) {
Object arg = joinPoint.getArgs()[0];
if(arg instanceof BaseRequest){
BaseRequest baseRequest = (BaseRequest) arg;
this.formatDateByTimeZone(baseRequest.getParams());
}else if(arg instanceof BaseEntity){
BaseEntity baseEntity = (BaseEntity) arg;
this.formatDateByTimeZone(baseEntity.getParams());
}
}
private void formatDateByTimeZone(Map<String, Object> params) {
String timezone = Objects.requireNonNull(ServletUtils.getRequest()).getHeader(HeaderConst.X_TIME_ZONE);
// 根据时区转换日期字符串
if(params.containsKey(BEGIN_TIME) && params.containsKey(END_TIME) && StrUtil.isNotBlank(timezone)){
Object begin = params.get(BEGIN_TIME);
Object end = params.get(END_TIME);
Date startDate = LocalDateTimeUtils.parseV2(begin.toString(), DatePattern.NORM_DATETIME_PATTERN, Integer.parseInt(timezone));
Date endDate = LocalDateTimeUtils.parseV2(end.toString(), DatePattern.NORM_DATETIME_PATTERN, Integer.parseInt(timezone));
params.put(START_DATE, startDate);
params.put(END_DATE, endDate);
}
}
}
工具类
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.TimeZone;
public class LocalDateTimeUtils {
public static String formatObject(Object date, String pattern, Integer offset) {
if (date == null || StrUtil.isBlank(pattern) || offset == null) {
return "";
}
if (date instanceof Date) {
return format((Date) date, pattern, offset);
}
else if (date instanceof String) {
return formatString((String) date, pattern, offset);
}
return "";
}
public static String format(Date date, String pattern, Integer offset) {
if (date == null || StrUtil.isBlank(pattern) || offset == null) {
return "";
}
TimeZone tz = TimeZone.getTimeZone("UTC");
if (offset != null) {
tz.setRawOffset(-offset * 60 * 1000);
}
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setTimeZone(tz);
String d = sdf.format(date);
return d;
}
public static String showAsTimezone(Integer offset) {
if (offset == null) {
return "";
}
String returnVal = "UTC";
if (offset > 0) {
returnVal += "-";
}
else {
returnVal += "+";
}
offset = Math.abs(offset);
int hour = offset / 60;
if (hour >= 10) {
returnVal += hour + ":";
}
else {
returnVal += "0" + hour + ":";
}
int minute = offset % 60;
if (minute >= 10) {
returnVal += minute;
}
else {
returnVal += "0" + minute;
}
return returnVal;
}
public static String formatString(String dateString, String pattern, Integer offset) {
if (StrUtil.isBlank(dateString) || StrUtil.isBlank(pattern) || offset == null) {
return "";
}
SimpleDateFormat sdfWithTimeZone = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
sdfWithTimeZone.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = null;
try {
date = sdfWithTimeZone.parse(dateString);
} catch (ParseException e) {
return "";
}
TimeZone tz = TimeZone.getTimeZone("UTC");
if (offset != null) {
tz.setRawOffset(-offset * 60 * 1000);
}
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setTimeZone(tz);
String d = sdf.format(date);
return d;
}
public static Date parse(String dateStr, String pattern, Integer offset) {
if (StrUtil.isBlank(dateStr) || StrUtil.isBlank(pattern) || offset == null) {
return null;
}
TimeZone tz = TimeZone.getTimeZone("UTC");
tz.setRawOffset(-offset * 60 * 1000);
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setTimeZone(tz);
Date d = null;
try {
d = sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return d;
}
public static Date parseV2(String dateStr, String pattern, Integer offset) {
if (StrUtil.isBlank(dateStr) || StrUtil.isBlank(pattern) || offset == null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
ZoneId zoneId = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(offset));
TimeZone timeZone = TimeZone.getTimeZone(zoneId);
sdf.setTimeZone(timeZone);
Date d = null;
try {
d = sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return d;
}
public static void main(String[] args) {
// 前台请求日期
Object begin = "2023-02-02 00:00:00";
Object end = "2023-02-02 23:59:59";
// 操作系统置为0时区
TimeZone.setDefault(TimeZone.getTimeZone("GMT+0:00"));
// 模拟东8区请求【比0时区快了8小时】(0时区转东八区,+8;东八区转0时区,-8)
Date startDate = LocalDateTimeUtils.parseV2(begin.toString(), DatePattern.NORM_DATETIME_PATTERN, Integer.parseInt("8"));
Date endDate = LocalDateTimeUtils.parseV2(end.toString(), DatePattern.NORM_DATETIME_PATTERN, Integer.parseInt("8"));
// Wed Feb 01 16:00:00 GMT+00:00 2023
// Thu Feb 02 15:59:59 GMT+00:00 2023
System.out.println(startDate);
System.out.println(endDate);
// 模拟西1区请求【比0时区慢了1小时】(0时区转西一区,-1;西一区转0时区,+1)
Date startDate1 = LocalDateTimeUtils.parseV2(begin.toString(), DatePattern.NORM_DATETIME_PATTERN, Integer.parseInt("-1"));
Date endDate1 = LocalDateTimeUtils.parseV2(end.toString(), DatePattern.NORM_DATETIME_PATTERN, Integer.parseInt("-1"));
// Thu Feb 02 01:00:00 GMT+00:00 2023
// Fri Feb 03 00:59:59 GMT+00:00 2023
System.out.println(startDate1);
System.out.println(endDate1);
}
}