mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
491 lines
12 KiB
491 lines
12 KiB
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/*
|
|
* datetime.c: Date and time routines grabbed from elsewhere.
|
|
*
|
|
* Authors:
|
|
* Miguel de Icaza (miguel@gnu.org)
|
|
* Morten Welinder <terra@gnome.org>
|
|
* Jukka-Pekka Iivonen <iivonen@iki.fi>
|
|
* Andreas J. Guelzow <aguelzow@taliesin.ca>
|
|
*/
|
|
|
|
#include <goffice/goffice-config.h>
|
|
#include "datetime.h"
|
|
|
|
#include <math.h>
|
|
|
|
#ifndef HAVE_GLIB26
|
|
#include "goffice/glib24_26-compat.h"
|
|
#endif
|
|
|
|
#define SECS_PER_DAY (24 * 60 * 60)
|
|
#define HALF_SEC (0.5 / SECS_PER_DAY)
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/* One less that the Julian day number of 19000101. */
|
|
static int date_origin = 0;
|
|
/* Julian day number of 19040101. */
|
|
static int date_origin_1904 = 0;
|
|
|
|
/*
|
|
* The serial number of 19000228. Excel allocates a serial number for
|
|
* the non-existing date 19000229.
|
|
*/
|
|
static int const date_serial_19000228 = 59;
|
|
|
|
static void
|
|
date_init (void)
|
|
{
|
|
/* Day 1 means 1st of January of 1900 */
|
|
GDate* date = g_date_new_dmy (1, 1, 1900);
|
|
date_origin = g_date_get_julian (date) - 1;
|
|
|
|
/* Day 0 means 1st of January of 1904 */
|
|
g_date_set_dmy (date, 1, 1, 1904);
|
|
date_origin_1904 = g_date_get_julian (date);
|
|
g_date_free (date);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
int
|
|
datetime_g_to_serial (GDate const *date, GODateConventions const *conv)
|
|
{
|
|
int day;
|
|
|
|
if (!date_origin)
|
|
date_init ();
|
|
|
|
if (conv && conv->use_1904)
|
|
return g_date_get_julian (date) - date_origin_1904;
|
|
day = g_date_get_julian (date) - date_origin;
|
|
return day + (day > date_serial_19000228);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
void
|
|
datetime_serial_to_g (GDate *res, int serial, GODateConventions const *conv)
|
|
{
|
|
if (!date_origin)
|
|
date_init ();
|
|
|
|
g_date_clear (res, 1);
|
|
if (conv && conv->use_1904)
|
|
g_date_set_julian (res, serial + date_origin_1904);
|
|
else if (serial > date_serial_19000228) {
|
|
if (serial == date_serial_19000228 + 1)
|
|
g_warning ("Request for date 19000229.");
|
|
g_date_set_julian (res, serial + date_origin - 1);
|
|
} else
|
|
g_date_set_julian (res, serial + date_origin);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
double
|
|
datetime_timet_to_serial_raw (time_t t, GODateConventions const *conv)
|
|
{
|
|
struct tm *tm = localtime (&t);
|
|
int secs;
|
|
GDate date;
|
|
|
|
g_date_clear (&date, 1);
|
|
g_date_set_time (&date, t);
|
|
secs = tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
|
|
return datetime_g_to_serial (&date, conv) +
|
|
secs / (double)SECS_PER_DAY;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
int
|
|
datetime_serial_raw_to_serial (double raw)
|
|
{
|
|
return (int) floor (raw + HALF_SEC);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
int
|
|
datetime_timet_to_serial (time_t t, GODateConventions const *conv)
|
|
{
|
|
return datetime_serial_raw_to_serial (datetime_timet_to_serial_raw (t, conv));
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
time_t
|
|
datetime_serial_to_timet (int serial, GODateConventions const *conv)
|
|
{
|
|
GDate gd;
|
|
struct tm tm;
|
|
|
|
datetime_serial_to_g (&gd, serial, conv);
|
|
g_date_to_struct_tm (&gd, &tm);
|
|
|
|
return mktime (&tm);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* This is time-only assuming a 24h day. It probably loses completely on */
|
|
/* days with summer time ("daylight savings") changes. */
|
|
|
|
int
|
|
datetime_serial_raw_to_seconds (double raw)
|
|
{
|
|
raw += HALF_SEC;
|
|
return (raw - floor (raw)) * SECS_PER_DAY;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
int
|
|
datetime_timet_to_seconds (time_t t)
|
|
{
|
|
/* we just want the seconds, actual date does not matter. So we can ignore
|
|
* the date convention (1900 vs 1904) */
|
|
return datetime_serial_raw_to_seconds (datetime_timet_to_serial_raw (t, NULL));
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
int
|
|
datetime_g_days_between (GDate const* date1, GDate const *date2)
|
|
{
|
|
g_return_val_if_fail (g_date_valid (date1), 0);
|
|
g_return_val_if_fail (g_date_valid (date2), 0);
|
|
|
|
return (int) (g_date_get_julian (date2) - g_date_get_julian (date1));
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
int
|
|
datetime_g_months_between (GDate const *date1, GDate const *date2)
|
|
{
|
|
g_return_val_if_fail (g_date_valid (date1), 0);
|
|
g_return_val_if_fail (g_date_valid (date2), 0);
|
|
|
|
/* find the difference according to the month and year ordinals,
|
|
but discount the last month if there are not enough days. */
|
|
return 12 * (g_date_get_year (date2) - g_date_get_year (date1))
|
|
+ g_date_get_month (date2) - g_date_get_month (date1)
|
|
- (g_date_get_day (date2) >= g_date_get_day (date1) ? 0 : 1);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
int
|
|
datetime_g_years_between (GDate const *date1, GDate const *date2)
|
|
{
|
|
int months;
|
|
|
|
g_return_val_if_fail (g_date_valid (date1), 0);
|
|
g_return_val_if_fail (g_date_valid (date2), 0);
|
|
|
|
months = datetime_g_months_between (date1, date2);
|
|
return months > 0 ? months / 12 : -(-months / 12);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* datetime_weeknum (GDate *date, int method)
|
|
* @date date
|
|
* @method week numbering method
|
|
*
|
|
* Returns week number according to the given method.
|
|
* 1: Week starts on Sunday. Days before first Sunday are in week 0.
|
|
* 2: Week starts on Monday. Days before first Monday are in week 0.
|
|
* 150: ISO 8601 week number. See datetime_isoweeknum.
|
|
*/
|
|
int
|
|
datetime_weeknum (GDate const *date, int method)
|
|
{
|
|
int res;
|
|
|
|
g_return_val_if_fail (g_date_valid (date), -1);
|
|
g_return_val_if_fail (method == WEEKNUM_METHOD_SUNDAY ||
|
|
method == WEEKNUM_METHOD_MONDAY ||
|
|
method == WEEKNUM_METHOD_ISO,
|
|
-1);
|
|
|
|
switch (method) {
|
|
case WEEKNUM_METHOD_SUNDAY:
|
|
res = g_date_get_sunday_week_of_year (date); break;
|
|
case WEEKNUM_METHOD_MONDAY:
|
|
res = g_date_get_monday_week_of_year (date); break;
|
|
case WEEKNUM_METHOD_ISO:
|
|
res = g_date_get_iso8601_week_of_year (date); break;
|
|
default: res = -1;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
static gint32
|
|
days_between_BASIS_MSRB_30_360 (GDate const *from, GDate const *to)
|
|
{
|
|
int y1, m1, d1, y2, m2, d2;
|
|
|
|
y1 = g_date_get_year (from);
|
|
m1 = g_date_get_month (from);
|
|
d1 = g_date_get_day (from);
|
|
y2 = g_date_get_year (to);
|
|
m2 = g_date_get_month (to);
|
|
d2 = g_date_get_day (to);
|
|
|
|
if (m1 == 2 && g_date_is_last_of_month (from))
|
|
d1 = 30;
|
|
if (d2 == 31 && d1 >= 30)
|
|
d2 = 30;
|
|
if (d1 == 31)
|
|
d1 = 30;
|
|
|
|
return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
|
|
}
|
|
|
|
static gint32
|
|
days_between_BASIS_MSRB_30_360_SYM (GDate const *from, GDate const *to)
|
|
{
|
|
int y1, m1, d1, y2, m2, d2;
|
|
|
|
y1 = g_date_get_year (from);
|
|
m1 = g_date_get_month (from);
|
|
d1 = g_date_get_day (from);
|
|
y2 = g_date_get_year (to);
|
|
m2 = g_date_get_month (to);
|
|
d2 = g_date_get_day (to);
|
|
|
|
if (m1 == 2 && g_date_is_last_of_month (from))
|
|
d1 = 30;
|
|
if (m2 == 2 && g_date_is_last_of_month (to))
|
|
d2 = 30;
|
|
if (d2 == 31 && d1 >= 30)
|
|
d2 = 30;
|
|
if (d1 == 31)
|
|
d1 = 30;
|
|
|
|
return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
|
|
}
|
|
|
|
static gint32
|
|
days_between_BASIS_30E_360 (GDate const *from, GDate const *to)
|
|
{
|
|
int y1, m1, d1, y2, m2, d2;
|
|
|
|
y1 = g_date_get_year (from);
|
|
m1 = g_date_get_month (from);
|
|
d1 = g_date_get_day (from);
|
|
y2 = g_date_get_year (to);
|
|
m2 = g_date_get_month (to);
|
|
d2 = g_date_get_day (to);
|
|
|
|
if (d1 == 31)
|
|
d1 = 30;
|
|
if (d2 == 31)
|
|
d2 = 30;
|
|
|
|
return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
|
|
}
|
|
|
|
static gint32
|
|
days_between_BASIS_30Ep_360 (GDate const *from, GDate const *to)
|
|
{
|
|
int y1, m1, d1, y2, m2, d2;
|
|
|
|
y1 = g_date_get_year (from);
|
|
m1 = g_date_get_month (from);
|
|
d1 = g_date_get_day (from);
|
|
y2 = g_date_get_year (to);
|
|
m2 = g_date_get_month (to);
|
|
d2 = g_date_get_day (to);
|
|
|
|
if (d1 == 31)
|
|
d1 = 30;
|
|
if (d2 == 31) {
|
|
d2 = 1;
|
|
m2++;
|
|
/* No need to check for m2 == 13 since 12*30 == 360 */
|
|
}
|
|
|
|
return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
|
|
}
|
|
|
|
/*
|
|
* days_between_basis
|
|
*
|
|
* @from : GDate *
|
|
* @to : GDate *
|
|
* @basis : basis_t
|
|
* see datetime.h and doc/fn-financial-basis.txt for details
|
|
*
|
|
* @in_order : dates are considered in order
|
|
*
|
|
* returns : Number of days strictly between from and to +1
|
|
*
|
|
*/
|
|
|
|
gint32
|
|
days_between_basis (GDate const *from, GDate const *to, basis_t basis)
|
|
{
|
|
int sign = 1;
|
|
|
|
if (g_date_compare (from, to) == 1) {
|
|
GDate const *tmp = from;
|
|
from = to;
|
|
to = tmp;
|
|
sign = -1;
|
|
}
|
|
|
|
switch (basis) {
|
|
case BASIS_ACT_ACT:
|
|
case BASIS_ACT_360:
|
|
case BASIS_ACT_365:
|
|
return sign * (g_date_get_julian (to) - g_date_get_julian (from));
|
|
case BASIS_30E_360:
|
|
return sign * days_between_BASIS_30E_360 (from, to);
|
|
case BASIS_30Ep_360:
|
|
return sign * days_between_BASIS_30Ep_360 (from, to);
|
|
case BASIS_MSRB_30_360_SYM:
|
|
return sign * days_between_BASIS_MSRB_30_360_SYM (from, to);
|
|
case BASIS_MSRB_30_360:
|
|
default:
|
|
return sign * days_between_BASIS_MSRB_30_360 (from, to);
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* coup_cd
|
|
*
|
|
* @res :
|
|
* @settlement: GDate *
|
|
* @maturity : GDate * must follow settlement strictly
|
|
* @freq : int divides 12 evenly
|
|
* @eom : gboolean whether to do special end of month
|
|
* handling
|
|
* @next : gboolean whether next or previous date
|
|
*
|
|
* returns : GDate * next or previous coupon date
|
|
*
|
|
* this function does not depend on the basis of counting!
|
|
*/
|
|
void
|
|
coup_cd (GDate *result, GDate const *settlement, GDate const *maturity,
|
|
int freq, gboolean eom, gboolean next)
|
|
{
|
|
int months, periods;
|
|
gboolean is_eom_special;
|
|
|
|
is_eom_special = eom && g_date_is_last_of_month (maturity);
|
|
|
|
g_date_clear (result, 1);
|
|
|
|
months = 12 / freq;
|
|
periods = (g_date_get_year(maturity) - g_date_get_year (settlement));
|
|
if (periods > 0)
|
|
periods = (periods - 1) * freq;
|
|
|
|
do {
|
|
g_date_set_julian (result, g_date_get_julian (maturity));
|
|
periods++;
|
|
g_date_subtract_months (result, periods * months);
|
|
if (is_eom_special) {
|
|
int ndays = g_date_get_days_in_month
|
|
(g_date_get_month (result),
|
|
g_date_get_year (result));
|
|
g_date_set_day (result, ndays);
|
|
}
|
|
} while (g_date_compare (settlement, result) < 0 );
|
|
|
|
if (next) {
|
|
g_date_set_julian (result, g_date_get_julian (maturity));
|
|
periods--;
|
|
g_date_subtract_months (result, periods * months);
|
|
if (is_eom_special) {
|
|
int ndays = g_date_get_days_in_month
|
|
(g_date_get_month (result),
|
|
g_date_get_year (result));
|
|
g_date_set_day (result, ndays);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
|
|
/*
|
|
* Returns the number of days in the coupon period of the settlement date.
|
|
* Currently, returns negative numbers if the branch is not implemented.
|
|
*/
|
|
double
|
|
coupdays (GDate const *settlement, GDate const *maturity,
|
|
GnmCouponConvention const *conv)
|
|
{
|
|
GDate prev, next;
|
|
|
|
switch (conv->basis) {
|
|
case BASIS_MSRB_30_360:
|
|
case BASIS_ACT_360:
|
|
case BASIS_30E_360:
|
|
case BASIS_30Ep_360:
|
|
return 360 / conv->freq;
|
|
case BASIS_ACT_365:
|
|
return 365.0 / conv->freq;
|
|
case BASIS_ACT_ACT:
|
|
default:
|
|
coup_cd (&next, settlement, maturity, conv->freq, conv->eom, TRUE);
|
|
coup_cd (&prev, settlement, maturity, conv->freq, conv->eom, FALSE);
|
|
return days_between_basis (&prev, &next, BASIS_ACT_ACT);
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
|
|
/*
|
|
* Returns the number of days from the beginning of the coupon period to
|
|
* the settlement date.
|
|
*/
|
|
double
|
|
coupdaybs (GDate const *settlement, GDate const *maturity,
|
|
GnmCouponConvention const *conv)
|
|
{
|
|
GDate prev_coupon;
|
|
coup_cd (&prev_coupon, settlement, maturity, conv->freq, conv->eom, FALSE);
|
|
return days_between_basis (&prev_coupon, settlement, conv->basis);
|
|
}
|
|
|
|
/**
|
|
* coupdaysnc :
|
|
* @settlement :
|
|
* @maturity :
|
|
* @freq :
|
|
* @basis :
|
|
* @eom :
|
|
*
|
|
* Returns the number of days from the settlement date to the next
|
|
* coupon date.
|
|
**/
|
|
double
|
|
coupdaysnc (GDate const *settlement, GDate const *maturity,
|
|
GnmCouponConvention const *conv)
|
|
{
|
|
GDate next_coupon;
|
|
coup_cd (&next_coupon, settlement, maturity, conv->freq, conv->eom, TRUE);
|
|
return days_between_basis (settlement, &next_coupon, conv->basis);
|
|
}
|
|
|
|
int
|
|
gnm_date_convention_base (GODateConventions const *conv)
|
|
{
|
|
g_return_val_if_fail (conv != NULL, 1900);
|
|
return conv->use_1904 ? 1904 : 1900;
|
|
}
|