【Java 8 Time】Java8时区时间运用详解,2万字助你通关java.time包

news2025/1/15 17:11:06

目录

  • 前言
  • 一、时区与时间
    • 1. 世界标准时:UTC、GMT、UT
    • 2. 地区时:Asia/Shanghai、UTC+8
    • 3. 时区:ZoneId、TimeZone
    • 4. 时间偏移量:ZoneOffset
    • 5. 时区简称:CTT、PRC
  • 二、主要时间类
    • 1. 重要时间接口:Temporal
    • 2. 时间单位、时间字段、时长:ChronoUnit、ChronoField、Duration
      • A .ChronoUnit
      • B .ChronoField
      • C .Duration
    • 3. 时间点:Instant
    • 4. 本地时间:LocalTime、LocalDate、LocalDateTime
    • 5. 偏移量时间:OffsetTime、OffsetDateTime
    • 6. 时区时间:ZonedDateTime
    • 7. 动态时间(时钟):Clock——SystemClock、FixedClock、OffsetClock、TickClock
    • 8. 时间属性查询:TemporalQuery
    • 9. 时间属性调整:TemporalAdjuster
    • 10. 时间转换(含Date)
  • 总结
    • 参考

前言

鉴于每次用到时间的相关方法时,都是通过谷歌搜索如何实现,自己对这些实现背后隐藏的内容知之甚少。所以,这次打算开一篇文章,学习JDK中主要的时间类,了解之前用到时间类、参数和方法代表的含义。



Java8 掌握Date与Java.time转换的核心思路,轻松解决各种时间转换问题




一、时区与时间

1. 世界标准时:UTC、GMT、UT

UTC(Coordinated Universal Time,世界标准时,世界协调时)是最主要的世界时间标准。UTC基于国际原子时,并通过不规则的加入闰秒来抵消地球自转变慢的影响。鉴于UTC已经是Java中主要的世界时表示方式,除非特殊提示否则下文中都UTC来表示世界标准时时间。


GMT(Greenwich Mean Time,格林尼治时间),由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,目前已经被原子钟报时的协调世界时(UTC)所取代。


UT(Universal Time,世界时),是一种以格林威治子夜起算的平太阳时。世界时是以地球自转为基准得到的时间尺度,其精度受到地球自转不均匀变化和极移的影响。

Instant(1.8)是jdk8时间框架的基础类,它以世界标准时UTC作为基础时区构建,基本上jdk8中的时间类都会于它打交道,支持以GMT、UT作为基础时区。
TimeZone(1.7前)它以世界标准时GMT作为基础时区构建,不支持UTC、UT时区。

用Instant来直接输出UTC当前的时间:

// Instant.now()代表UTC当前时间的秒 + 纳秒(从1970-1-1 00:00:00 开始所经过秒-纳秒数)
Instant instant = Instant.now(); // UTC(Z) ~ GMT ~ UT
System.out.println(instant);

输出如下时间:

2023-03-02T07:53:02.377Z



2. 地区时:Asia/Shanghai、UTC+8

地区时:是适用相同时区规则的地理区域,是UTC偏移量位置处的时间。在同一时刻,地区时根据UTC距当地的偏移量来计算其本地时间。


中国占据东五区、东六区、东七区、东八区、东九区5个时区,为了统一时间,1949年中华人民共和国成立后,中国大陆全境统一划为东八区,同时以北京时间作为全国唯一的标准时间。北京时间,又名中国标准时间(China Standard Time,CST,UTC+8),是我国的标准时间,比世界协调时快八小时(UTC+8)。注意CST并不能作为北京时间的时区ID来生成ZoneId,原因见时区简称。

对比一下世界标准时、其它地区时和北京时间的时间差异:

Instant instant = Instant.now();
System.out.println("UTC:" + instant);
System.out.println("纽约 UTC-5:" + instant.atZone(ZoneId.of("America/New_York")));
System.out.println("东京 UTC+9:" + instant.atZone(ZoneId.of("Asia/Tokyo")));
System.out.println("上海 UTC+8:" + instant.atZone(ZoneId.of("Asia/Shanghai")));

输出内容如下:

UTC:2023-03-02T08:37:10.736Z
纽约 UTC-5:2023-03-02T03:37:10.736-05:00[America/New_York]
东京 UTC+9:2023-03-02T17:37:10.736+09:00[Asia/Tokyo]
上海 UTC+8:2023-03-02T16:37:10.736+08:00[Asia/Shanghai]

可以通过这些网站查询各个时区以及主要城市的时区:timeanddate,时区列表



3. 时区:ZoneId、TimeZone

时区:是地球上使用的同一时间定义的国家或区域,它描述了这个国家或区域内的偏移量范围。

  • ZoneId代表时区。它的内部描述了这个国家或区域的整体时间规则。 即ZoneId = ZoneOffset+ ZoneRules
  • ZoneRules代表这个时区的时区规则细则,包含这个国家或区域的时区偏移量的变动和这个变动的时间,不同的时间这个时区可能会有不同的时间偏移量(ZoneOffset)情况。比如Asia/ShanghaiAsia/Singapore,虽然当前它们都使用的UTC+8这个时区的区时,但是不代表这些国家或地区的曾经或者未来还会使用这个区时,我国就在1986年至1991年使用过六年夏令时,即每年在4月中旬的第一个星期日2时整(北京时间)到9月中旬第一个星期日的凌晨2时整(北京夏令时),夏令时内时间将会向后调快一小时,使用+9:00时区的区时。ZoneRules中就完整的记录了这些变动。

时区和区时:时区是一个国家或区域,区时是这个国家或区域的本地时间

在Java中,以ZoneId(1.8)和TimeZone(1.1)来表示时区概念。
ZoneId:

当前默认时区

ZoneId zoneId = ZoneId.systemDefault(); // 本质是调用TimeZone.getDefault().toZoneId()
TimeZone timeZone = TimeZone.getDefault();
// 可以设置程序全局的默认时区
TimeZone.setDefault(timeZone);

ZoneId(1.8):
如下程序,展示了通过时区ID获取该时区的本地时间:

