聊聊Java时间和时区的使用与转换技巧!

前言

一般情况下我们做的都是国内项目,服务器和客户都部署在国内,默认使用的都是北京时间,就不存在时区差异上的问题了。前段时间由于做海外项目,应用系统需要做国际化,免不了时间也要做本地化处理,比如我把服务器部署在中国,但客户在美国,总不能让客户看到的是北京时间吧,他还得换算成美国时间,这种体验也太差劲了。

针对于这种跨时区时间问题,本文将围绕java中关于时区和时间的处理做一个阐述,帮助大家能在面对时间国际化问题时处理的得心应手。

时区

首先,我们要弄清楚,为什么全球不同地区会有时间上的差异,比如我在中国北京现在是晚上18点(大部分城市冬天的时候天应该都快黑了),而美国纽约是早上5点(天还没有亮)。原因是由于地球自转,不同地区阳光照射的角度是不同的,因此各地区昼夜时间也是不同的,而地球的自转是自西向东的,所以东边会比西边先看到太阳,东边使用的时间也比西边的早。

为了克服时间上的混乱,国际上就对全球区域进行了划分,用来管理时间,这就是所谓的时区。

时区划分

早在1884年在华盛顿召开的一次国际经度会议上,规定将全球划分为24个时区。它们是中时区(零时区)、东1-12区,西1-12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时。相邻两个时区的时间相差1小时。例如,我国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间晚1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。

实际上,世界上不少国家和地区都不严格按时区来计算时间。为了在全国范围内采用统一的时间,一般都把某一个时区的时间作为全国统一采用的时间。例如,我国把首都北京所在的东8区的时间作为全国统一的时间,称为北京时间。又例如,英国、法国、荷兰和比利时等国,虽地处中时区,但为了和欧洲大多数国家时间相一致,则采用东1区的时间。(来自百度百科) 

全球各时区划分如下图所示:聊聊Java时间和时区的使用与转换技巧!(来源于网络) 

中国时区如下:聊聊Java时间和时区的使用与转换技巧!(来源于网络) 

美国时区如下:聊聊Java时间和时区的使用与转换技巧!(来源于网络)

UTC与GMT

  • GMT

GMT(Greenwich Mean Time):即格林尼治标准时间,是以英国格林尼治天文台的观测结果为依据的时间。它是基于地球自转周期计算出来的,但随着地球自转速度的变化,其长度并非固定不变,一个周期约为86,400秒(也就是一天)。GMT曾是国际上广泛使用的时间标准,但现在主要作为时区的一个参考点,即零时区或格林尼治平时,时区的划分是以GMT为参考点,偏移量往东为正,往西为负,GMT曾经是国际上广泛使用的时间标准,直到1972年被UTC所取代。

  • UTC

UTC(Coordinated Universal Time):即世界协调时,是一个更为精确的时间体系,基于原子钟计算得出,并与地球自转周期无关。UTC是目前国际社会广泛采用的世界时间标准,它通过闰秒的方式来调整时间,以保持与地球自转周期的一致性。UTC不仅精确到秒,还能精确到更小的单位,因此在需要高精度时间同步的场合,如科学实验、卫星导航等领域,UTC是首选的时间标准。

在现代,UTC因其高精度而被广泛使用,而GMT则更多地作为一个历史概念和时区参考存在,可以说他们所表示的时间概念是一样的(原因是由于GMT是格林尼治标准时间,而英国格林尼治天文台刚好在UTC标准时间的零时区)。

时区和偏移量

目前全世界的各个国家使用UTC来协调各地时间,中时区(也就是零时区)是UTC的标准时间(可表示为UTC+00:00或UTC-00:00或直接用Z表示),以它为中心线,向东的方向每个时区依次递增一小时,比如中国北京时间的时区表示为UTC+08:00(表示比标准时间早8小时),向西的方向每个时区依次递减一小时,比如美国纽约时间的时区表示为UTC-05:00(表示比标准时间晚5小时)。每个时区距离UTC标准时间增加或减少的时间就是偏移量。

Java中,UTC时间的起点就是1970-01-01 00:00:00,这一点我们可以从java.util.Date构造方法中得知。传递的long类型的时间参数就是相对于1970-01-01 00:00:00的偏移量,单位是毫秒。聊聊Java时间和时区的使用与转换技巧!在JDK8之前,时区是用java.util.TimeZone这个类来表示的,没有具体的偏移量类,但在TimeZone这个类中有一些跟偏移量相关的方法可以使用,日期时间可以用java.util.Date这个类处理。

