/* * SpanDSP - a series of DSP components for telephony * * timezone.c - Timezone handling for time interpretation * * Written by Steve Underwood * * Copyright (C) 2010 Steve Underwood * * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 2.1, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /*! \file */ /* Timezone processing might not seem like a DSP activity, but getting the headers right on FAXes demands it. We need to handle multiple time zones within a process, for FAXes related to different parts of the globe, so the system timezone handling is not adequate. */ /* This timezone handling is derived from public domain software by Arthur David Olson which you may download from ftp://elsie.nci.nih.gov/pub at the time of writing. */ #if defined(HAVE_CONFIG_H) #include "config.h" #endif #include #include #include #include #include #include #include #include "spandsp/telephony.h" #include "spandsp/timezone.h" #include "spandsp/private/timezone.h" #if !defined(FALSE) #define FALSE 0 #endif #if !defined(TRUE) #define TRUE (!FALSE) #endif #define SECS_PER_MIN 60 #define MINS_PER_HOUR 60 #define HOURS_PER_DAY 24 #define DAYS_PER_WEEK 7 #define DAYS_PER_NON_LEAP_YEAR 365 #define DAYS_PER_LEAP_YEAR 366 #define SECS_PER_HOUR (SECS_PER_MIN*MINS_PER_HOUR) #define SECS_PER_DAY ((long int) SECS_PER_HOUR*HOURS_PER_DAY) #define MONTHS_PER_YEAR 12 #define TM_YEAR_BASE 1900 #define EPOCH_YEAR 1970 #define EPOCH_WDAY TM_THURSDAY #define isleap(y) (((y)%4) == 0 && (((y)%100) != 0 || ((y)%400) == 0)) #define isleap_sum(a, b) isleap((a)%400 + (b)%400) /* Unlike 's isdigit, this also works if c < 0 | c > UCHAR_MAX. */ #define is_digit(c) ((unsigned int) (c) - '0' <= 9) #define TZ_DEF_RULE_STRING ",M4.1.0,M10.5.0" #define JULIAN_DAY 0 /* Jn - Julian day */ #define DAY_OF_YEAR 1 /* n - day of year */ #define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */ static const char wildabbr[] = " "; static const char gmt[] = "GMT"; struct tz_rule_s { int r_type; /* Type of rule--see below */ int r_day; /* Day number of rule */ int r_week; /* Week number of rule */ int r_mon; /* Month number of rule */ long int r_time; /* Transition time of rule */ }; static const int mon_lengths[2][MONTHS_PER_YEAR] = { {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; static const int year_lengths[2] = { DAYS_PER_NON_LEAP_YEAR, DAYS_PER_LEAP_YEAR }; static int increment_overflow(int *number, int delta) { int number0; number0 = *number; *number += delta; return (*number < number0) != (delta < 0); } /*- End of function --------------------------------------------------------*/ static void set_tzname(tz_t *tz) { struct tz_state_s *sp; const struct tz_ttinfo_s *ttisp; int i; sp = &tz->state; tz->tzname[0] = wildabbr; tz->tzname[1] = wildabbr; for (i = 0; i < sp->typecnt; i++) { ttisp = &sp->ttis[i]; tz->tzname[ttisp->isdst] = &sp->chars[ttisp->abbrind]; } for (i = 0; i < sp->timecnt; i++) { ttisp = &sp->ttis[sp->types[i]]; tz->tzname[ttisp->isdst] = &sp->chars[ttisp->abbrind]; } } /*- End of function --------------------------------------------------------*/ /* Return the number of leap years through the end of the given year where, to make the math easy, the answer for year zero is defined as zero. */ static int leaps_thru_end_of(const int y) { return (y >= 0) ? (y/4 - y/100 + y/400) : -(leaps_thru_end_of(-(y + 1)) + 1); } /*- End of function --------------------------------------------------------*/ static struct tm *time_sub(const time_t * const timep, const long int offset, const struct tz_state_s * const sp, struct tm * const tmp) { const struct tz_lsinfo_s *lp; time_t tdays; const int *ip; int32_t corr; int32_t seconds; int32_t rem; int idays; int y; int hit; int i; corr = 0; hit = 0; i = sp->leapcnt; while (--i >= 0) { lp = &sp->lsis[i]; if (*timep >= lp->trans) { if (*timep == lp->trans) { hit = ((i == 0 && lp->corr > 0) || lp->corr > sp->lsis[i - 1].corr); if (hit) { while (i > 0 && sp->lsis[i].trans == sp->lsis[i - 1].trans + 1 && sp->lsis[i].corr == sp->lsis[i - 1].corr + 1) { hit++; --i; } } } corr = lp->corr; break; } } y = EPOCH_YEAR; tdays = *timep/SECS_PER_DAY; rem = *timep - tdays*SECS_PER_DAY; while (tdays < 0 || tdays >= year_lengths[isleap(y)]) { int newy; time_t tdelta; int idelta; int leapdays; tdelta = tdays / DAYS_PER_LEAP_YEAR; idelta = tdelta; if (tdelta - idelta >= 1 || idelta - tdelta >= 1) return NULL; if (idelta == 0) idelta = (tdays < 0) ? -1 : 1; newy = y; if (increment_overflow(&newy, idelta)) return NULL; leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1); tdays -= ((time_t) newy - y)*DAYS_PER_NON_LEAP_YEAR; tdays -= leapdays; y = newy; } seconds = tdays*SECS_PER_DAY; tdays = seconds/SECS_PER_DAY; rem += seconds - tdays*SECS_PER_DAY; /* Given the range, we can now fearlessly cast... */ idays = tdays; rem += (offset - corr); while (rem < 0) { rem += SECS_PER_DAY; idays--; } while (rem >= SECS_PER_DAY) { rem -= SECS_PER_DAY; idays++; } while (idays < 0) { if (increment_overflow(&y, -1)) return NULL; idays += year_lengths[isleap(y)]; } while (idays >= year_lengths[isleap(y)]) { idays -= year_lengths[isleap(y)]; if (increment_overflow(&y, 1)) return NULL; } tmp->tm_year = y; if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) return NULL; tmp->tm_yday = idays; /* The "extra" mods below avoid overflow problems. */ tmp->tm_wday = EPOCH_WDAY + ((y - EPOCH_YEAR) % DAYS_PER_WEEK)*(DAYS_PER_NON_LEAP_YEAR % DAYS_PER_WEEK) + leaps_thru_end_of(y - 1) - leaps_thru_end_of(EPOCH_YEAR - 1) + idays; tmp->tm_wday %= DAYS_PER_WEEK; if (tmp->tm_wday < 0) tmp->tm_wday += DAYS_PER_WEEK; tmp->tm_hour = (int) (rem/SECS_PER_HOUR); rem %= SECS_PER_HOUR; tmp->tm_min = (int) (rem/SECS_PER_MIN); /* A positive leap second requires a special * representation. This uses "... ??:59:60" et seq. */ tmp->tm_sec = (int) (rem%SECS_PER_MIN) + hit; ip = mon_lengths[isleap(y)]; for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; (tmp->tm_mon)++) idays -= ip[tmp->tm_mon]; tmp->tm_mday = (int) (idays + 1); tmp->tm_isdst = 0; return tmp; } /*- End of function --------------------------------------------------------*/ /* Given a pointer into a time zone string, scan until a character that is not * a valid character in a zone name is found. Return a pointer to that * character. */ static const char *get_tzname(const char *strp) { char c; while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && c != '+') strp++; return strp; } /*- End of function --------------------------------------------------------*/ /* Given a pointer into a time zone string, extract a number from that string. * Check that the number is within a specified range; if it is not, return * NULL. * Otherwise, return a pointer to the first character not part of the number. */ static const char *get_num(const char *strp, int * const nump, const int min, const int max) { char c; int num; if (strp == NULL || !is_digit(c = *strp)) return NULL; num = 0; do { num = num*10 + (c - '0'); if (num > max) return NULL; /* Illegal value */ c = *++strp; } while (is_digit(c)); if (num < min) return NULL; /* Illegal value */ *nump = num; return strp; } /*- End of function --------------------------------------------------------*/ /* Given a pointer into a time zone string, extract a number of seconds, * in hh[:mm[:ss]] form, from the string. * If any error occurs, return NULL. * Otherwise, return a pointer to the first character not part of the number * of seconds. */ static const char *get_secs(const char *strp, long int * const secsp) { int num; /* HOURS_PER_DAY*DAYS_PER_WEEK - 1 allows quasi-Posix rules like * "M10.4.6/26", which does not conform to Posix, * but which specifies the equivalent of * "02:00 on the first Sunday on or after 23 Oct". */ strp = get_num(strp, &num, 0, HOURS_PER_DAY*DAYS_PER_WEEK - 1); if (strp == NULL) return NULL; *secsp = num*(long int) SECS_PER_HOUR; if (*strp == ':') { strp = get_num(strp + 1, &num, 0, MINS_PER_HOUR - 1); if (strp == NULL) return NULL; *secsp += num*SECS_PER_MIN; if (*strp == ':') { /* SECS_PER_MIN allows for leap seconds. */ strp = get_num(strp + 1, &num, 0, SECS_PER_MIN); if (strp == NULL) return NULL; *secsp += num; } } return strp; } /*- End of function --------------------------------------------------------*/ /* Given a pointer into a time zone string, extract an offset, in * [+-]hh[:mm[:ss]] form, from the string. * If any error occurs, return NULL. * Otherwise, return a pointer to the first character not part of the time. */ static const char *get_offset(const char *strp, long int * const offsetp) { int neg = 0; if (*strp == '-') { neg = 1; strp++; } else if (*strp == '+') { strp++; } strp = get_secs(strp, offsetp); if (strp == NULL) return NULL; /* Illegal time */ if (neg) *offsetp = -*offsetp; return strp; } /*- End of function --------------------------------------------------------*/ /* Given a pointer into a time zone string, extract a rule in the form * date[/time]. See POSIX section 8 for the format of "date" and "time". * If a valid rule is not found, return NULL. * Otherwise, return a pointer to the first character not part of the rule. */ static const char *get_rule(const char *strp, struct tz_rule_s * const rulep) { if (*strp == 'J') { /* Julian day. */ rulep->r_type = JULIAN_DAY; strp = get_num(strp + 1, &rulep->r_day, 1, DAYS_PER_NON_LEAP_YEAR); } else if (*strp == 'M') { /* Month, week, day. */ rulep->r_type = MONTH_NTH_DAY_OF_WEEK; strp = get_num(strp + 1, &rulep->r_mon, 1, MONTHS_PER_YEAR); if (strp == NULL || *strp++ != '.') return NULL; strp = get_num(strp, &rulep->r_week, 1, 5); if (strp == NULL || *strp++ != '.') return NULL; strp = get_num(strp, &rulep->r_day, 0, DAYS_PER_WEEK - 1); } else if (is_digit(*strp)) { /* Day of the year. */ rulep->r_type = DAY_OF_YEAR; strp = get_num(strp, &rulep->r_day, 0, DAYS_PER_LEAP_YEAR - 1); } else { /* Invalid format */ return NULL; } if (strp == NULL) return NULL; if (*strp == '/') { /* Time specified. */ strp = get_secs(strp + 1, &rulep->r_time); } else { /* Default = 2:00:00 */ rulep->r_time = 2*SECS_PER_HOUR; } return strp; } /*- End of function --------------------------------------------------------*/ /* Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the * year, a rule, and the offset from UTC at the time that rule takes effect, * calculate the Epoch-relative time that rule takes effect. */ static time_t trans_time(const time_t janfirst, const int year, const struct tz_rule_s * const rulep, const long int offset) { int leapyear; time_t value; int i; int d; int m1; int yy0; int yy1; int yy2; int dow; value = 0; leapyear = isleap(year); switch (rulep->r_type) { case JULIAN_DAY: /* Jn - Julian day, 1 == January 1, 60 == March 1 even in leap * years. * In non-leap years, or if the day number is 59 or less, just * add SECS_PER_DAY times the day number-1 to the time of * January 1, midnight, to get the day. */ value = janfirst + (rulep->r_day - 1)*SECS_PER_DAY; if (leapyear && rulep->r_day >= 60) value += SECS_PER_DAY; break; case DAY_OF_YEAR: /* n - day of year. * Just add SECS_PER_DAY times the day number to the time of * January 1, midnight, to get the day. */ value = janfirst + rulep->r_day * SECS_PER_DAY; break; case MONTH_NTH_DAY_OF_WEEK: /* Mm.n.d - nth "dth day" of month m. */ value = janfirst; for (i = 0; i < rulep->r_mon - 1; i++) value += mon_lengths[leapyear][i]*SECS_PER_DAY; /* Use Zeller's Congruence to get day-of-week of first day of month. */ m1 = (rulep->r_mon + 9)%12 + 1; yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; yy1 = yy0/100; yy2 = yy0%100; dow = ((26*m1 - 2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1)%7; if (dow < 0) dow += DAYS_PER_WEEK; /* "dow" is the day-of-week of the first day of the month. Get * the day-of-month (zero-origin) of the first "dow" day of the * month. */ d = rulep->r_day - dow; if (d < 0) d += DAYS_PER_WEEK; for (i = 1; i < rulep->r_week; i++) { if (d + DAYS_PER_WEEK >= mon_lengths[leapyear][rulep->r_mon - 1]) break; d += DAYS_PER_WEEK; } /* "d" is the day-of-month (zero-origin) of the day we want. */ value += d*SECS_PER_DAY; break; } /* "value" is the Epoch-relative time of 00:00:00 UTC on the day in * question. To get the Epoch-relative time of the specified local * time on that day, add the transition time and the current offset * from UTC. */ return value + rulep->r_time + offset; } /*- End of function --------------------------------------------------------*/ /* Given a POSIX section 8-style TZ string, fill in the rule tables as appropriate. */ static int tzparse(const char *name, struct tz_state_s * const sp, const int lastditch) { const char *stdname; const char *dstname; size_t stdlen; size_t dstlen; long int stdoffset; long int dstoffset; long int theirstdoffset; long int theirdstoffset; long int theiroffset; unsigned char *typep; char *cp; int load_result; int isdst; int i; int j; int year; struct tz_rule_s start; struct tz_rule_s end; time_t *atp; time_t janfirst; time_t starttime; time_t endtime; dstname = NULL; stdname = name; if (lastditch) { stdlen = strlen(name); /* Length of standard zone name */ name += stdlen; if (stdlen >= sizeof(sp->chars)) stdlen = sizeof(sp->chars) - 1; stdoffset = 0; } else { name = get_tzname(name); stdlen = name - stdname; if (stdlen < 3) return -1; if (*name == '\0') return -1; name = get_offset(name, &stdoffset); if (name == NULL) return -1; } load_result = -1; if (load_result != 0) sp->leapcnt = 0; /* So, we're off a little */ if (*name != '\0') { dstname = name; name = get_tzname(name); dstlen = name - dstname; /* Length of DST zone name */ if (dstlen < 3) return -1; if (*name != '\0' && *name != ',' && *name != ';') { if ((name = get_offset(name, &dstoffset)) == NULL) return -1; } else { dstoffset = stdoffset - SECS_PER_HOUR; } if (*name == '\0' && load_result != 0) name = TZ_DEF_RULE_STRING; if (*name == ',' || *name == ';') { if ((name = get_rule(name + 1, &start)) == NULL) return -1; if (*name++ != ',') return -1; if ((name = get_rule(name, &end)) == NULL) return -1; if (*name != '\0') return -1; sp->typecnt = 2; /* Standard time and DST */ /* Two transitions per year, from EPOCH_YEAR to 2037. */ sp->timecnt = 2*(2037 - EPOCH_YEAR + 1); if (sp->timecnt > TZ_MAX_TIMES) return -1; sp->ttis[0].gmtoff = -dstoffset; sp->ttis[0].isdst = 1; sp->ttis[0].abbrind = stdlen + 1; sp->ttis[1].gmtoff = -stdoffset; sp->ttis[1].isdst = 0; sp->ttis[1].abbrind = 0; atp = sp->ats; typep = sp->types; janfirst = 0; for (year = EPOCH_YEAR; year <= 2037; year++) { starttime = trans_time(janfirst, year, &start, stdoffset); endtime = trans_time(janfirst, year, &end, dstoffset); if (starttime > endtime) { *atp++ = endtime; *typep++ = 1; /* DST ends */ *atp++ = starttime; *typep++ = 0; /* DST begins */ } else { *atp++ = starttime; *typep++ = 0; /* DST begins */ *atp++ = endtime; *typep++ = 1; /* DST ends */ } janfirst += year_lengths[isleap(year)]*SECS_PER_DAY; } } else { if (*name != '\0') return -1; /* Initial values of theirstdoffset and theirdstoffset. */ theirstdoffset = 0; for (i = 0; i < sp->timecnt; i++) { j = sp->types[i]; if (!sp->ttis[j].isdst) { theirstdoffset = -sp->ttis[j].gmtoff; break; } } theirdstoffset = 0; for (i = 0; i < sp->timecnt; i++) { j = sp->types[i]; if (sp->ttis[j].isdst) { theirdstoffset = -sp->ttis[j].gmtoff; break; } } /* Initially we're assumed to be in standard time. */ isdst = FALSE; theiroffset = theirstdoffset; /* Now juggle transition times and types tracking offsets as you do. */ for (i = 0; i < sp->timecnt; i++) { j = sp->types[i]; sp->types[i] = sp->ttis[j].isdst; if (sp->ttis[j].ttisgmt) { /* No adjustment to transition time */ } else { /* If summer time is in effect, and the * transition time was not specified as * standard time, add the summer time * offset to the transition time; * otherwise, add the standard time * offset to the transition time. */ /* Transitions from DST to DDST * will effectively disappear since * POSIX provides for only one DST * offset. */ if (isdst && !sp->ttis[j].ttisstd) sp->ats[i] += (dstoffset - theirdstoffset); else sp->ats[i] += (stdoffset - theirstdoffset); } theiroffset = -sp->ttis[j].gmtoff; if (sp->ttis[j].isdst) theirdstoffset = theiroffset; else theirstdoffset = theiroffset; } /* Finally, fill in ttis. ttisstd and ttisgmt need not be handled. */ sp->ttis[0].gmtoff = -stdoffset; sp->ttis[0].isdst = FALSE; sp->ttis[0].abbrind = 0; sp->ttis[1].gmtoff = -dstoffset; sp->ttis[1].isdst = TRUE; sp->ttis[1].abbrind = stdlen + 1; sp->typecnt = 2; } } else { dstlen = 0; sp->typecnt = 1; /* Only standard time */ sp->timecnt = 0; sp->ttis[0].gmtoff = -stdoffset; sp->ttis[0].isdst = 0; sp->ttis[0].abbrind = 0; } sp->charcnt = stdlen + 1; if (dstlen != 0) sp->charcnt += dstlen + 1; if ((size_t) sp->charcnt > sizeof(sp->chars)) return -1; cp = sp->chars; strncpy(cp, stdname, stdlen); cp += stdlen; *cp++ = '\0'; if (dstlen != 0) { strncpy(cp, dstname, dstlen); cp[dstlen] = '\0'; } return 0; } /*- End of function --------------------------------------------------------*/ static void tz_set(tz_t *tz, const char *tzstring) { const char *name = ""; struct tz_state_s *lclptr = &tz->state; if (tzstring) name = tzstring; /* See if we are already set OK */ if (tz->lcl_is_set > 0 && strcmp(tz->lcl_tzname, name) == 0) return; tz->lcl_is_set = strlen(name) < sizeof(tz->lcl_tzname); if (tz->lcl_is_set) strcpy(tz->lcl_tzname, name); if (name[0] == '\0') { /* User wants it fast rather than right, so, we're off a little. */ lclptr->leapcnt = 0; lclptr->timecnt = 0; lclptr->typecnt = 0; lclptr->ttis[0].isdst = 0; lclptr->ttis[0].gmtoff = 0; lclptr->ttis[0].abbrind = 0; strcpy(lclptr->chars, gmt); } else if (name[0] == ':' || tzparse(name, lclptr, FALSE) != 0) { tzparse(gmt, lclptr, TRUE); } set_tzname(tz); } /*- End of function --------------------------------------------------------*/ SPAN_DECLARE(int) tz_localtime(tz_t *tz, struct tm *tmp, time_t t) { struct tz_state_s *sp; const struct tz_ttinfo_s *ttisp; int i; sp = &tz->state; if (sp->timecnt == 0 || t < sp->ats[0]) { i = 0; while (sp->ttis[i].isdst) { if (++i >= sp->typecnt) { i = 0; break; } } } else { for (i = 1; i < sp->timecnt; i++) { if (t < sp->ats[i]) break; } i = (int) sp->types[i - 1]; } ttisp = &sp->ttis[i]; time_sub(&t, ttisp->gmtoff, sp, tmp); tmp->tm_isdst = ttisp->isdst; tz->tzname[tmp->tm_isdst] = &sp->chars[ttisp->abbrind]; return 0; } /*- End of function --------------------------------------------------------*/ SPAN_DECLARE(const char *) tz_tzname(tz_t *tz, int isdst) { return tz->tzname[(!isdst) ? 0 : 1]; } /*- End of function --------------------------------------------------------*/ SPAN_DECLARE(tz_t *) tz_init(tz_t *tz, const char *tzstring) { if (tz == NULL) { if ((tz = (tz_t *) malloc(sizeof(*tz))) == NULL) return NULL; } memset(tz, 0, sizeof(*tz)); tz->tzname[0] = tz->tzname[1] = wildabbr; tz_set(tz, tzstring); return tz; } /*- End of function --------------------------------------------------------*/ SPAN_DECLARE(int) tz_release(tz_t *tz) { return 0; } /*- End of function --------------------------------------------------------*/ SPAN_DECLARE(int) tz_free(tz_t *tz) { if (tz) free(tz); return 0; } /*- End of function --------------------------------------------------------*/ /*- End of file ------------------------------------------------------------*/