Hi, I finished the development of the Time module, at least until the unit tests from Anish get commited ;-).
The changes done are basically three: * Corrected some minor bug which prevented the local calendar being constructed in the correct way. * Finished UTC-ASN1 and Generalized-ASN1 string handling. * Modified the API to reflect that 'ASN1' is really 'UTC-ASN1'. Find the patch below. Cheers, -Aleksander # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: [EMAIL PROTECTED] # target_branch: file:///home/aleksander/Development/libgnupdf_repo\ # /trunk/ # testament_sha1: 41dcfcab92a845d23dd883c4f4e326e858d18c88 # timestamp: 2008-07-25 19:26:01 +0200 # base_revision_id: [EMAIL PROTECTED] # # Begin patch === modified file 'ChangeLog' --- ChangeLog 2008-07-25 13:30:00 +0000 +++ ChangeLog 2008-07-25 17:25:07 +0000 @@ -1,3 +1,26 @@ +2008-07-25 Aleksander Morgado <[EMAIL PROTECTED]> + + * src/base/pdf-time.c (pdf_time_get_cal): Function simplified to avoid + using pdf_i64_t when not really needed. Also small bugfix in that + function to correctly move the time value to local time depending on + the gmt_offset. + + * src/base/pdf-time-string.h (pdf_time_to_string_utc_asn1): Function + renamed from `pdf_time_to_string_asn1'. + (pdf_time_from_string_utc_asn1): Function renamed from + `pdf_time_from_string_asn1'. + + * src/base/pdf-time-string.c: Implemented to/from routines for + UTC ASN1 and Generalized ASN1 date formats, as specified in the ASN1 + reference. + (pdf_time_from_string_pdf): Function simplified. + (pdf_time_from_string_iso8601): Function simplified. + + * doc/gnupdf.texi: Renamed enumeration `PDF_TIME_FORMAT_ASN1' to + `PDF_TIME_FORMAT_UTC_ASN1'. + + * src/base/pdf-time.h: Idem. + 2008-07-25 Jose E. Marchesi <[EMAIL PROTECTED]> * src/base/pdf-list.h: Inline semantics in MacosX. === modified file 'doc/gnupdf.texi' --- doc/gnupdf.texi 2008-07-18 07:41:04 +0000 +++ doc/gnupdf.texi 2008-07-25 17:25:07 +0000 @@ -4624,10 +4624,10 @@ PDF date strings. @item PDF_TIME_FORMAT_ISO_8601 ISO 8601 date and time strings. [EMAIL PROTECTED] PDF_TIME_FORMAT_ASN1 [EMAIL PROTECTED] PDF_TIME_FORMAT_UTC_ASN1 UTC ASN1 date. @item PDF_TIME_FORMAT_GENERALIZED_ASN1 -ASN1 date. +Generalized ASN1 date. @end table @end deftp === modified file 'src/base/pdf-time-string.c' --- src/base/pdf-time-string.c 2008-07-13 19:50:22 +0000 +++ src/base/pdf-time-string.c 2008-07-25 17:23:24 +0000 @@ -31,46 +31,35 @@ #include <pdf-time-string.h> +/* In order to use this macro, make sure that every byte represents a digit */ +#define __GET_FIELD2(str,start,dest) \ + (dest += ((str[start]-48)*10 + (str[start+1]-48))); + + +/* Macro to check if given bytes are digits or not */ +#define __CHECK_MASK(mask, masklength, str, i) while(i<masklength) { \ + if(mask & (1 << i)) { \ + if((str[i] < '0') || \ + (str[i] > '9')) { \ + PDF_DEBUG_BASE("Expected digit and found '%c' in '%s'", str[i], str); \ + return PDF_EBADDATA; \ + } \ + } \ + i++; \ +} + /* Maximum length of strings, including trailing NUL */ #define PDF_MAX_ISO8601_STR_LENGTH 30 +#define PDF_MAX_UTCASN1_STR_LENGTH 19 +#define PDF_MAX_GENASN1_STR_LENGTH 21 #define PDF_MAX_PDFDATE_STR_LENGTH 24 - -/* Mask to check if a given expected character is actually a digit or not */ - - - -pdf_status_t -pdf_time_from_string_pdf(pdf_time_t time_var, - const pdf_char_t *time_str) +static pdf_status_t +pdf_time_check_string_pdf(const pdf_char_t *time_str, + const pdf_size_t time_str_length) { - /* - * From PDF Reference 1.7: ( D:YYYYMMDDHHmmSSOHH'mm' ) - * From ISO 32000: ( D:YYYYMMDDHHmmSSOHH'mm ) - * - * Notes: Year is mandatory, all the other fields may appear if the preceding - * also appear. - * - * D: = string "D:" - * YYYY = four-digit year - * MM = two-digit month (01=January, etc.) - * DD = two-digit day of month (01 through 31) - * HH = two digits of hour (00 through 23) - * mm = two digits of minute (00 through 59) - * SS = two digits of second (00 through 59) - * O = either '+', '-' or 'Z' - * HH = two digits of hour (00 through 23) for the GMT offset - * ' = string "'" - * MM = two digits of minute (00 through 59) for the GMT offset - * ' = string "'" (NOTE: Mandatory in 1.7, optional in ISO32000) - */ - struct pdf_time_cal_s calendar; - pdf_char_t duplicate_field[5]; - pdf_status_t ret_code; - pdf_i32_t gmt_offset = 0; - pdf_i32_t i; - pdf_size_t time_str_length = strlen((char *)time_str); - + pdf_i32_t i; + /* Check minimum length D:YYYY */ if(time_str_length < 6) { @@ -82,35 +71,20 @@ if(strncmp((char *)time_str,"D:",2) != 0) { PDF_DEBUG_BASE("Invalid PDF time string (no prefix): '%s'", - time_str); + time_str); return PDF_EBADDATA; } - + /* We need to check the input characters are digits when we expect digits and - * the opposite as well. Remember that for unconvertable bytes, atoi returns - * zero, so we must really be sure we pass digits to atoi */ - + * the opposite as well. */ + #define PDF_STRING_DIGIT_MASK 0x36FFFC /* 0011 0110 1111 1111 1111 1100 */ - + /* Don't really need to check again two first bytes, as already done before */ i = 2; - while(i < time_str_length) - { - if(PDF_STRING_DIGIT_MASK & (1 << i)) - { - /* Expected a digit at position 'i' */ - if((time_str[i] < '0') || \ - (time_str[i] > '9')) - { - PDF_DEBUG_BASE("Expected digit and found '%c' in '%s'", - time_str[i], time_str); - return PDF_EBADDATA; - } - } - /* Update index */ - i++; - } + __CHECK_MASK(PDF_STRING_DIGIT_MASK, time_str_length, time_str, i); + /* Check time zone definer */ if((time_str_length >=16) && \ (time_str[16] != 'Z') && \ @@ -118,10 +92,10 @@ (time_str[16] != '-')) { PDF_DEBUG_BASE("Invalid time zone definer '%c' found in '%s'", - time_str[16], time_str); + time_str[16], time_str); return PDF_EBADDATA; } - + /* Check additional ' characters. Remember that the last ' character is * mandatory in PDF Ref 1.7 but optional in ISO32000*/ if(((time_str_length >= 19) && \ @@ -130,75 +104,117 @@ (time_str[22] != '\''))) { PDF_DEBUG_BASE("Invalid separator found ('%c' or '%c') in '%s'", - time_str[19], time_str[22], time_str); + time_str[19], time_str[22], time_str); return PDF_EBADDATA; } - + return PDF_OK; +} + + +pdf_status_t +pdf_time_from_string_pdf(pdf_time_t time_var, + const pdf_char_t *time_str) +{ + /* + * From PDF Reference 1.7: ( D:YYYYMMDDHHmmSSOHH'mm' ) + * From ISO 32000: ( D:YYYYMMDDHHmmSSOHH'mm ) + * + * Notes: Year is mandatory, all the other fields may appear if the preceding + * also appear. + * + * D: = string "D:" + * YYYY = four-digit year + * MM = two-digit month (01=January, etc.) + * DD = two-digit day of month (01 through 31) + * HH = two digits of hour (00 through 23) + * mm = two digits of minute (00 through 59) + * SS = two digits of second (00 through 59) + * O = either '+', '-' or 'Z' + * HH = two digits of hour (00 through 23) for the GMT offset + * ' = string "'" + * MM = two digits of minute (00 through 59) for the GMT offset + * ' = string "'" (NOTE: Mandatory in 1.7, optional in ISO32000) + */ + struct pdf_time_cal_s calendar; + pdf_status_t ret_code; + pdf_i32_t gmt_offset = 0; + pdf_size_t time_str_length = strlen((char *)time_str); + + ret_code = pdf_time_check_string_pdf(time_str, time_str_length); + if(ret_code != PDF_OK) + { + PDF_DEBUG_BASE("Input Date in PDF format is not valid (%s)", + time_str); + return ret_code; + } /* Reset calendar (all integers to zero) */ memset(&calendar, 0, sizeof(calendar)); - -#define __GET_FIELD(str,start,length,dest) do { \ - memcpy(&duplicate_field[0], &str[start], length); \ - duplicate_field[length]=0; \ - dest += atoi((char *)&duplicate_field[0]); \ -} while(0) - - - /* Get year */ - __GET_FIELD(time_str, 2, 4, calendar.year); - /* Get month */ - if(time_str_length >= 8) /* D:YYYYMM */ + while(1) { - __GET_FIELD(time_str, 6, 2, calendar.month); + /* Get century */ + __GET_FIELD2(time_str, 2, calendar.year); + calendar.year *= 100; + /* Get year in century */ + __GET_FIELD2(time_str, 4, calendar.year); + /* more than year ? */ + if(time_str_length == 6) + break; + + /* Get month */ + __GET_FIELD2(time_str, 6, calendar.month); + /* more than month ? */ + if(time_str_length == 8) + break; + /* Get day */ - if(time_str_length >= 10) /* D:YYYYMMDD */ - { - __GET_FIELD(time_str, 8, 2, calendar.day); - /* Get hour */ - if(time_str_length >= 12) /* D:YYYYMMDDHH */ - { - __GET_FIELD(time_str, 10, 2, calendar.hour); - /* Get minutes */ - if(time_str_length >= 14) /* D:YYYYMMDDHHmm */ - { - __GET_FIELD(time_str, 12, 2, calendar.minute); - /* Get seconds */ - if(time_str_length >= 16) /* D:YYYYMMDDHHmmSS */ - { - __GET_FIELD(time_str, 14, 2, calendar.second); - /* Get time zone offset hours */ - if(time_str_length >= 19) /* D:YYYYMMDDHHmmSS0HH */ - { - __GET_FIELD(time_str, 17, 2, calendar.gmt_offset); - /* And convert it in minutes */ - calendar.gmt_offset *= 60; - - /* Get time zone offset minutes */ - if(time_str_length >= 22) /* D:YYYYMMDDHHmmSS0HH'MM */ - { - __GET_FIELD(time_str, 20, 2, calendar.gmt_offset); - /* And convert it in minutes */ - calendar.gmt_offset *= 60; - } - - /* Convert from minutes to seconds */ - calendar.gmt_offset *= 60; - - /* Set proper sign */ - if(time_str[16]=='-') - { - calendar.gmt_offset *= (-1); - } - } - } - } - } - } + __GET_FIELD2(time_str, 8, calendar.day); + /* more than day ? */ + if(time_str_length == 10) + break; + + /* Get hour */ + __GET_FIELD2(time_str, 10, calendar.hour); + /* more than hour ? */ + if(time_str_length == 12) + break; + + /* Get minute */ + __GET_FIELD2(time_str, 12, calendar.minute); + /* more than minute ? */ + if(time_str_length == 14) + break; + + /* Get second */ + __GET_FIELD2(time_str, 14, calendar.second); + /* more than second ? */ + if(time_str_length <= 17) /* Considering timezone offset separator */ + break; + + /* Get timezone offset hours */ + __GET_FIELD2(time_str, 17, calendar.gmt_offset); + /* And convert it in minutes */ + calendar.gmt_offset *= 60; + + /* Get timezone offset minutes */ + if(time_str_length > 19) + { + __GET_FIELD2(time_str, 20, calendar.gmt_offset); + } + + /* Convert from minutes to seconds */ + calendar.gmt_offset *= 60; + + /* Set proper sign */ + if(time_str[16]=='-') + { + calendar.gmt_offset *= (-1); + } + + /* Stop loop :-) */ + break; } - -#undef __GET_FIELD /* Get time value from break-down UTC calendar !*/ ret_code = pdf_time_from_cal(time_var, &calendar); @@ -211,20 +227,299 @@ return ret_code; } + +static pdf_status_t +pdf_time_check_string_utc_asn1(const pdf_char_t *time_str, + const pdf_size_t time_str_length) +{ + pdf_i32_t i; + pdf_i32_t base_mask; + pdf_i32_t mask_length; + pdf_bool_t with_gmt_offset; + +#define UTCASN1_STRING_DIGIT_MASK1 0x03FF /* 0000 0011 1111 1111 */ +#define UTCASN1_STRING_DIGIT_MASK2 0x0FFF /* 0000 1111 1111 1111 */ + + /* Check length */ + if((time_str_length == 11) || \ + (time_str_length == 15)) + { + base_mask = UTCASN1_STRING_DIGIT_MASK1; + mask_length = 10; + } + else if((time_str_length == 13) || \ + (time_str_length == 17)) + { + base_mask = UTCASN1_STRING_DIGIT_MASK2; + mask_length = 12; + } + else + { + PDF_DEBUG_BASE("Invalid UTC-ASN1 time string (invalid length): '%s'", + time_str); + return PDF_EBADDATA; + } + + /* Check if GMT offset is expected */ + with_gmt_offset = (time_str_length >=15) ? PDF_TRUE : PDF_FALSE; + + /* Check extra non-digit characters */ + if((!with_gmt_offset) && \ + (time_str[time_str_length-1] != 'Z')) + { + PDF_DEBUG_BASE("Expected UTC string, but not valid"); + return PDF_EBADDATA; + } + else if((with_gmt_offset) && \ + ((time_str[time_str_length-5] != '+') && \ + (time_str[time_str_length-5] != '-'))) + { + PDF_DEBUG_BASE("Expected non-UTC string, but not valid"); + return PDF_EBADDATA; + } + + /* Check mask if base string */ + i=0; + __CHECK_MASK(base_mask, mask_length, time_str, i); + /* Check mask of offset string if available */ + if(with_gmt_offset) + { + i=time_str_length-4; + __CHECK_MASK(0x000F, 4, time_str, i); + } + + return PDF_OK; +} + +static pdf_i32_t +pdf_time_get_century_in_sliding_window(pdf_i32_t year_in_century) +{ + pdf_i32_t full_year = -1; + pdf_time_t current; + struct pdf_time_cal_s current_cal; + current = pdf_time_new(); + + if((current != NULL) && \ + (pdf_time_set_to_current_utc_time(current) == PDF_OK) && \ + (pdf_time_get_utc_cal(current, ¤t_cal) == PDF_OK)) + { + /* Get century from full current year */ + pdf_i32_t century = 100 * (current_cal.year / 100); + /* Appy current century */ + full_year = century + year_in_century; + + /* Check if the century must be changed */ + if((full_year > current_cal.year) && \ + ((full_year - current_cal.year) > 50)) + { + full_year -= 100; + } + else if((full_year < current_cal.year) && \ + ((current_cal.year - full_year) > 50)) + { + full_year += 100; + } + } + + pdf_time_destroy(current); + return full_year; +} + pdf_status_t -pdf_time_from_string_asn1(pdf_time_t time_var, - const pdf_char_t *time_str) +pdf_time_from_string_utc_asn1(pdf_time_t time_var, + const pdf_char_t *time_str) { - /* TODO */ - return PDF_ERROR; + /* + * yymmddhhmmZ + * yymmddhhmmssZ + * yymmddhhmm+hhmm + * yymmddhhmm-hhmm + * yymmddhhmmss+hhmm + * yymmddhhmmss-hhmm + * + * Note: As year is only stored in 2 digits, a sliding window of [-50,+50] + * years will be used to get the century. + */ + struct pdf_time_cal_s calendar; + pdf_size_t time_str_length = strlen((char *)time_str); + + if(pdf_time_check_string_utc_asn1(time_str, time_str_length) != PDF_OK) + { + PDF_DEBUG_BASE("Input Date in UTC ASN1 format is not valid (%s)", + time_str); + return PDF_EBADDATA; + } + + /* Reset calendar (all integers to zero) */ + memset(&calendar, 0, sizeof(calendar)); + + while(1) + { + pdf_bool_t has_seconds = PDF_FALSE; + /* Get year in century */ + __GET_FIELD2(time_str, 0, calendar.year); + /* Get 4-digit year from 2-digit year */ + calendar.year = pdf_time_get_century_in_sliding_window(calendar.year); + + /* Get month */ + __GET_FIELD2(time_str, 2, calendar.month); + + /* Get day */ + __GET_FIELD2(time_str, 4, calendar.day); + + /* Get hour */ + __GET_FIELD2(time_str, 6, calendar.hour); + + /* Get minute */ + __GET_FIELD2(time_str, 8, calendar.minute); + + /* Get second if available */ + if((time_str[10] >= '0') && \ + (time_str[10] <= '9')) + { + has_seconds = PDF_TRUE; + __GET_FIELD2(time_str, 10, calendar.second); + } + + /* Check if we have GMT offset */ + if(time_str[time_str_length-1] == 'Z') + { + break; + } + + /* Get timezone offset hours */ + __GET_FIELD2(time_str, (has_seconds ? 13 : 11), calendar.gmt_offset); + /* And convert it in minutes */ + calendar.gmt_offset *= 60; + /* Get timezone offset minutes */ + __GET_FIELD2(time_str, (has_seconds ? 15 : 13), calendar.gmt_offset); + /* Convert from minutes to seconds */ + calendar.gmt_offset *= 60; + + /* Set proper sign */ + if(time_str[(has_seconds ? 12 : 10)] == '-') + { + calendar.gmt_offset *= (-1); + } + + /* Stop loop :-) */ + break; + } + + /* Get time value from break-down UTC calendar !*/ + return pdf_time_from_cal(time_var, &calendar); } pdf_status_t pdf_time_from_string_generalized_asn1(pdf_time_t time_var, const pdf_char_t *time_str) { - /* TODO */ - return PDF_ERROR; + /* + * Year: + * YYYY (eg 1997) + * Year and month: + * YYYYMM (eg 199707) + * Complete date: + * YYYYMMDD (eg 19970716) + * Complete date plus hours and minutes: + * YYYYMMDDhhmmTZD (eg 199707161920+01:00) + * Complete date plus hours, minutes and seconds: + * YYYYMMDDhhmmssTZD (eg 19970716192030+01:00) + * Complete date plus hours, minutes, seconds and a decimal fraction of a + * second + * YYYYMMDDThhmmss.sTZD (eg 1997071619:20:30.45+01:00) + * + * where: + * + * YYYY = four-digit year + * MM = two-digit month (01=January, etc.) + * DD = two-digit day of month (01 through 31) + * hh = two digits of hour (00 through 23) (am/pm NOT allowed) + * mm = two digits of minute (00 through 59) + * ss = two digits of second (00 through 59) + * s = one or more digits representing a decimal fraction of a second + * TZD = time zone designator (Z or +hh:mm or -hh:mm) + * + */ + struct pdf_time_cal_s calendar; + pdf_size_t time_str_length = strlen((char *)time_str); + + /* Check minimum length */ + if(time_str_length < 4) + { + PDF_DEBUG_BASE("Invalid Generalized ASN1 time string (too short): '%s'", + time_str); + return PDF_EBADDATA; + } + + /* Reset calendar */ + memset(&calendar, 0, sizeof(calendar)); + + while(1) + { + pdf_bool_t has_seconds = PDF_FALSE; + /* Get century */ + __GET_FIELD2(time_str, 0, calendar.year); + calendar.year *= 100; + /* Get year in century */ + __GET_FIELD2(time_str, 2, calendar.year); + /* more than year ? */ + if(time_str_length == 4) + break; + + + /* Get month */ + __GET_FIELD2(time_str, 4, calendar.month); + /* more than month ? */ + if(time_str_length == 6) + break; + + /* Get day */ + __GET_FIELD2(time_str, 6, calendar.day); + /* more than day ? */ + if(time_str_length == 8) + break; + + /* Get hour and minutes */ + __GET_FIELD2(time_str, 8, calendar.hour); + __GET_FIELD2(time_str, 10, calendar.minute); + + /* Get second if available */ + if((time_str[17] >= '0') && \ + (time_str[17] <= '9')) + { + has_seconds = PDF_TRUE; + __GET_FIELD2(time_str, 12, calendar.second); + } + + /* Note: Fractional part of seconds not considered */ + + if(time_str[time_str_length-1] == 'Z') + { + break; + } + + /* Get timezone offset hours */ + __GET_FIELD2(time_str, (time_str_length-4), calendar.gmt_offset); + /* And convert it in minutes */ + calendar.gmt_offset *= 60; + /* Get timezone offset minutes */ + __GET_FIELD2(time_str, (time_str_length-2), calendar.gmt_offset); + /* Convert from minutes to seconds */ + calendar.gmt_offset *= 60; + + /* Set proper sign */ + if(time_str[(time_str_length-5)]=='-') + { + calendar.gmt_offset *= (-1); + } + + /* Stop loop :-) */ + break; + } + + /* Get time value from break-down calendar !*/ + return pdf_time_from_cal(time_var, &calendar); } pdf_status_t @@ -259,10 +554,6 @@ * */ struct pdf_time_cal_s calendar; - pdf_char_t *duplicate; - pdf_char_t *walker; - pdf_status_t ret_code; - pdf_i32_t gmt_offset = 0; pdf_size_t time_str_length = strlen((char *)time_str); /* Check minimum length */ @@ -273,93 +564,74 @@ return PDF_EBADDATA; } - /* Initialize text walker */ - duplicate = (pdf_char_t *)pdf_alloc(time_str_length+1); - if(duplicate == NULL) - { - PDF_DEBUG_BASE("Problem allocating memory"); - return PDF_ENOMEM; - } - memcpy(duplicate, time_str, time_str_length); - walker = duplicate; - /* Reset calendar */ memset(&calendar, 0, sizeof(calendar)); - - /* Get year */ - duplicate[4] = '\0'; - calendar.year = atoi((char *)duplicate); - - /* Get month */ - if(time_str_length >= 7) + + while(1) { - duplicate[7] = '\0'; - calendar.month = atoi((char *)(&duplicate[5])); + pdf_bool_t has_seconds = PDF_FALSE; + /* Get century */ + __GET_FIELD2(time_str, 0, calendar.year); + calendar.year *= 100; + /* Get year in century */ + __GET_FIELD2(time_str, 2, calendar.year); + /* more than year ? */ + if(time_str_length == 4) + break; + + + /* Get month */ + __GET_FIELD2(time_str, 5, calendar.month); + /* more than month ? */ + if(time_str_length == 7) + break; /* Get day */ - if(time_str_length >= 10) - { - duplicate[10] = '\0'; - calendar.day = atoi((char *)(&duplicate[8])); - - /* Get hour and minutes */ - if(time_str_length >= 16+1) /* 1 is the minimum length for TZD */ - { - char next_field = duplicate[16]; - - /* Get hour */ - duplicate[13] = '\0'; - calendar.hour = atoi((char *)(&duplicate[11])); - /* Get minutes */ - duplicate[16] = '\0'; - calendar.minute = atoi((char *)(&duplicate[14])); - - /* Get Time Zone information */ - if(duplicate[time_str_length-1] == 'Z') - { - /* Time is given in UTC... do nothing */ - duplicate[time_str_length-1] = '\0'; - } - else - { - /* Need to parse time zone offset */ - pdf_i32_t hours_tz; - pdf_i32_t minutes_tz; - minutes_tz = atoi((char *)(&duplicate[time_str_length-2])); - duplicate[time_str_length-3] = '\0'; - hours_tz = atoi((char *)(&duplicate[time_str_length-5])); - - gmt_offset = 60*(minutes_tz + 60*hours_tz); - if(duplicate[time_str_length-6] == '-') - { - gmt_offset *= (-1); - } - } - - /* Read seconds if available */ - if(next_field == ':') - { - /* Ok, seconds available. Decimal part of the seconds will be - * ignored if it's available */ - duplicate[19] = '\0'; - calendar.second = atoi((char *)(&duplicate[17])); - } - } - } - } - - /* Set calendar as if it were UTC */ - calendar.gmt_offset = 0; - - /* Get time value from break-down UTC calendar !*/ - ret_code = pdf_time_from_cal(time_var, &calendar); - if(ret_code == PDF_OK) - { - /* Now set GMT offset in pdf_time_t */ - time_var->gmt_offset = gmt_offset; - } - - return ret_code; + __GET_FIELD2(time_str, 8, calendar.day); + /* more than day ? */ + if(time_str_length == 10) + break; + + /* Get hour and minutes */ + __GET_FIELD2(time_str, 11, calendar.hour); + __GET_FIELD2(time_str, 14, calendar.minute); + + /* Get second if available */ + if((time_str[17] >= '0') && \ + (time_str[17] <= '9')) + { + has_seconds = PDF_TRUE; + __GET_FIELD2(time_str, 17, calendar.second); + } + + /* Note: Fractional part of seconds not considered */ + + if(time_str[time_str_length-1] == 'Z') + { + break; + } + + /* Get timezone offset hours */ + __GET_FIELD2(time_str, (time_str_length-5), calendar.gmt_offset); + /* And convert it in minutes */ + calendar.gmt_offset *= 60; + /* Get timezone offset minutes */ + __GET_FIELD2(time_str, (time_str_length-2), calendar.gmt_offset); + /* Convert from minutes to seconds */ + calendar.gmt_offset *= 60; + + /* Set proper sign */ + if(time_str[(time_str_length-6)]=='-') + { + calendar.gmt_offset *= (-1); + } + + /* Stop loop :-) */ + break; + } + + /* Get time value from break-down calendar !*/ + return pdf_time_from_cal(time_var, &calendar); } @@ -417,18 +689,123 @@ } -/* Get Date as a string in ASN1 format */ +/* Get Date as a string in UTC-ASN1 format */ pdf_char_t * -pdf_time_to_string_asn1(const pdf_time_t time_var) +pdf_time_to_string_utc_asn1(const pdf_time_t time_var) { - return NULL; + pdf_char_t *str; + struct pdf_time_cal_s calendar; + + str = (pdf_char_t *)pdf_alloc(PDF_MAX_UTCASN1_STR_LENGTH*sizeof(pdf_char_t)); + if(str != NULL) + { + if(pdf_time_get_local_cal(time_var, &calendar) == PDF_OK) + { + pdf_i32_t smallyear; + /* Convert 4-digit year to 2-digit year */ + smallyear = calendar.year -1900; + while(smallyear > 99) + { + smallyear -= 100; + } + + if(calendar.gmt_offset != 0) + { + pdf_i32_t offset_hours; + pdf_i32_t offset_minutes; + + offset_hours = (((calendar.gmt_offset < 0) ? (-1) : (1)) * calendar.gmt_offset) / 3600; + offset_minutes = (((calendar.gmt_offset < 0) ? (-1) : (1)) * calendar.gmt_offset) % 3600; + /* yymmddhhmmss+hhmm + * yymmddhhmmss-hhmm + */ + sprintf((char *)str, "%s%d%s%d%s%d%s%d%s%d%s%d%c%s%d%s%d", \ + (smallyear < 10 ? "0" : ""), smallyear, + (calendar.month < 10 ? "0" : ""), calendar.month, + (calendar.day < 10 ? "0" : ""), calendar.day, + (calendar.hour < 10 ? "0" : ""), calendar.hour, + (calendar.minute < 10 ? "0" : ""), calendar.minute, + (calendar.second < 10 ? "0" : ""), calendar.second, + ((calendar.gmt_offset < 0) ? '-' : '+'), + (offset_hours < 10 ? "0" : ""), offset_hours, + (offset_minutes < 10 ? "0" : ""), offset_minutes); + } + else + { + /* + * yymmddhhmmssZ + */ + sprintf((char *)str, "%s%d%s%d%s%d%s%d%s%d%s%dZ", \ + (smallyear < 10 ? "0" : ""), smallyear, + (calendar.month < 10 ? "0" : ""), calendar.month, + (calendar.day < 10 ? "0" : ""), calendar.day, + (calendar.hour < 10 ? "0" : ""), calendar.hour, + (calendar.minute < 10 ? "0" : ""), calendar.minute, + (calendar.second < 10 ? "0" : ""), calendar.second); + } + } + else + { + PDF_DEBUG_BASE("Could not get local calendar from pdf_time_t..."); + pdf_dealloc(str); + str = NULL; + } + } + + return str; } /* Get Date as a string in Generalized ASN1 format */ pdf_char_t * pdf_time_to_string_generalized_asn1(const pdf_time_t time_var) { - return NULL; + pdf_char_t *str; + struct pdf_time_cal_s calendar; + + str = (pdf_char_t *)pdf_alloc(PDF_MAX_ISO8601_STR_LENGTH*sizeof(pdf_char_t)); + if(str != NULL) + { + /* YYYYMMDDhhmmssTZD (eg 19970716192030+01:00) */ + if(pdf_time_get_local_cal(time_var, &calendar) == PDF_OK) + { + if(calendar.gmt_offset != 0) + { + pdf_i32_t offset_hours; + pdf_i32_t offset_minutes; + + offset_hours = (((calendar.gmt_offset < 0) ? (-1) : (1)) * calendar.gmt_offset) / 3600; + offset_minutes = (((calendar.gmt_offset < 0) ? (-1) : (1)) * calendar.gmt_offset) % 3600; + sprintf((char *)str, "%4d%s%d%s%d%s%d%s%d%s%d%c%s%d%s%d", \ + calendar.year, + (calendar.month < 10 ? "0" : ""), calendar.month, + (calendar.day < 10 ? "0" : ""), calendar.day, + (calendar.hour < 10 ? "0" : ""), calendar.hour, + (calendar.minute < 10 ? "0" : ""), calendar.minute, + (calendar.second < 10 ? "0" : ""), calendar.second, + ((calendar.gmt_offset < 0) ? '-' : '+'), + (offset_hours < 10 ? "0" : ""), offset_hours, + (offset_minutes < 10 ? "0" : ""), offset_minutes); + } + else + { + sprintf((char *)str, "%4d%s%d%s%d%s%d%s%d%s%dZ", \ + calendar.year, + (calendar.month < 10 ? "0" : ""), calendar.month, + (calendar.day < 10 ? "0" : ""), calendar.day, + (calendar.hour < 10 ? "0" : ""), calendar.hour, + (calendar.minute < 10 ? "0" : ""), calendar.minute, + (calendar.second < 10 ? "0" : ""), calendar.second); + } + } + else + { + PDF_DEBUG_BASE("Could not get local calendar from pdf_time_t..."); + pdf_dealloc(str); + str = NULL; + } + } + + return str; } /* Get Date as a string in ISO8601 format */ @@ -444,14 +821,14 @@ /* YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00) */ if(pdf_time_get_local_cal(time_var, &calendar) == PDF_OK) { - if(calendar.gmt_offset != 0) + if(calendar.gmt_offset != 0) { pdf_i32_t offset_hours; pdf_i32_t offset_minutes; offset_hours = (((calendar.gmt_offset < 0) ? (-1) : (1)) * calendar.gmt_offset) / 3600; offset_minutes = (((calendar.gmt_offset < 0) ? (-1) : (1)) * calendar.gmt_offset) % 3600; - sprintf((char *)str, "%4d-%s%d-%s%dT%s%d:%s%d:%s%d.00%c%s%d:%s%d", \ + sprintf((char *)str, "%4d-%s%d-%s%dT%s%d:%s%d:%s%d%c%s%d:%s%d", \ calendar.year, (calendar.month < 10 ? "0" : ""), calendar.month, (calendar.day < 10 ? "0" : ""), calendar.day, @@ -464,7 +841,7 @@ } else { - sprintf((char *)str, "%4d-%s%d-%s%dT%s%d:%s%d:%s%d.00Z", \ + sprintf((char *)str, "%4d-%s%d-%s%dT%s%d:%s%d:%s%dZ", \ calendar.year, (calendar.month < 10 ? "0" : ""), calendar.month, (calendar.day < 10 ? "0" : ""), calendar.day, === modified file 'src/base/pdf-time-string.h' --- src/base/pdf-time-string.h 2008-06-25 01:22:56 +0000 +++ src/base/pdf-time-string.h 2008-07-25 17:23:24 +0000 @@ -38,8 +38,8 @@ /* Set time object contents based on Date in ASN1 format */ pdf_status_t -pdf_time_from_string_asn1(pdf_time_t time_var, - const pdf_char_t *time_str); +pdf_time_from_string_utc_asn1(pdf_time_t time_var, + const pdf_char_t *time_str); /* Set time object contents based on Date in Generalized ASN1 format */ pdf_status_t @@ -58,9 +58,9 @@ pdf_time_to_string_pdf(const pdf_time_t time_var); -/* Get Date as a string in ASN1 format */ +/* Get Date as a string in UTC-ASN1 format */ pdf_char_t * -pdf_time_to_string_asn1(const pdf_time_t time_var); +pdf_time_to_string_utc_asn1(const pdf_time_t time_var); /* Get Date as a string in Generalized ASN1 format */ pdf_char_t * === modified file 'src/base/pdf-time.c' --- src/base/pdf-time.c 2008-07-09 14:03:51 +0000 +++ src/base/pdf-time.c 2008-07-25 16:46:50 +0000 @@ -152,9 +152,9 @@ { /* Based on glibc's __offtime function */ - pdf_i64_t days; + pdf_i32_t days; pdf_i64_t aux64; - pdf_i64_t remaining; + pdf_i32_t remaining; pdf_i32_t years; pdf_i32_t months; pdf_time_t new_time_var; @@ -173,62 +173,55 @@ /* Modify time in the time object */ delta = pdf_time_span_new(); pdf_time_span_set_from_i32(&delta, time_var->gmt_offset); - pdf_time_add_span(time_var, delta); + pdf_time_add_span(new_time_var, delta); pdf_time_span_destroy(&delta); } - days = pdf_i64_new(0,0); aux64 = pdf_i64_new(0,0); - remaining = pdf_i64_new(0,0); /* Get date as days */ - pdf_i64_div_i32_divisor(&days, new_time_var->seconds, PDF_SECS_PER_DAY, &p_status); + pdf_i64_div_i32_divisor(&aux64, new_time_var->seconds, PDF_SECS_PER_DAY, &p_status); + days = pdf_i64_to_i32(aux64); /* Get time in seconds */ - pdf_i64_mod_i32_divisor(&remaining, new_time_var->seconds, PDF_SECS_PER_DAY, &p_status); - + pdf_i64_mod_i32_divisor(&aux64, new_time_var->seconds, PDF_SECS_PER_DAY, &p_status); + remaining = pdf_i64_to_i32(aux64); /* Get hours */ - pdf_i64_div_i32_divisor(&aux64, remaining, PDF_SECS_PER_HOUR, &p_status); - p_cal_time->hour = pdf_i64_to_i32(aux64); - + p_cal_time->hour = remaining / PDF_SECS_PER_HOUR; /* Get remaining */ - pdf_i64_mod_i32_divisor(&remaining, remaining, PDF_SECS_PER_HOUR, &p_status); - + remaining = remaining % PDF_SECS_PER_HOUR; /* Get minutes */ - pdf_i64_div_i32_divisor(&aux64, remaining, PDF_MINS_PER_HOUR, &p_status); - p_cal_time->minute = pdf_i64_to_i32(aux64); + p_cal_time->minute = remaining / PDF_MINS_PER_HOUR; /* Get seconds */ - pdf_i64_mod_i32_divisor(&aux64, remaining, PDF_MINS_PER_HOUR, &p_status); - p_cal_time->second = pdf_i64_to_i32(aux64); - + p_cal_time->second = remaining % PDF_MINS_PER_HOUR; /* Seems that Unix origin time was thursday */ - pdf_i64_add_i32(&aux64, days, 4, &p_status); - pdf_i64_mod_i32_divisor(&aux64, aux64, 7, &p_status); - p_cal_time->dow = pdf_i64_to_i32(aux64); + p_cal_time->dow = ((days+4)%7); - years = 1970; - /* while (days < 0 || days >= (__isleap (y) ? 366 : 365)) */ - while((pdf_i64_cmp_i32(days, 0) < 0) || \ - (pdf_i64_cmp_i32(days, \ - (pdf_time_is_leap_year_p(years) ? \ - PDF_DAYS_IN_YEAR+1 : \ - PDF_DAYS_IN_YEAR)) >= 0)) + + + while((days < 0) || \ + (days >= (pdf_time_is_leap_year_p(years) ? \ + (PDF_DAYS_IN_YEAR+1) : \ + (PDF_DAYS_IN_YEAR)))) { pdf_i32_t yg; yg = years; - pdf_i64_div_i32_divisor(&aux64, days, PDF_DAYS_IN_YEAR, &p_status); - yg += pdf_i64_to_i32(aux64); - pdf_i64_mod_i32_divisor(&aux64, days, PDF_DAYS_IN_YEAR, &p_status); - yg -= (pdf_i64_cmp_i32(aux64, 0) < 0); + /* Compute number of years (assuming all years of 365 days) between the + * origin and our date */ + yg += (days / PDF_DAYS_IN_YEAR); + /* Get number of remaining days after having added the fixed-size years + /* If the number of remaining days is less than zero, go down 1 year */ + yg -= ((days % PDF_DAYS_IN_YEAR) < 0); + #define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400) - - pdf_i64_subtraction_i32_sub(&days, days, ((yg - years)*PDF_DAYS_IN_YEAR + \ - LEAPS_THRU_END_OF (yg - 1) - \ - LEAPS_THRU_END_OF (years - 1) ), &p_status); + /* Remove number of days due to the leap years */ + days -= (((yg - years)*PDF_DAYS_IN_YEAR) + \ + (LEAPS_THRU_END_OF (yg - 1)) - \ + (LEAPS_THRU_END_OF (years - 1))); years = yg; } @@ -236,17 +229,15 @@ p_cal_time->year = years;// - 1900; for (months = 11; \ - pdf_i64_to_i32(days) < pdf_time_get_days_before_month(p_cal_time->year,months); \ + days < pdf_time_get_days_before_month(p_cal_time->year,months); \ --months) continue; - pdf_i64_subtraction_i32_sub(&days, \ - days, \ - pdf_time_get_days_before_month(p_cal_time->year,months), &p_status); + days -= pdf_time_get_days_before_month(p_cal_time->year,months); /* Set month and day of month */ p_cal_time->month = months; - p_cal_time->day = pdf_i64_to_i32(days) + 1; + p_cal_time->day = days + 1; /* Finally, set gmt offset */ p_cal_time->gmt_offset = new_time_var->gmt_offset; @@ -846,8 +837,8 @@ return pdf_time_to_string_pdf(time_var); case PDF_TIME_FORMAT_ISO_8601: return pdf_time_to_string_iso8601(time_var); - case PDF_TIME_FORMAT_ASN1: - return pdf_time_to_string_asn1(time_var); + case PDF_TIME_FORMAT_UTC_ASN1: + return pdf_time_to_string_utc_asn1(time_var); case PDF_TIME_FORMAT_GENERALIZED_ASN1: return pdf_time_to_string_generalized_asn1(time_var); default: @@ -870,8 +861,8 @@ return pdf_time_from_string_pdf(time_var, time_str); case PDF_TIME_FORMAT_ISO_8601: return pdf_time_from_string_iso8601(time_var, time_str); - case PDF_TIME_FORMAT_ASN1: - return pdf_time_from_string_asn1(time_var, time_str); + case PDF_TIME_FORMAT_UTC_ASN1: + return pdf_time_from_string_utc_asn1(time_var, time_str); case PDF_TIME_FORMAT_GENERALIZED_ASN1: return pdf_time_from_string_generalized_asn1(time_var, time_str); default: === modified file 'src/base/pdf-time.h' --- src/base/pdf-time.h 2008-07-02 00:41:19 +0000 +++ src/base/pdf-time.h 2008-07-16 20:08:02 +0000 @@ -90,7 +90,7 @@ enum pdf_time_format_e { PDF_TIME_FORMAT_PDF, PDF_TIME_FORMAT_ISO_8601, - PDF_TIME_FORMAT_ASN1, + PDF_TIME_FORMAT_UTC_ASN1, PDF_TIME_FORMAT_GENERALIZED_ASN1 }; # Begin bundle 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 7/+LuSKcKEgyz0hpgA==