package com.yami.trading.common.util; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Timestamp; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.*; import java.util.*; /** * 日期工具类 * * 参考资料: * https://blog.csdn.net/shuair/article/details/126694562 * https://www.cnblogs.com/convict/p/16180509.html * * 时区对照表: * http://t.zoukankan.com/kakaisgood-p-12523507.html * https://blog.csdn.net/LordForce/article/details/132101076 */ public class DateTimeTools { private final static Logger logger = LoggerFactory.getLogger(DateTimeTools.class); public static final String DEFAULT_DATA_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; // 处理类似格式:2022-07-08T04:02:43.85 public static final String DATA_TIME_FORMAT_2 = "yyyy-MM-dd'T'HH:mm:ss.SSS"; // 带时区格式 public static final String DATA_TIME_FORMAT_3 = "yyyy-MM-dd'T'HH:mm:ssZ"; public static final String DAY_FORMAT = "yyyy-MM-dd"; // 提前缓存各个时区的标记,以及各个时区和 UTC 时区的时差 public static TimeZone utcZone = TimeZone.getTimeZone("UTC"); /** * 基于系统时区,将指定 date 类型对象转换为 LocalDateTime 对象 * * @param date * @return */ public static LocalDateTime date2LocalDateTime(Date date) { if (date == null) { return null; } // 时区 ZoneId zoneId = ZoneId.systemDefault(); // 方式1 ZonedDateTime zonedDateTime = date.toInstant().atZone(zoneId); return zonedDateTime.toLocalDateTime(); // // 方式2 // return LocalDateTime.ofInstant(date.toInstant(), zoneId); } public static LocalDate date2LocalDate(Date date) { if (date == null) { return null; } // 时区 ZoneId zoneId = ZoneId.systemDefault(); // 方式1 ZonedDateTime zonedDateTime = date.toInstant().atZone(zoneId); return zonedDateTime.toLocalDate(); // // 方式2 // LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date.toInstant(), zoneId); // return localDateTime2.toLocalDate(); } public static LocalTime date2LocalTime(Date date) { if (date == null) { return null; } // 时区 ZoneId zoneId = ZoneId.systemDefault(); // 方式1 ZonedDateTime zonedDateTime = date.toInstant().atZone(zoneId); return zonedDateTime.toLocalTime(); // 方式2 // LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date.toInstant(), zoneId); // return localDateTime2.toLocalTime(); } /** * 将当前系统时区的时间转换成 Date 类型对象; * 注意:参数 time 关联的是当前系统时区的时间 * * @param time * @return */ public static Date localDateTime2Date(LocalDateTime time) { if (time == null) { return null; } // 时区 ZoneId zoneId = ZoneId.systemDefault(); Instant instant = time.atZone(zoneId).toInstant(); return Date.from(instant); } public static Date localDate2Date(LocalDate time) { if (time == null) { return null; } // 由于`LocalDate`不带有时间信息,所以必须设置时间,才能转 Date // 时区 ZoneId zoneId = ZoneId.systemDefault(); Instant instant; Date date; // 方式1:atStartOfDay,自动赋予午夜时间,返回 LocalDateTime,设置时区返回 ZonedDateTime,进而得到 Instant instant = time.atStartOfDay().atZone(zoneId).toInstant(); date = Date.from(instant); System.out.println(date); return date; // // 方式2 // instant = time.atStartOfDay(zoneId).toInstant(); // date = Date.from(instant); // System.out.println(date); // // // 方式3:atStartOfDay 的底层实现 // instant = time.atTime(LocalTime.MIDNIGHT).atZone(zoneId).toInstant(); // date = Date.from(instant); // System.out.println(date); // // // 方式4 // instant = LocalDateTime.of(time, LocalTime.MIDNIGHT).atZone(zoneId).toInstant(); // date = Date.from(instant); // System.out.println(date); // // // 方式5:通过 Timestamp 得到 Instant // instant = Timestamp.valueOf(time.atTime(LocalTime.MIDNIGHT)).toInstant(); // date = Date.from(instant); // System.out.println(date); // // // 方式6 // instant = Timestamp.valueOf(LocalDateTime.of(time, LocalTime.MIDNIGHT)).toInstant(); // date = Date.from(instant); // System.out.println(date); // // // 方式7:通过毫秒数初始化 Date 对象 // Timestamp timestamp = Timestamp.valueOf(time.atTime(LocalTime.MIDNIGHT)); // date = new Date(timestamp.getTime()); // System.out.println(date); } public static Date localTime2Date(LocalTime time) { if (time == null) { return null; } // 由于 LocalTime 不带有日期信息,所以必须设置日期,才能转 Date LocalDate nowDate = LocalDate.now(); // 时区 ZoneId zoneId = ZoneId.systemDefault(); Instant instant; Date date; instant = LocalDateTime.of(nowDate, time).atZone(zoneId).toInstant(); date = Date.from(instant); instant = Timestamp.valueOf(LocalDateTime.of(nowDate, time)).toInstant(); date = Date.from(instant); Timestamp timestamp = Timestamp.valueOf(LocalDateTime.of(nowDate, time)); date = new Date(timestamp.getTime()); return date; } public static Date getDateFromMs(long ms) { return new Date(ms); } public static LocalDate getLocalDateFromMs(long ms) { Date date = getDateFromMs(ms); return date2LocalDate(date); } public static LocalTime getLocalTimeFromMs(long ms) { Date date = getDateFromMs(ms); return date2LocalTime(date); } public static LocalDateTime getLocalDateTimeFromMs(long ms) { Date date = getDateFromMs(ms); return date2LocalDateTime(date); } /** * 获取当前天 * * @return String */ public static String getCurDayStr() { return new SimpleDateFormat(DAY_FORMAT).format(new Date()); } public static String getDayStr(Date time) { if (time == null) { return null; } return new SimpleDateFormat(DAY_FORMAT).format(time); } /** * 获取前一天 * 注意时区的影响!!! * * @return Date */ public static Date getPreviousDate() { Calendar begin = Calendar.getInstance(); begin.set(Calendar.DAY_OF_MONTH, begin.get(Calendar.DAY_OF_MONTH) - 1); begin.set(Calendar.HOUR_OF_DAY, 0); begin.set(Calendar.MINUTE, 0); begin.set(Calendar.SECOND, 0); return begin.getTime(); } /** * 获取下N天 * 注意时区的影响!!! * * @param date * 当前时间 * @param n * 下N天 * @return Date 下N天日期 */ public static Date getNextDate(Date date, int n) { if (date == null) { return null; } Calendar begin = Calendar.getInstance(); begin.setTime(date); begin.set(Calendar.DAY_OF_MONTH, begin.get(Calendar.DAY_OF_MONTH) + n); begin.set(Calendar.HOUR_OF_DAY, 0); begin.set(Calendar.MINUTE, 0); begin.set(Calendar.SECOND, 0); begin.set(Calendar.MILLISECOND, 0); return begin.getTime(); } /** * 添加秒数 * 注意时区的影响!!! * * @return Date */ public static Date addSecond(Long date, int second) { if (date == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(date); cal.add(Calendar.SECOND, second); return cal.getTime(); } /** * 添加小时 * 注意时区的影响!!! * * @param date * @param hour * @return Date */ public static Date addHour(Long date, int hour) { if (date == null || date <= 0) { return null; } Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(date); cal.add(Calendar.HOUR_OF_DAY, hour); return cal.getTime(); } /** * 添加小时 * 注意时区的影响!!! * * @param date * @param hour * @return Date */ public static Date addHour(Date date, int hour) { if (date == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(date);; cal.add(Calendar.HOUR_OF_DAY, hour); return cal.getTime(); } /** * 注意时区的影响!!! * * @param date * @param minutes * @return */ public static Date addMinutes(Date date, int minutes) { if (date == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.add(Calendar.MINUTE, minutes); return cal.getTime(); } /** * 添加秒数 * 注意时区的影响!!! * * @return Date */ public static Date addSecond(Date date, int second) { if (date == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.add(Calendar.SECOND, second); return cal.getTime(); } /** * 添加天数 * 注意时区的影响!!! * * @param date * @param day * @return Date */ public static Date addDay(Date date, int day) { if (date == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.add(Calendar.DAY_OF_YEAR, day); return cal.getTime(); } /** * 两个时间之间相差距离多少天 * * @param starttime 时间参数 1: * @param endtime 时间参数 2: * @return 相差天数 */ public static long getDistanceDays(String starttime, String endtime) { DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date one; Date two; long days = 0; try { one = df.parse(starttime); two = df.parse(endtime); long time1 = one.getTime(); long time2 = two.getTime(); long diff ; if (time1 dateAndTime = StrUtil.splitTrim(dateStr, ' '); final int size = dateAndTime.size(); if (size < 1 || size > 2) { // 非可被标准处理的格式 return StrUtil.str(dateStr); } dateStr = dateStr.replace("T", " "); final StringBuilder builder = StrUtil.builder(); // 日期部分("\"、"/"、"."、"年"、"月"都替换为"-") String datePart = dateAndTime.get(0).replaceAll("[/.年月]", "-"); datePart = StrUtil.removeSuffix(datePart, "日"); builder.append(datePart); // 时间部分 if (size == 2) { builder.append(' '); String timePart = dateAndTime.get(1).replaceAll("[时分秒]", ":"); timePart = StrUtil.removeSuffix(timePart, ":"); //将ISO8601中的逗号替换为. timePart = timePart.replace(',', '.'); builder.append(timePart); } return builder.toString(); } // /** // * 将当前时区时间转换成 UTC 时区时间 // * 注意:此处 Date 对象对应的时区都是系统默认时区 // * // * @param time // * @return // */ // public static Date toUtc(Date time) { // if (time == null) { // return null; // } // // String localTimeZoneId = TimeZone.getDefault().getID(); // long timeDiff = timeZoneDiffMap.get(localTimeZoneId); // return (new Date(time.getTime() - timeDiff * 1000L)); // } // // public static Date toUtc(Date time, ZoneId fromZone) { // if (time == null) { // return null; // } // if (fromZone == null) { // fromZone = ZoneId.systemDefault(); // } // // // 提取指定时区和 UTC 的时差 // long timeDiff = timeZoneDiffMap.get(fromZone.getId()); // // 对应时区的时间毫秒值减去时差,就是 UTC 时间 // return (new Date(time.getTime() - timeDiff * 1000L)); // } // // /** // * // * @Description: 基于零时区位置的展示时间,计算本地时间; // * 注意:基于返回值算出的时间毫秒值,算是标准时间毫秒值 // * // * @time 2020年7月31日 下午9:38:08 // * @author daydayup // * @param utcTime // * @return // * Date // * @throws // */ // public static Date fromUtc(Date utcTime) { // if (utcTime == null) { // return null; // } // // String localTimeZoneId = TimeZone.getDefault().getID(); // long timeDiff = timeZoneDiffMap.get(localTimeZoneId); // return (new Date(utcTime.getTime() + timeDiff * 1000L)); // } // // public static Date fromUtc(Date utcTime, ZoneId toZone) { // if (utcTime == null) { // return null; // } // if (toZone == null) { // toZone = ZoneId.systemDefault(); // } // // long timeDiff = timeZoneDiffMap.get(toZone.getId()); // return (new Date(utcTime.getTime() + timeDiff * 1000L)); // } /** * 基于当前系统时区指定的时间,计算目标时区的时间 * * @param fromTime : 系统时区的时间 * @param targetZone : 转换成目标时区 * @return */ public static Date convertZoneTime(Date fromTime, ZoneId targetZone) { return convertZoneTime(fromTime, ZoneId.systemDefault(), targetZone); } public static Date convertZoneTime(long fromTime, ZoneId targetZone) { if (fromTime <= 0) { return null; } String checkTimeStr = String.valueOf(fromTime); if (checkTimeStr.startsWith("1")) { if (checkTimeStr.length() == 10) { // 1000000000(秒) 对应 2001-09-09 09:46:40,识别为基于秒的时间戳 checkTimeStr = checkTimeStr + "000"; } } else { // 首位数大于 1,要么是 9几年的秒时间,要么是 9 几年的毫秒时间,基于秒的有 9 位数 if (checkTimeStr.length() <= 9) { // 基于秒的时间戳 checkTimeStr = checkTimeStr + "000"; } } fromTime = Long.parseLong(checkTimeStr); Date dateTime = new Date(fromTime); return convertZoneTime(dateTime, targetZone); } /** * 基于时区 fromZone 的时间 timeStr 解析成时区 toZone 的时间 * 注意:返回的是基于 toZone 时区的字面时间,不是系统时区的时间 * * @param timeStr * @param fromZone * @param toZone * @return */ public static Date convertZoneTime(String timeStr, ZoneId fromZone, ZoneId toZone) { // 注意:返回的 oriTime 是系统时区的时间值 Date oriTime = parseWithTimeZone(timeStr, fromZone); // 注意写法! return convertZoneTime(oriTime, ZoneId.systemDefault(), toZone); } /** * 基于时区 fromZone 的时间 time 解析成时区 toZone 的时间 * * @param time * @param fromZone * @param toZone * @return */ public static Date convertZoneTime(Date time, ZoneId fromZone, ZoneId toZone) { if (time == null) { return null; } // toZone 时区相对于 fromZone 时区的时间偏移 long timeDiff = getTimeOffset(fromZone, toZone); return (new Date(time.getTime() + timeDiff)); } /** * 将系统时区时间转换为对外展示配置的时区时间 * * @param localZoneTime * @return */ public static Date transferToShowTime(Date localZoneTime) { if (localZoneTime == null) { return null; } String outPutTimeZoneId = TimeZoneContext.showTimeZoneId.get(); if (StrUtil.isBlank(outPutTimeZoneId)) { throw new RuntimeException("错误的使用方式"); } Date retDate = convertZoneTime(localZoneTime, ZoneId.of(outPutTimeZoneId)); return retDate; } /** * 具体业务中使用的方法 * * @param fromTime * @return */ public static Date transferToShowTime(Long fromTime) { if (fromTime == null || fromTime <= 0) { return null; } String checkTimeStr = String.valueOf(fromTime); if (checkTimeStr.startsWith("1")) { if (checkTimeStr.length() == 10) { // 1000000000(秒) 对应 2001-09-09 09:46:40,识别为基于秒的时间戳 checkTimeStr = checkTimeStr + "000"; } } else { // 首位数大于 1,要么是 9几年的秒时间,要么是 9 几年的毫秒时间,基于秒的有 9 位数 if (checkTimeStr.length() <= 9) { // 基于秒的时间戳 checkTimeStr = checkTimeStr + "000"; } } fromTime = Long.parseLong(checkTimeStr); Date dateTime = new Date(fromTime); return transferToShowTime(dateTime); } /** * 注意:本方法用于将当前系统时区时间戳,参考预期的展示时间,转换为客户端时区能正确解析的时间戳。 * 此处涉及多个时区偏移换算问题,其他业务在不清楚本方法功能时不要随意使用!!! * 本方法目前专用于将业务 API 返回给前端的时间戳值做加工,返回给前端解析时间。 * * @param localTimestamp * @return */ public static long transferShowTimeToClientTime(Long localTimestamp) { if (localTimestamp == null || localTimestamp <= 0) { return 0L; } // 时间戳类型: 1-毫秒时间戳, 2-秒时间戳 int timestampType = 1; String checkTimeStr = String.valueOf(localTimestamp); if (checkTimeStr.startsWith("1")) { if (checkTimeStr.length() == 10) { // 1000000000(秒) 对应 2001-09-09 09:46:40,识别为基于秒的时间戳 checkTimeStr = checkTimeStr + "000"; timestampType = 2; } } else { // 首位数大于 1,要么是 9几年的秒时间,要么是 9 几年的毫秒时间,基于秒的有 9 位数 if (checkTimeStr.length() <= 9) { // 基于秒的时间戳 checkTimeStr = checkTimeStr + "000"; timestampType = 2; } } localTimestamp = Long.parseLong(checkTimeStr); String showTimeZoneId = TimeZoneContext.showTimeZoneId.get(); if (StrUtil.isBlank(showTimeZoneId)) { throw new RuntimeException("错误的使用方式"); } String clientTimeZoneId = TimeZoneContext.clientTimeZoneId.get(); if (StrUtil.isBlank(clientTimeZoneId)) { throw new RuntimeException("错误的使用方式"); } long timeOffset = getTimeOffset(ZoneId.of(clientTimeZoneId), ZoneId.of(showTimeZoneId)); //logger.info("----------------> DateTimeTools.transferShowTimeToClientTime 传入时间戳:{}, showTimeZoneId:{}, clientTimeZoneId:{}, timeOffset:{}", // localTimestamp, showTimeZoneId, clientTimeZoneId, timeOffset); long retTimestamp = localTimestamp + timeOffset; if (timestampType == 1) { // 以毫秒入,以毫秒出 return retTimestamp; } else if (timestampType == 2) { // 以秒入,以秒出 return retTimestamp / 1000L; } // TODO return retTimestamp; } /** * 将基于 fromZone 时区的时间值 inputTime,转换为对应的系统时间 * * @param inputTime : 请求参数传入的字符串时间值 * @param timeFormat : 时间格式 * @param inputTimeZone : 参数时间使用的时区,如果未指定时区,则从当前线程上下文中自动提取时区 * @return */ public static Date readQueryTime(String inputTime, String timeFormat, ZoneId inputTimeZone) { if (StrUtil.isBlank(inputTime)) { return null; } if (inputTimeZone == null) { String inPutTimeZoneId = TimeZoneContext.showTimeZoneId.get(); if (StrUtil.isBlank(inPutTimeZoneId)) { throw new RuntimeException("不能识别当前时间所属的时区,无法解析"); } inputTimeZone = ZoneId.of(inPutTimeZoneId); } return convertZoneTime(inputTime, inputTimeZone, ZoneId.systemDefault()); } /** * 计算指定时区当前时间到 UTC 时间的时差,返回单位为毫秒 * 当值大于 0,代表当前时区更早,例如当前时区已经 12 点了,utc 时区才 11 点。 * * 参考资料: * https://qa.1r1g.com/sf/ask/1105223941/ * * @return */ public static long getTimeOffset(ZoneId zoneId) { LocalDateTime refNow = LocalDateTime.now(); ZoneId refZoneId = ZoneId.of("Z"); ZonedDateTime refTime = refNow.atZone(refZoneId); ZonedDateTime curZoneTime = refNow.atZone(zoneId); Duration diff = Duration.between(curZoneTime, refTime); return diff.toMillis(); } /** * 计算 targetZone 时区时间偏移量比 sourceZone 时区时间偏移量大的值 * * @param sourceZone * @param targetZone * @return */ public static long getTimeOffset(ZoneId sourceZone, ZoneId targetZone) { LocalDateTime refNow = LocalDateTime.now(); ZonedDateTime refTime = refNow.atZone(sourceZone); ZonedDateTime targetZoneTime = refNow.atZone(targetZone); Duration diff = Duration.between(targetZoneTime, refTime); return diff.toMillis(); } public static void main(String[] args) { TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("GMT-4"))); long now = System.currentTimeMillis(); TimeZoneContext.showTimeZoneId.set("GMT+4"); TimeZoneContext.clientTimeZoneId.set("GMT+3"); long retNow = transferShowTimeToClientTime(now / 1000L); System.out.println("----> now:" + now + ", retNow:" + retNow); } /** * 时区资料: * https://zhuanlan.zhihu.com/p/538934548 * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones * * @param args */ public static void main2(String[] args) { // for (String oneZone : TimeZone.getAvailableIDs()) { // System.out.println("-----> oneZone:" + oneZone); // } //TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("GMT+4"))); System.out.println("-----> defaultTimeZone:" + ZoneId.systemDefault()); Date now = new Date(); // 展示 GMT-4 时区当前时间 TimeZoneContext.showTimeZoneId.set("GMT-4"); Date showTime = transferToShowTime(now); Date innerTime = readQueryTime("2023-11-08 12:00:00", "yyyy-MM-dd HH:mm:ss", ZoneId.of("GMT-3")); System.out.println("-----> now:" + DateUtil.formatDateTime(now) + ", showTime:" + DateUtil.formatDateTime(showTime) + ", innerTime:" + DateUtil.formatDateTime(innerTime)); // 注意: atZone 的用途是,将系统时区所在的时刻,应用到目标时区来解释,同时返回的时间是系统时区的时间。 // 而不是误以为的:将系统时区对应的时间,换算成目标时区的时间,并将这个目标时区的时间换算成系统时区的时间 LocalDateTime localNow = LocalDateTime.now(); Instant convertInstant = localNow.atZone(ZoneId.of("GMT-4")).toInstant(); Date convertDateTime = Date.from(convertInstant); System.out.println("-----> convertDateTime:" + DateUtil.formatDateTime(convertDateTime)); String timeStr = "2023-11-08 12:00:00"; Date strTime1 = parseWithTimeZone(timeStr, ZoneId.of("Etc/GMT+4")); // 返回: 2023-11-09 00:00:00 Date strTime2 = parseWithTimeZone(timeStr, ZoneId.of("Etc/GMT-8")); // 返回: 2023-11-08 12:00:00 System.out.println("-----> strTime1:" + DateUtil.formatDateTime(strTime1) + ", strTime2:" + DateUtil.formatDateTime(strTime2)); // 符合预期 Date convertTime1 = convertZoneTime(timeStr, ZoneId.of("Etc/GMT+4"), ZoneId.of("Etc/GMT+5")); System.out.println("-------> convertTime1: " + DateUtil.formatDateTime(convertTime1)); // 符合预期 long offset1 = getTimeOffset(ZoneId.of("GMT+0800")); long offset2 = getTimeOffset(ZoneId.of("Z")); long offset3 = getTimeOffset(ZoneId.of("GMT+0800"), ZoneId.of("GMT+7")); System.out.println("------> offset1:" + offset1 + ", offset2:" + offset2 + ", offset3:" + offset3); Date localNowTime = new Date(); TimeZone tz1 = TimeZone.getTimeZone("GMT+0500");// Etc/GMT+4 America/New_York (北京时间比纽约时间早 13 个小时) SimpleDateFormat timeZoneDateFormat1 = new SimpleDateFormat(DEFAULT_DATA_TIME_FORMAT); timeZoneDateFormat1.setTimeZone(tz1); TimeZone tz2 = TimeZone.getTimeZone("GMT+4:00");// Etc/GMT+4 America/New_York (北京时间比纽约时间早 13 个小时) SimpleDateFormat timeZoneDateFormat2 = new SimpleDateFormat(DEFAULT_DATA_TIME_FORMAT); timeZoneDateFormat2.setTimeZone(tz2); // 符合预期 System.out.println("------> tz1.time:" + timeZoneDateFormat1.format(localNowTime) + ", tz2.time:" + timeZoneDateFormat2.format(localNowTime)); // try { // Date time = timeZoneDateFormat1.parse(normalize(timeStr)); // String targetTime = timeZoneDateFormat1.format(localNowTime); // System.out.println("-----> localTime1:" + DateUtil.formatDateTime(time) + // ", localTime2:" + timeZoneDateFormat1.format(time) + ", targetTime:" + targetTime); // } catch (Exception e) { // throw new RuntimeException("日期转换报错"); // } // now1 是北京时区的时间值 LocalDateTime now1 = LocalDateTime.now(); long now1time = System.currentTimeMillis(); // 现在是北京时间 2023-11-06 22:00:00, 已经是 Etc/GMT 时区上偏离 0 时区的 -8 得到的时间,下面将获取此时 +4 时区的时间(相对于当前时区的时间), // 这代表 +4 时区上此时已经是当前北京时间 12 个小时后的时间了,这既是目标时区的当时时间(例如北京地方 12点,目标地方已经夜里 24点),也可理解为当前 // 时区的一个未来时间 ZoneId zoneId = ZoneId.of("Etc/GMT+4"); // 北京时区是: Etc/GMT-8 // 计算当前在 ETC/GMT+4 时区的时间 Instant instant = now1.atZone(zoneId).toInstant(); Date date1 = Date.from(instant); long now2time = date1.getTime(); // 以下显示:无论是从时间字符串打印来看,还是时间戳差值来看,都是 12 个小时后的时间 System.out.println("-----> now1 at Etc/GMT+4 : " + DateUtil.formatDateTime(date1) + ", offset: " + (now2time - now1time)); // 符合预期 Date now2 = new Date(); Date date2 = convertZoneTime(now2, ZoneId.of("Etc/GMT-8"), ZoneId.of("Etc/GMT+4")); System.out.println("-----> now2 at Etc/GMT+4 : " + DateUtil.formatDateTime(date2) + ", offset: " + (date2.getTime() - now2.getTime())); // 符合预期 Date now22 = new Date(); Date date22 = convertZoneTime(now22, ZoneId.of("GMT+3"), ZoneId.of("GMT+4")); System.out.println("-----> now22 at GMT+4 : " + DateUtil.formatDateTime(date22)); // 符合预期 long timeoffset1 = getTimeOffset(ZoneId.of("Etc/GMT-8")); long timeoffset2 = getTimeOffset(ZoneId.of("Etc/GMT+4")); System.out.println("------> timeoffset1:" + timeoffset1 + ", timeoffset2:" + timeoffset2); // 符合预期 Date now3 = new Date(); Date date3 = convertZoneTime(now3, ZoneId.of("Etc/GMT+4")); System.out.println("-----> now3 at Etc/GMT+4 : " + DateUtil.formatDateTime(date3) + ", offset: " + (date3.getTime() - now3.getTime())); // Etc/GMT+4 地方所在的 date3 时刻,也是当前时区 Etc/GMT-8 的一个未来时间(但是是一个虚拟的 Etc/GMT+4 地方的时间) // Etc/GMT+4 时区的当前时间,转换为 Etc/GMT-8 时区的时间,是要在此基础上加 12 个小时,符合预期 Date now4 = new Date(); Date date4 = convertZoneTime(now3, ZoneId.of("Etc/GMT+4"), ZoneId.of("Etc/GMT-8")); System.out.println("-----> date4 at Etc/GMT-8 : " + DateUtil.formatDateTime(date4) + ", offset: " + (now4.getTime() - date4.getTime())); // 符合预期 Date date5 = convertZoneTime("2023-11-08 12:00:00", ZoneId.of("GMT+8"), ZoneId.of("GMT-4")); System.out.println("-----> date5 at GMT-4 : " + DateUtil.formatDateTime(date5)); // 符合预期 Date localNowTime2 = new Date(); String date61 = standardTimeFormat(localNowTime2, ZoneId.of("GMT+8")); String date62 = standardTimeFormat(localNowTime2, ZoneId.of("GMT-4")); System.out.println("-----> date61 : " + date61 + ", date62:" + date62); // for (int i = 0; i < 200; i++) { // Date now = new Date(); // LocalDateTime locatTime = date2LocalDateTime (now); // if (now.getTime() != DateTimeTools.localDateTime2Date(locatTime).getTime()) { // System.out.println("----------------> 时间转换有差距, now:" + DateUtil.formatDateTime(now)); // } // // try { // Thread.sleep(500L); // } catch (Exception e) { // e.printStackTrace(); // } // } } private static void testTimeZoneOffset() { // 问题:随着时间的不同,通过以下方式计算时差不是一个固定值,目前看来有时是 4 个小时,有时是 5 个小时!!!! // String refTimeStr = "2022-09-15 01:38:01"; // 字面时间 String refTimeStr = "2000-01-02 01:00:00"; // 字面时间 SimpleDateFormat refSDF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat refSDF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); refSDF1.setTimeZone(utcZone); refSDF2.setTimeZone(TimeZone.getTimeZone("America/New_York")); try { Date utcTime = refSDF1.parse(refTimeStr); Date mdTime = refSDF2.parse(refTimeStr); logger.info("utcTime时分秒:{}-{}-{} {}:{}, mdTime时分秒:{}-{}-{} {}:{}", utcTime.getYear(), utcTime.getMonth(), utcTime.getDay(), utcTime.getHours(), utcTime.getMinutes(), mdTime.getYear(), mdTime.getMonth(), mdTime.getDay(), mdTime.getHours(), mdTime.getMinutes()); logger.info("utcTime:{}, mdTime:{}, 时差:{} 个小时", DateUtil.formatDateTime(utcTime), DateUtil.formatDateTime(mdTime), (mdTime.getTime() - utcTime.getTime())/3600000L); } catch (ParseException e) { logger.error("[DateTimeUtils static] 初始化时区信息报错:", e); } } }