Logo Search packages:      
Sourcecode: gretl version File versions  Download package

calendar.c

/*
 * Copyright (c) 1989, 1993, 1994
 *    The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Kim Letkeman.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by the University of
 *    California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* The following calendar code is based on the source code for the
   unix "cal" program, the provenance of which is given in the above
   notice.  Allin Cottrell, March 2002.
*/

#include "libgretl.h"

/**
 * SECTION:calendar
 * @short_description: functions for working with dates
 * @title: Calendar
 * @include: libgretl.h
 *
 * Here we have various functions dealing with calendar dates;
 * for the most part these are designed for handling daily
 * time-series data. Most of the basic code is derived from 
 * the unix "cal" program which is released under the BSD 
 * license (reproduced in the libgretl source file calendar.c).
 */

/* leap year -- account for gregorian reformation in 1752 */
#define     leap_year(yr) \
      ((yr) <= 1752 ? !((yr) % 4) : \
      (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400))

/* number of centuries since 1700, not inclusive */
#define     centuries_since_1700(yr) \
      ((yr) > 1700 ? (yr) / 100 - 17 : 0)

/* number of centuries since 1700 whose modulo of 400 is 0 */
#define     quad_centuries_since_1700(yr) \
      ((yr) > 1600 ? ((yr) - 1600) / 400 : 0)

/* number of leap years between year 1 and this year, not inclusive */
#define     leap_years_since_year_1(yr) \
      ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))

static int days_in_month[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};

int week1stday = 0; /* 1 for Monday, 0 for Sunday */

#define     SATURDAY          6           /* 1 Jan 1 was a Saturday */
#define     NUMBER_MISSING_DAYS     11          /* 11 day correction */

static int day_in_year (int day, int month, int year)
{
    int i, leap = leap_year(year);

    for (i=1; i<month; i++) {
      day += days_in_month[leap][i];
    }

    return day;
}

/**
 * epoch_day_from_ymd:
 * @y: year.
 * @m: month.
 * @d: day of month.
 * 
 * Returns: the epoch day number, or -1 on failure.
 */

long epoch_day_from_ymd (int y, int m, int d)
{
    long temp;

    if (y < 0 || m < 0 || d < 0) {
      return -1;
    }

    if (y > 9999 || m > 12 || d > 31) {
      return -1;
    }

    temp = (long)(y - 1) * 365 + leap_years_since_year_1(y - 1)
      + day_in_year(d, m, y);

    return temp;
}

/**
 * get_epoch_day:
 * @date: string representation of calendar date, in form
 * YY[YY]/MM/DD.
 * 
 * Returns: the epoch day number, or -1 on failure.
 */

long get_epoch_day (const char *date)
{
    long temp;
    int year, month, day;

    if (sscanf(date, "%d/%d/%d", &year, &month, &day) != 3) {
      return -1;
    }

    if (year < 0 || month < 0 || day < 0) {
      return -1;
    }

    if (year > 9999 || month > 12 || day > 31) {
      return -1;
    }

    if (year < 100) {
      year = FOUR_DIGIT_YEAR(year);
    }

    temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
      + day_in_year(day, month, year);

    return temp;
}

/**
 * calendar_obs_number:
 * @date: string representation of calendar date, in form
 * YY[YY]/MM/DD.
 * @dset: pointer to dataset information.
 * 
 * Returns: The zero-based observation number for the given
 * date within the current data set.
 */

