8个小时是多少毫秒,一小时等于多少毫秒多少分钟

首页 > 教育 > 作者:YD1662024-04-04 21:12:03

Java平台时间系统的设计方案


几乎任何事物都会有“起点”这样的概念,比如人生的起点就是我们出生的那一刻。
Java平台时间系统的起点就是世界时间(UTC)1970年1月1日凌晨零点零分零秒。用专业的写法是“1970-01-01T00:00:00Z”,最后的大写字母“Z”指的是0时区的意思。
在Java平台时间系统里,这个起点用单词“epoch”表示,就是“新纪元、新时代”的意思。
一般来说如果一个事物有起点,那么通常该事物也会有一个叫做“偏移量”的概念。人一出生,就有了年龄,这就是个偏移量,一旦工作,就有了工龄,这也是个偏移量。
Java平台时间系统就是用偏移量来表示时间的,表面上看起来有年月日时分秒,其实底层就是一个long类型的整数,就是自起点开始经过的毫秒数。
这一点可以很容易说明:

1Datenow=newDate(); 2System.out.println(now); 3System.out.println(now.getTime()); 4System.out.println(System.currentTimeMillis());

输出结果如下:

1FriMar0613:52:41CST2020 21583473961398 31583473961398

可能有的读者会问,那如何表示1970年以前的时间呢?
当然也是采用偏移量啊,只不过这个偏移量是个负的罢了,估计很多人都没见过负的毫秒数,那就来看看吧。
那就把年份设置成1969年试试吧:

1Calendarbefore=Calendar.getInstance(); 2before.set(Calendar.YEAR,1969); 3System.out.println(before.getTimeInMillis());

输出结果如下:

1-25985142623

看到了吧,就是一个负的整数。
偏移量和时区有关吗?


有一个更有意思的问题浮现了出来,全球有24个时区,那这个偏移量和时区有关吗?
如果无关,则所有时区的偏移量都一样,那时间也应该都一样啊,可事实是都不一样。
如果有关,则所有时区的偏移量都不一样,那就有24个偏移量,感觉似乎也不太对。
孰对孰错,试试便知,那就干起来吧。
获取上海、伦敦、芝加哥三个地方(所在时区)的时间:

1Calendarcn=Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai")); 2Calendaren=Calendar.getInstance(TimeZone.getTimeZone("Europe/London")); 3Calendarus=Calendar.getInstance(TimeZone.getTimeZone("America/Chicago"));

打印出来看看:

1System.out.println(getDate(cn)); 2System.out.println(getDate(en)); 3System.out.println(getDate(us));

输出结果如下:

12020-2-613:54:17 22020-2-605:54:17 32020-2-523:54:17

可以看到,时间是正确的。
再把它们的毫秒数打印出来看看:

1System.out.println(cn.getTimeInMillis()); 2System.out.println(en.getTimeInMillis()); 3System.out.println(us.getTimeInMillis());

输出结果如下:

11583474057356 21583474057356 31583474057356

结论是:偏移量都一样,和时区是无关的。那日期为啥是不同的呢?这就是时区的功劳了。
再用Java8的时间API来验证一遍。
同样创建三个地方的当地时间:

1LocalDateTimecnldt=LocalDateTime.now(ZoneId.of("Asia/Shanghai")); 2LocalDateTimeenldt=LocalDateTime.now(ZoneId.of("Europe/London")); 3LocalDateTimeusldt=LocalDateTime.now(ZoneId.of("America/Chicago"));

打印出来看看:

1System.out.println(cnldt); 2System.out.println(enldt); 3System.out.println(usldt);

输出结果如下:

12020-03-06T13:54:17.370 22020-03-06T05:54:17.372 32020-03-05T23:54:17.372

同样时间是正确的。然后再打印出秒数:

1System.out.println(cnldt.toEpochSecond(ZoneOffset.of(" 8"))); 2System.out.println(enldt.toEpochSecond(ZoneOffset.of("Z"))); 3System.out.println(usldt.toEpochSecond(ZoneOffset.of("-6")));

