datetime.cpp

来自「Shorthand是一个强大的脚本语言」· C++ 代码 · 共 826 行 · 第 1/2 页

CPP
826
字号
/////////////////////////////////////////////////////////////////////////////
// $Header: /shorthand/src/datetime.cpp 5     1/09/03 7:14p Arm $
//---------------------------------------------------------------------------
// This file is part of "libAndrix" library - a collection of classes
// and functions developed by Andrei Remenchuk.
//---------------------------------------------------------------------------
// While you may own complete copyright on the project with which you have
// received this file, the author reserves the right to use code contained
// in this very file for any purposes, including publishing and usage in
// any free or commercial software.
//
// You may re-distribute this file or re-use it in your own free or
// commercial software provided that this text is included in the file.
// If you change this file you must include clear notice stating that
// you changed this file and the date of change.
//
// This statement doesn't apply to other files that are part of the same
// package unless otherwise noted.
//---------------------------------------------------------------------------
// (c) 1998-2002 Andrei Remenchuk <andrei@remenchuk.com>
//---------------------------------------------------------------------------
// datetime.cpp - datetime object implementation
/////////////////////////////////////////////////////////////////////////////
#include "config.h"

#include <stdlib.h>
#include <math.h>
#include <ctype.h>

#ifdef SOLARIS
#define _POSIX_PTHREAD_SEMANTICS
#include <time.h>
#include <sys/time.h>
#endif


#include "datetime.h"
#include "cstring.h"
#include "regexx.h"

/*
 * GREGORIAN_ROME is Julian Date Number (JDN) when conversion 
 * from Julian to Gregorian calendar took place - October 4, 1582.
 * The day of October 4, 1582 was followed by October 15, 1582.
 *
 * This constant is necessary for dates arithmetic.
 */
#define GREGORIAN_ROME 2299160

/* Make sure C runtime library timezone variables are set */
static int tz_set_globals() { /*tzset();*/ return 0; }
static int tz_dummy = tz_set_globals();

/**
 * Returns true if year is leap
 */
bool year_is_leap(int year) {
   if (year <= 1600 && year%4==0) return true;
   else if (year%4 == 0 && (year%100 != 0 || year%400 == 0)) return true;
   else return false;
}


/**
 * Returns number of leap years between 1 A.D. and specified year,
 * not including specified year itself.
 */
static int leap_delta(int year) {
    register int delta, y;
    y = year-1;
    delta = y/4;
    if (y <= 1699) return delta;
    else return delta - (y-1600)/100 + (y-1600)/400;
}