System.out.println(ZonedDateTime.now(ZoneId.of("UTC"))); // 2023-03-22T07:22:03.133Z[UTC]
System.out.println(ZonedDateTime.now(ZoneId.of("GMT"))); // 2023-03-22T07:22:03.133Z[GMT]
System.out.println(ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))); // 2023-03-22T15:22:03.133+08:00[Asia/Shanghai]

TimeZone(1.1):
Date类中没有时区概念 可以借助SimpleDateFormat实现:

TimeZone utc = TimeZone.getTimeZone("UTC+8");
TimeZone gmt = TimeZone.getTimeZone("GMT+8");
TimeZone ut = TimeZone.getTimeZone("UT+8");
Date date = new Date(); // 2023-04-14 11:04:17
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(utc);
System.out.println(sdf.format(date)); // 2023-04-14 03:04:17
sdf.setTimeZone(gmt);
System.out.println(sdf.format(date)); // 2023-04-14 11:04:17
sdf.setTimeZone(ut);
System.out.println(sdf.format(date)); // 2023-04-14 03:04:17

上面的程序中,UTC+8、GMT+8、UT+8三个时区并没有输出相同的时间,只有GMT+8正确的输出了当前时间,原因是TimeZone是基于GMT构建的,它并不支持UTC+8、UT+8这两种时间标准时的偏移设置。此外以UTC作为时区ID时可以被识别成功,UT则不行。

刚开始用的时候,由于使用UTC与UT没报错,我还以为TimeZone支持这两种世界标准时,但是通过分析源码才发现TimeZone会将所有没有解析成功的时区ID转为GMT。

