/* Attention! This function returns a static string. */ static const char * pil_str (vbi_pil pil) { static char buffer[32]; switch (pil) { case VBI_PIL_TIMER_CONTROL: return "TC"; case VBI_PIL_INHIBIT_TERMINATE: return "RI/T"; case VBI_PIL_INTERRUPTION: return "INT"; case VBI_PIL_CONTINUE: return "CONT"; case VBI_PIL_NSPV: return "NSPV/END"; default: snprintf (buffer, sizeof (buffer), "%02u%02uT%02u%02u", VBI_PIL_MONTH (pil), VBI_PIL_DAY (pil), VBI_PIL_HOUR (pil), VBI_PIL_MINUTE (pil)); return buffer; } }
static vbi_bool valid_pil_lto_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, int seconds_east) { time_t t; t = valid_pil_lto_to_time (pil & VBI_PIL (/* month */ 15, /* day */ 31, /* hour */ 0, /* minute */ 0), start, seconds_east); if (unlikely ((time_t) -1 == t)) { if (VBI_ERR_INVALID_PIL == errno) { /* Annex F: "Invalid days - indefinite time window". */ *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } else { return FALSE; } } /* EN 300 231 Section 9.3. */ /* Just adding a number of seconds is safe because UTC does not observe DST and POSIX time_t does not count leap seconds. The results are unambiguous because leap seconds are inserted or removed just before midnight and we only return full hours. */ if (unlikely (t > TIME_MAX - 28 * 60 * 60)) { errno = EOVERFLOW; return FALSE; } if (VBI_PIL_HOUR (pil) < 4) { if (unlikely (t < 4 * 60 * 60)) { errno = EOVERFLOW; return FALSE; } *begin = t - 4 * 60 * 60; } else { *begin = t; } *end = t + 28 * 60 * 60; return TRUE; }
/** * @param pil Program Identification Label. * * Determines if @a pil represents a valid date and time. * * Since PILs have no year field February 29th is considered valid. * You can find out if this date is valid in a given year with the * vbi_pil_to_time() function. * * 24:00 is not valid (an unreal hour) as defined in EN 300 231 * Annex F and EIA 608-B Section 9.5.1.1. * * @returns * @c TRUE if @a pil represents a valid date and time, @c FALSE * if @a pil contains an unreal date or time (e.g. Jan 0 27:61), * a service code or unallocated code. * * @since 0.2.34 */ vbi_bool vbi_pil_is_valid_date (vbi_pil pil) { unsigned int month; unsigned int day; month = VBI_PIL_MONTH (pil); day = VBI_PIL_DAY (pil); /* Note this also checks for zero month and day. */ return (month - 1 < 12 && day - 1 < month_days[month - 1] && VBI_PIL_HOUR (pil) < 24 && VBI_PIL_MINUTE (pil) < 60); }
/** * @internal * @param pil vbi_pil to print. * @param fp Destination stream. * * Prints a vbi_pil as service code or date and time string without * trailing newline. This is intended for debugging. */ void _vbi_pil_dump (vbi_pil pil, FILE * fp) { switch (pil) { case VBI_PIL_TIMER_CONTROL: fputs ("TC", fp); break; case VBI_PIL_INHIBIT_TERMINATE: fputs ("RI/T", fp); break; case VBI_PIL_INTERRUPTION: fputs ("INT", fp); break; case VBI_PIL_CONTINUE: fputs ("CONT", fp); break; case VBI_PIL_NSPV: /* VBI_PIL_NSPV (PDC) == VBI_PIL_END (XDS) */ fputs ("NSPV/END", fp); break; default: fprintf (fp, "%05x (%02u-%02u %02u:%02u)", pil, VBI_PIL_MONTH (pil), VBI_PIL_DAY (pil), VBI_PIL_HOUR (pil), VBI_PIL_MINUTE (pil)); break; } }
/** * @param pil Program Identification Label (PIL) to convert. * @param start The most recently announced start time of the * program. If zero the current system time will be used. * @param tz A time zone name in the same format as the TZ environment * variable. If @c NULL the current value of TZ will be used. * * This function converts a PIL to a time_t in the same manner * localtime() converts a broken-down time to time_t. * * Since PILs do not contain a year field, the year is determined from * the @a start parameter, that is the most recently announced start * time of the program or "AT-1" in EN 300 231 parlance. If @a pil * contains a month more than five months after @a start, @a pil is * assumed to refer to an earlier date than @a start. * * @a pil is assumed to be a time in the time zone @a tz. @a start * will be converted to a local time in the same time zone to * determine the correct year. * * Teletext packet 8/30 format 2, VPS and DVB PDC descriptors give a * PIL relative to the time zone of the intended audience of the * program. Ideally the time zone would be specified as a geographic * area like "Europe/London", such that the function can determine * the correct offset from UTC and if daylight-saving time is in * effect at the specified date. See the documentation of the * localtime() function and the TZ environment variable for details. * * XDS Current/Future Program ID packets give a PIL relative to UTC. * Just specify time zone "UTC" in this case. * * @returns * The PIL as a time_t, that is the number of seconds since * 1970-01-01 00:00 UTC. On error the function * returns (time_t) -1: * - @a pil does not contain a valid date or time. February 29th is * a valid date only if the estimated year is a leap year. * - @a tz is empty or contains an equal sign '='. * - @a start is zero and the current system time could not be * determined. * - The time specified by @a pil, @a start and @a tz cannot be * represented as a time_t value. * - Insufficient memory was available. * * @since 0.2.34 * * @bug * This function is not thread safe unless @a tz is @c NULL. That is * a limitation of the C library which permits the conversion of a * broken-down time in an arbitrary time zone only by setting the TZ * environment variable. The function may also fail to restore the * value of TZ if insufficient memory is available. */ time_t vbi_pil_to_time (vbi_pil pil, time_t start, const char * tz) { struct tm tm; char *old_tz; time_t result; int saved_errno; if (unlikely (!vbi_pil_is_valid_date (pil))) { #if 3 == VBI_VERSION_MINOR errno = VBI_ERR_INVALID_PIL; #else errno = 0; #endif return (time_t) -1; } if (NULL != tz && 0 == strcmp (tz, "UTC")) { time_t t; t = valid_pil_lto_to_time (pil, start, /* seconds_east */ 0); #if 2 == VBI_VERSION_MINOR errno = 0; #endif return t; } if (unlikely (!localtime_tz (&tm, &old_tz, start, tz))) { #if 2 == VBI_VERSION_MINOR errno = 0; #endif return (time_t) -1; } if (unlikely (!tm_mon_mday_from_pil (&tm, pil))) { saved_errno = EOVERFLOW; goto failed; } if (unlikely (!tm_leap_day_check (&tm))) { saved_errno = VBI_ERR_INVALID_PIL; goto failed; } tm.tm_hour = VBI_PIL_HOUR (pil); tm.tm_min = VBI_PIL_MINUTE (pil); tm.tm_sec = 0; tm.tm_isdst = -1; /* unknown */ result = mktime (&tm); if (unlikely ((time_t) -1 == result)) goto failed; if (unlikely (!restore_tz (&old_tz, tz))) { #if 2 == VBI_VERSION_MINOR errno = 0; #endif return (time_t) -1; } return result; failed: if (unlikely (!restore_tz (&old_tz, tz))) { #if 2 == VBI_VERSION_MINOR errno = 0; #endif return (time_t) -1; } #if 3 == VBI_VERSION_MINOR errno = saved_errno; #else errno = 0; #endif return (time_t) -1; }
static time_t valid_pil_lto_to_time (vbi_pil pil, time_t start, int seconds_east) { struct tm tm; /* Some system calls below may not set errno on failure, but we must distinguish those errors from VBI_ERR_INVALID_PIL for valid_pil_lto_validity_window(). */ errno = 0; CLEAR (tm); if ((time_t) -1 == start) { if (unlikely ((time_t) -1 == time (&start))) { /* time() can fail but POSIX defines no error code. On Linux EFAULT is possible. */ if (0 == errno) errno = VBI_ERR_NO_TIME; return (time_t) -1; } } if (seconds_east < 0) { /* Note start can be negative. */ if (unlikely (start < -seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } else { if (unlikely (start > TIME_MAX - seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } start += seconds_east; if (unlikely (NULL == gmtime_r (&start, &tm))) return (time_t) -1; if (unlikely (!tm_mon_mday_from_pil (&tm, pil))) { errno = EOVERFLOW; return (time_t) -1; } if (unlikely (!tm_leap_day_check (&tm))) { errno = VBI_ERR_INVALID_PIL; return (time_t) -1; } tm.tm_hour = VBI_PIL_HOUR (pil); tm.tm_min = VBI_PIL_MINUTE (pil); tm.tm_sec = 0; start = timegm (&tm); if (unlikely ((time_t) -1 == start)) return (time_t) -1; if (seconds_east > 0) { /* Note start can be negative. */ if (unlikely (start < seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } else { if (unlikely (start > TIME_MAX + seconds_east)) { errno = EOVERFLOW; return (time_t) -1; } } return start - seconds_east; }
static vbi_bool valid_pil_validity_window (time_t * begin, time_t * end, vbi_pil pil, time_t start, const char * tz) { char *old_tz; struct tm tm; struct tm tm2; time_t stop; int saved_errno; /* EN 300 231 Section 9.3 and Annex F. */ old_tz = NULL; if (NULL != tz && 0 == strcmp (tz, "UTC")) { return valid_pil_lto_validity_window (begin, end, pil, start, /* seconds_east */ 0); } if (unlikely (!localtime_tz (&tm, &old_tz, start, tz))) return FALSE; if (unlikely (!tm_mon_mday_from_pil (&tm, pil))) { saved_errno = EOVERFLOW; goto failed; } if (unlikely (!tm_leap_day_check (&tm))) { /* Annex F: "Invalid days - indefinite time window". */ if (!restore_tz (&old_tz, tz)) return FALSE; *begin = TIME_MIN; *end = TIME_MAX; return TRUE; } tm.tm_hour = 0; tm.tm_min = 0; tm.tm_sec = 0; tm.tm_isdst = -1; /* unknown */ tm2 = tm; if (VBI_PIL_HOUR (pil) < 4) { --tm.tm_mday; tm.tm_hour = 20; } start = mktime (&tm); if (unlikely ((time_t) -1 == start)) goto failed; tm2.tm_mday += 1; tm2.tm_hour = 4; stop = mktime (&tm2); if (unlikely ((time_t) -1 == stop)) goto failed; if (unlikely (!restore_tz (&old_tz, tz))) return FALSE; *begin = start; *end = stop; return TRUE; failed: if (unlikely (!restore_tz (&old_tz, tz))) return FALSE; errno = saved_errno; return FALSE; }