23

I'm developing an app that can only be used during a certain time of the day. I can't get the local device time because the user can easily change the device time thereby allowing access to the application for any time of the day.

Is there a way to get the current date and time from an Apple server (or if not,) is there any other way?

summea
  • 7,390
  • 4
  • 32
  • 48
kraitz
  • 345
  • 1
  • 3
  • 13

8 Answers8

12

I think that you should be using an Internet time server. They are using a standardized protocol called NTP. The iOS has built-in support for reading NTP server time, but this is not accessible to you as an application developer. Either you implement this yourself, or you could maybe use an open source library, like ios-ntp or HS NTP. I have not used any of them myself. It is probably a good idea to check the position of the device and figure out the timezone, to get a real bulletproof solution.

Here you could read more about the servers and stuff; NIST Internet Time Service

Ideally, you should have a list of servers in the application. If one of them fails, you call the next NTP server in the list.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Johan Karlsson
  • 1,136
  • 1
  • 14
  • 37
  • @kraitz I found an open source implementation that you maybe could use. The answer is updated accordingly. – Johan Karlsson Feb 17 '12 at 08:27
  • ios-ntp contains very much vendor's code. And class names are not very good to include them in project. – kelin Aug 20 '14 at 12:30
  • Current version of ios-ntp have an critical error make network time you got is wrong. You can use `https://github.com/huynguyencong/NHNetworkTime` as a replacement – huynguyen Sep 20 '15 at 17:59
5
- (NSDate *) CurrentDate 
{
    if ([self hasInternetConnectivity]) // this tests Internet connectivity based off Apple's Reachability sample code
    {
        NSSURL * scriptUrl = [NSURL URLWithString: @"http:// <yoursite>. Com / <the 2 line php script>. Php"];
        NSData * data = [NSData dataWithContentsOfURL: scriptUrl];

        if (data! = nil) {
            NSString * tempString = [NSString stringWithUTF8String: [data bytes]];
            NSDate * currDate = [NSDate dateWithTimeIntervalSince1970: [tempString doubleValue]];
            NSLog (@ "String returned from the site is:% @ and date is:% @", tempString, [currDate description]);
           return currDate;
             }
        else {
           NSLog (@ "nsdata download failed");
           return [NSDate date];
        }
    }
    else {
    NSLog (@ "InternetConnectivity failed");
        return [NSDate date];
    }
}
Duck
  • 34,902
  • 47
  • 248
  • 470
Karan Shah
  • 744
  • 5
  • 15
  • What kind of server are we talking about here? Do you need to implement your own server? – Johan Karlsson Jan 31 '12 at 06:56
  • i think what shah mentioned is to host a php file on a server which prints the current date time. the code above will read the text and return as a NSDate. if i'm not wrong. – kraitz Jan 31 '12 at 07:50
  • This example would be more helpful if there was a sample server implementation code. Right now it does not feel complete. – Johan Karlsson Nov 08 '12 at 06:44
  • Bear in mind that this doesn't take into account the time for the network request to complete. For most situations thats probably alright though – ADAM Mar 08 '13 at 01:56
  • This code had a bug when I implemented it in my app, for users with weak internet connections. I implemented a fix in my own answer below, and I added the server code for anyone who needs that too. – Nerrolken Nov 19 '17 at 20:07
2

You can use that open source to get time from default NTP server, or choose your server: https://github.com/huynguyencong/NHNetworkTime

[[NHNetworkClock sharedNetworkClock] syncWithComplete:^{
        NSLog(@"%s - Time synced %@", __PRETTY_FUNCTION__, [NSDate networkDate]);
    }];

And use:

NSDate *networkDate = [NSDate networkDate];
huynguyen
  • 7,616
  • 5
  • 35
  • 48
1

I believe your requirement can be met by some web services which return json data.

One of them are jsontest.com: http://date.jsontest.com. Here is what it returns:

{
   "time": "02:11:29 AM",
   "milliseconds_since_epoch": 1513563089492,
   "date": "12-18-2017"
} 

The "milliseconds_since_epoch" represents milliseconds since 1970, so it's easy to convert to Date,by using Date.init(timeIntervalSince1970: milliseconds_since_epoch/1000). Then we can use Calendar class to get local time.

Eric Yuan
  • 1,213
  • 11
  • 13
0

Karan's answer had some typos, and also failed to check for a response of "0" instead of just "nil". (I implemented it in my app and I got several false-positives from weak connections or unauthorized networks.)

I've adapted it below, and added the corresponding server code for those who don't know what Karan was referring to. This is implemented as a category method in an NSDate extension, so you can call it using [NSDate verifiedDate];