private static TimeZone getTimeZone(String ID, boolean fallback) {
    TimeZone tz = ZoneInfo.getTimeZone(ID); // 时区ID中仅存在UTC,不存在其偏移设置即UTC+1这种格式;不存在UT。
    if (tz == null) {
        tz = parseCustomTimeZone(ID); // 解析GMT的自定义偏移时间设置,如GMT+14:17
        if (tz == null && fallback) {
            tz = new ZoneInfo(GMT_ID, 0); // 所有未被识别的时区都会被设置为“GMT+0”
.....
}

TimeZone中可以正确使用的ID


ZoneId与TimeZone兼容性
为了兼容ZoneId带来的改变,1.8版本中为TimeZone类添加了ZoneId与TimeZone互转的方法:

ZoneId zone = ZoneId.of("Asia/Shanghai");
TimeZone timeZone = TimeZone.getTimeZone(zone);
ZoneId toZoneId = timeZone.toZoneId();

查看可用时区
如上所示,通过ZoneId和TimeZone都是通过时区ID来创建对应时区,那么你要怎么知道有哪些时区ID可用呢?
可以通过如下方式查看可用ID:

// ZoneId可用的时区ID
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
// TimeZone可用的时区ID
String[] availableIds = TimeZone.getAvailableIDs();
// 或通过ZoneInfoFile查看时区ID
String[] zoneIds = ZoneInfoFile.getZoneIds();

查看指定时区
如果要查找指定偏移量位置的时区可以通过如下方式实现(单位毫秒):

// 查找位于+8时区的所有时区ID
String[] availableIDs = ZoneInfo.getAvailableIDs(8 * 60 * 60 * 1000);
String[] availableIDs1= TimeZone.getAvailableIDs(8 * 60 * 60 * 1000);

查看时区的历史区时
我国就在1986年至1991年使用过六年夏令时(每年在4月中旬的第一个星期日2时整(北京时间)到9月中旬第一个星期日的凌晨2时整(北京夏令时),时间调快一个小时)。ZoneId由于记录了这些时区的历史变动,所以它可以很轻易的得到我国实行夏令时当时的本地时间:

// 输出曾经的夏令时时间
ZoneId ctt = ZoneId.of("Asia/Shanghai");
Instant parse = Instant.parse("1986-06-01T00:00:00.000Z");
LocalDateTime localDateTime = LocalDateTime.ofInstant(parse, ctt); // 1986-06-01T09:00
ZoneOffset offset = ctt.getRules().getOffset(parse); // +09:00



4. 时间偏移量:ZoneOffset

时间偏移量,是时区和特定地点的时间差异,通常以小时和分钟为单位。由于世界各国位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差,或者时间偏移量、时区偏移量。


ZoneOffset支持的偏移量范围(-18:00 to +18:00),它表示的是从UTC开始的偏移量范围,相对于世界标准时所在的0°(经度)来说,每相隔一个时区(15°)即相差一小时,往东经一个时区,时间快1h,偏移量+1,反之相反。


ZoneOffset与ZoneId

  • ZoneId内部包含了一系列的偏移量,它表示的是这个国家和区域的历史偏移量变动情况,而且这个时区的偏移量存在持续变动的可能。
  • ZoneOffset表示的是固定偏移量,这些偏移量永远是固定的。(ZoneId和ZoneOffset的区别即是ZonedDateTime和OffsetDateTime的区别)

时间偏移量通常以如下的格式显示:

  • ±[hh]:[mm]
  • ±[hh][mm]
  • ±[hh]
  • ±[h]

在Java中,表示时间偏移量的类为:ZoneOffset(1.8)、TimeZone(1.1)

  1. ZoneId可以通过当前时间获取这个时区的当前偏移量ZoneOffset。
  2. ZoneOffset表明有指定数量的时差,类中的时间偏移量以秒或毫秒数存储,如偏移量为:+02:30,那么其存储的值为(2 * 60 + 30) * 60 /s。

ZoneIdZoneOffset

Instant instant = Instant.now();
// 由于时区的偏移量存在变动可能,因此需要根据当前时间来获取这个时区当前的时间偏移量
ZoneOffset offset = ZoneId.of("Asia/Shanghai").getRules().getOffset(instant);
ZoneOffset zoneOffset = ZoneOffset.of("+02:30"); // ZoneOffset.ofHoursMinutes(2, 30);

TimeZone

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+12:24"));



5. 时区简称:CTT、PRC

时区ID是代表一系列偏移量的特定区域。时区简称则是时区ID的缩写或简称。

时区简称对照表

// ZoneId时区ID简称对照表
Map<String, String> shortIds = ZoneId.SHORT_IDS;

// ZoneInfoFile时区ID简称对照表,这也是TimeZone内置的简称表
Map<String, String> aliasMap = ZoneInfoFile.getAliasMap();

ZoneId(1.8)
上海时间(“CTT”, “Asia/Shanghai”)。

// 使用时区简称
ZoneId ctt = ZoneId.of("CTT", ZoneId.SHORT_IDS);
// 或者使用TimeZone版本的简称来获取时区ID,该表中也有("PRC", "Asia/Shanghai")
ZoneId zoneCst = ZoneId.of("CTT", ZoneInfoFile.getAliasMap());
// 或者自定义简称ID也行……

TimeZone(1.1)

// 使用时区简称 
// TimeZone做了一个兼容处理,如果找不到对应的时区ID,则尝试寻找简称对照表
TimeZone timeZoneCtt = TimeZone.getTimeZone("CTT");

注意

  1. ZoneId要使用时区简称,必须搭配时区简称表使用,而TimeZone则不用。
  2. ZoneId的简称更新后,与TimeZone中的简称存在不匹配的情况。因此在TimeZone使用一些简称时,转ZoneId可能会出现时区错乱,反之同理。
  3. CST(中国标准时间),在ZoneId和TimeZone中都无法正确识别,因为CST已经被美国的地方时区占用了:CST -> Central Standard Time (North America) America/Chicago。
  4. 一般我们要表达中国所在时区时,Asia/Shanghai、Asia/Chongqing、UTC+8、GMT+8、UT+8等时区ID、PRC、CTT等时区简称、+8、+08:00等偏移量设置都可以。





二、主要时间类

1. 重要时间接口:Temporal

Temporal:框架级接口,定义对时间对象的读写访问,比如对日期、时间、偏移量或这些对象的一些组合的读写访问。这个接口对实现的可变性没有限制,但是强烈建议将该类的实现类设为不可变(final)。
Temporal实例支持读写操作,它同样存在线程安全问题。同StringTemporal的所有实现类的写操作都会生产新的对象而非修改原对象,所以它们都是线程安全的。

主要实现类
在这里插入图片描述


主要方法

TemporalAccessor

  1. 判断当前时间类是否支持这个时间字段TemporalAccessor.isSupported(TemporalField field),如果不支持的话,那么执行参数中带TemporalField的方法都会报错。
  2. 获取当前时间类指定时间字段的值范围TemporalAccessor.range(TemporalField field)
  3. 获取当前时间类指定时间字段的值,精度为intTemporalAccessor.get(TemporalField field)
  4. 获取当前时间类指定时间字段的值,精度为longTemporalAccessor.getLong(TemporalField field)
  5. 获取当前时间类的指定属性值TemporalAccessor.query(TemporalQuery<R> query)

Temporal

  1. 判断当前时间类是否支持这个时间单位Temporal.isSupported(TemporalUnit unit),如果不支持的话,那么执行参数中带TemporalUnit的方法都会报错。
  2. 调整当前时间类的局部时间Temporal.with(TemporalField field, long newValue)
  3. 调整当前时间类的局部时间Temporal.with(TemporalAdjuster adjuster)
  4. 时间类增加N个单位的时间Temporal.plus(long amountToAdd, TemporalUnit unit)
  5. 时间类减少N个单位的时间Temporal.minus(long amountToSubtract, TemporalUnit unit)
  6. 比较两个时间相差多少个时间单位Temporal.until(Temporal endExclusive, TemporalUnit unit)
  7. 时间类增加N个时间长度Temporal.plus(TemporalAmount amount)
  8. 时间类减少N个时间长度Temporal.minus(TemporalAmount amount)
    在这里插入图片描述



2. 时间单位、时间字段、时长:ChronoUnit、ChronoField、Duration

由于这三个类中有很多相同的方法,也有一些方法是基于TemporalAccessor和Temporal类的,所以可能会省略这一部分内容。

A .ChronoUnit

ChronoUnit(1.8)|TimeUnit(1.5):枚举类,代表一段日期周期的时间单位。

ChronoUnit枚举的时间单位

NANOS("Nanos", Duration.ofNanos(1)),
MICROS("Micros", Duration.ofNanos(1000)),
MILLIS("Millis", Duration.ofNanos(1000_000)),
SECONDS("Seconds", Duration.ofSeconds(1)),
MINUTES("Minutes", Duration.ofSeconds(60)),
HOURS("Hours", Duration.ofSeconds(3600)),
HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),
DAYS("Days", Duration.ofSeconds(86400)),
// ……

ChronoUnit方法使用

ChronoUnit的使用比较简单,提一下比较重要的方法:

  • 比较两个时间类相差多少个单位TemporalUnit.between(Temporal temporal1Inclusive, Temporal temporal2Exclusive),要求temporal2Exclusive参数能够转为temporal1Inclusive这个时间类,否则将会抛java.time.DateTimeException,它实际上调用的是Temporal.until方法。
Instant instant = Instant.now();
ChronoUnit hours = ChronoUnit.HOURS;
// 时间差值计算
System.out.println("不同时间类的相差时间:" + hours.between(instant.atOffset(ZoneOffset.ofHours(-5)), instant.atOffset(ZoneOffset.ofHours(8))));
System.out.println("不同时间类的相差时间:" + hours.between(instant.atZone(ZoneId.of("-5")), instant.atZone(ZoneId.of("+8"))));
System.out.println("不同时间类的相差时间:" + hours.between(LocalDateTime.ofInstant(instant, ZoneId.of("-5")), LocalDateTime.ofInstant(instant, ZoneId.of("+8"))));

计算结果如下:

不同时间类的相差时间:0
不同时间类的相差时间:0
不同时间类的相差时间:13

ChronoUnit.between计算结果差异原因:
OffsetDateTime、ZonedDateTime在计算差值的时候,会将两个类的偏移量和时区通过OffsetDateTime.withOffsetSameInstant(ZoneOffset offset)ZonedDateTime.withZoneSameInstant(ZoneId zone)方法转为相同的偏移量和时区,而后计算它们在相同时间线上的时间差值。而LocalDateTime则不然,由于它不具备任何时区和偏移量的性质,导致它就是一个固定的模糊时间(无法用于形容任何时间点上的时间),它的计算也只是比较两个时间的差值。

不同时间类的between计算:

long d1 = ChronoUnit.DAYS.between(LocalDateTime.now(), ZonedDateTime.now());
long d2 = ChronoUnit.DAYS.between(OffsetDateTime.now(), ZonedDateTime.now());
long d3 = ChronoUnit.DAYS.between(ZonedDateTime.now(), OffsetDateTime.now());
long d4 = ChronoUnit.DAYS.between(ZonedDateTime.now(), LocalDateTime.now()); // 抛出异常

在Temporal实现类中的运用

ChronoUnit在源码里的其它时间类中用处不小,一般用在如下方法中:

  1. 当前时间类是否支持这个时间单位Temporal.isSupported(TemporalUnit unit),如果不支持的话,那么执行下面几个方法都会报错。
  2. 时间类增加N个单位的时间Temporal.plus(long amountToAdd, TemporalUnit unit)
  3. 时间类减少N个单位的时间Temporal.minus(long amountToSubtract, TemporalUnit unit)
  4. 比较两个时间相差多少个时间单位Temporal.until(Temporal endExclusive, TemporalUnit unit)
LocalDate localDate = LocalDate.parse("2023-03-21");
System.out.println(localDate.isSupported(ChronoUnit.DAYS) && localDate.isSupported(ChronoUnit.MONTHS) 
	&& localDate.isSupported(ChronoUnit.YEARS)); // true
System.out.println(localDate.plus(10, ChronoUnit.DAYS)); // 2023-03-31
System.out.println(localDate.minus(1, ChronoUnit.MONTHS)); // 2023-02-21
System.out.println(localDate.until(localDate.minus(10, ChronoUnit.YEARS), ChronoUnit.YEARS)); // -10

B .ChronoField

ChronoUnit表示的是时间单位,那么ChronoField表示什么呢?

你可以把它理解为更易于人类查看和使用的友好时间字段

我们可以通过日历知道当前是今年的几月几号星期几,通过时钟知道当前是多少时分秒,通过太阳来判断一天中的上午和下午,那么Java时间类要怎么知道并将其转换表达给我们呢?在Java8中可以通过ChronoField时间字段类实现。


通过这个枚举,我们可以知道当前时钟的秒SECOND_OF_MINUTE、分MINUTE_OF_HOUR、小时HOUR_OF_DAY,一天什么时间处于上午或下午AMPM_OF_DAY,一年中当前时间是几月MONTH_OF_YEAR几号DAY_OF_MONTH星期几DAY_OF_WEEK。java通过取模求余等操作计算时间戳处于某个ChronoField枚举的哪一个时间范围内,从而计算出更直观的时间表现形式。

ChronoField部分枚举

//……
SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second"),
MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute"),
HOUR_OF_DAY("HourOfDay", HOURS, DAYS, ValueRange.of(0, 23), "hour"),
DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday"),
DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day"),
MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month"),
// ……

简单提一下ValueRange.of(long minSmallest, long minLargest, long maxSmallest, long maxLargest)的四个参数:

  • minSmallest minLargest:这两个值是限定最小值范围的,表示的是最小值处于这个区间内[minSmallest, minLargest] 。
  • maxSmallest maxLargest:这两个值是限定最大值范围,表示的是最大值处于这个区间内[maxSmallest, maxLargest]。如上面的月份ValueRange.of(1, 28, 31),月份的最大值就位于[28, 31]这个区间内。

获取时间字段值
通过getFrom方法可以获取指定时间的某个时间字段,它实际调用的是TemporalAccessor.get或TemporalAccessor.getLong方法。

LocalDateTime now = LocalDateTime.now();
ChronoField.DAY_OF_YEAR.getFrom(now);
ChronoField.DAY_OF_MONTH.getFrom(now);
ChronoField.DAY_OF_WEEK.getFrom(now);

在TemporalAccessor实现类中的运用

ChronoField是TemporalField的实现类,广泛用于各个时间类中。一般ChronoField有如下用途:

  1. 判断当前时间类是否支持这个时间字段TemporalAccessor.isSupported(TemporalField field),如果不支持的话,那么执行参数中带TemporalField的方法都会报错。
  2. 获取当前时间类指定时间字段的值范围TemporalAccessor.range(TemporalField field)
  3. 获取当前时间类指定时间字段的值,精度为intTemporalAccessor.get(TemporalField field)
  4. 获取当前时间类指定时间字段的值,精度为longTemporalAccessor.getLong(TemporalField field)

使用起来都比较简单,需要注意:

  • get方法是获取now这个时间点的某个时间字段的,譬如是几点钟
  • range则是获取处于now这个时间点下的某个时间字段的范围,譬如这个时间点所在的月有多少天。
LocalDateTime now = LocalDateTime.now(); // 2023-04-21T18:34:57.537
System.out.println(now.isSupported(ChronoField.AMPM_OF_DAY) && now.isSupported(ChronoField.DAY_OF_MONTH)); // true
// 0代表am 1代表pm
System.out.println(now.get(ChronoField.AMPM_OF_DAY)); // 1
System.out.println(now.getLong(ChronoField.MILLI_OF_SECOND)); // 537
System.out.println(now.range(ChronoField.DAY_OF_MONTH)); // 1-30 月份范围是在1——28~31,4月只有30天,所以这里是1-30

C .Duration

Duration:代表时间的持续的长度,也就是时长,存储时间字段为seconds)和毫秒nanoseconds),在Duration中,1秒、1分30秒、2天10小时表示的都是时长。
Duration实例支持修改其单位和具体长度,它的所有修改操作都同String一样,会生产新的对象而非修改原对象,所以它是线程安全的。

