Java 时区国际化处理

该项目针对海外业务的时区问题,采取服务器和数据库统一设置为0时区,前端传入日期字符串和时区信息。AOP切面在请求处理前,根据请求头的时区偏移量将日期字符串转换为0时区的Date对象,以便于后续业务逻辑处理。工具类使用Hutool库进行日期和时区的转换操作。
摘要由CSDN通过智能技术生成

前言

项目主打海外业务,不可避免碰到时区的问题。目前的解决方案是,服务器、数据库统一设置为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);
	}

}

Java国际化设计文档是一个用于指导软件开发人员在Java平台上实现国际化功能的文档。它主要包含以下内容: 1. 目标和背景:介绍为什么需要进行国际化设计,以及国际化设计的目标是什么。可以提到软件产品面向全球市场的需求和用户体验的重要性。 2. 国际化规范:明确国际化设计的规范和标准,确保团队在实现国际化功能时能够遵循一致的开发流程和方法。例如,规定代码中使用字符编码的方式、资源文件的命名规则、日期和时间格式的处理等。 3. 多语言支持:说明如何实现多语言支持。这包括如何提供多语言的资源文件,以及如何在程序中动态加载和切换不同语言的资源。 4. 文本翻译和本地化:描述如何进行文本翻译和本地化工作。这包括如何选择合适的翻译工具,如何管理翻译工作流程,以及如何确保翻译的准确性和一致性。 5. 时区和日期时间处理:介绍如何正确处理不同时区和日期时间格式。这包括如何在程序中获取和显示不同时区的日期时间,以及如何处理夏令时等时区相关的问题。 6. 跨文化设计:讨论如何考虑不同文化背景下的用户需求和习惯,以便提供更好的用户体验。这包括对数值、货币、单位、图形和颜色等方面的处理。 7. 测试和质量保证:指导如何进行国际化功能的测试和质量保证工作。这包括编写测试用例、模拟不同环境和语言设置的测试等。 8. 文档和培训支持:提供相关文档和培训材料,以便开发人员了解和掌握国际化设计的方法和技巧。 通过这个设计文档,软件开发团队可以系统地了解和理解如何在Java平台上实现国际化功能,从而为用户提供更好的多语言和跨文化支持的软件产品。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宁漂打工仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值