从JDK8开始新加入了java.time.ZoneId表示时区,java.time.ZoneOffset表示偏移量,日期时间新加入了java.time.LocalDateTime。后面我们对根据时区对时间进行处理跟这几个类有关。

默认时区

获取默认时区方法:

System.out.println(TimeZone.getDefault());
//输出结果
sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null]

可以看到,中国默认的时区是东八区,标识是“Asia/Shanghai”,这个offset表示的就是相对于UTC时间的偏移量,28800000是毫秒,换算成小时就是28800000 / 1000 / 60 / 60 = 8,距UTC标准时间+8小时。

获取Java中所有的时区id,可以用如下方法:

String[] availableIDs = TimeZone.getAvailableIDs();
for (String id : availableIDs) {
    System.out.println(id);
}

输出:

Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
...
America/Miquelon
America/Moncton
America/Monterrey
America/Montevideo
America/Montreal
America/Montserrat
America/Nassau
America/New_York
...
Asia/Shanghai
Asia/Singapore
Asia/Srednekolymsk
Asia/Taipei
...
Cuba
EET
EST5EDT
Egypt
Eire
Etc/GMT
Etc/GMT+0
Etc/GMT+1
Etc/GMT+10
Etc/GMT+11
Etc/GMT+12
Etc/GMT+2
Etc/GMT+3
Etc/GMT+4
Etc/GMT+5
Etc/GMT+6
Etc/GMT+7
Etc/GMT+8
Etc/GMT+9
Etc/GMT-0
Etc/GMT-1
...
Europe/Rome
Europe/Samara
Europe/San_Marino
Europe/Sarajevo
...
US/Michigan
US/Mountain
US/Pacific
US/Pacific-New
US/Samoa
UTC
Universal
W-SU
WET
Zulu
EST
HST
MST
ACT
AET
AGT
ART
AST
BET
BST
CAT
CNT
CST
...

有628个可用的时区ID(注意:不同版本的JDK可能输出结果不一样),通过输出结果大部分“/”分隔的,前面表示各个洲的名字,后面是著名城市的名字,当前还有一些其他的表示GMT(格林尼治时间)、CST(中国标准时间)、UTC(世界协调时间,也就是世界标准时间)等。

默认时区也是可以修改的,有以下三种方式:

  1. 代码主动设置:TimeZone.setDefault(TimeZone.getTimeZone(“America/New_York”));
  2. JVM参数:-Duser.timezone=America/New_York
  3. 设置操作系统主机时区,一般由运维操作,比如在使用docker容器的时候一般Dockerfile里都会加一句“ENV TZ=Asia/Shanghai”之类的表示指定时区。

时间戳

Unix时间戳(Unix timestamp),或称Unix时间(Unix time)、POSIX时间(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。Unix时间戳不仅被使用在Unix 系统、类Unix系统中,也在许多其他操作系统中被广泛采用。多数Unix系统将时间戳以一个32位整型进行保存,这可能会在2038年1月19日产生一 些问题(Y2038问题),感兴趣的可以搜索了解下。

在java里可以通过System.currentTimeMillis()获取当前时间戳,注意这个时间戳是到毫秒的。

System.out.println(System.currentTimeMillis());
//输出
//1706008477028

虽然我们可以这样获取到时间戳,但是时间戳其实是跟时区无关的,比如同一时刻,在美国或者中国或者其他国家,使用此方法获取到的时间戳都是一样的。

TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println(System.currentTimeMillis()/1000);
TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
System.out.println(System.currentTimeMillis()/1000);
TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
System.out.println(System.currentTimeMillis()/1000);

输出:

1706018125
1706018125
1706018125

这个时间戳除以了1000所以得到的是秒数,可以看到不同时区,在同一时刻得到的时间戳偏移量是一样的。

这样我们就反过来想,如果我知道了UTC标准时间戳,也知道了时区(或者偏移量),是不是就可以算出本地的时间呢,答案是可以的,看一下后面的时间处理。

时间处理

TimeZone

这是JDK8之前的时区处理类,常用的方法如下:

/**
 * 获取系统默认的时区
 */

public static TimeZone getDefault() {
    return (TimeZone) getDefaultRef().clone();
}

/**
 * 设置系统默认的时区
 * @param zone 待设置的时区对象
 */

public static void setDefault(TimeZone zone)
{
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new PropertyPermission
                           ("user.timezone""write"));
    }
    defaultTimeZone = zone;
}