时长计算

Instant now = Instant.now();
Duration days = Duration.ofDays(1);
System.out.printf("时间长度:%s,增加后:%s,倍数计算:%s \n", days, 
	days.plus(Duration.ofHours(6)), days.multipliedBy(10));
// 时间长度:PT24H,增加后:PT30H,倍数计算:PT240H

时间计算
Duration通过addTosubtractFrom来增加或减少时长,它实际调用的是Temporal.plus和Temporal.minus方法。

System.out.printf("时间加减:%s,增加后:%s,减少后:%s \n", now, days.addTo(now), days.subtractFrom(now));

// 时间加减:2023-03-10T09:44:20.420Z,增加后:2023-03-11T09:44:20.420Z,减少后:2023-03-09T09:44:20.420Z 

在TemporalAccessor实现类中的运用

Duration是TemporalAmount的实现类。一般Duration有如下用途:

  1. 时间类增加N个时间长度Temporal.plus(TemporalAmount amount)
  2. 时间类减少N个时间长度Temporal.minus(TemporalAmount amount)

Duration的使用与ChronoUnit有所不同,Duration是支持计算的时长,所以它不拘泥于某个具体的时间单位,你可以在一次时间加减中,添加任意的年月日时分秒日期

Instant now = Instant.now(); // 2023-04-23T03:53:40.525Z
Duration plus = Duration.ofDays(10).plus(Duration.ofHours(10)).plus(Duration.ofMinutes(10));
System.out.println(plus); // PT250H10M
System.out.println(now.plus(plus)); // 2023-05-03T14:03:40.525Z



