今天同事提交了一个 Bug Fix,把基于 CFAbsoluteTimeGetCurrent()
计算的 Time Stamp 改成了基于 [[NSDate date] timeIntervalSince1970]
计算,原因是 CFAbsoluteTimeGetCurrent()
是从 2001 年 1 月 1 日 0 点开始计算的。
/* absolute time is the time interval since the reference date */
/* the reference date (epoch) is 00:00:00 1 January 2001. */
CFAbsoluteTime CFAbsoluteTimeGetCurrent(void);
那么问题来了,为什么 CF 的时间戳不是从大家熟悉的 1970-01-01 00:00:00-00
开始,而是从 2001 开始呢?一开始我想,是不是很早以前那个 epoch 32 位 Int 用完的问题,但是那个是 2038 年问题,跟 2001 年接近的是 Y2K 问题,也跟这个无关。
所以我们直接看 CF 的源码看看有没有线索。
CFAbsoluteTime CFAbsoluteTimeGetCurrent(void) {
CFAbsoluteTime ret;
struct timeval tv;
gettimeofday(&tv, NULL);
ret = (CFTimeInterval)tv.tv_sec - kCFAbsoluteTimeIntervalSince1970;
ret += (1.0E-6 * (CFTimeInterval)tv.tv_usec);
return ret;
}
很简单就是走内核接口取了下时间,然后 - kCFAbsoluteTimeIntervalSince1970
,这个数字就是 1970 和 2001 的差距,以秒为单位。
const CFTimeInterval kCFAbsoluteTimeIntervalSince1970 = 978307200.0L;
所以这个函数的实现就是简单地减去了这么多秒,也没有留下注释。
最后在 Stack Overflow 的这个问题 Why are dates calculated from January 1st, 1970? 发现原来历史上有一直存在多种不同的 Epochs。只是因为 1970 是 Unix 用的,也是 POSIX 标准所以比较多人知道而已。
维基百科的 Epoch 词条里列举了 15 种 Epochs:
Epoch date | Notable uses | Rationale for selection |
---|---|---|
0 January 1 BC | MATLAB | |
1 January AD 1 | Microsoft .NET, Go, REXX, Rata Die | Common Era, ISO 2014, RFC 3339 |
14 October 1582 | SPSS | |
15 October 1582 | UUID version 1 | The date of the Gregorian reform to the Christian calendar. |
1 January 1601 | NTFS, COBOL, Win32/Win64 (NT time epoch) | 1601 was the first year of the 400-year Gregorian calendar cycle at the time Windows NT was made. |
31 December 1840 | MUMPS programming language | 1841 was a non-leap year several years before the birth year of the oldest living US citizen when the language was designed. |
17 November 1858 | VMS, United States Naval Observatory, DVB SI 16-bit day stamps, other astronomy-related computations | 17 November 1858, 00:00:00 UT is the zero of the Modified Julian Day (MJD) equivalent to Julian day 2400000.5 |
30 December 1899 | Microsoft COM DATE, Object Pascal, LibreOffice Calc, Google Sheets | Technical internal value used by Microsoft Excel; for compatibility with Lotus 1-2-3. |
31 December 1899 | Dyalog APL, Microsoft C/C++ 7.0 | Chosen so that (date mod 7) would produce 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, and 6=Saturday. Microsoft’s last version of non-Visual C/C++ used this, but was subsequently reverted. |
0 January 1900 | Microsoft Excel,Lotus 1-2-3 | While logically 0 January 1900 is equivalent to 31 December 1899, these systems do not allow users to specify the latter date. Since 1900 is incorrectly treated as a leap year in these systems, 0 January 1900 actually corresponds to the historical date of 30 December 1899. |
1 January 1900 | Network Time Protocol, IBM CICS, Mathematica, RISC OS, VME, Common Lisp, Michigan Terminal System | |
1 January 1904 | LabVIEW, Apple Inc.'s classic Mac OS, JMP Scripting Language, Palm OS, MP4, Microsoft Excel (optionally), IGOR Pro | 1904 is the first leap year of the 20th century. |
1 January 1960 | SAS System | |
31 December 1967 | Pick OS and variants (jBASE, Universe, Unidata, Revelation, Reality) | Chosen so that (date mod 7) would produce 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, and 6=Saturday. |
1 January 1970 | Unix Epoch aka POSIX time, used by Unix and Unix-like systems (Linux, macOS), and programming languages: most C/C++ implementations, Java, JavaScript, Perl, PHP, Python, Ruby, Tcl, ActionScript. Also used by Precision Time Protocol. | |
1 January 1978 | AmigaOS. The Commodore Amiga hardware systems were introduced between 1985 and 1994. Latest OS version 4.1 (December 2016). AROS, MorphOS. | |
1 January 1980 | IBM BIOS INT 1Ah, DOS, OS/2, FAT12, FAT16, FAT32, exFAT filesystems | The IBM PC with its BIOS as well as 86-DOS, MS-DOS and PC DOS with their FAT12 file system were developed and introduced between 1980 and 1981. |
6 January 1980 | Qualcomm BREW, GPS, ATSC 32-bit time stamps | GPS counts weeks (a week is defined to start on Sunday) and 6 January is the first Sunday of 1980. |
1 January 2000 | AppleSingle, AppleDouble, PostgreSQL, ZigBee UTCTime | |
1 January 2001 | Apple's Cocoa framework | 2001 is the year of the release of Mac OS X 10.0 (but NSDate for Apple's EOF 1.0 was developed in 1994). |
最接近现在的时间是苹果的 1 January 2001
。因为乔帮主回归苹果后发布的 OS X 就是在 2001 年(有点像纪念 iPhone 发布时间,所有官方宣发的 iPhone 锁屏界面都停留在 9:41)。
所以 CF
接口的时间戳都从 2001 年开始,CoreData 也是。从上表可以看到,主流的编程语言如 C/C++, Java, JavaScript, Perl, PHP, Python, Ruby, Tcl, ActionScript 都是用的 1970 的 Unix Epoch,也许是因为这个所以给大家一种全世界都用 1970 的错觉。
这种“约定但不俗成”的时间潜规则让我想起几年前为了给 Finder 下载中文件加一个下载中的进度条,用了 Progress API。但是怎么设置都不生效,后来在 StackOverflow 的帮助下发现需要把正在下载的文件加一个特殊日期时间戳(NSFileCreationDate): 1984-01-24 08:00:00 +0000
才能生效。
这个时间,就是第一台 Macintosh 发布的时间。