/**
 * 这是JDK8开始提供的方法,用于ZoneId和TimeZone的转换
 * 获取指定时区的TimeZone对象
 * @param zoneId 时区id
 */

public static TimeZone getTimeZone(ZoneId zoneId) {
    String tzid = zoneId.getId(); // throws an NPE if null
    char c = tzid.charAt(0);
    if (c == '+' || c == '-') {
        tzid = "GMT" + tzid;
    } else if (c == 'Z' && tzid.length() == 1) {
        tzid = "UTC";
    }
    return getTimeZone(tzid, true);
}

setDefault这个方法很关键,可以全局设置系统的默认时区,在使用new Date()或者LocalDateTime.now()的时候就是使用的默认时区。示例:

// 获取默认时区
System.out.println(TimeZone.getDefault());
// 设置默认时区
TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
// 从指定的ZoneId获取TimeZone
System.out.println(TimeZone.getTimeZone(ZoneId.of("Europe/London")));

输出:

sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=3600000,useDaylight=true,transitions=242,lastRule=java.util.SimpleTimeZone[id=Europe/London,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]

ZoneId

ZoneId是JDK8开始新加的处理时区的类,他是一个抽象类,但提供的静态方法已经够日常使用了。

/**
 * 获取指定时区id的ZoneId对象
 * @param zoneId 时区id
 */

public static ZoneId of(String zoneId) {
    return of(zoneId, true);
}

/**
 * 通过ZoneOffset对象获取ZoneId对象
 * @param prefix只能这三种GMT、UTC、UT
 * @param offset 带偏移量的ZoneOffset对象
 */

public static ZoneId ofOffset(String prefix, ZoneOffset offset) {
    Objects.requireNonNull(prefix, "prefix");
    Objects.requireNonNull(offset, "offset");
    if (prefix.length() == 0) {
        return offset;
    }

    if (!prefix.equals("GMT") && !prefix.equals("UTC") && !prefix.equals("UT")) {
         throw new IllegalArgumentException("prefix should be GMT, UTC or UT, is: " + prefix);
    }

    if (offset.getTotalSeconds() != 0) {
        prefix = prefix.concat(offset.getId());
    }
    return new ZoneRegion(prefix, offset.getRules());
}

/**
 * 获取系统默认ZoneId对象
 */

public static ZoneId systemDefault() {
    return TimeZone.getDefault().toZoneId();
}

/**
 * 从另一个TemporalAccessor对象获取ZoneId对象
 * @param temporal 带时区的TemporalAccessor对象
 */

public static ZoneId from(TemporalAccessor temporal) {
    ZoneId obj = temporal.query(TemporalQueries.zone());
    if (obj == null) {
        throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " +
                temporal + " of type " + temporal.getClass().getName());
    }
    return obj;
}

/**
 * 获取系统支持的所有ZoneId列表
 */

public static Set<String> getAvailableZoneIds() {
    return ZoneRulesProvider.getAvailableZoneIds();
}

ZoneId可以通过of方法和ofOffset方法获取指定的时区,也可以通过systemDefault这个方法获取默认时区,仔细看方法体是调用的TimeZone.getDefault().toZoneId()获得的,所以使用TimeZone.setDefault方法设置默认全局时区后,只要在代码逻辑里没有指定时区,都会用到这个指定的默认时区,这也是在做国际化项目中,对时间统一处理的一种常用做法。

例子:

System.out.println(ZoneId.of("+08:00"));
System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(8)));
System.out.println(ZoneId.systemDefault());
System.out.println(ZoneId.from(ZonedDateTime.now(Clock.systemUTC())));
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set.size());

输出:

+08:00
UTC+08:00
Asia/Shanghai
Z
600

ZoneOffset

ZoneOffset也是JDK8新加的类,继承了ZoneId,它用于表示从协调世界时间(UTC)的偏移量,通常以小时和分钟的形式表示。例如,如果一个地区的本地时间比UTC时间快5个小时30分钟,那么这个地区的ZoneOffset就是+05:30。

需要注意的是,尽管ZoneOffset可以表示时差,但它并不包含时区的完整信息。时区通常包括夏令时等更复杂的规则,而这些信息是由ZoneId类提供的。因此,在进行日期和时间的操作时,建议使用ZoneId而不是ZoneOffset来获取更准确的本地时间。