3. 时间点:Instant

Instant:该类表示的是UTC时间线上的某个瞬时时间点的纳秒值,也因此它能很容易的转为各种时间类。

  • 同除了时钟类外的其它Temporal实现类一样,表示的是一个静态时间实例。
  • Instant实现了TemporalTemporalAccessor接口的所有方法,方法描述和使用见ChronoUnit、ChronoField、Duration。

主要属性

private final long seconds; // 秒
private final int nanos; // 纳秒

创建实例
Instant无法通过构造函数初始化,可以通过如下方式创建实例(后续的时间类创建方式大同小异,后面的类省略):

Instant EPOCH = new Instant(0, 0); // EPOCH为Instant内部属性,表示时间1970-01-01 00:00:00
Instant instant = Instant.now();
Instant now = Instant.now(Clock.systemUTC());
Instant ofEpochMilli = Instant.ofEpochMilli(System.currentTimeMillis());
Instant parse = Instant.parse("2023-04-14T08:22:28.987Z");

时间对比
时间是用秒seconds和毫秒nanos存储,谁的值小谁就越早,反之越晚。其它时间类的比较也类似。

boolean after = instant.isAfter(minus); // true
boolean before = instant.isBefore(plus); // true



4. 本地时间:LocalTime、LocalDate、LocalDateTime

基于ISO-8601日历系统,它不属于UTC时间线上的任意一个时间点,仅仅表示一个含义较为模糊的本地时间。
前面多次提到它表述的是模糊时间,是因为它由Instant.getEpochSecond()+ZoneOffset.getTotalSeconds()转化而来,且转化后并没有保存额外的偏移量时间,导致它无法直接还原为UTC时间点。通俗来讲,给你一个LocalDateTime且不告诉你这个地区的时区或偏移量,你就不可能知道这个时间描述的是哪个时区或地点的时间。在国际化项目中一般不推荐使用本地时间,如果确实需要使用本地时间的话,需要确保你知道转换成这个本地时间的时差或时区值。

  • LocalDate:存储年月日数据,如2023-04-19。
  • LocalTime:存储时分秒纳秒数据,如13:45:30.123456789。
  • LocalDateTime:存储LocalTime、LocalDate数据,它集成了前两个类,后文方法都是以LocalDateTime为示例。
  • 这三个类都实现了TemporalTemporalAccessor接口的所有方法,方法描述和使用见ChronoUnit、ChronoField、Duration。

主要属性

// LocalTime
private final byte hour;
private final byte minute;
private final byte second;
private final int nano;
// LocalDate
private final int year;
private final short month;
private final short day;
// LocalDateTime
private final LocalDate date;
private final LocalTime time;

三者转换

// 转LocalDateTime
LocalDateTime of = LocalDateTime.of(localDate, localTime);
localDate.atTime(localTime);
localTime.atDate(localDate);
// 转LocalDate、LocalTime
of.toLocalDate();
of.toLocalTime();

获取时间字段
由于LocalDate、LocalTime中就保存时间的年 月 日 周 时 分 秒 纳秒属性,所以可以轻易拿到它们。根据这七个基础属性外,还可以延伸出其它内容:获取当前是星期几、几月几号。

int year = now.getYear();
int monthValue = now.getMonthValue();
Month month = now.getMonth();
DayOfWeek dayOfWeek = now.getDayOfWeek();
int dayOfMonth = now.getDayOfMonth();

时间加减
LocalDateTime对时间的加减方法进行了扩展,它支持通过方法来直接加减年 月 日 周 时 分 秒 纳秒,这些方法所产生的日期也同样是一个新的实例(日期不变时不会生成新实例)。OffsetDateTime和ZonedDateTime的时间加减同名方法正是基于LocalDateTime的。

// 不推荐使用这种方式来多次操作改动一个LocalDateTime对象,这种方式会频繁创建实例。
LocalDateTime plusAndMinus = now.plusYears(1).plusMonths(12).plusWeeks(1).minusDays(40)
	.minusHours(1).minusMinutes(1).plusSeconds(120).plusNanos(10);

局部时间调整
与上面的时间加减不同,这里是将LocalDate、LocalTime中的某个属性修改为某个值,并且根据变动结果生成一个新的实例对象:

LocalDateTime withLocal = now.withYear(2022).withMonth(12).withDayOfMonth(23)
	.withHour(10).withMinute(55).withSecond(12).withNano(123456);



5. 偏移量时间:OffsetTime、OffsetDateTime

基于ISO-8601日历系统,表示的是某个UTC偏移量下的瞬时时间点。
OffsetDateTime用法与LocalDateTime大致相当,可以理解为如下内容:

  • OffsetDateTime = ZoneOffset + LocalDateTime
  • OffsetTime= ZoneOffset + LocalTime
  • 这两者都实现了TemporalTemporalAccessor接口的所有方法,方法描述和使用见ChronoUnit、ChronoField、Duration。

主要属性

