From 1057c99ca375a6b53ca079cede47fe2e9dce4d8b Mon Sep 17 00:00:00 2001 From: dmaclach Date: Thu, 3 Jan 2019 08:22:28 -0800 Subject: Add GTMTimeUtils (#229) Utilities for relatively common desire to know the launch time of an app, or the boot time of a device. --- Foundation/GTMTimeUtils.h | 35 ++++++++++++++++++ Foundation/GTMTimeUtils.m | 72 +++++++++++++++++++++++++++++++++++++ Foundation/GTMTimeUtilsTest.m | 45 +++++++++++++++++++++++ GTM.xcodeproj/project.pbxproj | 12 +++++++ GTMiPhone.xcodeproj/project.pbxproj | 10 ++++++ GoogleToolboxForMac.podspec | 4 +++ 6 files changed, 178 insertions(+) create mode 100644 Foundation/GTMTimeUtils.h create mode 100644 Foundation/GTMTimeUtils.m create mode 100644 Foundation/GTMTimeUtilsTest.m diff --git a/Foundation/GTMTimeUtils.h b/Foundation/GTMTimeUtils.h new file mode 100644 index 0000000..9b49532 --- /dev/null +++ b/Foundation/GTMTimeUtils.h @@ -0,0 +1,35 @@ +// +// GTMTimeUtils.h +// +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import + +// Return the date that the app was launched. +NSDate *GTMAppLaunchDate(void); + +// Return the date that the device was started. Note on the simulator that this +// returns the date that the computer was started, not the simulator. +NSDate *GTMBootDate(void); + +// Convert a timeval to NSTimeInterval. +NSTimeInterval GTMTimeValToNSTimeInterval(struct timeval time); + +// Timeval versions of the functions above if timevals are a more useful +// structure to work with. +struct timeval GTMBootTimeRelativeTo1970(void); +struct timeval GTMAppLaunchTimeRelativeTo1970(void); + diff --git a/Foundation/GTMTimeUtils.m b/Foundation/GTMTimeUtils.m new file mode 100644 index 0000000..2b551e4 --- /dev/null +++ b/Foundation/GTMTimeUtils.m @@ -0,0 +1,72 @@ +// +// GTMTimeUtils.m +// +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMTimeUtils.h" + +#include + +#import "GTMDefines.h" + +NSTimeInterval GTMTimeValToNSTimeInterval(struct timeval time) { + return time.tv_sec + (time.tv_usec / (double)USEC_PER_SEC); +} + +struct timeval GTMBootTimeRelativeTo1970(void) { + struct timeval bootTime = { 0, 0 }; + int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + size_t bootTimeSize = sizeof(bootTime); + if (sysctl(mib, 2, &bootTime, &bootTimeSize, NULL, 0) != 0) { + _GTMDevAssert(errno == 0, @"sysctl error - %d", errno); + struct timeval invalid = { 0, 0 }; + return invalid; + } + return bootTime; +} + +struct timeval GTMAppLaunchTimeRelativeTo1970(void) { + id_t pid = getpid(); + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid }; + const size_t mibSize = sizeof(mib) / sizeof(mib[0]); + size_t infoSize = 0; + + // Get initial size of KERN_PROC data structure. + if (sysctl(mib, mibSize, NULL, &infoSize, NULL, 0) != 0) { + _GTMDevAssert(errno == 0, @"sysctl error - %d", errno); + struct timeval invalid = { 0, 0 }; + return invalid; + } + struct kinfo_proc info; + if (sysctl(mib, mibSize, &info, &infoSize, NULL, 0) != 0) { + _GTMDevAssert(errno == 0, @"sysctl error - %d", errno); + struct timeval invalid = { 0, 0 }; + return invalid; + } + return info.kp_proc.p_starttime; +} + +NSDate *GTMAppLaunchDate() { + NSTimeInterval ti = + GTMTimeValToNSTimeInterval(GTMAppLaunchTimeRelativeTo1970()); + return [NSDate dateWithTimeIntervalSince1970:ti]; +} + +NSDate *GTMBootDate() { + NSTimeInterval ti = + GTMTimeValToNSTimeInterval(GTMBootTimeRelativeTo1970()); + return [NSDate dateWithTimeIntervalSince1970:ti]; +} diff --git a/Foundation/GTMTimeUtilsTest.m b/Foundation/GTMTimeUtilsTest.m new file mode 100644 index 0000000..471cad0 --- /dev/null +++ b/Foundation/GTMTimeUtilsTest.m @@ -0,0 +1,45 @@ +// +// GTMTimeUtilsTest.m +// +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMSenTestCase.h" +#import "GTMTimeUtils.h" + +@interface GTMTimeUtilsTest : GTMTestCase +@end + +@implementation GTMTimeUtilsTest + +- (void)testAppLaunchDate { + // Basic test to verify that "now" is after appLaunch. + NSDate *now = [NSDate date]; + NSDate *appLaunch = GTMAppLaunchDate(); + + XCTAssertEqual([now compare:appLaunch], NSOrderedDescending, + @"now: %@ appLaunch: %@", now, appLaunch); +} + +- (void)testBootDate { + // Basic test to verify that appLaunch occurred after boot. + NSDate *appLaunch = GTMAppLaunchDate(); + NSDate *boot = GTMBootDate(); + + XCTAssertEqual([appLaunch compare:boot], NSOrderedDescending, + @"appLaunch: %@ boot: %@", appLaunch, boot); +} + +@end diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj index 6ab0a65..148019d 100644 --- a/GTM.xcodeproj/project.pbxproj +++ b/GTM.xcodeproj/project.pbxproj @@ -57,6 +57,9 @@ 8B45A2AC0DA49C47001148C5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B45A2A80DA49C47001148C5 /* main.m */; }; 8B45A2B30DA49CA9001148C5 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; 8B4D78080E40AFFA00EFEDD8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B45A1990DA46AAA001148C5 /* QuartzCore.framework */; }; + 8B5769A721CD77D600D924D3 /* GTMTimeUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B5769A521CD77D600D924D3 /* GTMTimeUtils.h */; }; + 8B5769A821CD77D600D924D3 /* GTMTimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5769A621CD77D600D924D3 /* GTMTimeUtils.m */; }; + 8B5769AB21CD7ACF00D924D3 /* GTMTimeUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5769A921CD798000D924D3 /* GTMTimeUtilsTest.m */; }; 8B61FDC00E4CDB8000FF9C21 /* GTMStackTrace.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B61FDBF0E4CDB8000FF9C21 /* GTMStackTrace.m */; }; 8B6C15930F356E6400E51E5D /* GTMNSObject+KeyValueObserving.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6C15910F356E6400E51E5D /* GTMNSObject+KeyValueObserving.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8B6C15940F356E6400E51E5D /* GTMNSObject+KeyValueObserving.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6C15920F356E6400E51E5D /* GTMNSObject+KeyValueObserving.m */; }; @@ -278,6 +281,9 @@ 8B45A28A0DA49B99001148C5 /* GTMUIUnitTestingHarness.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GTMUIUnitTestingHarness.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8B45A2A70DA49C47001148C5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8B45A2A80DA49C47001148C5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8B5769A521CD77D600D924D3 /* GTMTimeUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMTimeUtils.h; sourceTree = ""; }; + 8B5769A621CD77D600D924D3 /* GTMTimeUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMTimeUtils.m; sourceTree = ""; }; + 8B5769A921CD798000D924D3 /* GTMTimeUtilsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMTimeUtilsTest.m; sourceTree = ""; }; 8B5B4ABC15BF31050081A96C /* CodeCoverage.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CodeCoverage.xcconfig; sourceTree = ""; }; 8B5B4ABD15BF31050081A96C /* CodeCoverageStatic.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CodeCoverageStatic.xcconfig; sourceTree = ""; }; 8B5B4ABE15BF31050081A96C /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; @@ -719,6 +725,9 @@ F48FE2920D198D24009257D2 /* GTMSystemVersion.h */, F48FE2930D198D24009257D2 /* GTMSystemVersion.m */, F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */, + 8B5769A521CD77D600D924D3 /* GTMTimeUtils.h */, + 8B5769A621CD77D600D924D3 /* GTMTimeUtils.m */, + 8B5769A921CD798000D924D3 /* GTMTimeUtilsTest.m */, ); path = Foundation; sourceTree = ""; @@ -787,6 +796,7 @@ F93207DE0F4B82DB005F37EA /* GTMSQLite.h in Headers */, F42E094C0D199BBF00D5DDE0 /* GTMGeometryUtils.h in Headers */, F42E094F0D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.h in Headers */, + 8B5769A721CD77D600D924D3 /* GTMTimeUtils.h in Headers */, F42E09510D199BBF00D5DDE0 /* GTMNSString+HTML.h in Headers */, F42E09540D199BBF00D5DDE0 /* GTMSystemVersion.h in Headers */, F428FF030D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h in Headers */, @@ -1122,6 +1132,7 @@ 8BFE6E911282371200B5C894 /* GTMNSObject+KeyValueObservingTest.m in Sources */, 8BFE6E921282371200B5C894 /* GTMNSScanner+JSONTest.m in Sources */, 8BFE6E941282371200B5C894 /* GTMNSString+FindFolderTest.m in Sources */, + 8B5769AB21CD7ACF00D924D3 /* GTMTimeUtilsTest.m in Sources */, 8BFE6E951282371200B5C894 /* GTMNSString+HTMLTest.m in Sources */, 8BFE6E971282371200B5C894 /* GTMNSString+URLArgumentsTest.m in Sources */, 8BFE6E981282371200B5C894 /* GTMNSString+XMLTest.m in Sources */, @@ -1154,6 +1165,7 @@ 33C374390DD8D44800E97817 /* GTMNSDictionary+URLArguments.m in Sources */, 8B7DCBBD0DFF0F5D0017E983 /* GTMMethodCheck.m in Sources */, F41A6F830E02EC3600788A6C /* GTMSignalHandler.m in Sources */, + 8B5769A821CD77D600D924D3 /* GTMTimeUtils.m in Sources */, F425977A0E23FE3A003BEA3E /* GTMNSString+FindFolder.m in Sources */, F98680C30E2C163D00CEE8BF /* GTMLogger.m in Sources */, F98681970E2C20C800CEE8BF /* GTMLogger+ASL.m in Sources */, diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj index 82f5de5..5974e05 100644 --- a/GTMiPhone.xcodeproj/project.pbxproj +++ b/GTMiPhone.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 64D0F5ED0FD3E68400506CC7 /* GTMUIImage+Resize_100x100.png in Resources */ = {isa = PBXBuildFile; fileRef = 64D0F5DB0FD3E68400506CC7 /* GTMUIImage+Resize_100x100.png */; }; 67A7820C0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */; }; 8B2C72EB1D9EBBA10027BD14 /* GTMTestTimerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B2C72EA1D9EBBA10027BD14 /* GTMTestTimerTest.m */; }; + 8B5769B121CD7C8300D924D3 /* GTMTimeUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5769AD21CD7C7600D924D3 /* GTMTimeUtilsTest.m */; }; + 8B5769B221CD7C8600D924D3 /* GTMTimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5769AC21CD7C7600D924D3 /* GTMTimeUtils.m */; }; 8B7651D81D9C872F00DB2C59 /* GTMLogger+ASL.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7651D61D9C872C00DB2C59 /* GTMLogger+ASL.m */; }; 8B7651D91D9C873200DB2C59 /* GTMLogger+ASLTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7651D71D9C872C00DB2C59 /* GTMLogger+ASLTest.m */; }; 8B7651E11D9C89B800DB2C59 /* GTMRoundedRectPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7651E01D9C89B800DB2C59 /* GTMRoundedRectPath.m */; }; @@ -135,6 +137,9 @@ 8B3AA8F10E032FC7007E31B5 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = ""; }; 8B3AA8F20E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArgumentsTest.m"; sourceTree = ""; }; 8B3AA9330E0336AC007E31B5 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + 8B5769AC21CD7C7600D924D3 /* GTMTimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMTimeUtils.m; sourceTree = ""; }; + 8B5769AD21CD7C7600D924D3 /* GTMTimeUtilsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMTimeUtilsTest.m; sourceTree = ""; }; + 8B5769AE21CD7C7600D924D3 /* GTMTimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMTimeUtils.h; sourceTree = ""; }; 8B6C18710F3769D200E51E5D /* GTMNSObject+KeyValueObserving.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSObject+KeyValueObserving.h"; sourceTree = ""; }; 8B6C18720F3769D200E51E5D /* GTMNSObject+KeyValueObserving.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+KeyValueObserving.m"; sourceTree = ""; }; 8B6C18730F3769D200E51E5D /* GTMNSObject+KeyValueObservingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+KeyValueObservingTest.m"; sourceTree = ""; }; @@ -346,6 +351,9 @@ 8BC047760DAE928A00C2D1CA /* Foundation */ = { isa = PBXGroup; children = ( + 8B5769AE21CD7C7600D924D3 /* GTMTimeUtils.h */, + 8B5769AC21CD7C7600D924D3 /* GTMTimeUtils.m */, + 8B5769AD21CD7C7600D924D3 /* GTMTimeUtilsTest.m */, 0BBC768810FEF61D0006FABE /* GTMStringEncoding.h */, 0BBC768910FEF61D0006FABE /* GTMStringEncoding.m */, 0BBC768A10FEF61D0006FABE /* GTMStringEncodingTest.m */, @@ -627,6 +635,7 @@ 8BF753DB1D9DB3080010A295 /* GTMSQLite.m in Sources */, 8B82CF0C1D9C1C3B007182AA /* GTMNSNumber+64Bit.m in Sources */, 8B82CF041D9C1C3B007182AA /* GTMLightweightProxy.m in Sources */, + 8B5769B221CD7C8600D924D3 /* GTMTimeUtils.m in Sources */, 8B82CF0B1D9C1C3B007182AA /* GTMNSFileHandle+UniqueName.m in Sources */, 8B7651E11D9C89B800DB2C59 /* GTMRoundedRectPath.m in Sources */, 8B82CF021D9C1C3B007182AA /* GTMStringEncoding.m in Sources */, @@ -666,6 +675,7 @@ 8B82CF391D9C2373007182AA /* GTMLoggerRingBufferWriterTest.m in Sources */, 8B7651D91D9C873200DB2C59 /* GTMLogger+ASLTest.m in Sources */, 8B82CF351D9C2353007182AA /* GTMMethodCheckTest.m in Sources */, + 8B5769B121CD7C8300D924D3 /* GTMTimeUtilsTest.m in Sources */, 8B82CF481D9C2373007182AA /* GTMSystemVersionTest.m in Sources */, 8B82CF2C1D9C1CC5007182AA /* GTMStringEncodingTest.m in Sources */, 8B82CF4D1D9C2385007182AA /* GTMUIFont+LineHeightTest.m in Sources */, diff --git a/GoogleToolboxForMac.podspec b/GoogleToolboxForMac.podspec index 2a78a00..2d850c0 100644 --- a/GoogleToolboxForMac.podspec +++ b/GoogleToolboxForMac.podspec @@ -151,6 +151,10 @@ Pod::Spec.new do |s| sp.dependency 'GoogleToolboxForMac/Defines', "#{s.version}" end + s.subspec 'TimeUtils' do |sp| + sp.source_files = 'Foundation/GTMTimeUtils.{h,m}' + end + s.subspec 'iPhone' do |sp| sp.platform = :ios, '5.0' sp.source_files = -- cgit v1.2.3