常用的获取ZoneOffset是通过of方法调用的。聊聊Java时间和时区的使用与转换技巧!可以看到支持传入的时间格式,Z表示0时区,偏移量是0,其他情况需要使用“+”或“-”开头,然后是时分秒,表示相对零时区的增加或减少的偏移量。零时区偏移量是0,可以用如下方式表示:

ZoneOffset z1 = ZoneOffset.of("+0");
System.out.println(z1);
ZoneOffset z2 = ZoneOffset.of("-0");
System.out.println(z2);
ZoneOffset z3 = ZoneOffset.of("Z");
System.out.println(z3);

//输出结果
Z
Z
Z
ZoneOffset z1 = ZoneOffset.of("+8");
System.out.println(z1);
ZoneOffset z2 = ZoneOffset.of("+08:30:10");
System.out.println(z2);
ZoneOffset z3 = ZoneOffset.of("-6");
System.out.println(z3);
ZoneOffset z4 = ZoneOffset.of("-06:21:50");
System.out.println(z4);
//输出结果
+08:00
+08:30:10
-06:00
-06:21:50

ZoneId与TimeZone相互转换

JDK8之前的TimeZone和从JDK8开始的ZoneId是支持相互转换的,这样就相互兼容了这两种时区对时间的处理,

  • TimeZone转ZoneId:
ZoneId zoneId1 = TimeZone.getTimeZone("Asia/Shanghai").toZoneId();
System.out.println(zoneId1);
ZoneId zoneId2 = TimeZone.getTimeZone("GMT+08:00").toZoneId();
System.out.println(zoneId2);

输出:

Asia/Shanghai
GMT+08:00
  • ZoneId转TimeZone
TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("+08:00"));
System.out.println(timeZone);

输出:

sun.util.calendar.ZoneInfo[id="GMT+08:00",offset=28800000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]

获取时间

获取时间JDK8之前有Date类型,从JDK8开始还可以用新加的LocalDateTime这个类。

获取系统当前本地时间

使用Date和LocalDateTime都可以直接获取默认时区的当前时间,也可通过设置默认时区,获得指定时区的当前时间。

// 使用默认时区
System.out.println(new Date());
System.out.println(LocalDateTime.now());
// 重新设置默认时区
TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
System.out.println(new Date());
System.out.println(LocalDateTime.now());

输出结果:

Fri Jan 24 16:45:53 CST 2024
2024-01-24T16:45:53.149
Fri Jan 24 03:45:53 EST 2024
2024-01-24T03:45:53.150

获取系统当前UTC时间

Date类型的对象可以通过SimpleDateFormat的setTimeZone方法设置时区为零时区,通过格式化方式转成UTC时间字符串,然后可以通过解析这个字符串再转成Date对象,这个Date对象所表示的就是UTC时间了。

LocalDateTime可以通过now方法设置ZoneId为零时区,再获取UTC时间。一般会将UTC时间的字符串形式表示成“yyyy-MM-dd’T’HH:mm:ss’Z’”。T表示后面的是时间,Z表示的是零时区。

System.out.println("当前本地时间Date:" + new Date());
System.out.println("当前本地时间LocalDateTime:" + LocalDateTime.now());
// Date类型
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
// 使用format()方法将Date对象转换为字符串
String utcDateString = sdf.format(date);
System.out.println("当前Date对象转的UTC时间字符串:" + utcDateString);
// 使用parse()方法将字符串解析回Date对象
Date utcDate = null;
try {
    utcDate = sdf.parse(utcDateString);
    System.out.println("UTC时间戳(毫秒):" + utcDate.getTime());
catch (ParseException e) {
    e.printStackTrace();
}
//LocalDateTime类型
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("UTC"));
String utcLocalDateTime = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()));
System.out.println("当前LocalDateTime对象转的UTC时间字符串:" + utcLocalDateTime);

输出结果:

当前本地时间Date:Fri Jan 24 17:04:15 CST 2024
当前本地时间LocalDateTime:2024-01-24T17:04:15.517
当前Date对象转的UTC时间字符串:2024-01-24T09:04:15Z
UTC时间戳(毫秒):1706058255000
当前LocalDateTime对象转的UTC时间字符串:2024-01-24T09:04:15Z

可以看到国内比UTC标准时间早8小时。

获取指定时区或偏移量的当前时间