// OffsetTime
private final LocalTime time;
private final ZoneOffset offset;
// OffsetDateTime
private final LocalDateTime dateTime;
private final ZoneOffset offset;

调整偏移量

  1. withOffsetSameLocal:仅调整偏移量。
OffsetDateTime of = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(8));
System.out.println(of); // 2023-04-23T17:32:09.640+08:00
System.out.println(of.withOffsetSameLocal(ZoneOffset.ofHours(-5))); // 2023-04-23T17:32:09.640-05:00
  1. withOffsetSameInstant:调整偏移量时同时调整本地时间至该偏移量处。
System.out.println(of.withOffsetSameInstant(ZoneOffset.ofHours(8))); // 2023-04-23T17:33:31.002+08:00
System.out.println(of.withOffsetSameInstant(ZoneOffset.ofHours(-5))); // 2023-04-23T04:33:31.002-05:00



6. 时区时间:ZonedDateTime

基于ISO-8601日历系统,表示的是某个时区的瞬时时间点。
这个类涵盖了所有日期和时间的属性:存储精度为毫秒、有本地日期时间、时区信息和偏移量。所以它是java8time包中功能最广的类,一般也是通过该类作为中间类进行时间类的转换。
ZonedDateTime用法与LocalDateTime也差不多:

  • ZonedDateTime= ZoneId + ZoneOffset + LocalDateTime
  • ZonedDateTime实现了TemporalTemporalAccessor接口的所有方法,方法描述和使用见ChronoUnit、ChronoField、Duration。

主要属性

private final LocalDateTime dateTime;
private final ZoneOffset offset;
private final ZoneId zone;

创建实例

ZonedDateTime zonedDateTime = ZonedDateTime.now();
ZonedDateTime.now(ZoneId.systemDefault());
ZonedDateTime.from(OffsetDateTime.now()); // 从有时区属性的时间转换
ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
ZonedDateTime.of(LocalDateTime.now(), ZoneId.systemDefault());
ZonedDateTime.ofStrict(LocalDateTime.now(), ZoneOffset.ofHours(8), ZoneId.of("Asia/Shanghai")); // 偏移量和时区必须匹配
ZonedDateTime.parse("2023-04-14T08:22:28.987+08:00");

调整时区

  1. withZoneSameLocal:仅调整时区。
OffsetDateTime of = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(8));
System.out.println(of); // 2023-04-23T17:32:09.640+08:00
System.out.println(of.withOffsetSameLocal(ZoneOffset.ofHours(-5))); // 2023-04-23T17:32:09.640-05:00
  1. withZoneSameInstant:调整时区时同时调整本地时间为该时区的区时。
System.out.println(of.withOffsetSameInstant(ZoneOffset.ofHours(8))); // 2023-04-23T17:33:31.002+08:00
System.out.println(of.withOffsetSameInstant(ZoneOffset.ofHours(-5))); // 2023-04-23T04:33:31.002-05:00



7. 动态时间(时钟):Clock——SystemClock、FixedClock、OffsetClock、TickClock

Clock时钟:与前面的几种静态时间不同,它能实时获取当前时间线的时间点,包括日期和时间。时钟类虽然有时区概念,但是它的内部不会用到时区,所有输出的时间瞬时点或时间戳都是UTC+0的时间,这个时区一般会用在实例化其它时间类。
Clock是一个抽象类,它有如下4种实现类(静态内部类,只能通过Clock的静态方法创建实例):

  • SystemClock:系统时钟,调用该时钟获取时间,将会实时返回**System.currentTimeMillis()**的值。
  • FixedClock:静态时钟,调用该时钟的时间,将会一直返回初始化该时钟的时间。这个类是静态时间可以用来测试其它时钟类。
  • OffsetClock:偏移时钟,该时钟存在一个基础时钟Clock和偏移时长Duration,每次调用该时钟的时间时,将会返回基础时钟时间±偏移时间。该时钟一般用来模拟过去或未来的时间,以便触发某种操作。
  • TickClock:滴答时钟,该时钟存在一个基础时钟Clock和间隔时长Duration,调用该时钟获取时间,将会返回已经过时间线上距离该时长的最近的时间。

实例化Clock

Clock systemClock = Clock.systemDefaultZone();
Clock.fixed(Instant.now(), ZoneId.systemDefault());
Clock.offset(systemClock, Duration.ofDays(1));
Clock.tick(systemClock, Duration.ofMinutes(10));

以Clock实例化其它时间类
你可能会注意到,前面提到的几种类的now(Clock clock)方法就是以Clock为参数的构造方法,并且无参方法now()都是通过Clock.systemUTC()now(Clock.systemDefaultZone())来获取当前时间戳和本地默认时区的:

Instant instant = Instant.now(Clock.systemUTC());
LocalDateTime localDateTime = LocalDateTime.now(Clock.systemUTC());
OffsetDateTime offsetDateTime = OffsetDateTime.now(Clock.systemUTC());
ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemDefaultZone());

使用时钟类
Clock类有两个常用方法,Instant instant()获取当前的瞬时时间点、long millis()获取当前的时间戳,方法的使用还是比较简单的,这里就主要说一下TickClock。

Clock clock = Clock.fixed(Instant.parse("2023-05-04T01:20:20Z"), ZoneId.systemDefault());
Clock tick = Clock.tick(clock, Duration.ofMinutes(3));
Clock.tickMinutes(ZoneId.systemDefault());
Clock.tickSeconds(ZoneId.systemDefault());
System.out.println(clock.instant()); // 2023-05-04T01:20:20Z
System.out.println(tick.instant()); // 2023-05-04T01:18:00Z

这里间隔时长设置为3分钟,从0分钟开始,每3分钟会滴答一次,已过时间中离20分钟最近的一次滴答是18分钟。

这里将间隔时长设置为0-59分钟内的任意时间,这个滴答时钟运行都是正常的,奇怪的是设置为61时,这里的值就变为了2023-05-04T00:40:00Z。源码中文档的表述是:“Obtains a clock that returns instants from the specified clock truncated to the nearest occurrence of the specified duration”,翻译过来就是:获得一个时钟,该时钟返回从指定时钟截断到最近出现的指定时长的实例。这个时钟在源码中的计算方式为:millis - Math.floorMod(millis, tickNanos / 1000_000L)。这里不知道是属于bug,还是什么。



