你应该知道的时间知识

时间是一个大家既熟悉又陌生的概念,大家每天都在用,但对于时间大家又有很多不了解的内容,比如闰秒、时区、夏令时等等。最近在做全球支付的项目,在项目中也遇到一些关于时间的问题,比如时间的保存、展示、查询等等。自己抽空查了很多资料,在此把我了解的和大家分享下,也希望对大家有帮助。

如果你觉得时间很简单,可以先看一下这个文章《你确信你了解时间吗?》,本文稍后也会说下这篇文章中提的事情。时间的处理是很复杂的,里面有很多坑,下面简单罗列几项:

  1. 闰年的规则:规则很复杂,不过好在有规则
  2. 时区的规则:一段时间内规则明确,如果考虑历史就呵呵了
  3. 夏令时的规则:跟各个地区的当地政策有关,既然是政策就不能公式化了
  4. 闰秒的规则:是随机加的,会临时通知的,我们都不知道哪天会加下一个闰秒

时间的定义

首先时间是什么呢,你会发现没有一个合适的定义,维基百科提到一个争议较小的定义为“时间是时钟量测的物理量”。那么时间是如何测量的呢,很早之前很多时间单位都是用天文概念定义的,包括。由于年是使用地球绕太阳转的周期定义,月采用月球绕地球公转周期定义,日采用地球自转周期来定义,但这几个周期都是相对独立的,各自之间的换算都不是整数,例如一年并不都是365天,回归年(太阳年)=365.242199174日,所以就有了闰年的概念,有些年份的2月会多1天,为29天。目前使用的闰年规则为: + 4的倍数是闰年。 + 100的倍数不是闰年。 + 400的倍数是闰年

2000年千年虫的问题其中一个就是因为这个闰年问题,因为2000年是闰年,但很多程序没有考虑到这个场景(另一个问题是采用2位数值来保存年而引起了处理错误)。月的概念在这就不说了,因为它已经偏离最初的定义太多了,中国的农历应该是比较接近自然定义的,但那个太复杂了,-_-

秒的定义和闰秒

下面再说下秒的定义,最开始秒的定义是太阳日(或地球自转)的1/86400,但随着近代测量越来越精确,发现这种天文秒的精度太低了,而且地球自转的周期本身的也是波动的,近代观测结果是逐渐变慢的,这样就无法提供一个准确的秒了。在1967年第13届国际计量大会上通过一项决议,重新对秒进行了定义,定义为铯-133原子基态两个超精细能级间跃迁辐射9,192,631,770周所持续的时间,也就是我们现在说的原子时。原子时的定义是近代的一个重大进步,目前原子钟的精度可以达到每2000万年才误差1秒,此精度还在不断提高中。你可能觉得时间精度和我们生活关系不大,这你就错了,我们每天赖以使用的GPS核心原理就是原子时,我们的手机是根据天空中的多个卫星发送的位置(卫星的位置)和时间信号,然后跟自己的时间通过复杂的公式,来计算出自己的当前位置。随着原子钟的时间精度提高,定位的精度从石英钟时代的14米减小到了现在的2.9米。

秒被重新定义为原子时后(目前我们所用的时间UTC也采用原子时),由于原子钟的精度很高,比原来使用的天文秒要精确很多。闰秒就是为了校正原子秒和天文秒的误差而存在的。因为天文秒是根据地球自转来确定的,但是地球自转的周期不是固定的,它本身是有波动的,这样就到这一天不再是86400秒了,可能会有些误差,现在规定如果误差达到0.9秒就需要进行调整,会在协调世界时(UTC,在时刻上和格林威治时间GMT尽量一致)的6月30日、或12月31日的23:59:59秒后再加上1秒,也就是23:59:60。目前只有增加闰秒,是因为地球自转在减慢,由于地球自转的波动是随机的,也就导致闰秒是随机公布的,谁也不知道下次是什么时候。加入随机的闰秒后,一分钟可能是61秒或60秒。因此在UTC系统的时间尺度中,秒和比秒小的单位(毫秒、微妙等)其长度是固定的,但是对于分钟和比分大的单位(小时、天、周等)的长度都是可变的。闰秒给大家带来了很多困扰,例如日本,由于其时区为UTC+09:00,所以闰秒调整会出现在上午9点,这个时候让你调表是个麻烦事,所以日本强烈建议取消闰秒。闰秒带来的另一个问题是计算机使用的时间,这里需要说一下:大家都知道时间是在计算机中是表示为一个long整数的,但是闰秒是不计算在现在的时间戳内的,也就是long整数中是没有这1秒的,这样在闰秒后操作系统时间会出现倒流,这个原因曾导致过程序崩溃,对此不同系统处理各异,Linux会让后续时间变慢以适应时间倒流,Windows则直接忽略。闰秒的另一个问题就是导致时长精度问题,如果你直接拿两个时间戳相减得到的不是真正的秒数,还需要加上这段时间发生的闰秒数。