Date类型可以通过设置SimpleDateFormat的时区来获取指定时区的时间,LocalDateTime类型的可以指定ZoneId,比如获取纽约时间(ZoneId为America/New_York或者-5,纽约比UTC晚5小时)

System.out.println("当前本地时间Date:" + new Date());
System.out.println("当前本地时间LocalDateTime:" + LocalDateTime.now());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println("当前纽约时间Date:" + sdf.format(new Date()));
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("America/New_York"));
String nyLocalDateTime = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.getDefault()));
System.out.println("当前纽约时间LocalDateTime:" + nyLocalDateTime);

输出结果:

当前本地时间Date:Fri Jan 24 17:21:55 CST 2024
当前本地时间LocalDateTime:2024-01-24T17:21:55.990
当前纽约时间Date:2024-01-24 04:21:56
当前纽约时间LocalDateTime:2024-01-24 04:21:56

可以看到美国纽约比中国晚13小时。

时间在不同时区的转换

一般情况下,在项目中对于不同国家地区的用户展示本地化时间,对用户来说体验就很好,所以通常会采用将时间存成long类型的UTC时间戳,这样在存储上就统一了,跟时区无关。在对外展示上,可以根据应用系统所在的时区或者指定的时区转换成本地时间即可。

Date类型的对象时间表示当前时刻的偏移量,也就是相对于1970-01-01 00:00:00所经过的毫秒数,是跟时区无关的。

注意:中国虽然用的是北京时间,但是东八区是用“Asia/Shanghai”这个ID来表示的,没有“Asia/Beijing”这一说啊(通过前面的TimeZone.getAvailableIDs获取的也是没有“Asia/Beijing”的)。

System.out.println(TimeZone.getTimeZone("Asia/Beijing"));

输出:

sun.util.calendar.ZoneInfo[id="GMT",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]

虽然通过这个“Asia/Beijing”打印出了结果,但是仔细看,输出的ID是GMT也就是标准时间,offset=0,因为java在处理这个的时候如果没有获取到时区,默认给的就是GMT标准时间,使用的时候一定要注意。

//通过传递当前毫秒数的时间偏移量构造Date对象
Date currDate = new Date(System.currentTimeMillis());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 北京时间
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println("毫秒数:" + currDate.getTime() + ", 北京时间:" + simpleDateFormat.format(currDate));
// 纽约时间
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println("毫秒数:" + currDate.getTime() + ", 纽约时间:" + simpleDateFormat.format(currDate));

输出:

毫秒数:1706317910011, 北京时间:2024-01-27 09:11:50
毫秒数:1706317910011, 纽约时间:2024-01-26 20:11:50

通过例子可以看出,两个地区的毫秒数是一样的,也就证明了Date与时区的无关系,通过SimpleDateFormat的setTimeZone方法指定时区,就可以得到本地时间。

另外Date类的toString方法,是根据默认时区进行了转化的,获取的就是应用系统所在时区(或者指定的默认时区)的本地时间。使用SimpleDateFormat去格式化或解析时间的时候如果没有指定时区也使用的是应用系统默认的时区。

UTC时间戳转指定时区时间

java中时间戳一般是用到毫秒的,可以通过Date或者LocalDateTime来构造时间。

  • Date

指定一个时间偏移量构造Date对象,然后通过指定时区就可以得到不同地区的本地时间了。

//通过传递毫秒数的时间偏移量构造Date对象
Date currDate = new Date(1706317910011L);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("毫秒数:" + currDate.getTime() + ", 默认时间:" + simpleDateFormat.format(currDate));
// 北京时间
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
System.out.println("毫秒数:" + currDate.getTime() + ", 北京时间:" + simpleDateFormat.format(currDate));
// 纽约时间
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println("毫秒数:" + currDate.getTime() + ", 纽约时间:" + simpleDateFormat.format(currDate));

输出结果:

毫秒数:1706317910011, 默认时间:2024-01-27 09:11:50
毫秒数:1706317910011, 北京时间:2024-01-27 09:11:50
毫秒数:1706317910011, 纽约时间:2024-01-26 20:11:50
  • LocalDateTime

可以通过LocalDateTime的ofInstant方法,参数有时间偏移量和时区,来构造LocalDateTime对象,然后通过DateTimeFormatter对象格式化输出结果。