static const char* weekday_names[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char* month_names[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

// month sizes
const unsigned char month_sizes[12] = {31,28,31,30, 31, 30, 31, 31, 30, 31, 30, 31};

// zero-based year-day-numbers of first day of each month 
const short month_deltas[12] = {0,31,59,90,120,151,181,212,243,273,304,334};

/**
 * Converts year/month/day to JDN (Julian Day Number) WITHOUT validation
 * of year/month/day components. This function handles only A.D. days.
 */
unsigned int date2julian_relaxed(int year, int month, int day)
{
    // 1721424 is a Julian Day Number of the first day of Common Era (1-Jan-1Y)
    // this is optimized rule, unoptimized looks like this:
    // int j = 1721424 + (year-1)*365 + month_delta[month-1] + day - 1;
    int j = 1721058 + year*365 + month_deltas[month-1] + day;

    if (year_is_leap(year) && month>2) j++;
    j += leap_delta(year);
    if (j>GREGORIAN_ROME) j -= 10;

    return j;

}

/**
 * Determines weekday of arbitrary date (0=Sunday,1=Monday,...).
 */
int determine_weekday(int year, int month, int day)
{
    int k = day;
    int m = month;
    if (month <= 2) { year--; m+=10; } else m -= 2;
    int Y = year%100;
    int C = year/100;
    int W = (k  + ((int)floor(2.6*m - 0.2)) - 2*C + Y + Y/4 + C/4)%7;
    if (W < 0) W = W + 7;
    return W;
}



/**
 * Converts year/month/day to JDN (Julian Day Number) WITH validation
 * of year/month/day components. If components are invalid, exception 
 * is thrown.
 * This function is optimized to handle only A.D. dates.
 */
unsigned int date2julian_strict(int year, int month, int day)
{
    if (year < 1600)
        throw new ShhObjectException(4101, "Invalid year in date operation: %d", year);
    if (month < 1 || month > 12) 
        throw new ShhObjectException(4102, "Invalid month in date operation: %d", month);
    if (month == 2) {
       if (!year_is_leap(year) && day>28) throw new ShhObjectException(4103, "Invalid day number in date operation: %d", day);
    } else 
       if (day > month_sizes[month-1]) throw new ShhObjectException(4103, "Invalid day number in date operation: %d", day);
    
    return date2julian_relaxed(year, month, day);
}


/**
 * Returns JDN of first day of the year
 */
int get_year_start(int year)
{
    return date2julian_relaxed(year, 1, 1);
}

/**
 * Converts JDN (Julian Day Number) to year/month/day
 * Basic validation is performed and if JDN is zero is not in valid range,
 * output variables are not modified and false is returned.
 * If conversion is successfull, return value is true
 */
bool julian2date_relaxed(unsigned long jdn, int* year, int* month, int* day)
{
    // B.C. dates are returned as January 1st, Year 1.
    if (jdn <= 1721424) return false;

    int yday;
    int ystart_year, ystart_month, ystart_day;

    if (jdn == 0) return false;

    // this is approximate estimation of year to speed further calculations up.
    // it's start should be less than `jdn' argument
    ystart_year = jdn/365 - 4713 - jdn/532900;

    if (ystart_year == 0) return false;

    ystart_month = ystart_day = 1;
    unsigned int julian_ystart = date2julian_relaxed( ystart_year, ystart_month, ystart_day );
    if (julian_ystart < 0) 
    { 
        return false;
    }
    
    //XASSERT( julian_ystart <= jdn );

    // hop to the nearest julian year start, 
    while( (yday = jdn-julian_ystart+1) > 365 ) 
    {
        if (yday == 366 && year_is_leap(ystart_year)) break;
        ystart_year++;
        julian_ystart = date2julian_relaxed( ystart_year, ystart_month, ystart_day );
    }

    *year = ystart_year;
    bool leap = year_is_leap(*year);

    // yday #60 is 29-Feb in leap year and 1-Mar is regular year
    // For days after 29-Feb in leap years, we decrease yday,
    // because in leap year days after 29Feb have greater numbers

    if (yday == 60 && leap) 
    { 
        *month = 2; *day = 29; 
    } 
    else 
    {
        if (yday > 60 && leap) yday--; // SVCH
        *month = yday / 32;
        while (yday - month_deltas[*month] > month_sizes[*month]) (*month)++;
    
        //XASSERT(*month>=0 && *month<=11);
        //DEBUGF(1, "yday=%d month=%d month_deltas[month]=%d\n", yday, *month, month_deltas[*month]);

        *day = yday - month_deltas[*month];
        (*month)++;

    }
    return true;
}


/**
 * Converts JDN (Julian Day Number) to year/month/day
 * Throws exception when date is invalid.
 */
void julian2date_strict(unsigned long j, int* year, int* month, int* day)
{
    if (! julian2date_relaxed(j, year, month, day))
    {
        throw new ShhObjectException(4105, "Invalid integer-to-date conversion: cannot derive date from number %u", j);
    }
}


/**
 * Portable method for determining local timezone. On Windows, this doesn't rely
 * on any environment variables and takes information from more reliable
 * Win32 API functions.
 * On Unix, it relies on global timezone variables.
 * 
 * The return value is offset of the local timezone from GMT/UTC in minutes,
 * including any daylight adjustments.
 */
int portable_zone_offset()
{
#ifdef WIN32

    TIME_ZONE_INFORMATION local_zone;
    memset(&local_zone, 0, sizeof(local_zone));
    DWORD mode = GetTimeZoneInformation(&local_zone);
    if (mode == TIME_ZONE_ID_DAYLIGHT)
    {
        local_zone.Bias += local_zone.DaylightBias;
    }
    return local_zone.Bias;

#else

#ifdef SOLARIS

    time_t clock = time(NULL);
    struct tm  local_stm;
    localtime_r(&clock, &local_stm);

    int bias;
    if (local_stm.tm_isdst > 0)
        bias = altzone / 60;
    else
        bias = timezone / 60;
    return bias;

    /*
    struct tm  greenwich_stm;
    gmtime_r   (&clock, &greenwich_stm);

    int bias = mktime(&greenwich_stm) - clock;
    bias /= 60;
    if (greenwich_stm.tm_isdst > 0) bias += 60;
    return bias;*/

#else // !SOLARIS

    time_t clock = time(NULL);
    struct tm local_stm; localtime_r(&clock, &local_stm);
    //struct tm universal_stm;   gmtime_r(&clock, &universal_stm);
    //int bias = mktime(&universal_stm) - mktime(&local_stm);
    int bias = -local_stm.tm_gmtoff;
    bias /= 60;
    return bias;

#endif // !SOLARIS
#endif // !WIN32
}




datetime::datetime()
{
    local();
}

datetime::datetime(const SYSTEMTIME* t)
{
    memcpy(&m_time, t, sizeof(SYSTEMTIME));
}

datetime::datetime(const datetime& other)
{
    memcpy(&m_time, &other.m_time, sizeof(SYSTEMTIME));
}

datetime::~datetime()
{
}


void datetime::import_stm(const struct tm* stm)
{
    m_time.wYear    = stm->tm_year + 1900;
    m_time.wMonth   = stm->tm_mon + 1;
    m_time.wDay     = stm->tm_mday;
    m_time.wHour    = stm->tm_hour;
    m_time.wMinute  = stm->tm_min;
    m_time.wSecond  = stm->tm_sec;
    m_time.wMilliseconds = 0;
}
   

void datetime::export_stm(struct tm* stm) const
{
    stm->tm_year = m_time.wYear - 1900;
    stm->tm_mon  = m_time.wMonth - 1;
    stm->tm_mday = m_time.wDay;
    stm->tm_hour = m_time.wHour;
    stm->tm_min  = m_time.wMinute;
    stm->tm_sec  = m_time.wSecond;
}


void datetime::local()
{
#ifdef WIN32
    GetLocalTime(&m_time);
#else
    time_t t = time(NULL);
    struct tm stm;
    memcpy(&stm, localtime(&t), sizeof(struct tm));
    import_stm(&stm);
#endif
}

// normalizes time 
bool datetime::normalize()
{
#ifdef WIN32
    FILETIME ft;
    return SystemTimeToFileTime(&m_time, &ft) &&
           FileTimeToSystemTime(&ft, &m_time);
#else
    return true;
#endif
}


// returns true if time is not zero
bool datetime::is_valid() const
{
    if (m_time.wYear == 0) return false;
    if (m_time.wMonth == 0) return false;
    if (m_time.wDay == 0) return false;
    return true;
}

// exports value to "YYYY-MM-DD HH:MM:SS" format
void datetime::export_common(string& s) const
{
    s.printf("%04d-%02d-%02d %02d:%02d:%02d", 
        m_time.wYear, m_time.wMonth, m_time.wDay,
        m_time.wHour, m_time.wMinute, m_time.wSecond);
}

// imports value from "YYYY-MM-DD HH:MM:SS" format
bool datetime::import(const char* s)
{
    memset(&m_time, 0, sizeof(SYSTEMTIME));
    int n = string::split(s, "i-i-ii:i:i",
        &m_time.wYear, &m_time.wMonth, &m_time.wDay,
        &m_time.wHour, &m_time.wMinute, &m_time.wSecond
    );
    if (n < 5) return false;
    return normalize();
}


/**
 * Imports value from RFC-822 "e-mail" format which is
 * "Wed, 24 Jul 2002 22:04:50 -0700". This format is also used in HTTP headers, cookies
 * and a number of other places.
 * Some reasoable deviations from the standard are allowed (like two-digit year, 
 * omitted time, etc).
 */
bool datetime::import_rfc(const char* s)
{
    string weekday, month, zone, year, day, hour, min, sec;
    int offset = 0;
        
    RX date_rx("([a-z]+, *)?([0-9]+)[ :-]+([a-z]+)[ :-]+([0-9]+) +([0-9]+):([0-9]+):([0-9]+) +(.*)$");
    if (!date_rx.match(s)) return false;

    date_rx.submatch(2, day);
    date_rx.submatch(3, month);
    date_rx.submatch(4, year);
    date_rx.submatch(5, hour);
    date_rx.submatch(6, min);
    date_rx.submatch(7, sec);
    date_rx.submatch(8, zone);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?