输出结果如下:

11583474057 21583474057 31583474057

可以看到,它们经过的秒数是一样的。
备注:中国时间东8时区,英国时间0时区,美国时间西6时区。
这里主要想说的是,在之前的Java中是使用毫秒来衡量偏移量的,自Java8开始就使用秒和纳秒来衡量偏移量,纳秒是指最后那一个不完整的1秒。
纳秒是10的9次方分之一秒,比毫秒精确了100万倍,所有Java8的时间系统较之以前更精确了,当然是理论上的啦。
时区是颇为复杂的


大家不要小看时区,它绝对比我们认为的“不就是差几个小时嘛”要复杂些。
时区在划分时主要考虑当地的居民生活和上班情况,所以时区是和地区有密切关联的。因此时区的名字也都以地理位置来标识的。
具体格式是:大洲或大洋名称/城市或著名地点或方位名称,如Asia/Shanghai,Europe/London,America/Chicago。
当然了也有一些不规则的,如MST7MDT、US/Hawaii、SystemV/CST6、Zulu、NZ-CHAT,也许是历史遗留问题或其它原因吧,不去深究了。
在Java8中时区用ZoneId表示,意思是一个地区的ID,ID就是标识嘛,所以我觉得ZoneId更应该理解为一个地区而非一个时区。可能有人会觉得为啥不用TimeZone来表示时区呢?遗憾的是在JDK1.1的时候这个名字就被用了,而且表示的就是时区。
时区可以按如下的方式创建:

1ZoneId.of("Asia/Shanghai"); 2ZoneId.of("Europe/London"); 3ZoneId.of("America/Chicago");

采用地理位置的方式来命名时区是比较生活化的,貌似一下子很难和时间计算联系在一起。
其实时区的本质不就是距离标准(0时区)时间的偏移量嘛,所以时区就是基于起点(0时区)的偏移量。这样是不是仿佛一下具有了计算性。
这个偏移量用ZoneOffset表示,0时区偏移量是0,可以表示为:

1ZoneOffset.of(" 0"); 2ZoneOffset.of("-0");

注意,虽然“ 0”和“-0”在算术上是相等的,但这里是时区格式的字符串,所以“ ”和“-”是不能省略的。
0时区是时区的起点,比较特殊,因此还专门有一个字母来表示,就是大写字母“Z”,因此可以这样:

1ZoneOffset.of("Z");

相信大家都知道了“ ”和“-”的意思了,那我就再赘述一遍吧。
加号( )表示0时区东边的时区,如中国的东8时区,可以表示为:

1ZoneOffset.of(" 8");

减号(-)表示0时区西边的时区,如美国的西6时区,可以表示为:

1ZoneOffset.of("-6");

上面的“ 8”表示比标准时间早8个小时,“-6”表示比标准时间晚6个小时。
既然整小时都被支持了,那分钟也应该被支持的啊,没错,分钟也是支持的,像这样:

1ZoneOffset.of(" 01:30"); 2ZoneOffset.of("-02:20");

" 01:30"表示比标准时间早1小时30分,"-02:20"表示比标准时间晚2小时20分。
既然分钟都支持了,那干脆连秒也支持了吧,是的,秒也是支持的,像这样:

1ZoneOffset.of(" 03:40:50"); 2ZoneOffset.of("-04:50:30");

含义和上面一样,只是多了个秒而已。
需要说明的是,Java8支持的时间偏移量范围是从“-18:00”到“ 18:00”,横跨36个小时,远超过24个时区。
理论上讲,ZoneId和ZoneOffset应该具有某种联系,因为它们的目的是一样的,只是从不同的角度来描述,都表示一个地方的当地时间距离标准时间的差值。
实际上ZoneOffset继承了ZoneId,所以“Asia/Shanghai”和“ 8”其实是一样的,表示上海的当地时间比标准时间早8个小时,很简单吧,要是都这么简单那就好了。
曾经混乱的地理时区及其转换


