处理时间戳时丢失双精度
Losing double precision when dealing with timestamps
我有一个简单的XCTestCase
:
func testExample() {
let date = "2015-09-21T20:38:54.379912Z";// as NSString;
let date1 = 1442867934.379912;
XCTAssertEqual(date1, NSDate.sam_dateFromISO8601String(date).timeIntervalSince1970);
}
这个测试在不应该通过的时候通过了,原因有二:
1442867934.379912
在测试中变为 1442867934.379911
,即使它实际上只是一个重新打印的变量
sam_
函数(在下面复制)似乎以相同的方式思考,millisecond
变量变为 millisecond double 0.37991200000000003 0.37991200000000003
,稍后从 NSDate
转换为 double
,似乎失去了微秒精度(调试器):
po NSDate.sam_dateFromISO8601String(date).timeIntervalSince1970 -> 1442867934.37991
po 1442867934.379912 -> 1442867934.37991
知道为什么吗?微秒精度(小数点后 6 位)对我的应用程序非常重要,我需要使用 iso8601
格式在 NSString
和 NSDate
之间无缝转换。
+ (NSDate *)sam_dateFromISO8601String:(NSString *)iso8601 {
// Return nil if nil is given
if (!iso8601 || [iso8601 isEqual:[NSNull null]]) {
return nil;
}
// Parse number
if ([iso8601 isKindOfClass:[NSNumber class]]) {
return [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)iso8601 doubleValue]];
}
// Parse string
else if ([iso8601 isKindOfClass:[NSString class]]) {
const char *str = [iso8601 cStringUsingEncoding:NSUTF8StringEncoding];
size_t len = strlen(str);
if (len == 0) {
return nil;
}
struct tm tm;
char newStr[25] = "";
BOOL hasTimezone = NO;
// 2014-03-30T09:13:00Z
if (len == 20 && str[len - 1] == 'Z') {
strncpy(newStr, str, len - 1);
}
// 2014-03-30T09:13:00-07:00
else if (len == 25 && str[22] == ':') {
strncpy(newStr, str, 19);
hasTimezone = YES;
}
// 2014-03-30T09:13:00.000Z
else if (len == 24 && str[len - 1] == 'Z') {
strncpy(newStr, str, 19);
}
// 2014-03-30T09:13:00.000000Z
else if (len == 27 && str[len - 1] == 'Z') {
strncpy(newStr, str, 19);
}
// 2014-03-30T09:13:00.000-07:00
else if (len == 29 && str[26] == ':') {
strncpy(newStr, str, 19);
hasTimezone = YES;
}
// Poorly formatted timezone
else {
strncpy(newStr, str, len > 24 ? 24 : len);
}
// Timezone
size_t l = strlen(newStr);
if (hasTimezone) {
strncpy(newStr + l, str + len - 6, 3);
strncpy(newStr + l + 3, str + len - 2, 2);
} else {
strncpy(newStr + l, "+0000", 5);
}
// Add null terminator
newStr[sizeof(newStr) - 1] = 0;
if (strptime(newStr, "%FT%T%z", &tm) == NULL) {
return nil;
}
double millisecond = 0.0f;
NSString *subStr = [[iso8601 componentsSeparatedByString:@"."].lastObject substringToIndex:6];
millisecond = subStr.doubleValue/1000000.f;
time_t t;
t = mktime(&tm);
return [NSDate dateWithTimeIntervalSince1970:t + millisecond];
}
NSAssert1(NO, @"Failed to parse date: %@", iso8601);
return nil;
}
1442867934.379912
和 1442867934.379911
很可能等于相同的数字,只是打印方式不同(一个四舍五入,另一个截断)......如果微秒时间对你很重要,也许你应该看看https://developer.apple.com/library/mac/qa/qa1398/_index.html
特别是因为 [NSDate date] 会随着时钟的变化而剧烈变化...而绝对时间 api 不会...
我最终只是简单地更改了我的 API 以传递 double
对象的 Time
表示。即 1442867934.379912
完全不影响 ISO8601
。
NSDate
本机支持此功能。所以我不必解析任何字符串。因此,性能是一个额外的好处。
我在 Rails/Rabl/Oj 中遇到的几个怪癖:
- 需要将
double
作为 string
发送。 iOS 上的 JSONKit
似乎不喜欢超过 15 个有效数字,JSON
规范也不喜欢。
Rabl(初始化器):
Oj.default_options = {
use_to_json: true,
second_precision: 6,
float_precision: 16
}
默认 to_f
没有发送我需要的精度。
我有一个简单的XCTestCase
:
func testExample() {
let date = "2015-09-21T20:38:54.379912Z";// as NSString;
let date1 = 1442867934.379912;
XCTAssertEqual(date1, NSDate.sam_dateFromISO8601String(date).timeIntervalSince1970);
}
这个测试在不应该通过的时候通过了,原因有二:
1442867934.379912
在测试中变为1442867934.379911
,即使它实际上只是一个重新打印的变量sam_
函数(在下面复制)似乎以相同的方式思考,millisecond
变量变为millisecond double 0.37991200000000003 0.37991200000000003
,稍后从NSDate
转换为double
,似乎失去了微秒精度(调试器):po NSDate.sam_dateFromISO8601String(date).timeIntervalSince1970 -> 1442867934.37991
po 1442867934.379912 -> 1442867934.37991
知道为什么吗?微秒精度(小数点后 6 位)对我的应用程序非常重要,我需要使用 iso8601
格式在 NSString
和 NSDate
之间无缝转换。
+ (NSDate *)sam_dateFromISO8601String:(NSString *)iso8601 {
// Return nil if nil is given
if (!iso8601 || [iso8601 isEqual:[NSNull null]]) {
return nil;
}
// Parse number
if ([iso8601 isKindOfClass:[NSNumber class]]) {
return [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)iso8601 doubleValue]];
}
// Parse string
else if ([iso8601 isKindOfClass:[NSString class]]) {
const char *str = [iso8601 cStringUsingEncoding:NSUTF8StringEncoding];
size_t len = strlen(str);
if (len == 0) {
return nil;
}
struct tm tm;
char newStr[25] = "";
BOOL hasTimezone = NO;
// 2014-03-30T09:13:00Z
if (len == 20 && str[len - 1] == 'Z') {
strncpy(newStr, str, len - 1);
}
// 2014-03-30T09:13:00-07:00
else if (len == 25 && str[22] == ':') {
strncpy(newStr, str, 19);
hasTimezone = YES;
}
// 2014-03-30T09:13:00.000Z
else if (len == 24 && str[len - 1] == 'Z') {
strncpy(newStr, str, 19);
}
// 2014-03-30T09:13:00.000000Z
else if (len == 27 && str[len - 1] == 'Z') {
strncpy(newStr, str, 19);
}
// 2014-03-30T09:13:00.000-07:00
else if (len == 29 && str[26] == ':') {
strncpy(newStr, str, 19);
hasTimezone = YES;
}
// Poorly formatted timezone
else {
strncpy(newStr, str, len > 24 ? 24 : len);
}
// Timezone
size_t l = strlen(newStr);
if (hasTimezone) {
strncpy(newStr + l, str + len - 6, 3);
strncpy(newStr + l + 3, str + len - 2, 2);
} else {
strncpy(newStr + l, "+0000", 5);
}
// Add null terminator
newStr[sizeof(newStr) - 1] = 0;
if (strptime(newStr, "%FT%T%z", &tm) == NULL) {
return nil;
}
double millisecond = 0.0f;
NSString *subStr = [[iso8601 componentsSeparatedByString:@"."].lastObject substringToIndex:6];
millisecond = subStr.doubleValue/1000000.f;
time_t t;
t = mktime(&tm);
return [NSDate dateWithTimeIntervalSince1970:t + millisecond];
}
NSAssert1(NO, @"Failed to parse date: %@", iso8601);
return nil;
}
1442867934.379912
和 1442867934.379911
很可能等于相同的数字,只是打印方式不同(一个四舍五入,另一个截断)......如果微秒时间对你很重要,也许你应该看看https://developer.apple.com/library/mac/qa/qa1398/_index.html
特别是因为 [NSDate date] 会随着时钟的变化而剧烈变化...而绝对时间 api 不会...
我最终只是简单地更改了我的 API 以传递 double
对象的 Time
表示。即 1442867934.379912
完全不影响 ISO8601
。
NSDate
本机支持此功能。所以我不必解析任何字符串。因此,性能是一个额外的好处。
我在 Rails/Rabl/Oj 中遇到的几个怪癖:
- 需要将
double
作为string
发送。 iOS 上的JSONKit
似乎不喜欢超过 15 个有效数字,JSON
规范也不喜欢。
Rabl(初始化器):
Oj.default_options = {
use_to_json: true,
second_precision: 6,
float_precision: 16
}
默认 to_f
没有发送我需要的精度。