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.
694 lines
17 KiB
694 lines
17 KiB
/********************************************************************\
|
|
* date.c -- utility functions to handle the date (adjusting, get *
|
|
* current date, etc.) for xacc (X-Accountant) *
|
|
* Copyright (C) 1997 Robin D. Clark *
|
|
* Copyright (C) 1998, 1999, 2000 Linas Vepstas *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* 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 General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
|
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
|
* *
|
|
* Author: Rob Clark rclark@cs.hmc.edu *
|
|
* *
|
|
\********************************************************************/
|
|
|
|
#define _GNU_SOURCE
|
|
#define __EXTENSIONS__
|
|
|
|
#include "config.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#ifdef HAVE_LANGINFO_D_FMT
|
|
#include <langinfo.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "date.h"
|
|
#include "gnc-engine-util.h"
|
|
|
|
#define NANOS_PER_SECOND 1000000000
|
|
|
|
#ifdef HAVE_LANGINFO_D_FMT
|
|
# define GNC_D_FMT (nl_langinfo (D_FMT))
|
|
#else
|
|
# define GNC_D_FMT "%Y-%m-%d"
|
|
#endif
|
|
|
|
/* This is now user configured through the gnome options system() */
|
|
static DateFormat dateFormat = DATE_FORMAT_US;
|
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static short module = MOD_ENGINE;
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static void
|
|
timespec_normalize(Timespec *t)
|
|
{
|
|
if(t->tv_nsec > NANOS_PER_SECOND)
|
|
{
|
|
t->tv_sec+= (t->tv_nsec / NANOS_PER_SECOND);
|
|
t->tv_nsec= t->tv_nsec % NANOS_PER_SECOND;
|
|
}
|
|
|
|
if(t->tv_nsec < - NANOS_PER_SECOND)
|
|
{
|
|
t->tv_sec+= - (-t->tv_nsec / NANOS_PER_SECOND);
|
|
t->tv_nsec = - (-t->tv_nsec % NANOS_PER_SECOND);
|
|
}
|
|
|
|
if (t->tv_sec > 0 && t->tv_nsec < 0)
|
|
{
|
|
t->tv_sec--;
|
|
t->tv_nsec = NANOS_PER_SECOND + t->tv_nsec;
|
|
}
|
|
|
|
if (t->tv_sec < 0 && t->tv_nsec > 0)
|
|
{
|
|
t->tv_sec++;
|
|
t->tv_nsec = - NANOS_PER_SECOND + t->tv_nsec;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
gboolean
|
|
timespec_equal (const Timespec *ta, const Timespec *tb)
|
|
{
|
|
if(ta == tb) return TRUE;
|
|
if(ta->tv_sec != tb->tv_sec) return FALSE;
|
|
if(ta->tv_nsec != tb->tv_nsec) return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
gint
|
|
timespec_cmp(const Timespec *ta, const Timespec *tb)
|
|
{
|
|
if(ta == tb) return 0;
|
|
if(ta->tv_sec < tb->tv_sec) return -1;
|
|
if(ta->tv_sec > tb->tv_sec) return 1;
|
|
if(ta->tv_nsec < tb->tv_nsec) return -1;
|
|
if(ta->tv_nsec > tb->tv_nsec) return 1;
|
|
return 0;
|
|
}
|
|
|
|
Timespec
|
|
timespec_diff(const Timespec *ta, const Timespec *tb)
|
|
{
|
|
Timespec retval;
|
|
retval.tv_sec = ta->tv_sec - tb->tv_sec;
|
|
retval.tv_nsec = ta->tv_nsec - tb->tv_nsec;
|
|
timespec_normalize(&retval);
|
|
return retval;
|
|
}
|
|
|
|
Timespec
|
|
timespec_abs(const Timespec *t)
|
|
{
|
|
Timespec retval = *t;
|
|
|
|
timespec_normalize(&retval);
|
|
if (retval.tv_sec < 0)
|
|
{
|
|
retval.tv_sec = - retval.tv_sec;
|
|
retval.tv_nsec = - retval.tv_nsec;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* timespecCanonicalDayTime
|
|
* given a timepair contains any time on a certain day (local time)
|
|
* converts it to be midday that day.
|
|
*/
|
|
|
|
Timespec
|
|
timespecCanonicalDayTime(Timespec t)
|
|
{
|
|
struct tm tm, *result;
|
|
Timespec retval;
|
|
time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
|
|
result = localtime(&t_secs);
|
|
tm = *result;
|
|
tm.tm_sec = 0;
|
|
tm.tm_min = 0;
|
|
tm.tm_hour = 12;
|
|
tm.tm_isdst = -1;
|
|
retval.tv_sec = mktime(&tm);
|
|
retval.tv_nsec = 0;
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* setDateFormat
|
|
* set date format to one of US, UK, CE, OR ISO
|
|
* checks to make sure it's a legal value
|
|
* Args: DateFormat: enumeration indicating preferred format
|
|
* returns: nothing
|
|
*
|
|
* Globals: dateFormat
|
|
**/
|
|
|
|
void setDateFormat(DateFormat df)
|
|
{
|
|
if(df >= DATE_FORMAT_FIRST && df <= DATE_FORMAT_LAST)
|
|
{
|
|
dateFormat = df;
|
|
}
|
|
else
|
|
{ /* hack alert - is this what we should be doing here? */
|
|
PERR("non-existent date format set");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* printDate
|
|
* Convert a date as day / month / year integers into a localized string
|
|
* representation
|
|
*
|
|
* Args: buff - pointer to previously allocated character array; its size
|
|
* must be at lease MAX_DATE_LENTH bytes.
|
|
* day - day of the month as 1 ... 31
|
|
* month - month of the year as 1 ... 12
|
|
* year - year (4-digit)
|
|
*
|
|
* Return: nothing
|
|
*
|
|
* Globals: global dateFormat value
|
|
*/
|
|
void
|
|
printDate (char * buff, int day, int month, int year)
|
|
{
|
|
if (!buff) return;
|
|
|
|
/* Note that when printing year, we use %-4d in format string;
|
|
* this causes a one, two or three-digit year to be left-adjusted
|
|
* when printed (i.e. padded with blanks on the right). This is
|
|
* important while the user is editing the year, since erasing a
|
|
* digit can temporarily cause a three-digit year, and having the
|
|
* blank on the left is a real pain for the user. So pad on the
|
|
* right.
|
|
*/
|
|
switch(dateFormat)
|
|
{
|
|
case DATE_FORMAT_UK:
|
|
sprintf (buff, "%2d/%2d/%-4d", day, month, year);
|
|
break;
|
|
case DATE_FORMAT_CE:
|
|
sprintf (buff, "%2d.%2d.%-4d", day, month, year);
|
|
break;
|
|
case DATE_FORMAT_ISO:
|
|
sprintf (buff, "%04d-%02d-%02d", year, month, day);
|
|
break;
|
|
case DATE_FORMAT_LOCALE:
|
|
{
|
|
struct tm tm_str;
|
|
|
|
tm_str.tm_mday = day;
|
|
tm_str.tm_mon = month - 1; /* tm_mon = 0 through 11 */
|
|
tm_str.tm_year = year - 1900; /* this is what the standard
|
|
* says, it's not a Y2K thing */
|
|
tm_str.tm_hour = 0;
|
|
tm_str.tm_min = 0;
|
|
tm_str.tm_sec = 0;
|
|
tm_str.tm_isdst = -1;
|
|
|
|
strftime (buff, MAX_DATE_LENGTH, GNC_D_FMT, &tm_str);
|
|
}
|
|
break;
|
|
|
|
case DATE_FORMAT_US:
|
|
default:
|
|
sprintf (buff, "%2d/%2d/%-4d", month, day, year);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
printDateSecs (char * buff, time_t t)
|
|
{
|
|
struct tm *theTime;
|
|
|
|
if (!buff) return;
|
|
|
|
theTime = localtime (&t);
|
|
|
|
printDate (buff, theTime->tm_mday,
|
|
theTime->tm_mon + 1,
|
|
theTime->tm_year + 1900);
|
|
}
|
|
|
|
char *
|
|
xaccPrintDateSecs (time_t t)
|
|
{
|
|
char buff[100];
|
|
printDateSecs (buff, t);
|
|
return g_strdup (buff);
|
|
}
|
|
|
|
const char *
|
|
gnc_print_date (Timespec ts)
|
|
{
|
|
static char buff[MAX_DATE_LENGTH];
|
|
time_t t;
|
|
|
|
t = ts.tv_sec + (ts.tv_nsec / 1000000000.0);
|
|
|
|
printDateSecs (buff, t);
|
|
|
|
return buff;
|
|
}
|
|
|
|
/**
|
|
* scanDate
|
|
* Convert a string into day / month / year integers according to
|
|
* the current dateFormat value.
|
|
*
|
|
* Args: buff - pointer to date string
|
|
* day - will store day of the month as 1 ... 31
|
|
* month - will store month of the year as 1 ... 12
|
|
* year - will store the year (4-digit)
|
|
*
|
|
* Return: nothing
|
|
*
|
|
* Globals: global dateFormat value
|
|
*/
|
|
void
|
|
scanDate (const char *buff, int *day, int *month, int *year)
|
|
{
|
|
char *dupe, *tmp, *first_field, *second_field, *third_field;
|
|
int iday, imonth, iyear;
|
|
struct tm *now;
|
|
time_t secs;
|
|
|
|
if (!buff) return;
|
|
|
|
dupe = g_strdup (buff);
|
|
|
|
tmp = dupe;
|
|
first_field = NULL;
|
|
second_field = NULL;
|
|
third_field = NULL;
|
|
|
|
/* use strtok to find delimiters */
|
|
if (tmp) {
|
|
first_field = strtok (tmp, ".,-+/\\()");
|
|
if (first_field) {
|
|
second_field = strtok (NULL, ".,-+/\\()");
|
|
if (second_field) {
|
|
third_field = strtok (NULL, ".,-+/\\()");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if any fields appear blank, use today's date */
|
|
time (&secs);
|
|
now = localtime (&secs);
|
|
iday = now->tm_mday;
|
|
imonth = now->tm_mon+1;
|
|
iyear = now->tm_year+1900;
|
|
|
|
/* get numeric values */
|
|
switch (dateFormat)
|
|
{
|
|
case DATE_FORMAT_LOCALE:
|
|
if (buff[0] != '\0')
|
|
{
|
|
struct tm thetime;
|
|
|
|
strptime (buff, GNC_D_FMT, &thetime);
|
|
|
|
iday = thetime.tm_mday;
|
|
imonth = thetime.tm_mon + 1;
|
|
iyear = thetime.tm_year + 1900;
|
|
}
|
|
break;
|
|
case DATE_FORMAT_UK:
|
|
case DATE_FORMAT_CE:
|
|
if (first_field) iday = atoi (first_field);
|
|
if (second_field) imonth = atoi (second_field);
|
|
if (third_field) iyear = atoi (third_field);
|
|
break;
|
|
case DATE_FORMAT_ISO:
|
|
if (first_field) iyear = atoi (first_field);
|
|
if (second_field) imonth = atoi (second_field);
|
|
if (third_field) iday = atoi (third_field);
|
|
break;
|
|
case DATE_FORMAT_US:
|
|
default:
|
|
if (first_field) imonth = atoi (first_field);
|
|
if (second_field) iday = atoi (second_field);
|
|
if (third_field) iyear = atoi (third_field);
|
|
break;
|
|
}
|
|
|
|
g_free (dupe);
|
|
|
|
/* if the year entered is smaller than 100, assume we mean the current
|
|
century (and are not revising some roman emperor's books) */
|
|
if (iyear < 100)
|
|
iyear += ((int) ((now->tm_year+1950-iyear)/100)) * 100;
|
|
|
|
if (year) *year=iyear;
|
|
if (month) *month=imonth;
|
|
if (day) *day=iday;
|
|
}
|
|
|
|
/**
|
|
* dateSeparator
|
|
* Return the field separator for the current date format
|
|
*
|
|
* Args: none
|
|
*
|
|
* Return: date character
|
|
*
|
|
* Globals: global dateFormat value
|
|
*/
|
|
char dateSeparator ()
|
|
{
|
|
static char locale_separator = '\0';
|
|
|
|
switch (dateFormat)
|
|
{
|
|
case DATE_FORMAT_CE:
|
|
return '.';
|
|
case DATE_FORMAT_ISO:
|
|
return '-';
|
|
case DATE_FORMAT_US:
|
|
case DATE_FORMAT_UK:
|
|
default:
|
|
return '/';
|
|
case DATE_FORMAT_LOCALE:
|
|
if (locale_separator != '\0')
|
|
return locale_separator;
|
|
else
|
|
{ /* Make a guess */
|
|
char string[256];
|
|
struct tm *tm;
|
|
time_t secs;
|
|
char *s;
|
|
|
|
secs = time(NULL);
|
|
tm = localtime(&secs);
|
|
strftime(string, sizeof(string), GNC_D_FMT, tm);
|
|
|
|
for (s = string; s != '\0'; s++)
|
|
if (!isdigit(*s))
|
|
return (locale_separator = *s);
|
|
}
|
|
}
|
|
|
|
return '\0';
|
|
}
|
|
|
|
/********************************************************************\
|
|
* iso 8601 datetimes should look like 1998-07-02 11:00:00.68-05
|
|
\********************************************************************/
|
|
/* hack alert -- this routine returns incorrect values for
|
|
* dates before 1970 */
|
|
|
|
static Timespec
|
|
gnc_iso8601_to_timespec(const char *str, int do_localtime)
|
|
{
|
|
char buf[4];
|
|
Timespec ts;
|
|
struct tm stm;
|
|
long int nsec =0;
|
|
|
|
ts.tv_sec=0;
|
|
ts.tv_nsec=0;
|
|
if (!str) return ts;
|
|
|
|
stm.tm_year = atoi(str) - 1900;
|
|
str = strchr (str, '-'); if (str) { str++; } else { return ts; }
|
|
stm.tm_mon = atoi(str) - 1;
|
|
str = strchr (str, '-'); if (str) { str++; } else { return ts; }
|
|
stm.tm_mday = atoi(str);
|
|
|
|
str = strchr (str, ' '); if (str) { str++; } else { return ts; }
|
|
stm.tm_hour = atoi(str);
|
|
str = strchr (str, ':'); if (str) { str++; } else { return ts; }
|
|
stm.tm_min = atoi(str);
|
|
str = strchr (str, ':'); if (str) { str++; } else { return ts; }
|
|
stm.tm_sec = atoi (str);
|
|
|
|
/* the decimal point, optionally present ... */
|
|
/* hack alert -- this algo breaks if more than 9 decimal places present */
|
|
if (strchr (str, '.'))
|
|
{
|
|
int decimals, i, multiplier=1000000000;
|
|
str = strchr (str, '.') +1;
|
|
decimals = strcspn (str, "+- ");
|
|
for (i=0; i<decimals; i++) multiplier /= 10;
|
|
nsec = atoi(str) * multiplier;
|
|
}
|
|
stm.tm_isdst = -1;
|
|
|
|
/* timezone format can be +hh or +hhmm or +hh.mm (or -) */
|
|
str += strcspn (str, "+-");
|
|
buf[0] = str[0];
|
|
buf[1] = str[1];
|
|
buf[2] = str[2];
|
|
buf[3] = 0;
|
|
stm.tm_hour -= atoi(buf);
|
|
|
|
str +=3;
|
|
if ('.' == *str) str++;
|
|
if (isdigit (*str) && isdigit (*(str+1)))
|
|
{
|
|
int cyn;
|
|
/* copy sign from hour part */
|
|
if ('+' == buf[0]) { cyn = -1; } else { cyn = +1; }
|
|
buf[0] = str[0];
|
|
buf[1] = str[1];
|
|
buf[2] = str[2];
|
|
buf[3] = 0;
|
|
stm.tm_min += cyn * atoi(buf);
|
|
}
|
|
|
|
/* adjust for the local timezone */
|
|
if (do_localtime)
|
|
{
|
|
struct tm *tm;
|
|
int tz_hour;
|
|
time_t secs = mktime (&stm);
|
|
|
|
/* The call to localtime is 'bogus', but it forces 'timezone' to be set.
|
|
* Note that we must use the accurate date, since the value of 'gnc_timezone'
|
|
* includes daylight savings corrections for that date. */
|
|
tm = localtime (&secs);
|
|
tz_hour = gnc_timezone(tm)/3600;
|
|
stm.tm_hour -= tz_hour;
|
|
stm.tm_min -= (gnc_timezone(tm) - 3600*tz_hour)/60;
|
|
}
|
|
|
|
/* compute number of seconds */
|
|
ts.tv_sec = mktime (&stm);
|
|
ts.tv_nsec = nsec;
|
|
|
|
return ts;
|
|
}
|
|
|
|
Timespec
|
|
gnc_iso8601_to_timespec_local(const char *str)
|
|
{
|
|
return gnc_iso8601_to_timespec(str, 1);
|
|
}
|
|
|
|
Timespec
|
|
gnc_iso8601_to_timespec_gmt(const char *str)
|
|
{
|
|
return gnc_iso8601_to_timespec(str, 0);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
char *
|
|
gnc_timespec_to_iso8601_buff (Timespec ts, char * buff)
|
|
{
|
|
int len;
|
|
int tz_hour, tz_min;
|
|
char cyn;
|
|
time_t tmp;
|
|
struct tm parsed;
|
|
|
|
tmp = ts.tv_sec;
|
|
localtime_r(&tmp, &parsed);
|
|
|
|
tz_hour = gnc_timezone (&parsed) / 3600;
|
|
tz_min = (gnc_timezone (&parsed) - 3600*tz_hour) / 60;
|
|
if (0>tz_min) { tz_min +=60; tz_hour --; }
|
|
if (60<=tz_min) { tz_min -=60; tz_hour ++; }
|
|
|
|
/* we also have to print the sign by hand, to work around a bug
|
|
* in the glibc 2.1.3 printf (where %+02d fails to zero-pad)
|
|
*/
|
|
cyn = '-';
|
|
if (0>tz_hour) { cyn = '+'; tz_hour = -tz_hour; }
|
|
|
|
len = sprintf (buff, "%4d-%02d-%02d %02d:%02d:%02d.%06ld %c%02d%02d",
|
|
parsed.tm_year + 1900,
|
|
parsed.tm_mon + 1,
|
|
parsed.tm_mday,
|
|
parsed.tm_hour,
|
|
parsed.tm_min,
|
|
parsed.tm_sec,
|
|
ts.tv_nsec / 1000,
|
|
cyn,
|
|
tz_hour,
|
|
tz_min);
|
|
|
|
/* return pointer to end of string */
|
|
buff += len;
|
|
return buff;
|
|
}
|
|
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
/* hack alert -- this routine returns incorrect values for
|
|
* dates before 1970 */
|
|
|
|
time_t
|
|
xaccDMYToSec (int day, int month, int year)
|
|
{
|
|
struct tm stm;
|
|
time_t secs;
|
|
|
|
stm.tm_year = year - 1900;
|
|
stm.tm_mon = month - 1;
|
|
stm.tm_mday = day;
|
|
stm.tm_hour = 0;
|
|
stm.tm_min = 0;
|
|
stm.tm_sec = 0;
|
|
stm.tm_isdst = -1;
|
|
|
|
/* compute number of seconds */
|
|
secs = mktime (&stm);
|
|
|
|
return secs;
|
|
}
|
|
|
|
time_t
|
|
xaccScanDateS (const char *str)
|
|
{
|
|
int month, day, year;
|
|
|
|
scanDate (str, &day, &month, &year);
|
|
|
|
return xaccDMYToSec (day,month,year);
|
|
}
|
|
|
|
#define THIRTY_TWO_YEARS 0x3c30fc00LL
|
|
|
|
static Timespec
|
|
gnc_dmy2timespec_internal (int day, int month, int year, gboolean start_of_day)
|
|
{
|
|
Timespec result;
|
|
struct tm date;
|
|
long long secs = 0;
|
|
long long era = 0;
|
|
|
|
year -= 1900;
|
|
|
|
/* make a crude attempt to deal with dates outside the range of Dec
|
|
* 1901 to Jan 2038. Note we screw up centennial leap years here so
|
|
* hack alert */
|
|
if ((2 > year) || (136 < year))
|
|
{
|
|
era = year / 32;
|
|
year %= 32;
|
|
if (0 > year) { year += 32; era -= 1; }
|
|
}
|
|
|
|
date.tm_year = year;
|
|
date.tm_mon = month - 1;
|
|
date.tm_mday = day;
|
|
|
|
if (start_of_day)
|
|
{
|
|
date.tm_hour = 0;
|
|
date.tm_min = 0;
|
|
date.tm_sec = 0;
|
|
}
|
|
else
|
|
{
|
|
date.tm_hour = 23;
|
|
date.tm_min = 59;
|
|
date.tm_sec = 59;
|
|
}
|
|
|
|
date.tm_isdst = -1;
|
|
|
|
/* compute number of seconds */
|
|
secs = mktime (&date);
|
|
|
|
secs += era * THIRTY_TWO_YEARS;
|
|
|
|
result.tv_sec = secs;
|
|
result.tv_nsec = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
Timespec
|
|
gnc_dmy2timespec (int day, int month, int year)
|
|
{
|
|
return gnc_dmy2timespec_internal (day, month, year, TRUE);
|
|
}
|
|
|
|
Timespec
|
|
gnc_dmy2timespec_end (int day, int month, int year)
|
|
{
|
|
return gnc_dmy2timespec_internal (day, month, year, FALSE);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
long int
|
|
gnc_timezone (struct tm *tm)
|
|
{
|
|
g_return_val_if_fail (tm != NULL, 0);
|
|
|
|
#ifdef HAVE_STRUCT_TM_GMTOFF
|
|
/* tm_gmtoff is seconds *east* of UTC and is
|
|
* already adjusted for daylight savings time. */
|
|
return -(tm->tm_gmtoff);
|
|
#else
|
|
/* timezone is seconds *west* of UTC and is
|
|
* not adjusted for daylight savings time.
|
|
* In Spring, we spring forward, wheee! */
|
|
return timezone - (tm->tm_isdst > 0 ? 60 * 60 : 0);
|
|
#endif
|
|
}
|
|
|
|
/********************** END OF FILE *********************************\
|
|
\********************************************************************/
|