世界时间标准是一步步建立起来的,那么在标准建立之前,一定会有相对混乱的地方。一段时间用这个时区,一段时间又改为别的时区,而且还有可能反复。
空口无凭?那就上证据,从爱国主义角度出发,先看中国的时区情况:

1[Overlapat1901-01-01T00:00 08:05:43to 08:00], 2[Gapat1940-06-01T00:00 08:00to 09:00], 3[Overlapat1940-10-13T00:00 09:00to 08:00], 4[Gapat1941-03-15T00:00 08:00to 09:00], 5[Overlapat1941-11-02T00:00 09:00to 08:00], 6[Gapat1942-01-31T00:00 08:00to 09:00], 7[Overlapat1945-09-02T00:00 09:00to 08:00], 8[Gapat1946-05-15T00:00 08:00to 09:00], 9[Overlapat1946-10-01T00:00 09:00to 08:00], 10[Gapat1947-04-15T00:00 08:00to 09:00], 11[Overlapat1947-11-01T00:00 09:00to 08:00], 12[Gapat1948-05-01T00:00 08:00to 09:00], 13[Overlapat1948-10-01T00:00 09:00to 08:00], 14[Gapat1949-05-01T00:00 08:00to 09:00], 15[Overlapat1949-05-28T00:00 09:00to 08:00], 16[Gapat1986-05-04T02:00 08:00to 09:00], 17[Overlapat1986-09-14T02:00 09:00to 08:00], 18[Gapat1987-04-12T02:00 08:00to 09:00], 19[Overlapat1987-09-13T02:00 09:00to 08:00], 20[Gapat1988-04-17T02:00 08:00to 09:00], 21[Overlapat1988-09-11T02:00 09:00to 08:00], 22[Gapat1989-04-16T02:00 08:00to 09:00], 23[Overlapat1989-09-17T02:00 09:00to 08:00], 24[Gapat1990-04-15T02:00 08:00to 09:00], 25[Overlapat1990-09-16T02:00 09:00to 08:00], 26[Gapat1991-04-14T02:00 08:00to 09:00], 27[Overlapat1991-09-15T02:00 09:00to 08:00]

我们来解释下,这些都是什么意思。“Overlap”是重叠的意思,比如我把时间从9点调整到8点,那么从8点到9点这1个小时会再走一遍,这就是时间重叠。
“Gap”是裂缝的意思,比如我把时间从9点调整到10点,那么从9点到10点这1个小时就不用走了,相当于直接蹦过去了,这就是时间裂缝。
再进一步说,有重叠的说明时间是往回(后)调了,有裂缝的说明时间是往早(前)调了。
所以,“1901-01-01T00:00 08:05:43 to 08:00”表达的意思是,中国在“1901-01-01T00:00”的时刻,把我们的时间偏移量从“ 08:05:43”调整到“ 08:00”,就是往回调整了5分43秒。所以是“Overlap”,即重叠。


中国后续的全部都是在东8时区和东9时区之间的调整,最后一次是在“1991年09月15日凌晨02点00分”从“ 09:00(东9区)”到“ 08:00(东8区)”,自此直到现在,中国都是使用的东8区时间。
这些都是已经发生过的历史,Java时间系统在设计时不可能不管它的,是要支持的,所以我说时区还是有点复杂的。哈哈,历史的包袱还是有点沉重的。
美国啊,就更复杂了,中国好歹只有北京时间,美国的时间就不统一了,有东部时间、中部时间、山地时间、太平洋时间、阿拉斯加时间、夏威夷时间。
而且它的时区变换也是异常多的,大概将近200次,这里只展示一部分,这里展示的是芝加哥的当地时间,属于美国中部时间:

