由一个非 Unix Timestamp 想到的

2020年5月27日 · 4 years ago

由一个非 Unix Timestamp 想到的

今天同事提交了一个 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 发布的时间

参考资料