int calendar_obs_number (const char *date, const DATASET *dset)
{
    long ed0 = (long) dset->sd0;
    long t = get_epoch_day(date);

    if (t == -1) return -1;
    
    /* subtract starting day for dataset */
    t -= ed0;

    if (dset->pd == 52) {
      /* weekly data */
      t /= 7;
    } else if (dset->pd == 5 || dset->pd == 6) { 
      /* daily, 5- or 6-day week: subtract number of irrelevant days */
      int startday = (((ed0 - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
      int wkends = (t + startday - 1) / 7;

#ifdef CAL_DEBUG
      printf("calendar_obs_number: ed0=%d, date=%s, t=%d, startday=%d, wkends=%d\n", 
             (int) ed0, date, (int) t, startday, wkends);
#endif

      if (dset->pd == 5) {
          t -= (2 * wkends);
      } else {
          t -= wkends;
      }
    }

    return (int) t;
}

static int t_to_epoch_day (int t, long start, int wkdays)
{
    int startday = (((start - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
    int wkends = (t + startday - 1) / wkdays;

    if (wkdays == 5) wkends *= 2;

    return start + t + wkends;
}

/**
 * calendar_date_string:
 * @str: string to be filled out.
 * @t: zero-based index of observation.
 * @dset: pointer to dataset information.
 * 
 * Writes to @str the calendar representation of the date of
 * observation @t, in the form YY[YY]/MM/DD.
 */

void calendar_date_string (char *str, int t, const DATASET *dset)
{
    int rem, yr;
    int add, day, mo = 0, modays = 0;
    long yrstart, dfind;

    if (dset->pd == 52) {
      dfind = (long) dset->sd0 + 7 * t;
    } else if (dset->pd == 7) {
      dfind = (long) dset->sd0 + t;
    } else {
      dfind = t_to_epoch_day(t, (long) dset->sd0, dset->pd);
    }

    yr = 1 + (double) dfind / 365.248; 
    
    yrstart = (long)(yr - 1) * 365 + leap_years_since_year_1(yr - 1);
    rem = dfind - yrstart;

    if (rem <= 0) {
      yr--;
      yrstart = (long)(yr - 1) * 365 + leap_years_since_year_1(yr - 1);
      rem = dfind - yrstart;
    }

    while (modays < rem) {
      mo++;
      add = days_in_month[leap_year(yr)][mo];
      if (modays + add < rem) modays += add;
      else break;
    }

    day = rem - modays;

    if (strlen(dset->stobs) == 8) {
      sprintf(str, "%02d/%02d/%02d", yr % 100, mo, day);
    } else {
      sprintf(str, "%04d/%02d/%02d", yr, mo, day);
    }
}

/**
 * MS_excel_date_string:
 * @date: date string to be filled out.
 * @mst: MS Excel-type date code: days since base.
 * @pd: periodicity of data (or 0 if unknown).
 * @d1904: set to 1 if the base is 1904/01/01; otherwise
 * the base is assumed to be 1899/12/31.
 * 
 * Writes to @date the calendar representation of the date of
 * observation @mst, in the form YYYY/MM/DD if @pd is 0, 5,
 * 6, 7 or 52 (unknown, daily, or weekly frequency), otherwise 
 * in the appropriate format for annual, quarterly or monthly data.
 * 
 * Returns: 0.
 */

int MS_excel_date_string (char *date, int mst, int pd, int d1904)
{
    int yr = (d1904)? 1904 : 1900;
    int day = (d1904)? 2 : 1;
    int mo = 1;
    int leap, drem;

    if (mst == 0) {
      if (d1904) {
          day = 1;
      } else {
          yr = 1899;
          mo = 12;
          day = 31;
      }
    } else if (mst > 0) {
      drem = mst + d1904;

      while (1) {
          int yd = 365 + leap_year(yr);

          /* MS tomfoolery */
          if (yr == 1900) yd++;

          if (drem > yd) {
            drem -= yd;
            yr++;
          } else {
            break;
          }
      }

      leap = leap_year(yr) + (yr == 1900);

      for (mo=1; mo<13; mo++) {
          int md = days_in_month[leap][mo];

          if (drem > md) {
            drem -= md;
          } else {
            day = drem;
            break;
          }
      }
    } else {
      /* mst < 0, date prior to base */
      drem = - (mst + d1904);

      yr = (d1904)? 1903 : 1899;

      while (1) {
          int yd = 365 + leap_year(yr);

          if (drem > yd) {
            drem -= yd;
            yr--;
          } else {
            break;
          }
      }

      leap = leap_year(yr);

      for (mo=12; mo>0; mo--) {
          int md = days_in_month[leap][mo];

          if (drem >= md) {
            drem -= md;
          } else {
            day = md - drem;
            break;
          }
      }
    }

    if (pd == 1) {
      sprintf(date, "%d", yr);
    } else if (pd == 12) {
      sprintf(date, "%d:%02d", yr, mo);
    } else if (pd == 4) {
      int qtr = 1 + mo / 3.25;

      sprintf(date, "%d:%d", yr, qtr);
    } else {
      sprintf(date, "%04d/%02d/%02d", yr, mo, day);
    }

    return 0;
}

/**
 * get_dec_date:
 * @date: calendar representation of date: YYYY/MM/DD.
 * 
 * Returns: representation of date as year plus fraction of year.
 */

double get_dec_date (const char *date)
{
    char tmp[OBSLEN];
    int yr, mo, day;
    long ed0, edn, edt;
    double dyr, frac;

    if (sscanf(date, "%d/%d/%d", &yr, &mo, &day) != 3) {
      return NADBL;
    }

    edt = get_epoch_day(date);
    sprintf(tmp, "%04d/01/01", yr);
    ed0 = get_epoch_day(tmp);
    sprintf(tmp, "%04d/12/31", yr);
    edn = get_epoch_day(tmp);

    if (yr < 100) {
      yr = FOUR_DIGIT_YEAR(yr);
    }

    dyr = yr;
    frac = ((double) edt - ed0) / ((double) edn - ed0 + 1.0);

    return dyr + frac;
}

static int day_of_week_from_ymd (int yr, int mo, int day)
{
    int c, d;

    /* Uspensky and Heaslet, Elementary Number Theory (1939) */

    if (mo < 3) {
      yr--;
      mo += 10;
    } else {
      mo -= 2;
    }

    c = yr / 100;
    d = yr % 100;

    return ((day % 7) + ((int) floor(2.6 * mo - 0.2) % 7) + 
          (d % 7) + ((int) floor(d / 4.0) % 7) + ((int) floor(c / 4.0) % 7)
          - ((2 * c) % 7)) % 7; 
}

/**
 * day_of_week:
 * @yr: year, preferably 4-digit.
 * @mo: month, 1 to 12.
 * @day: day in month, 1 to 31.
 * @err: location to receive error code.
 *
 * Returns: the day of the week for the supplied date
 * (Sunday = 0, Monday = 1, ...) or %NADBL on failure 
 * (the date is invalid).
 */

double day_of_week (int yr, int mo, int day, int *err)
{
    if (yr < 0 || mo <= 0 || day <= 0) {
      *err = E_DATA;
      return NADBL;
    }

    if (mo > 12 || day > 31) {
      *err = E_DATA;
      return NADBL;
    } 

    if (day > days_in_month[leap_year(yr)][mo]) {
      *err = E_DATA;
      return NADBL;
    } 

    if (yr < 100) {
      yr = FOUR_DIGIT_YEAR(yr);
    }

    return day_of_week_from_ymd(yr, mo, day);
}

#define day_in_calendar(w, d) ((w == 6 && d != 0) || \
                               (w == 5 && d != 0 && d != 6))

/**
 * get_day_of_week:
 * @date: calendar representation of date, [YY]YY/MM/DD
 * 
 * Returns: day of week as integer, Sunday = 0.
 */

int get_day_of_week (const char *date)
{
    int yr, mo, day;

    if (sscanf(date, "%d/%d/%d", &yr, &mo, &day) != 3) {
      return -1;
    }

    if (yr < 100) {
      yr = FOUR_DIGIT_YEAR(yr);
    }

    return day_of_week_from_ymd(yr, mo, day);
}

/**
 * day_starts_month:
 * @d: day of month, 1-based
 * @m: month number, 1-based
 * @y: 4-digit year
 * @wkdays: number of days in week (7, 6 or 5)
 * @pad: content set = 1 if the first day of the month
 * can reasonably be padded by a missing value (Jan 1)
 * 
 * Returns: 1 if the day is the "first day of the month", 
 * allowance made for the possibility of a 5- or 6-day week, else 0.
 */

int day_starts_month (int d, int m, int y, int wkdays, int *pad)
{
    int ret = 0;

    if (wkdays == 7) {
      if (d == 1) {
          ret = 1;
      } else if (m == 1 && d == 2) {
          *pad = 1;
          ret = 1;
      }
    } else {
      /* 5- or 6-day week: check for first weekday or non-Sunday */
      int i, wd;

      for (i=1; i<6; i++) {
         wd = day_of_week_from_ymd(y, m, i); 
         if (day_in_calendar(wkdays, wd)) break;
      }
      if (d == i) {
          ret = 1;
      } else if (m == 1 && d == i + 1) {
          *pad = 1;
          ret = 1;
      }
    }
    
    return ret;
}

/**
 * day_ends_month:
 * @d: day of month, 1-based
 * @m: month number, 1-based
 * @y: 4-digit year
 * @wkdays: number of days in week (7, 6 or 5)
 * 
 * Returns: 1 if the day is the "last day of the month", 
 * allowance made for the possibility of a 5- or 6-day week, else 0.
 */

int day_ends_month (int d, int m, int y, int wkdays)
{
    int ret = 0;
    int leap = (m == 2)? leap_year(y) : 0;
    int dm = days_in_month[leap][m];

    if (wkdays == 7) {
      ret = (d == dm);
    } else {
      /* 5- or 6-day week: check for last weekday or non-Sunday */
      int i, wd;

      for (i=dm; i>0; i--) {
          wd = day_of_week_from_ymd(y, m, i);
          if (day_in_calendar(wkdays, wd)) break;
      }
      ret = (d == i);   
    } 

    return ret;
}

/**
 * get_days_in_month:
 * @mon: month number, 1-based
 * @yr: 4-digit year
 * @wkdays: number of days in week (7, 6 or 5)
 * 
 * Returns: the number of days in the month, allowance made 
 * for the possibility of a 5- or 6-day week.
 */

int get_days_in_month (int mon, int yr, int wkdays)
{
    int ret = 0;
    int leap = (mon == 2)? leap_year(yr) : 0;
    int dm = days_in_month[leap][mon];

    if (wkdays == 7) {
      ret = dm;
    } else {
      int i, wd;

      for (i=0; i<dm; i++) {
          wd = day_of_week_from_ymd(yr, mon, i + 1);
          if (day_in_calendar(wkdays, wd)) ret++;
      }     
    } 

    return ret;
}

/**
 * days_in_month_before:
 * @yr: 4-digit year
 * @mon: month number, 1-based
 * @day: day in month.
 * @wkdays: number of days in week (7, 6 or 5)
 * 
 * Returns: the number of relevant days in the month prior to
 * the supplied date, allowing for the possibility of a 5- or 
 * 6-day week.
 */

int days_in_month_before (int yr, int mon, int day, int wkdays)
{
    int ret = 0;

    if (wkdays == 7) {
      ret = day - 1;
    } else {
      int i, wd;

      for (i=1; i<day; i++) {
          wd = day_of_week_from_ymd(yr, mon, i);
          if (day_in_calendar(wkdays, wd)) ret++;
      }     
    } 

    return ret;    
}

/**
 * days_in_month_after:
 * @yr: 4-digit year
 * @mon: month number, 1-based
 * @day: day in month.
 * @wkdays: number of days in week (7, 6 or 5)
 * 
 * Returns: the number of relevant days in the month after
 * the supplied date, allowing for the possibility of a 5- or 
 * 6-day week.
 */

int days_in_month_after (int yr, int mon, int day, int wkdays)
{
    int ret = 0;
    int leap = (mon == 2)? leap_year(yr) : 0;
    int dm = days_in_month[leap][mon];

    if (wkdays == 7) {
      ret = dm - day;
    } else {
      int i, wd;

      for (i=dm; i>day; i--) {
          wd = day_of_week_from_ymd(yr, mon, i);
          if (day_in_calendar(wkdays, wd)) ret++;
      }
    }

    return ret;    
}

/**
 * n_hidden_missing_obs:
 * @dset: dataset information.
 *
 * For daily data with user-supplied data strings, 
 * determine the number of "hidden" missing observations,
 * i.e. the difference between the actual number of
 * observations and the number that should be there,
 * according to the calendar. Allowance is made for 
 * 5- or 6-day data, via the data frequency given
 * in @dset.
 *
 * Returns: number of hidden observations.
 */

int n_hidden_missing_obs (const DATASET *dset)
{
    int t1, t2;
    int cal_n;

    if (!dated_daily_data(dset) || dset->S == NULL) {
      return 0;
    }
    
    t1 = calendar_obs_number(dset->S[0], dset);
    t2 = calendar_obs_number(dset->S[dset->n - 1], dset);

    cal_n = t2 - t1 + 1;

    return cal_n - dset->n;
}

/**
 * guess_daily_pd:
 * @dset: dataset information.
 *
 * Based on user-supplied daily date strings recorded in
 * @dset, try to guess whether the number of observations
 * per week is 5, 6 or 7 (given that some observations 
 * may be missing).
 *
 * Returns: best quess at data frequency.
 */

int guess_daily_pd (const DATASET *dset)
{
    int t, wd, pd = 5;
    int wdbak = -1;
    int havesat = 0;
    int gotsat = 0, gotsun = 0;
    int contig = 0;

    wd = get_day_of_week(dset->S[0]);
    if (6 - wd < dset->n) {
      havesat = 1;
    }

    for (t=0; t<dset->n && t<28; t++) {
      wd = get_day_of_week(dset->S[t]);
      if (wd == 0) {
          gotsun = 1;
      } else if (wd == 6) {
          gotsat = 1;
      }
      if ((wdbak + 1) % 7 == wd) {
          contig++;
      }
      wdbak = wd;
    }

    if (gotsat && gotsun) {
      pd = 7;
    } else if (contig > 10) {
      if (gotsun) pd = 7;
      else if (gotsat) pd = 6;
    } else if (dset->n > 7) {
      if (!gotsun && !gotsat) {
          pd = 5;
      } else if (!gotsun) {
          pd = 6;
      }
    } else if (havesat && !gotsat) {
      pd = 5;
    } else {
      pd = 7;
    }

    return pd;
}


Generated by  Doxygen 1.6.0   Back to index