ossp-pkg/tai/tai_cal.c
/*
** OSSP tai - Time Handling
** Copyright (c) 2002-2003 Ralf S. Engelschall <rse@engelschall.com>
** Copyright (c) 2002-2003 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2002-2003 Cable & Wireless Deutschland <http://www.cw.com/de/>
**
** This file is part of OSSP tai, a time handling library
** which can be found at http://www.ossp.org/pkg/lib/tai/.
**
** Permission to use, copy, modify, and distribute this software for
** any purpose with or without fee is hereby granted, provided that
** the above copyright notice and this permission notice appear in all
** copies.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 AUTHORS AND COPYRIGHT HOLDERS AND THEIR
** 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.
**
** tai_calendar.c: calendar calculations
*/
#include "tai.h"
#include "tai_p.h"
#include "tai_cal.h"
/* the Gregorian calendar */
#define TAI_CAL_SECS_PER_MIN 60
#define TAI_CAL_MINS_PER_HOUR 60
#define TAI_CAL_HOURS_PER_DAY 24
#define TAI_CAL_DAYS_PER_WEEK 7
#define TAI_CAL_DAYS_PER_NYEAR 365
#define TAI_CAL_DAYS_PER_LYEAR 366
#define TAI_CAL_SECS_PER_HOUR (TAI_CAL_SECS_PER_MIN * TAI_CAL_MINS_PER_HOUR)
#define TAI_CAL_SECS_PER_DAY (TAI_CAL_SECS_PER_HOUR * TAI_CAL_HOURS_PER_DAY)
#define TAI_CAL_MONS_PER_YEAR 12
/* 2^64/2 (=2^63) meaning Thu 1972-01-01 00:00:00 UTC was Thu 1972-01-01 00:00:10 TAI */
#define TAI_EPOCH_BASE "8000000000000000"
#define TAI_EPOCH_YEAR 1970
#define TAI_EPOCH_WDAY TAI_CAL_THURSDAY
/* A year that has 366 days instead of 365 days, is a leap year,
February has 29 days instead of 28 in a leap year. Leap years fall on
any year that either can be evenly divisible by 4 and is not evenly
divisible by 100, or is evenly divisible by 400. For example, the
year 2000 is a leap year, but 1900 is not. */
#define tai_cal_is_leap_year(y) \
(((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
#define tai_leapdays_between_years(y1,y2) \
( ((y1) / 4 - (y1) / 100 + (y1) / 400) \
- ((y2) / 4 - (y2) / 100 + (y2) / 400))
static const int tai_cal_mon_len[2][TAI_CAL_MONS_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 tai_cal_year_len[2] = {
TAI_CAL_DAYS_PER_NYEAR,
TAI_CAL_DAYS_PER_LYEAR
};
struct tai_leapsec_st {
ui64_t trans;
int corr;
} tai_tab_leapsec[] = {
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1972 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1972 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1973 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1974 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1975 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1976 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1977 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1978 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1979 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1981 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1982 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1983 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1985 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1987 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1989 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1990 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1992 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1993 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1994 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1995 Dec 31 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 }, /* 1997 Jun 30 23:59:60 */
{ ui64_cons(00,00,00,00,00,00,00,00), 1 } /* 1998 Dec 31 23:59:60 */
};
tai_rc_t tai_tai2cal(const tai_t *tai, tai_cal_t *cal, long offset, int isdst)
{
ui64_t secs;
ui64_t days;
ui64_t rem;
ui64_t tmp;
ui64_t tmp2;
int ls_corr;
int ls_hit;
int cmp;
int yleap;
int year;
int dyear;
int ddays;
int i;
/* calculate (rounded) number of total seconds */
secs = tai->sec;
if (ui64_cmp(tai->asec, tai_half_sec_in_asec) > 0)
ui64_addn(secs, 1, NULL);
/* calculate involved leap seconds */
ls_corr = 0;
ls_hit = 0;
for (i = 0; i < (sizeof(tai_tab_leapsec)/sizeof(tai_tab_leapsec[0])); i++) {
cmp = ui64_cmp(secs, tai_tab_leapsec[i].trans);
if (cmp > 0)
break;
if (cmp == 0)
ls_hit = tai_tab_leapsec[i].corr;
ls_corr += tai_tab_leapsec[i].corr;
}
if (ls_corr > 0)
secs = ui64_addn(secs, ls_corr, NULL);
else
secs = ui64_subn(secs, -ls_corr, NULL);
/* perform timezone offset correction */
if (offset > 0)
secs = ui64_addn(secs, offset, NULL);
else
secs = ui64_subn(secs, -offset, NULL);
cal->offs = offset;
/* perform Daylight Saving Time (DST) offset correction */
if (isdst)
secs = ui64_addn(secs, 1, NULL); /* FIXME */
cal->isdst = isdst;
/* days = secs / TAI_CAL_SECS_PER_DAY
rem = secs % TAI_CAL_SECS_PER_DAY */
tmp = ui64_n2i(TAI_CAL_SECS_PER_DAY);
days = ui64_div(secs, tmp, &rem);
/* hour = rem / TAI_CAL_SECS_PER_HOUR
rem = rem % TAI_CAL_SECS_PER_HOUR */
tmp = ui64_n2i(TAI_CAL_SECS_PER_HOUR);
tmp = ui64_div(rem, tmp, &rem);
cal->hour = (int)ui64_i2n(tmp);
/* min = rem / TAI_CAL_SECS_PER_MIN
rem = rem % TAI_CAL_SECS_PER_MIN */
tmp = ui64_n2i(TAI_CAL_SECS_PER_MIN);
tmp = ui64_div(rem, tmp, &rem);
cal->min = (int)ui64_i2n(tmp);
/* sec = rem + ls_hit */
cal->sec = (int)ui64_i2n(rem) + ls_hit;
/* wday = (TAI_EPOCH_WDAY + days) % TAI_CAL_DAYS_PER_WEEK */
/* FIXME: TAI is not 0 on EPOCH */
tmp = ui64_addn(days, TAI_EPOCH_WDAY, NULL);
tmp2 = ui64_n2i(TAI_CAL_DAYS_PER_WEEK);
ui64_div(tmp, tmp2, &tmp);
cal->wday = (int)ui64_i2n(tmp);
/* What about the -10 offset in 1972 (epoch)? */
/* calculate year */
epoch = ui64_s2i(TAI_EPOCH_BASE, NULL, 16);
epoch = ui64_divn(epoch, TAI_CAL_SECS_PER_DAY, NULL);
year = TAI_EPOCH_YEAR;
if (ui64_cmp(days, epoch) > 0) {
/* date after the epoch */
ui64_sub(days, epoch, &days);
dyear = ui64_i2n(ui64_div(days, ui64_n2i(TAI_CAL_DAYS_PER_NYEAR), NULL));
ddays = ui64_muln(ui64_n2i(dyear), TAI_CAL_DAYS_PER_NYEAR);
ddays = ui64_addn(ddays, tai_leapdays_between_years(year+dyear-1, year-1));
days = ui64_sub(days, ddays, NULL);
years += dyear;
}
else {
/* date before the epoch */
ui64_sub(epoch, days, &days);
dyear = ui64_i2n(ui64_div(days, ui64_n2i(TAI_CAL_DAYS_PER_NYEAR), NULL));
ddays = ui64_muln(ui64_n2i(dyear), TAI_CAL_DAYS_PER_NYEAR);
ddays = ui64_addn(ddays, tai_leapdays_between_years(year+dyear-1, year-1));
days = ui64_sub(days, ddays, NULL);
years -= dyear;
}
cal->year = year;
/* calculate day of the year */
cal->yday = ui64_i2n(days);
/* calculate month of the year */
cal->mon = 0;
while (days >= tai_cal_mon_len[yleap][cal->mon]) {
days = ui64_subn(days, tai_cal_mon_len[yleap][cal->mon], NULL);
cal->mon++;
}
/* calculate day of the month */
cal->mday = ui64_i2n(days) + 1;
return TAI_OK;
}
tai_rc_t tai_cal2tai(tai_t *tai, const tai_cal_t *cal, long *offset, int *isdst)
{
/* FIXME: binary search with tai_tai2cal!! See mktime implementations!! */
return TAI_OK;
}