long offset = 1706317910011L;
// 通过ofPattern构造的DateTimeFormatter是时区无关的,只做格式化显示
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 默认时间
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(offset),ZoneId.systemDefault());
System.out.println("默认时间:" + localDateTime.format(dateTimeFormatter));
// 北京时间
LocalDateTime localDateTime1 = LocalDateTime.ofInstant(Instant.ofEpochMilli(offset), ZoneId.of("Asia/Shanghai"));
System.out.println("北京时间:" + localDateTime1.format(dateTimeFormatter));
// 纽约时间
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(Instant.ofEpochMilli(offset), ZoneId.of("America/New_York"));
System.out.println("纽约时间:" + localDateTime2.format(dateTimeFormatter));

输出结果:

默认时间:2024-01-27 09:11:50
北京时间:2024-01-27 09:11:50
纽约时间:2024-01-26 20:11:50

当前指定时区时间转换成UTC时间

当前本地时间可以通过LocalDateTime加时区获取,然后通过LocalDateTime的atZone方法转成ZonedDateTime对象,ZonedDateTime对象通过withZoneSameInstant方法指定UTC时区就可以获取到UTC时间了。

// 通过ofPattern构造的DateTimeFormatter是时区无关的,只做格式化显示
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 默认时间
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("默认时间:" + localDateTime.format(dateTimeFormatter));
ZonedDateTime utc = localDateTime.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC"));
System.out.println("utc时间:" + utc.format(dateTimeFormatter));
// 北京时间
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("北京时间:" + localDateTime1.format(dateTimeFormatter));
ZonedDateTime utc1 = localDateTime1.atZone(ZoneId.of("Asia/Shanghai")).withZoneSameInstant(ZoneId.of("UTC"));
System.out.println("utc时间:" + utc1.format(dateTimeFormatter));
// 纽约时间
LocalDateTime localDateTime2 = LocalDateTime.now(ZoneId.of("America/New_York"));
System.out.println("纽约时间:" + localDateTime2.format(dateTimeFormatter));
ZonedDateTime utc2 = localDateTime2.atZone(ZoneId.of("America/New_York")).withZoneSameInstant(ZoneId.of("UTC"));
System.out.println("utc时间:" + utc2.format(dateTimeFormatter));

输出结果:

默认时间:2024-01-27 10:11:44
utc时间:2024-01-27 02:11:44
北京时间:2024-01-27 10:11:44
utc时间:2024-01-27 02:11:44
纽约时间:2024-01-26 21:11:44
utc时间:2024-01-27 02:11:44

当前指定时区时间转另一个时区时间

通过ZonedDateTime对象来实现指定时区时间转成另一个时区时间。

// 通过ofPattern构造的DateTimeFormatter是时区无关的,只做格式化显示
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 北京时间
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("北京时间:" + localDateTime1.format(dateTimeFormatter));
ZonedDateTime utc1 = localDateTime1.atZone(ZoneId.of("Asia/Shanghai")).withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("纽约时间:" + utc1.format(dateTimeFormatter));

输出结果:

北京时间:2024-01-27 10:18:29
纽约时间:2024-01-26 21:18:29

总结

本文可总结如下:

  • 全球划分了24个时区,UTC零时区作为协调标准时间,其他各时区都以相对零时区的偏移量表示。
  • 可以通过TimeZone.setDefault方法在代码中设置应用系统的默认时区,这个方法灵活度是最高的。
  • JDK8之前的时区TimeZone对象和从JDK8开始后的ZoneId对象可以相关转换,方便对于Date类型和LocalDateTime这两种时间的处理。
  • 给定一个字符串的时间需要加上时区才有意义,可利用SimpleDateFormat等之类的格式化类,指定时区,将字符串解析成时间对象,也可将时间对象格式化成字符串展示。
  • 指定时区的时间,可以通过ZonedDateTime类进行转换到另一个时区的时间。

JDK中已经帮我们封装好了大部分的关于时区和时间的处理,可以利用现有的方法很方便的实现各种时区时间转换,在国际化项目中可以封装好各种转换方法,方便调用。文中列出的都是常见的基本操作,在JDK中还有很多关于时区时间的处理类和方法,感兴趣的可自行研究。另外如果涉及到多线程使用,需要考虑线程安全的问题。

欢迎关注公众号,欢迎分享、点赞、在看

聊聊Java时间和时区的使用与转换技巧!



原文始发于微信公众号(小新成长之路):聊聊Java时间和时区的使用与转换技巧!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/238263.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!