8. 时间属性查询:TemporalQuery

函数式接口

@FunctionalInterface
public interface TemporalQuery<R> {
    R queryFrom(TemporalAccessor temporal);
}

作用
TemporalQuery的作用是查询时间实例(Instant、LocaDateTime、Year等)拥有的某个属性对象,比如获取这个时间下某个时间单位的长度、最小时间精度、时区、偏移量、本地时间等。这个查询类很有意思,由于Java8time包中的时间类都实现了Temporal和TemporalAccessor接口,使得TemporalQuery在这些类上都可以运行并获取它们的时间属性,你完全可以利用这个查询提取任何想要的数据,当然前提是这个时间类支持这个属性。不过这一块的内容比较多,限于篇幅就不多说了。

使用

Integer nano = Instant.now().query(temporal -> temporal.get(ChronoField.NANO_OF_SECOND)); // 获取时间的纳秒数:nanos
Integer milli = Instant.now().query(temporal -> temporal.get(ChronoField.MILLI_OF_SECOND)); // 获取时间的毫秒数:nanos / 1000_000
ZoneId query = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).query(TemporalQueries.zoneId()); // 获取时间的时区
LocalDate localDate = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).query(TemporalQueries.localDate()); //获取本地日期



9. 时间属性调整:TemporalAdjuster

函数式接口

@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}

作用
TemporalAdjuster主要作用是根据当前的时间类调整来目标时间类,它被所有的时间类实现,因此适用于所有的时间类实例,不过,由于每个时间类拥有的属性不同,如果给没有某个属性的时间类赋予这个属性就会抛出异常:java.time.temporal.UnsupportedTemporalTypeException。Temporal.with(TemporalAdjuster adjuster),这个方法就是基于TemporalAdjuster接口实现的时间调整。很多时间类的源码都用到了with这个功能。

使用
比如我们要将LocalDateTime的时间调整为当天的最后一刻或者变更年份:

LocalDateTime now = LocalDateTime.now();
LocalDateTime adjustInto = (LocalDateTime) LocalTime.MAX.adjustInto(now); // 2023-04-19T23:59:59.999999999
LocalDateTime adjustInto1 = (LocalDateTime) Year.of(2025).adjustInto(now); // 2025-04-19T13:46:45.908

10. 时间转换(含Date)

时间转换的规则

  1. 转Instant:去掉时区、偏移量的设置和影响,并将时间转为UTC+0的时间,以时间戳存储(纳秒+秒)。
  2. 转ZonedDateTime:转换的时间需要申明时区或偏移量,并将时间转为LocalDateTime,以LocalDateTime存储时间。
  3. 转OffsetDateTime:转换的时间需要申明偏移量,并将时间转为LocalDateTime,以LocalDateTime存储时间。
  4. 转LocalDateTime:根据偏移量或时区将UTC+0时间加减至这些区域的本地时间,以LocalDate和LocalTime存储时间。
  5. 转Date:将时间转为时间戳,以时间戳存储时间。

时间转换可以参考这里:Java8 掌握Date与Java.time转换的核心思路,轻松解决各种时间转换问题





总结

最近加班严重,花了近两个月时间才把这篇文章完善得差不多了。本来打算只对简单了解一下LocalDateTime、LocalDate等常用类,没想到看源码越看越懵,没法只有多花了很多时间把整个体系了解了一下。总的来说,java.time还有很多类都没深入过,一些内容了解得还不够深,期待后续补全吧,不过像Year,YearMonth这种简单的类就不会再写文章出来了。可能后续会单独写写TemporalQuery和TemporalAdjuster的使用方法,以及各个时间类的转换、常用时间方法等等。

如果看到结尾觉得这篇文章还有用处的话,🙏请大家多支持支持,多多点赞收藏关注,球球了(_)。





参考

java.time包源码
Clock
Time zone
Class ZoneId
How to set time zone of a java.util.Date?
What’s the difference between Instant and LocalDateTime?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/491540.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测试用例覆盖不全面的解决方法

测试用例覆盖不全面的解决方法 问题分析 在测试用例设计过程中&#xff0c;容易出现思维受限或者需求盲区&#xff0c;我们不可能完全覆盖用户使用的所有场景&#xff0c;编写测试用例的时不可能把所有的场景都能想周全&#xff0c;把所有的场景下的情况都写成测试用例去模拟、…

SLAM论文速递:SLAM—— (2023)Amos-SLAM:一种基于视觉和几何的抗动态双阶段SLAM方法—5.05(1)

论文信息 题目&#xff1a; Amos-SLAM:An Anti-Dynamics Two-stage SLAM Approach Amos-SLAM:一种基于视觉和几何的抗动态双阶段SLAM方法论文地址&#xff1a; https://arxiv.org/pdf/2302.11747.pdf发表期刊&#xff1a; Computer Science > Robotics标签 xxxx 摘要 传统…

图神经网络:在KarateClub数据集上动手实现图神经网络

文章说明&#xff1a; 1)参考资料&#xff1a;PYG官方文档。超链。 2)博主水平不高&#xff0c;如有错误还望批评指正。 3)我在百度网盘上传了这篇文章的jupyter notebook。超链。提取码8888。 文章目录 文献阅读&#xff1a;代码实操&#xff1a; 文献阅读&#xff1a; 参考文…

基于ArkUI框架开发——图片模糊处理的实现

原文&#xff1a;基于ArkUI框架开发——图片模糊处理的实现&#xff0c;点击链接查看更多技术内容。 现在市面上有很多APP&#xff0c;都或多或少对图片有模糊上的设计&#xff0c;所以&#xff0c;图片模糊效果到底怎么实现的呢&#xff1f; 首先&#xff0c;我们来了解下模糊…

面向万物智联的应用框架的思考和探索(中)

原文&#xff1a;面向万物智联的应用框架的思考和探索&#xff08;中&#xff09;&#xff0c;点击链接查看更多技术内容。 应用框架&#xff0c;是操作系统连接开发者生态&#xff0c;实现用户体验的关键基础设施。其中&#xff0c;开发效率和运行体验是永恒的诉求&#xff0c…