下面是历史上所有闰秒统计:

实行年份 6.30 23:59:60 12.31 23:59:60 实行年份 6.30 23:59:60 12.31 23:59:60
1972年 +1秒 +1秒 1989年   +1秒
1973年   +1秒 1990年   +1秒
1974年   +1秒 1992年 +1秒  
1975年   +1秒 1993年 +1秒  
1976年   +1秒 1994年 +1秒  
1977年   +1秒 1995年   +1秒
1978年   +1秒 1997年 +1秒  
1979年   +1秒 1998年   +1秒
1981年 +1秒   2005年   +1秒
1982年 +1秒   2008年   +1秒
1983年 +1秒   2012年 +1秒  
1985年 +1秒   2015年 +1秒  
1987年   +1秒 2016年   +1秒

时区和夏令时

时区和夏令时这两个概念更是个政治概念了。你可以看下世界时区的划分,发现很乱,这里面很多政治因素在里面,我们需要知道的就是各个时区是相对协调世界时UTC做了调整,例如我国是UTC+08:00。在处理时区时要指定它的地区名称,而不是国家(一个国家可能跨多个时区,例如美国),也不是位置,虽然时区和位置有关,但不是线性的。说完了时区还要再说下夏令时,夏令时又叫日光节约时间(Daylight saving time,或Summer time),是为了节约能源让大家在夏天早起1小时(也就是把时钟拨快1小时),各个地区对于夏令时有不同的规定,而且不同年份的开始日期还可能不同(跟政府政策有关)。我国在1986-1991年实行夏令时,后来因为中国地域辽阔且修改事件效果不好且影响大家生活,后来作废了。

程序处理应注意的内容

好了,到这时间的概念大家应该都清楚了。下面再说下程序中时间处理中应该注意的。

  1. 数据库中存储的时间、操作系统的时间都是一个long整数,它是指格林威治时间1970年01月01日00时00分00秒起至现在的总秒数(或是总毫秒数);
  2. 只有在展示时才将此时间戳换算成本地时间,换算时需要考虑时区和夏令时。当然了如果你直接将本地时间字符串存到数据库中,那很多转换工作需要你自己做了,需要考虑清楚。在多时区处理时一般将UTC时间存入数据库,便于后续处理。
  3. 时间包括两个属性,一个是时间戳,即相对于UTC的某个时点的偏移量;另一个是时区,即事件发生是所在的时区信息,这个信息可以根据你的业务场景分析是否需要
  4. 在java中如果你输出显示时间,默认是本地时间,和系统设置有关,如果要输出某个时区的时间需要自己指定时区;另外这个本地时区时间会考虑夏令时,当然了因为夏令时和政府政策有关,java不可能实时获知这些信息,如果你想自己控制,则需要建立一个自己专用的夏令时起止时间表,自己进行换算(我们海外系统就是自己保存的夏令时信息),这时请不要再使用Asia/Shanghai这种,请使用UTC+08:00这种,会避免程序自身夏令时的处理影响。

下面给大家展示一个测试程序,通过这个能看出java程序的处理方式。值得一提的是上面酷壳提到的这个时间现在并不特殊,反而另一个时间是特殊的(1900-12-31 23:54:16),我不是说那篇文章的不对,而是很可能是近期政府对历史的时间定义有调整。