+(NSDate*)verifiedDate {
    if([self hasInternetConnectivity]) {
        NSURL *scriptUrl = [NSURL URLWithString:@"http://[yourwebsite].com/timestamp.php"];
        NSData *data = [NSData dataWithContentsOfURL: scriptUrl];

        if(data != nil) {
            NSString *tempString = [NSString stringWithUTF8String:[data bytes]];

            if([tempString doubleValue] > 946684800) { // Date is at least later than 2000 AD, or else something went wrong
                NSDate *currDate = [NSDate dateWithTimeIntervalSince1970:[tempString doubleValue]];
                NSLog(@"verifiedDate: String returned from the site is: %@ and date is: %@", tempString, [currDate description]);
                return currDate;
            } else {
                NSLog(@"verifiedDate: Server returned false timestamp (%@)", tempString);
                return [NSDate date];
            }
        } else {
            NSLog(@"verifiedDate: NSData download failed");
            return [NSDate date];
        }
    } else {
        NSLog(@"verifiedDate: InternetConnectivity failed");
        return [NSDate date];
    }
}

And here's the server code, stored in your server's root folder as "timestamp.php"

<?php
    echo(time());
?>
Nerrolken
  • 1,975
  • 3
  • 24
  • 53
0

Here is my code, using 2 different web services in case one of them is down:

NSString *urlString = [NSString stringWithFormat:@"http://date.jsontest.com"];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
NSError *error;

NSNumber *milliSecondsSince1970 = nil;

if (data != nil) {
    NSDictionary *json =[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
    milliSecondsSince1970 = [json valueForKey:@"milliseconds_since_epoch"];

    NSLog(@"milliSecondsSince1970 = %@", milliSecondsSince1970);
}
else {
    urlString = [NSString stringWithFormat:@"https://currentmillis.com/time/seconds-since-unix-epoch.php"];
    url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    data = [[NSData alloc] initWithContentsOfURL:url];

    if (data!=nil) {

        NSString *dbleStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        double dble = [dbleStr doubleValue]*1000;
        milliSecondsSince1970 = [NSNumber   numberWithDouble:dble];
        NSLog(@"milliSecondsSince1970 currentMillis = %@", milliSecondsSince1970);
    }

    else {
        double dateIntervalInSecondsSince1970 = [NSDate date].timeIntervalSince1970*1000;
        milliSecondsSince1970 = [NSNumber numberWithDouble:dateIntervalInSecondsSince1970] ;

        NSLog(@"milliSecondsSince1970 NSDate = %@", milliSecondsSince1970);
    }

}
0

If you don’t want use other web services you can validate the receipt every time the user opens the app , apple will return to you a json file which includes current gmt , I use the receipt to check for subscription status and I fetch the time from this file

-1

Here is a minimalistic function that gets the timestamp from apple's ntp server. It gets the time from time.apple.com without any additional libraries.

+ (NSDate *)lsDateFromOnlineServer
{
    struct hostent *server = gethostbyname("time.apple.com");
    if (!server)
        return nil;
    
    int udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (udpSocket < 0)
        return nil;
    
    struct timeval timeout;
    timeout.tv_sec = 30;
    timeout.tv_usec = 0;
    setsockopt(udpSocket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
    
    struct sockaddr_in socketAddress = {0};
    socketAddress.sin_family = AF_INET;
    socketAddress.sin_port = htons(123);
    memcpy(&socketAddress.sin_addr, server->h_addr_list[0], server->h_length);
    
    if (connect(udpSocket, (struct sockaddr *)&socketAddress, sizeof(socketAddress)) < 0) {
        close(udpSocket);
        return nil;
    }
    
    unsigned char ntpPacket[48] = {0};
    ntpPacket[0] = 0x1B;
    
    if (write(udpSocket, &ntpPacket, sizeof(ntpPacket)) < 0) {
        close(udpSocket);
        return nil;
    }
    
    if (read(udpSocket, &ntpPacket, sizeof(ntpPacket)) < 0) {
        close(udpSocket);
        return nil;
    }
    
    close(udpSocket);
    
    NSTimeInterval ntpTimestampDelta = 2208988800u;
    NSTimeInterval ntpTimestamp = ntohl(*((uint32_t *)(ntpPacket + 40)));
    NSTimeInterval ntpOverflowDelta = ntpTimestamp < ntpTimestampDelta ? 0xFFFFFFFF : 0;
    
    if (ntpTimestamp == 0)
        return nil;
    
    NSTimeInterval timestamp = ntpTimestamp - ntpTimestampDelta + ntpOverflowDelta;
    return [NSDate dateWithTimeIntervalSince1970:timestamp];
}

Note that it uses synchronous socket so you might want to use it from a background thread. Also it is included in my library with various extensions: LSCategories

Leszek Szary
  • 9,763
  • 4
  • 55
  • 62