【路径规划】基于麻雀搜索算法的栅格法路径规划 机器人路径规划【Matlab代码#21】

文章目录 1. 原始SSA算法2. 机器人路径规划环境创建3. 路径规划模型建立4. 部分代码展示5. 仿真结果展示6. 资源获取方式 1. 原始SSA算法 2. 机器人路径规划环境创建 对机器人工作空间的进行环境建模是机器人路径规划研究的重要前提。栅格法为环境建模提供了一种简洁有效的方法…

法规标准-GB/T 33577标准解读(2017版)

GB/T 33577是做什么的&#xff1f; GB/T 33577全名为智能交通系统-前方车辆碰撞预警系统(FVCWS)-性能要求和测试步骤&#xff0c;其中主要是对FVCWS系统的功能要求、性能要求及测试步骤进行了介绍。由于ISO 15623-2013内容与本法规内容相同&#xff0c;故可沿用此法规内容 FV…

【谷粒商城之消息队列RabbitMQ】

本笔记内容为尚硅谷谷粒商城消息队列RabbitMQ部分 目录 一、概述 二、简介 三、Docker安装RabbitMQ 四、Springboot整合RabbitMQ 1、引入spring-boot-starter-amqp 2、application.yml配置 3、测试RabbitMQ 1. AmqpAdmin-管理组件 2.RabbitTemplate-消息发送处理组件…

Wikidata实操

1. Wikidata 简介 Wikidata 即维基数据&#xff0c;是维基百科的一个项目。个项目已经在维基百科德国分部开始进行&#xff0c;项目完成之后&#xff0c;将会交给维基百科基金会进行操作和维护。&#xff08;具体百度即可&#xff0c;不多赘述&#xff09; 官网&#xff1a;htt…

操作系统考试复习—第三章 优先级倒置 死锁问题

当前OS广泛采用优先级调度算法和抢占方式&#xff0c;然而在系统中存在着影响进程运行的资源从而可能产生"优先级倒置"现象 具体解释为&#xff1a;在原本的调度算法设计中&#xff0c;高优先级进程可以抢占低优先级的CPU资源&#xff0c;先执行高优先级任务。但是存…

【STM32】在使用STM32Cube.IDE时更改时钟频率后代码跳进异常中断

目录 1、前言2、问题与复现办法3、解决的问题的过程 1、前言 这是在项目中无意发现的问题&#xff0c;其实有同样更复杂的工程可以运行&#xff0c;但是后来发现新建一个简单工程反而运行不了了&#xff0c;但是同样更复杂的工程可以运行说明本来同事原来已经不知道在哪里找到…

Vmware安装Kali

需要准备两个东西&#xff0c;kali镜像和VMware软件 下载kali iso 下载界面有三个可选择的 install是安装版&#xff0c;安装使用&#xff1b; Live版可以直接启动运行&#xff1b; netinstaller是网络安装&#xff0c;需要从网络上下载&#xff0c;文件本身只有引导作用&…

Idea Jrebel 报错:Cannot reactivate, offline seat in use ...

Idea Jrebel 报错&#xff1a;Cannot reactivate, offline seat in use ... 一、问题描述 在使用idea Jrebel续期的时候&#xff0c;修改idea激活服务器地址时&#xff0c;遇到报错&#xff1a;Cannot reactivate, offline seat in use. Click Work online in JRebel configura…

基于aspnet个人博客网站dzkf6606程序

系统使用Visual studio.net2010作为系统开发环境&#xff0c;并采用ASP.NET技术&#xff0c;使用C#语言&#xff0c;以SQL Server为后台数据库。 1&#xff0e;系统登录&#xff1a;系统登录是用户访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括用户名、密码和…

探索卡尔曼滤波在位姿估计中的魅力:无人机与自动驾驶的关键技术揭秘

摘要&#xff1a;在本博客中&#xff0c;我们将探讨卡尔曼滤波在位姿估计领域的应用&#xff0c;特别是在无人机和自动驾驶场景中的重要性。我们将详细介绍卡尔曼滤波的原理、优势及其在无人机、自动驾驶等实际案例中的应用。此外&#xff0c;我们还将关注卡尔曼滤波在其他领域…

【服务器数据恢复】同友存储上的虚拟机数据恢复案例

服务器数据恢复环境&#xff1a; 同友存储&#xff0c;底层由数块物理硬盘组建的raid5磁盘阵列&#xff0c;存储池划分若干lun&#xff0c;每个lun下有数台虚拟机。 服务器故障&#xff1a; 未知原因导致存储崩溃&#xff0c;无法启动&#xff0c;虚拟机全部丢失&#xff0c;其…

linux中基础开发工具的使用

1.linux中的软件包管理器 1.1什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序.但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上, 通过包管理器可以很…

软件管理员密码的作用 如何设置软件管理员密码?

在使用夏冰加密软件的过程中&#xff0c;很多软件都是可以设置软件管理员密码的。那么你知道管理员密码有什么用吗&#xff1f;又该如何设置软件管理员密码呢&#xff1f;下面我们来了解一下吧。 软件管理员密码是什么意思&#xff1f; 软件管理员密码就是软件的密码&#xff…

毕业5年,技术越来越好,混的却越来越差...

别人都是越来越好&#xff0c;而我是越来越差&#xff01; 17年&#xff0c;从一个普通的本科毕业&#xff0c;那个时候的我&#xff0c;很迷茫&#xff0c;简历上的求职岗位都不知道写什么&#xff0c;因为家里是农村的&#xff0c;朴实的父母也帮不上什么忙&#xff0c;关于…

KDBR-IV变压器空负载短路损耗测试仪

一、产品概述 本产品是我公司针对不良电力用户偷逃基本电费、私自增容问题而研发设计的仪器&#xff0c;用于变压器容量、空载、负载等特性参数测量的高精密仪器。本仪器为多功能测量仪器&#xff0c;相当于往常两种测试仪器&#xff1a;即变压器容量测试仪变压器特性参数测试仪…