cssmdatetime.cpp   [plain text]


/*
 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 * cssmdatetime.cpp -- CSSM date and time utilities for the Mac
 */

#ifdef __MWERKS__
#define _CPP_CSSM_DATE_TIME_UTILS
#endif

#include "cssmdatetime.h"

#include <string.h>
#include <stdio.h>
#include <security_utilities/errors.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <CoreFoundation/CFDate.h>
#include <CoreFoundation/CFTimeZone.h>
#include <ctype.h>
#include <stdlib.h>

namespace Security
{

namespace CSSMDateTimeUtils
{

#define UTC_TIME_NOSEC_LEN			11
#define UTC_TIME_STRLEN				13
#define GENERALIZED_TIME_STRLEN		15
#define LOCALIZED_UTC_TIME_STRLEN	17
#define LOCALIZED_TIME_STRLEN		19
#define MAX_TIME_STR_LEN			30


void
GetCurrentMacLongDateTime(sint64 &outMacDate)
{
	CFTimeZoneRef timeZone = CFTimeZoneCopyDefault();
	CFAbsoluteTime absTime = CFAbsoluteTimeGetCurrent();
	absTime += CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
	CFRelease(timeZone);
	outMacDate = sint64(double(absTime + kCFAbsoluteTimeIntervalSince1904));
}

void
TimeStringToMacSeconds (const CSSM_DATA &inUTCTime, uint32 &ioMacDate)
{
    sint64 ldt;
    TimeStringToMacLongDateTime(inUTCTime, ldt);
    ioMacDate = uint32(ldt);
}

/*
 * Given a CSSM_DATA containing either a UTC-style or "generalized time"
 * time string, convert to 32-bit Mac time in seconds.
 * Returns nonzero on error. 
 */
void
TimeStringToMacLongDateTime (const CSSM_DATA &inUTCTime, sint64 &outMacDate)
{
	char 		szTemp[5];
	unsigned 	len;
	int 		isUtc;
	sint32 		x;
	sint32 		i;
	char 		*cp;

	CFGregorianDate date;
	::memset( &date, 0, sizeof(date) );

	if ((inUTCTime.Data == NULL) || (inUTCTime.Length == 0))
    {
    	MacOSError::throwMe(paramErr);
  	}

  	/* tolerate NULL terminated or not */
  	len = inUTCTime.Length;
  	if (inUTCTime.Data[len - 1] == '\0')
  		len--;

  	switch(len)
    {
  		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
  			isUtc = 1;
  			break;
  		case GENERALIZED_TIME_STRLEN:	// 4-digit year
  			isUtc = 0;
  			break;
  		default:						// unknown format 
            MacOSError::throwMe(paramErr);
  	}

  	cp = (char *)inUTCTime.Data;
  	
	/* check that all characters except last are digits */
    for(i=0; i<(sint32)(len - 1); i++) {
		if ( !(isdigit(cp[i])) ) {
            MacOSError::throwMe(paramErr);
		}
	}

  	/* check last character is a 'Z' */
  	if(cp[len - 1] != 'Z' )	{
        MacOSError::throwMe(paramErr);
  	}

  	/* YEAR */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	if(!isUtc) {
		/* two more digits */
		szTemp[2] = *cp++;
		szTemp[3] = *cp++;
		szTemp[4] = '\0';
	}
	else { 
		szTemp[2] = '\0';
	}
	x = atoi( szTemp );
	if(isUtc) {
		/* 
		 * 2-digit year. 
		 *   0  <= year <= 50 : assume century 21
		 *   50 <  year <  70 : illegal per PKIX
		 *   70 <  year <= 99 : assume century 20
		 */
		if(x <= 50) {
			x += 100;
		}
		else if(x < 70) {
            MacOSError::throwMe(paramErr);
		}
		/* else century 20, OK */

		/* bug fix... we need to end up with a 4-digit year! */
		x += 1900;
	}
  	/* by definition - tm_year is year - 1900 */
  	//tmp->tm_year = x - 1900;
  	date.year = x;

  	/* MONTH */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	/* in the string, months are from 1 to 12 */
	if((x > 12) || (x <= 0)) {
        MacOSError::throwMe(paramErr);
	}
	/* in a tm, 0 to 11 */
  	//tmp->tm_mon = x - 1;
  	date.month = x;

 	/* DAY */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	/* 1..31 in both formats */
	if((x > 31) || (x <= 0)) {
        MacOSError::throwMe(paramErr);
	}
  	//tmp->tm_mday = x;
  	date.day = x;

	/* HOUR */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	if((x > 23) || (x < 0)) {
        MacOSError::throwMe(paramErr);
	}
	//tmp->tm_hour = x;
	date.hour = x;

  	/* MINUTE */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	if((x > 59) || (x < 0)) {
        MacOSError::throwMe(paramErr);
	}
  	//tmp->tm_min = x;
  	date.minute = x;

  	/* SECOND */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
  	x = atoi( szTemp );
	if((x > 59) || (x < 0)) {
        MacOSError::throwMe(paramErr);
	}
  	//tmp->tm_sec = x;
  	date.second = x;

	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
	CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(date, timeZone);
	CFRelease(timeZone);

	// Adjust abstime to local timezone
	timeZone = CFTimeZoneCopyDefault();
	absTime += CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
	CFRelease(timeZone);

	outMacDate = sint64(double(absTime + kCFAbsoluteTimeIntervalSince1904));
}

void MacSecondsToTimeString(uint32 inMacDate, uint32 inLength, void *outData)
{
    sint64 ldt = sint64(uint64(inMacDate));
    MacLongDateTimeToTimeString(ldt, inLength, outData);
}

void MacLongDateTimeToTimeString(const sint64 &inMacDate,
                                        uint32 inLength, void *outData)
{
	// @@@ this code is close, but on the fringe case of a daylight savings time it will be off for a little while
	CFAbsoluteTime absTime = inMacDate - kCFAbsoluteTimeIntervalSince1904;

	// Remove local timezone component from absTime
	CFTimeZoneRef timeZone = CFTimeZoneCopyDefault();
	absTime -= CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
	CFRelease(timeZone);

	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
	CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(absTime, timeZone);
	CFRelease(timeZone);

    if (inLength == 16)
    {
        sprintf((char *)(outData), "%04d%02d%02d%02d%02d%02dZ",
                int(date.year % 10000), date.month, date.day,
                date.hour, date.minute, int(date.second));
    }
    else if (inLength == 14)
    {
 		/* UTC - 2 year digits - code which parses this assumes that
		 * (2-digit) years between 0 and 49 are in century 21 */
        sprintf((char *)(outData), "%02d%02d%02d%02d%02d%02dZ",
                int(date.year % 100), date.month, date.day,
                date.hour, date.minute, int(date.second));
    }
    else
        MacOSError::throwMe(paramErr);
}

void
CFDateToCssmDate(CFDateRef date, char *outCssmDate)
{
	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
	CFGregorianDate gd = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime(date), timeZone);
	sprintf(outCssmDate, "%04d%02d%02d%02d%02d%02dZ", (int)gd.year, gd.month, gd.day, gd.hour, gd.minute, (unsigned int)gd.second);
	CFRelease(timeZone);
}

void
CssmDateToCFDate(const char *cssmDate, CFDateRef *outCFDate)
{
	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
	CFGregorianDate gd;
	unsigned int year, month, day, hour, minute, second;
	sscanf(cssmDate, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second);
	gd.year = year;
	gd.month = month;
	gd.day = day;
	gd.hour = hour;
	gd.minute = minute;
	gd.second = second;
	*outCFDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
	CFRelease(timeZone);
}

int
CssmDateStringToCFDate(const char *cssmDate, unsigned int len, CFDateRef *outCFDate)
{
	CFTimeZoneRef timeZone;
	CFGregorianDate gd;
	CFTimeInterval ti=0;
	char szTemp[5];
	unsigned isUtc=0, isLocal=0, x, i;
	unsigned noSeconds=0;
	char *cp;

	if((cssmDate == NULL) || (len == 0) || (outCFDate == NULL))
    	return 1;
  	
  	/* tolerate NULL terminated or not */
  	if(cssmDate[len - 1] == '\0')
  		len--;

  	switch(len) {
		case UTC_TIME_NOSEC_LEN:		// 2-digit year, no seconds, not y2K compliant
   			isUtc = 1;
			noSeconds = 1;
  			break;
 		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
  			isUtc = 1;
  			break;
  		case GENERALIZED_TIME_STRLEN:	// 4-digit year
			//isUtc = 0;
  			break;
		case LOCALIZED_UTC_TIME_STRLEN:	// "YYMMDDhhmmssThhmm" (where T=[+,-])
			isUtc = 1;
			// deliberate fallthrough
		case LOCALIZED_TIME_STRLEN:		// "YYYYMMDDhhmmssThhmm" (where T=[+,-])
			isLocal = 1;
			break;
  		default:						// unknown format 
  			return 1;
  	}
  	
  	cp = (char *)cssmDate;
  	
	/* check that all characters except last (or timezone indicator, if localized) are digits */
	for(i=0; i<(len - 1); i++) {
		if ( !(isdigit(cp[i])) )
			if ( !isLocal || !(cp[i]=='+' || cp[i]=='-') )
				return 1;
	}
	/* check last character is a 'Z', unless localized */
	if(!isLocal && cp[len - 1] != 'Z' )	{
		return 1;
	}

  	/* YEAR */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	if(!isUtc) {
		/* two more digits */
		szTemp[2] = *cp++;
		szTemp[3] = *cp++;
		szTemp[4] = '\0';
	}
	else { 
		szTemp[2] = '\0';
	}
	x = atoi( szTemp );
	if(isUtc) {
		/* 
		 * 2-digit year. 
		 *   0  <= year <  50 : assume century 21
		 *   50 <= year <  70 : illegal per PKIX
		 *   70 <  year <= 99 : assume century 20
		 */
		if(x < 50) {
			x += 2000;
		}
		else if(x < 70) {
			return 1;
		}
		else {
			/* century 20 */
			x += 1900;			
		}
	}
	gd.year = x;

  	/* MONTH */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	/* in the string, months are from 1 to 12 */
	if((x > 12) || (x <= 0)) {
    	return 1;
	}
	gd.month = x;

 	/* DAY */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	/* 1..31 in both formats */
	if((x > 31) || (x <= 0)) {
		return 1;
	}
	gd.day = x;

	/* HOUR */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	if((x > 23) || (x < 0)) {
		return 1;
	}
	gd.hour = x;

  	/* MINUTE */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	if((x > 59) || (x < 0)) {
		return 1;
	}
	gd.minute = x;

  	/* SECOND */
	if(noSeconds) {
		gd.second = 0;
	}
	else {
		szTemp[0] = *cp++;
		szTemp[1] = *cp++;
		szTemp[2] = '\0';
		x = atoi( szTemp );
		if((x > 59) || (x < 0)) {
			return 1;
		}
		gd.second = x;
	}
	
	if (isLocal) {
		/* ZONE INDICATOR */
		ti = (*cp++ == '+') ? 1 : -1;
	  	/* ZONE HH OFFSET */
		szTemp[0] = *cp++;
		szTemp[1] = *cp++;
		szTemp[2] = '\0';
		x = atoi( szTemp ) * 60 * 60;
		ti *= x;
	  	/* ZONE MM OFFSET */
		szTemp[0] = *cp++;
		szTemp[1] = *cp++;
		szTemp[2] = '\0';
		x = atoi( szTemp ) * 60;
		ti += ((ti < 0) ? (x*-1) : x);
	}
	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, ti);
	if (!timeZone) return 1;
	*outCFDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
	CFRelease(timeZone);
	
	return 0;
}

}; // end namespace CSSMDateTimeUtils

} // end namespace Security