package com.zyh.mytest;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class TimeTest {

	private static final String TIME_FORMAT_STR = "yyyy-MM-dd HH:mm:ss";

	private SimpleDateFormat utc8Sdf;
	private SimpleDateFormat utcSdf;
	private SimpleDateFormat shanghaiSdf;

	@Before
	public void setUp() {
		utc8Sdf = new SimpleDateFormat(TIME_FORMAT_STR);
		utc8Sdf.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));

		utcSdf = new SimpleDateFormat(TIME_FORMAT_STR);
		utcSdf.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));

		// 输出全部时区
		// for (String s : TimeZone.getAvailableIDs()){
		// System.out.println(s);
		// }
		shanghaiSdf = new SimpleDateFormat(TIME_FORMAT_STR);
		shanghaiSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
	}

	/**
	 * 测试时区的时差
	 * 
	 * @throws ParseException
	 */
	@Test
	public void testTimeZone() throws ParseException {
		Date utc8Dt = utc8Sdf.parse("2017-10-01 13:04:56");
		Assert.assertEquals(1506834296000L, utc8Dt.getTime());

		Date utcDt = utcSdf.parse("2017-10-01 13:04:56");
		Assert.assertEquals(1506863096000L, utcDt.getTime());

		Assert.assertEquals((long) (8 * 60 * 60 * 1000), utcDt.getTime() - utc8Dt.getTime());// 8H

	}

	/**
	 * 测试夏令时
	 * 
	 * @throws ParseException
	 */
	@Test
	public void testDst() throws ParseException {

		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
		sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
		Date shanghaiDt = shanghaiSdf.parse("1988-07-02 12:00:00");
		Assert.assertEquals(583815600000L, shanghaiDt.getTime());

		Date utcDt = utcSdf.parse("1988-07-02 12:00:00");
		Assert.assertEquals(583848000000L, utcDt.getTime());

		Assert.assertEquals((long) (9 * 60 * 60 * 1000), utcDt.getTime() - shanghaiDt.getTime());// 9H
	}

	/**
	 * 测试闰秒
	 * 
	 * @throws ParseException
	 */
	@Test
	public void testLeapSeconds() throws ParseException {
		Date utcDt1 = utcSdf.parse("2008-12-31 23:59:59");
		Assert.assertEquals(1230767999000L, utcDt1.getTime());
		Date utcDt2 = utcSdf.parse("2008-12-31 23:59:60");
		Assert.assertEquals(1230768000000L, utcDt2.getTime());// +1s
		Date utcDt3 = utcSdf.parse("2009-01-01 00:00:00");
		Assert.assertEquals(1230768000000L, utcDt3.getTime());// =23:59:60
	}

	/**
	 * 测试本地时间调整1,没有异常
	 * 
	 * @throws ParseException
	 */
	@Test // 本地时间,时间调整
	public void testLocalTime() throws ParseException {
		Date shanghaiDt = shanghaiSdf.parse("1927-12-31 23:54:07");
		Assert.assertEquals(-1325491553000L, shanghaiDt.getTime());

		Date shanghaiDt2 = shanghaiSdf.parse("1927-12-31 23:54:08");
		Assert.assertEquals(-1325491552000L, shanghaiDt2.getTime());

		Assert.assertEquals((long) (1 * 1000), shanghaiDt2.getTime() - shanghaiDt.getTime());// 1S
	}

	/**
	 * 测试本地时间调整2
	 * 
	 * @throws ParseException
	 */
	@Test
	public void test6() throws ParseException {

		Date shanghaiDt = shanghaiSdf.parse("1900-01-01 12:00:00");
		Date utcDt = utcSdf.parse("1900-01-01 12:00:00");
		Assert.assertEquals((long) ((8 * 60 * 60 + 5 * 60 + 43) * 1000), utcDt.getTime() - shanghaiDt.getTime());// 8:05:43

		Date shanghaiDt2 = shanghaiSdf.parse("1901-01-01 12:00:00");
		Date utcDt2 = utcSdf.parse("1901-01-01 12:00:00");
		Assert.assertEquals((long) (8 * 60 * 60 * 1000), utcDt2.getTime() - shanghaiDt2.getTime());// 8H

		Date shanghaiDt3 = shanghaiSdf.parse("1900-12-31 23:54:16");
		Date utcDt3 = utcSdf.parse("1900-12-31 23:54:16");
		Assert.assertEquals((long) ((8 * 60 * 60 + 5 * 60 + 43) * 1000), utcDt3.getTime() - shanghaiDt3.getTime());// 8:05:43

		Date shanghaiDt4 = shanghaiSdf.parse("1900-12-31 23:54:17");
		Date utcDt4 = utcSdf.parse("1900-12-31 23:54:17");
		Assert.assertEquals((long) (8 * 60 * 60 * 1000), utcDt4.getTime() - shanghaiDt4.getTime());// 8H
		
		Assert.assertEquals((long) (( 5 * 60 + 44)* 1000), shanghaiDt4.getTime() - shanghaiDt3.getTime());// 00:05:43
	}
}