1[Overlapat1883-11-18T12:09:24-05:50:36to-06:00], 2 3[Gapat1918-03-31T02:00-06:00to-05:00], 4[Overlapat1918-10-27T02:00-05:00to-06:00], 5[Gapat1919-03-30T02:00-06:00to-05:00], 6[Overlapat1919-10-26T02:00-05:00to-06:00], 7[Gapat1920-06-13T02:00-06:00to-05:00], 8[Overlapat1920-10-31T02:00-05:00to-06:00], 9[Gapat1921-03-27T02:00-06:00to-05:00], 10[Overlapat1921-10-30T02:00-05:00to-06:00], 11[Gapat1922-04-30T02:00-06:00to-05:00], 12[Overlapat1922-09-24T02:00-05:00to-06:00], 13 14。。。。。。。。。。 15 16[Gapat2005-04-03T02:00-06:00to-05:00], 17[Overlapat2005-10-30T02:00-05:00to-06:00], 18[Gapat2006-04-02T02:00-06:00to-05:00], 19[Overlapat2006-10-29T02:00-05:00to-06:00], 20[Gapat2007-03-11T02:00-06:00to-05:00], 21[Overlapat2007-11-04T02:00-05:00to-06:00], 22[Gapat2008-03-09T02:00-06:00to-05:00], 23[Overlapat2008-11-02T02:00-05:00to-06:00]

可以看到首次调整是在“1883-11-18T12:09:24”的时候把时间偏移量从“-05:50:36”调整到了“-06:00”,等于回调了9分24秒,所以是“Overlap”,即重叠。


仔细看的话会发现后续的调整都集中到每年的3/4/6月份和9/10/11月份,而且都是在西5区和西6区之间的变换。
相信大家都已经猜出来了,美国是分“冬令时(正常时间)”和“夏令时”的国家。所以每年都会调整2次,那为什么上面的最后一次调整是2008年呢?后续的调整呢?
上面那些都是历史了,所以需要都记录下来,其实这个调整是有规律的,因此只需要记录下规律,而不需要记录每次变更的日志了。
美国芝加哥(中部时间)当地的冬令时和夏令时的变换规律是:

1[Gap-06:00to-05:00,SUNDAYonorafterMARCH8at02:00WALL,standardoffset-06:00], 2[Overlap-05:00to-06:00,SUNDAYonorafterNOVEMBER1at02:00WALL,standardoffset-06:00]

冬令时到夏令时的转换是在,每年3月8日及其之后最近的一个周日凌晨2点,把时区从“-6”变到“-5”,即提前1小时,所以是“Gap”裂缝。
夏令时到冬令时的转换是在,每年11月1日及其之后最近的一个周日凌晨2点,把时区从“-5”变到“-6”,即延后1小时,所以是“Overlap”重叠。
“standard offset -06:00”的意思是,这里(当地)的标准时间偏移量是比UTC晚6个小时,为了照顾当地人们的生活和上班习惯,在夏天到来时,把时间提前1个小时。
“WALL”这个单词是墙的意思,所以“at 02:00 WALL”的意思就是在你看到墙上挂的钟表是凌晨2点的时候。是对当前正在使用(还未调整)的时间的一种指代吧。
上面那些已经记录下来的转换历史日志,是为了对过去时间的计算用的,而这个转换规则,是为了对未来的时间计算用的。
还好中国没有冬令时和夏令时的概念,中国只是改变了上下班的时间,冬天下班早些,因此中国没有转换规则,一年四季都是比UTC早8小时。
“当地时间”的计算方法


在Java时间系统里,时间就是自“时间起点”开始经过的毫秒数,这对全球24个时区都是一样的。
如果把这个毫秒数直接转化为时间,它对应的就是UTC时间,即0时区的时间,也是英国伦敦的时间。
如果某地不是位于0时区的话,那就再加上或减去当地时区对应的时间偏移量,得到的就是当地时间。
比如中国就是“毫秒数”再加上8个小时对应的毫秒数,美国中部就是”毫秒数“再减去6个小时对应的毫秒数。
不要以为这样就完事了,历史上同一个地方的时区都是比较混乱的,可能反复变换过几十次甚至上百次,那么这个地方对应的时区到底该怎么取呢?
还好,上面说了,Java时间系统已经记录下了每个地方时区变更历史日志了,这些反复的变更其实构成了一个个连续的区间。
每个区间的两端都是一个日期(时间),其实也是一个“毫秒数”。这样当我们拿到一个时间“毫秒数”后,就去和这个地方的所有变更区间两端的“毫秒数”进行比对。
确认出我们拿到的这个“毫秒数”落到了哪个区间,然后就使用这个区间对应的时区时间偏移量即可。这样所有的历史(过去的)时间就都算出来了。
那对于未来的时间呢?像美国那样的有冬令时和夏令时变换规则的,就按规则去计算。像中国这种没有变换规则的,就按历史上最后一次变换后对应的时区时间偏移量去计算。
即如果不出意外的话,中国永远是采用东8区,时间永远比UTC早8小时。
从“毫秒数”计算出具体时间


首先需要说明的是,Java8获取的还是毫秒级别的偏移量,而且和之前的方法是一样,并不是直接获取的纳秒。
证明如下图01:

8个小时是多少毫秒,一小时等于多少毫秒多少分钟(1)

后来又将毫秒转换为秒和纳秒,证明如下图02:

8个小时是多少毫秒,一小时等于多少毫秒多少分钟(2)


所以说Java8时间系统的精度并没有提升,至少在某些方面没有提升。
当毫秒被转化为秒和纳秒后,首先要加上或减去时区的时间偏移量,这个偏移量是精确到秒级的。所以不影响纳秒的数值。
然后开始计算日期和时间,日期和时间肯定要分开计算的,用秒数除以86400(每天的秒数)并取整得到的就是自1970-01-01经过的天数,这个天数可能是负的。
由于大月为31天/月,小月为30天/月,2月份为平年28天/闰年29天,所以从天数转化为年/月/日的时候也是比较繁琐的,而且正的天数是往后算,负的天数是往前算,也是不一样的。
日期这就算出来了,然后再算时间。用计算天数时剩下(不足1天)的秒数,再加上纳秒那部分,去计算出时/分/秒/纳秒,这部分的计算要相对容易些了。
这样时间(LocalTime)也计算出来了,在加上前面算出来的日期(LocalDate),就是现在的日期时间(LocalDateTime)了。
这就是JDK8里面的计算方法,如下图03:

8个小时是多少毫秒,一小时等于多少毫秒多少分钟(3)


时间的获取与跨时区转换
获取自己所在地区的当前时间,是这样子的:

1LocalDateTime.now();


Java会利用操作系统设置的地区信息。
如果要获取指定地区的当前时间,需要自己指定一个时区(地区),是这样子的:

1LocalDateTime.now(ZoneId.of("America/Chicago"));


如果知道了一个地区的时间偏移量,那就指定一个时区偏(地区)移量,也可以这样子:

1LocalDateTime.now(ZoneOffset.of("-6"));


如果要获取UTC(标准)时间,可以这样子:

1LocalDateTime.now(ZoneId.of("Europe/London"));2LocalDateTime.now(ZoneOffset.of("Z"));


因为伦敦时间就是标准时间,也是0时区时间,也是没有时区偏移量的时间,“Z”的意思就是偏移量为0。
如果在一个非常确定的情况下进行跨时区转换时间的话,是这样子的:

1ZoneOffsetTransitionzot=ZoneOffsetTransition.of(LocalDateTime.now().withNano(0),ZoneOffset.of(" 8"),ZoneOffset.of("-6"));2zot.getDateTimeBefore();3zot.getDateTimeAfter();


of方法的第一个参数是待转换的时间,第二个参数是该时间对应的偏移量,第三个参数是转换后的偏移量。
其实内部原理很简单,就是加上或减去这两个偏移量之间的差值。
由于过去很多地方都进行过时区的多次反复变更,如果想知道某个地方过去的某个时间当时所采用的时区,可以这样子:

1ZoneRulesrules=ZoneId.of("Asia/Shanghai").getRules();2LocalDateTimesomeTime=//过去的某个时间;3ZoneOffsetoffset=rules.getOffset(someTime);