参考资料

  1. How Does an Atomic Clock Work?
  2. Working with time zones in Ruby on Rails
  3. 你确信你了解时间吗?
  4. 时间频率和铯原子钟
  5. 关于闰秒
  6. 元旦我国第27次闰秒将来临:多一秒世界有何不同
  7. 关于Oracle Timezone的一点总结
  8. ISO8601 Dates in Ruby

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

论管理能力与技术能力

近期一直在准备项目管理的考试,现在算是告一段落,这里把自己的一些想法整理记录一下,希望也能给大家有所帮助。

这里主要说一下管理和技术的不同。其实两者差别还是挺大的,甚至很多题目都是以资深技术人员担任项目经理遇到的问题来出题的(看的我都有些深有同感,哈哈)。我个人算是比较偏技术的了,对技术也有些研究,喜欢学习一些新东西,也喜欢做一些有挑战的事。下面从我自身的体会分别说下我对技术和管理的看法:

技术更偏向于自身能力的提高,这体现在深度(例如某方面的专家)和广度(例如对整个架构的理解),但不管如何都是侧重自身见解、能力。这些能力往往是可以看出来的,可以从你对于某个问题的看法,甚至从你对于某个问题的解决方法,从你认为事情的难易程度以及问题的关注点上都会有所体现。这就是所谓的高度(或者说格局),这对于技术人员的成长很重要。作为技术人员你需要不断学习新知识,另外也需要对底层知识例如操作系统、算法等有所有了解,在内心深处搭建属于你自己的知识大厦。学习这些知识是需要花费大量的时间的,包括阅读和练习。长期的与机器或书本打交道,也导致很多技术人员不善于与人交流,不善言辞。

管理能力则更侧重于领导力,能够带领团队一起完成一件事情,充分利用每个人的特长,各司其职,共同向着某个目标迈进。一个合格的领导不但要有对本行业很了解,更需要有强大的领导力,很好的沟通协调能力,风险控制能力。通过学习项目管理的知识,也让我对项目管理有了系统的了解。项目管理从整体上看,包括项目整体管理、立项管理、范围管理、时间管理、成本管理、质量管理、人力资源管理、沟通管理、风险管理、采购管理、配置管理。作为一个合格的项目经理,需要制定统一的项目计划,明确项目范围,并做好进度控制、风险控制、成本控制、质量控制,并且要做好团队建设、建立一个综合素质过硬的团队,通过合理的激励手段使项目组成员有成就感。可以看出这里面更强调的是沟通协调、问题识别跟踪、合理的计划安排、合理的制度流程。所以很多从技术转过来的人很容易不太适应角色转换,从而虽然自己技术很强,但项目整体进度滞后、客户不满意、需求蔓延、质量低下。最近我看一些电视或电影时也重点关注了里面的领导角色,发现好的角色都是有担当的,能够心系团队,他们有独特的人格魅力,在危险的时刻冷静应对,带领团队为了目标努力奋斗,另外合格的领导最重要的一点就是要有眼光,为团队指明方向。

上面说的并不是做技术的人就不能当领导,相反做技术的当领导是有自己的优势的。这种优势首先体现在自己的专家权力,能够对问题有准确的判断,如果再加上对业务的了解,就可以有更高的眼界,更大的格局。做领导的人尤其是一线领导,如果对技术一点不懂是很被动的,往往也不能服众,当手下遇到问题你也只能给予心理上的安慰了。我更倾向于认为技术领导是一种知识跨界,通过对管理领域的了解来使你的综合能力得到提升,最大化发挥你的价值(不仅仅是你个人的价值,而是整个团队的价值)。当一个领导思考问题的时候开始以团队的角度思考,也就是他开始转变为一个领导者了。

我个人接触管理有段时间了,通过项目管理的学习,让我开始对管理有了系统的理解。很多事情我也开始结合自己的工作的状态进行思考,很多事情都变得豁然开朗了,也更加明确遇到问题应该如何解决,如何正确的做正确的事情。管理是一门很大的学问,我后面也会努力提升自己,包括理论和实践,让我的团队成员跟着我提升自我、快速成长。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》