就是根据地区获取到该地区的变换规则,根据规则获取过去某个时间当时的偏移量,当然这个时间也可以是未来的时间。
这在一般情况下都会得到唯一的准确的结果,但发生在日期调整的特殊时刻时就不是这样的了。
比如美国在夏天到来时会在某个周日的凌晨2点把时间往前调一个小时,就是从2点直接蹦到3点,时间偏移量就是从-6变为-5。
如果我们要找2点半对应的时间偏移量,其实是没有的。因为这个时间根本就没有出现过,是被蹦过去了。这是时间裂缝,我们等于掉到裂缝里了。
同样美国在冬天到来时会在某个周日的凌晨2点把时间往回调一个小时,就是从2点直接退到1点,时间偏移量就是从-5变为-6。
如果我们要找1点半对应的时间偏移量,其实是有2个。因为这个时间实际上出现过两次,因为1点到2点又重复走了一遍。这就是时间重复,我们等于掉到重复里了。
对于这两种情况,系统给的是调整前的时间偏移量,而且明确说明这只是个“最佳”结果而非“正确”结果,应用程序应该自己认真对待这种情况。
系统给出的这个“最佳”结果,对于过去的时间和未来的时间都是一样的,即在“临界区”的时间段内选的都是调整前的时间偏移量。
这个是使用当地的时间获取当地的时间变换规则,其实还有更麻烦的场景。像下面这个。
就是我们想知道在中国过去(或未来)的某个时间的时候,美国的芝加哥对应时间是几点?
这时候其实需要知道在中国的这个时间的时候,美国芝加哥的时间的偏移量是多少?
因为芝加哥的时间偏移量也是反复变化的,所以还需像上面那样去获取,就是这样子:

1ZoneRulesusaRules=ZoneId.of("America/Chicago").getRules();2LocalDateTimechinaTime=//中国过去的某个时间;


可是遗憾的是,我们不能用中国的当地时间去获取芝加哥对应时候的时间偏移量。因为中国的时间是按中国的偏移量算出来的哦。
那怎么办呢?方法还是有的。有一点一定要记清楚,就是在某一瞬间,虽然全球时间各不一样,但是经过的“毫秒数”却都是一样的。
所以先把中国过去的这个时间转化为“毫秒数”,或者说转化为那一瞬间,然后再用这一瞬间去获取芝加哥在这一瞬间的时间偏移量。
因为这一瞬间是全球都一样的。首先用中国的变换规则获取中国过去那个时间的偏移量,因为从时间到瞬间的变换需要知道时间偏移量。
因为不知道时间偏移量的话,我们无法确定这个时间是哪里的时间,可能是现在东8区的时间,也可能是1个小时前东9区的时间,还可能是1个小时后东7区的时间。
我去,好麻烦啊,先用中国变换规则和中国时间计算出那一瞬间吧,像这样子:

1ZoneRuleschinaRules=ZoneId.of("Asia/Shanghai").getRules();2ZoneOffsetchinaOffset=chinaRules.getOffset(chinaTime);3Instantinstant=chinaTime.toInstant(chinaOffset);


算出的这个瞬间instant是世界通用的,然后用它去计算芝加哥在这一瞬间的时间偏移量,像这样子:

1ZoneRulesusaRules=ZoneId.of("America/Chicago").getRules();2ZoneOffsetusaOffset=usaRules.getOffset(instant);


现在事情已经明朗了,待转换的时间,转换前时间偏移量,转换后时间偏移量这三者都有了,就变成一个确定的情况了。
方法和一开始用的是一样的,像这样子:

1ZoneOffsetTransitionchina2usa=ZoneOffsetTransition.of(chinaTime,chinaOffset,usaOffset);2china2usa.getDateTimeBefore();3china2usa.getDateTimeAfter();


现在终于可以说一句,时区不是颇为复杂,而是相当复杂啊。
时间系统的常用类揭秘
对系统默认时区的获取依然是依赖TimeZone这个很早期的类,如下图04:

8个小时是多少毫秒,一小时等于多少毫秒多少分钟(4)

首页 12345下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.