RFC: msvcrt: support dates after year 2038 in mktime64

Robert Wilhelm robert.wilhelm at gmx.net
Sun May 30 11:40:40 CDT 2010


This fixes bug 19465 by using code from http://code.google.com/p/y2038/
which is under MIT License. 






>From 247809c24358518b8b480a92f658c087d5f94f81 Mon Sep 17 00:00:00 2001
From: Robert Wilhelm <robert.wilhelm at gmx.net>
Date: Sun, 30 May 2010 18:09:06 +0200
Subject: support dates after year 2038 in mktime64.

---
 dlls/msvcrt/Makefile.in     |    1 +
 dlls/msvcrt/tests/time.c    |   13 +
 dlls/msvcrt/time.c          |   16 +-
 dlls/msvcrt/time64.c        |  826
+++++++++++++++++++++++++++++++++++++++++++
 dlls/msvcrt/time64.h        |   81 +++++
 dlls/msvcrt/time64_config.h |   76 ++++
 dlls/msvcrt/time64_limits.h |   95 +++++
 7 files changed, 1105 insertions(+), 3 deletions(-)
 create mode 100644 dlls/msvcrt/time64.c
 create mode 100644 dlls/msvcrt/time64.h
 create mode 100644 dlls/msvcrt/time64_config.h
 create mode 100644 dlls/msvcrt/time64_limits.h

diff --git a/dlls/msvcrt/Makefile.in b/dlls/msvcrt/Makefile.in
index 3b2d293..f908fe2 100644
--- a/dlls/msvcrt/Makefile.in
+++ b/dlls/msvcrt/Makefile.in
@@ -32,6 +32,7 @@ C_SRCS = \
 	string.c \
 	thread.c \
 	time.c \
+	time64.c \
 	undname.c \
 	wcs.c
 
diff --git a/dlls/msvcrt/tests/time.c b/dlls/msvcrt/tests/time.c
index 7f0a80b..19bc4da 100644
--- a/dlls/msvcrt/tests/time.c
+++ b/dlls/msvcrt/tests/time.c
@@ -131,6 +131,7 @@ static void test_mktime(void)
     DWORD res =  GetTimeZoneInformation(&tzinfo);
     struct tm my_tm, sav_tm;
     time_t nulltime, local_time;
+    __time64_t local_time64;
     char TZ_env[256];
     char buffer[64];
     int year;
@@ -226,6 +227,18 @@ static void test_mktime(void)
     local_time = mktime(&my_tm);
     ok((local_time == -1), "(bad time) mktime returned %d, expected -1
\n", (int)local_time);
 
+    /* _mktime64*/
+    my_tm = sav_tm;
+    my_tm.tm_year = 139;
+    my_tm.tm_mon = 6;
+    my_tm.tm_mday = 23;
+    my_tm.tm_hour = 3;
+    my_tm.tm_min = 32;
+    my_tm.tm_sec = 4;
+    local_time64 = _mktime64(&my_tm);
+ 
+    ok( local_time64 == 2195001124LL, "mktime returned %I64d, expected
2195001124LL\n", local_time64);
+
     my_tm = sav_tm;
     /* TEST that we are independent from the TZ variable */
     /*Argh, msvcrt doesn't have setenv() */
diff --git a/dlls/msvcrt/time.c b/dlls/msvcrt/time.c
index 2e8828c..4b56939 100644
--- a/dlls/msvcrt/time.c
+++ b/dlls/msvcrt/time.c
@@ -31,11 +31,14 @@
 #endif
 #include <limits.h>
 
+#include <stdio.h>
+
 #include "msvcrt.h"
 #include "mtdll.h"
 #include "winbase.h"
 #include "winnls.h"
 #include "wine/debug.h"
+#include "time64.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(msvcrt);
 
@@ -90,11 +93,11 @@ static inline void unix_tm_to_msvcrt( struct
MSVCRT_tm *dest, const struct tm *s
  */
 MSVCRT___time64_t CDECL MSVCRT__mktime64(struct MSVCRT_tm *mstm)
 {
-    time_t secs;
+    MSVCRT___time64_t secs;
     struct tm tm;
 
     msvcrt_tm_to_unix( &tm, mstm );
-    secs = mktime( &tm );
+    secs = mktime64( &tm );
     unix_tm_to_msvcrt( mstm, &tm );
 
     return secs < 0 ? -1 : secs;
@@ -105,7 +108,14 @@ MSVCRT___time64_t CDECL MSVCRT__mktime64(struct
MSVCRT_tm *mstm)
  */
 MSVCRT___time32_t CDECL MSVCRT__mktime32(struct MSVCRT_tm *mstm)
 {
-    return MSVCRT__mktime64( mstm );
+    MSVCRT___time32_t secs;
+    struct tm tm;
+
+    msvcrt_tm_to_unix( &tm, mstm );
+    secs = mktime( &tm );
+    unix_tm_to_msvcrt( mstm, &tm );
+
+    return secs < 0 ? -1 : secs;
 }
 
 /**********************************************************************
diff --git a/dlls/msvcrt/time64.c b/dlls/msvcrt/time64.c
new file mode 100644
index 0000000..e791876
--- /dev/null
+++ b/dlls/msvcrt/time64.c
@@ -0,0 +1,826 @@
+/* 
+
+Copyright (c) 2007-2010  Michael G Schwern
+
+This software originally derived from Paul Sheer's pivotal_gmtime_r.c.
+
+The MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a
copy
+of this software and associated documentation files (the "Software"),
to deal
+in the Software without restriction, including without limitation the
rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN
+THE SOFTWARE.
+
+*/
+
+/*
+
+Programmers who have available to them 64-bit time values as a 'long
+long' type can use localtime64_r() and gmtime64_r() which correctly
+converts the time even on 32-bit systems. Whether you have 64-bit time 
+values will depend on the operating system.
+
+localtime64_r() is a 64-bit equivalent of localtime_r().
+
+gmtime64_r() is a 64-bit equivalent of gmtime_r().
+
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include "time64.h"
+#include "time64_limits.h"
+
+
+/* Spec says except for stftime() and the _r() functions, these
+   all return static memory.  Stabbings! */
+static struct TM   Static_Return_Date;
+static char        Static_Return_String[35];
+
+static const int days_in_month[2][12] = {
+    {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 julian_days_by_month[2][12] = {
+    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
+    {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
+};
+
+static char wday_name[7][4] = {
+    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static char mon_name[12][4] = {
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const int length_of_year[2] = { 365, 366 };
+
+/* Some numbers relating to the gregorian cycle */
+static const Year     years_in_gregorian_cycle   = 400;
+#define               days_in_gregorian_cycle      ((365 * 400) + 100 -
4 + 1)
+static const Time64_T seconds_in_gregorian_cycle =
days_in_gregorian_cycle * 60LL * 60LL * 24LL;
+
+/* Year range we can trust the time funcitons with */
+#define MAX_SAFE_YEAR 2037
+#define MIN_SAFE_YEAR 1971
+
+/* 28 year Julian calendar cycle */
+#define SOLAR_CYCLE_LENGTH 28
+
+/* Year cycle from MAX_SAFE_YEAR down. */
+static const int safe_years_high[SOLAR_CYCLE_LENGTH] = {
+    2016, 2017, 2018, 2019,
+    2020, 2021, 2022, 2023,
+    2024, 2025, 2026, 2027,
+    2028, 2029, 2030, 2031,
+    2032, 2033, 2034, 2035,
+    2036, 2037, 2010, 2011,
+    2012, 2013, 2014, 2015
+};
+
+/* Year cycle from MIN_SAFE_YEAR up */
+static const int safe_years_low[SOLAR_CYCLE_LENGTH] = {
+    1996, 1997, 1998, 1971,
+    1972, 1973, 1974, 1975,
+    1976, 1977, 1978, 1979,
+    1980, 1981, 1982, 1983,
+    1984, 1985, 1986, 1987,
+    1988, 1989, 1990, 1991,
+    1992, 1993, 1994, 1995,
+};
+
+/* This isn't used, but it's handy to look at */
+static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
+    5, 0, 1, 2,     /* 0       2016 - 2019 */
+    3, 5, 6, 0,     /* 4  */
+    1, 3, 4, 5,     /* 8       1996 - 1998, 1971*/
+    6, 1, 2, 3,     /* 12      1972 - 1975 */
+    4, 6, 0, 1,     /* 16 */
+    2, 4, 5, 6,     /* 20      2036, 2037, 2010, 2011 */
+    0, 2, 3, 4      /* 24      2012, 2013, 2014, 2015 */
+};
+
+/* Let's assume people are going to be looking for dates in the future.
+   Let's provide some cheats so you can skip ahead.
+   This has a 4x speed boost when near 2008.
+*/
+/* Number of days since epoch on Jan 1st, 2008 GMT */
+#define CHEAT_DAYS  (1199145600 / 24 / 60 / 60)
+#define CHEAT_YEARS 108
+
+#define IS_LEAP(n)      ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4)
&& (((n) + 1900) % 100))) != 0)
+#define WRAP(a,b,m)     ((a) = ((a) <  0  ) ? ((b)--, (a) + (m)) : (a))
+
+#ifdef USE_SYSTEM_LOCALTIME
+#    define SHOULD_USE_SYSTEM_LOCALTIME(a)  (       \
+    (a) <= SYSTEM_LOCALTIME_MAX &&              \
+    (a) >= SYSTEM_LOCALTIME_MIN                 \
+)
+#else
+#    define SHOULD_USE_SYSTEM_LOCALTIME(a)      (0)
+#endif
+
+#ifdef USE_SYSTEM_GMTIME
+#    define SHOULD_USE_SYSTEM_GMTIME(a)     (       \
+    (a) <= SYSTEM_GMTIME_MAX    &&              \
+    (a) >= SYSTEM_GMTIME_MIN                    \
+)
+#else
+#    define SHOULD_USE_SYSTEM_GMTIME(a)         (0)
+#endif
+
+/* Multi varadic macros are a C99 thing, alas */
+#ifdef TIME_64_DEBUG
+#    define TIME64_TRACE(format) (fprintf(stderr, format))
+#    define TIME64_TRACE1(format, var1)    (fprintf(stderr, format,
var1))
+#    define TIME64_TRACE2(format, var1, var2)    (fprintf(stderr,
format, var1, var2))
+#    define TIME64_TRACE3(format, var1, var2, var3)    (fprintf(stderr,
format, var1, var2, var3))
+#else
+#    define TIME64_TRACE(format) ((void)0)
+#    define TIME64_TRACE1(format, var1) ((void)0)
+#    define TIME64_TRACE2(format, var1, var2) ((void)0)
+#    define TIME64_TRACE3(format, var1, var2, var3) ((void)0)
+#endif
+
+
+static int is_exception_century(Year year)
+{
+    int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
+    TIME64_TRACE1("# is_exception_century: %s\n", is_exception ?
"yes" : "no");
+
+    return(is_exception);
+}
+
+
+/* Compare two dates.
+   The result is like cmp.
+   Ignores things like gmtoffset and dst
+*/
+int cmp_date( const struct TM* left, const struct tm* right ) {
+    if( left->tm_year > right->tm_year )
+        return 1;
+    else if( left->tm_year < right->tm_year )
+        return -1;
+
+    if( left->tm_mon > right->tm_mon )
+        return 1;
+    else if( left->tm_mon < right->tm_mon )
+        return -1;
+
+    if( left->tm_mday > right->tm_mday )
+        return 1;
+    else if( left->tm_mday < right->tm_mday )
+        return -1;
+
+    if( left->tm_hour > right->tm_hour )
+        return 1;
+    else if( left->tm_hour < right->tm_hour )
+        return -1;
+
+    if( left->tm_min > right->tm_min )
+        return 1;
+    else if( left->tm_min < right->tm_min )
+        return -1;
+
+    if( left->tm_sec > right->tm_sec )
+        return 1;
+    else if( left->tm_sec < right->tm_sec )
+        return -1;
+
+    return 0;
+}
+
+
+/* Check if a date is safely inside a range.
+   The intention is to check if its a few days inside.
+*/
+int date_in_safe_range( const struct TM* date, const struct tm* min,
const struct tm* max ) {
+    if( cmp_date(date, min) == -1 )
+        return 0;
+
+    if( cmp_date(date, max) == 1 )
+        return 0;
+
+    return 1;
+}
+
+
+/* timegm() is not in the C or POSIX spec, but it is such a useful
+   extension I would be remiss in leaving it out.  Also I need it
+   for localtime64()
+*/
+Time64_T timegm64(const struct TM *date) {
+    Time64_T days    = 0;
+    Time64_T seconds = 0;
+    Year     year;
+    Year     orig_year = (Year)date->tm_year;
+    int      cycles  = 0;
+
+    if( orig_year > 100 ) {
+        cycles = (orig_year - 100) / 400;
+        orig_year -= cycles * 400;
+        days      += (Time64_T)cycles * days_in_gregorian_cycle;
+    }
+    else if( orig_year < -300 ) {
+        cycles = (orig_year - 100) / 400;
+        orig_year -= cycles * 400;
+        days      += (Time64_T)cycles * days_in_gregorian_cycle;
+    }
+    TIME64_TRACE3("# timegm/ cycles: %d, days: %lld, orig_year: %lld
\n", cycles, days, orig_year);
+
+    if( orig_year > 70 ) {
+        year = 70;
+        while( year < orig_year ) {
+            days += length_of_year[IS_LEAP(year)];
+            year++;
+        }
+    }
+    else if ( orig_year < 70 ) {
+        year = 69;
+        do {
+            days -= length_of_year[IS_LEAP(year)];
+            year--;
+        } while( year >= orig_year );
+    }
+
+    days += julian_days_by_month[IS_LEAP(orig_year)][date->tm_mon];
+    days += date->tm_mday - 1;
+
+    seconds = days * 60 * 60 * 24;
+
+    seconds += date->tm_hour * 60 * 60;
+    seconds += date->tm_min * 60;
+    seconds += date->tm_sec;
+
+    return(seconds);
+}
+
+
+static int check_tm(struct TM *tm)
+{
+    /* Don't forget leap seconds */
+    assert(tm->tm_sec >= 0);
+    assert(tm->tm_sec <= 61);
+
+    assert(tm->tm_min >= 0);
+    assert(tm->tm_min <= 59);
+
+    assert(tm->tm_hour >= 0);
+    assert(tm->tm_hour <= 23);
+
+    assert(tm->tm_mday >= 1);
+    assert(tm->tm_mday <=
days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
+
+    assert(tm->tm_mon  >= 0);
+    assert(tm->tm_mon  <= 11);
+
+    assert(tm->tm_wday >= 0);
+    assert(tm->tm_wday <= 6);
+    
+    assert(tm->tm_yday >= 0);
+    assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
+
+#ifdef HAS_TM_TM_GMTOFF
+    assert(tm->tm_gmtoff >= -24 * 60 * 60);
+    assert(tm->tm_gmtoff <=  24 * 60 * 60);
+#endif
+
+    return 1;
+}
+
+
+/* The exceptional centuries without leap years cause the cycle to
+   shift by 16
+*/
+static Year cycle_offset(Year year)
+{
+    const Year start_year = 2000;
+    Year year_diff  = year - start_year;
+    Year exceptions;
+
+    if( year > start_year )
+        year_diff--;
+
+    exceptions  = year_diff / 100;
+    exceptions -= year_diff / 400;
+
+    TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n",
+          year, exceptions, year_diff);
+
+    return exceptions * 16;
+}
+
+/* For a given year after 2038, pick the latest possible matching
+   year in the 28 year calendar cycle.
+
+   A matching year...
+   1) Starts on the same day of the week.
+   2) Has the same leap year status.
+
+   This is so the calendars match up.
+
+   Also the previous year must match.  When doing Jan 1st you might
+   wind up on Dec 31st the previous year when doing a -UTC time zone.
+
+   Finally, the next year must have the same start day of week.  This
+   is for Dec 31st with a +UTC time zone.
+   It doesn't need the same leap year status since we only care about
+   January 1st.
+*/
+static int safe_year(const Year year)
+{
+    int safe_year;
+    Year year_cycle;
+
+    if( year >= MIN_SAFE_YEAR && year <= MAX_SAFE_YEAR ) {
+        return (int)year;
+    }
+
+    year_cycle = year + cycle_offset(year);
+
+    /* safe_years_low is off from safe_years_high by 8 years */
+    if( year < MIN_SAFE_YEAR )
+        year_cycle -= 8;
+
+    /* Change non-leap xx00 years to an equivalent */
+    if( is_exception_century(year) )
+        year_cycle += 11;
+
+    /* Also xx01 years, since the previous year will be wrong */
+    if( is_exception_century(year - 1) )
+        year_cycle += 17;
+
+    year_cycle %= SOLAR_CYCLE_LENGTH;
+    if( year_cycle < 0 ) 
+        year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
+
+    assert( year_cycle >= 0 );
+    assert( year_cycle < SOLAR_CYCLE_LENGTH );
+    if( year < MIN_SAFE_YEAR )
+        safe_year = safe_years_low[year_cycle];
+    else if( year > MAX_SAFE_YEAR )
+        safe_year = safe_years_high[year_cycle];
+    else
+        assert(0);
+
+    TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n",
+          year, year_cycle, safe_year);
+
+    assert(safe_year <= MAX_SAFE_YEAR && safe_year >= MIN_SAFE_YEAR);
+
+    return safe_year;
+}
+
+
+void copy_tm_to_TM64(const struct tm *src, struct TM *dest) {
+    if( src == NULL ) {
+        memset(dest, 0, sizeof(*dest));
+    }
+    else {
+#       ifdef USE_TM64
+            dest->tm_sec        = src->tm_sec;
+            dest->tm_min        = src->tm_min;
+            dest->tm_hour       = src->tm_hour;
+            dest->tm_mday       = src->tm_mday;
+            dest->tm_mon        = src->tm_mon;
+            dest->tm_year       = (Year)src->tm_year;
+            dest->tm_wday       = src->tm_wday;
+            dest->tm_yday       = src->tm_yday;
+            dest->tm_isdst      = src->tm_isdst;
+
+#           ifdef HAS_TM_TM_GMTOFF
+                dest->tm_gmtoff  = src->tm_gmtoff;
+#           endif
+
+#           ifdef HAS_TM_TM_ZONE
+                dest->tm_zone  = src->tm_zone;
+#           endif
+
+#       else
+            /* They're the same type */
+            memcpy(dest, src, sizeof(*dest));
+#       endif
+    }
+}
+
+
+void copy_TM64_to_tm(const struct TM *src, struct tm *dest) {
+    if( src == NULL ) {
+        memset(dest, 0, sizeof(*dest));
+    }
+    else {
+#       ifdef USE_TM64
+            dest->tm_sec        = src->tm_sec;
+            dest->tm_min        = src->tm_min;
+            dest->tm_hour       = src->tm_hour;
+            dest->tm_mday       = src->tm_mday;
+            dest->tm_mon        = src->tm_mon;
+            dest->tm_year       = (int)src->tm_year;
+            dest->tm_wday       = src->tm_wday;
+            dest->tm_yday       = src->tm_yday;
+            dest->tm_isdst      = src->tm_isdst;
+
+#           ifdef HAS_TM_TM_GMTOFF
+                dest->tm_gmtoff  = src->tm_gmtoff;
+#           endif
+
+#           ifdef HAS_TM_TM_ZONE
+                dest->tm_zone  = src->tm_zone;
+#           endif
+
+#       else
+            /* They're the same type */
+            memcpy(dest, src, sizeof(*dest));
+#       endif
+    }
+}
+
+
+/* Simulate localtime_r() to the best of our ability */
+struct tm * fake_localtime_r(const time_t *time, struct tm *result) {
+    const struct tm *static_result = localtime(time);
+
+    assert(result != NULL);
+
+    if( static_result == NULL ) {
+        memset(result, 0, sizeof(*result));
+        return NULL;
+    }
+    else {
+        memcpy(result, static_result, sizeof(*result));
+        return result;
+    }
+}
+
+
+/* Simulate gmtime_r() to the best of our ability */
+struct tm * fake_gmtime_r(const time_t *time, struct tm *result) {
+    const struct tm *static_result = gmtime(time);
+
+    assert(result != NULL);
+
+    if( static_result == NULL ) {
+        memset(result, 0, sizeof(*result));
+        return NULL;
+    }
+    else {
+        memcpy(result, static_result, sizeof(*result));
+        return result;
+    }
+}
+
+
+static Time64_T seconds_between_years(Year left_year, Year right_year)
{
+    int increment = (left_year > right_year) ? 1 : -1;
+    Time64_T seconds = 0;
+    int cycles;
+
+    if( left_year > 2400 ) {
+        cycles = (left_year - 2400) / 400;
+        left_year -= cycles * 400;
+        seconds   += cycles * seconds_in_gregorian_cycle;
+    }
+    else if( left_year < 1600 ) {
+        cycles = (left_year - 1600) / 400;
+        left_year += cycles * 400;
+        seconds   += cycles * seconds_in_gregorian_cycle;
+    }
+
+    while( left_year != right_year ) {
+        seconds += length_of_year[IS_LEAP(right_year - 1900)] * 60 * 60
* 24;
+        right_year += increment;
+    }
+
+    return seconds * increment;
+}
+
+
+Time64_T mktime64(struct TM *input_date) {
+    struct tm safe_date;
+    struct TM date;
+    Time64_T  time;
+    Year      year = input_date->tm_year + 1900;
+
+    if( date_in_safe_range(input_date, &SYSTEM_MKTIME_MIN,
&SYSTEM_MKTIME_MAX) )
+    {
+        copy_TM64_to_tm(input_date, &safe_date);
+        time = (Time64_T)mktime(&safe_date);
+        copy_tm_to_TM64(&safe_date, input_date);
+        return time;
+    }
+
+    /* Have to make the year safe in date else it won't fit in
safe_date */
+    date = *input_date;
+    date.tm_year = safe_year(year) - 1900;
+    copy_TM64_to_tm(&date, &safe_date);
+
+    time = (Time64_T)mktime(&safe_date);
+    copy_tm_to_TM64(&safe_date, input_date);
+
+    time += seconds_between_years(year, (Year)(safe_date.tm_year +
1900));
+
+    return time;
+}
+
+
+/* Because I think mktime() is a crappy name */
+Time64_T timelocal64(struct TM *date) {
+    return mktime64(date);
+}
+
+
+struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
+{
+    int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
+    Time64_T v_tm_tday;
+    int leap;
+    Time64_T m;
+    Time64_T time = *in_time;
+    Year year = 70;
+    int cycles = 0;
+
+    assert(p != NULL);
+
+    /* Use the system gmtime() if time_t is small enough */
+    if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
+        time_t safe_time = (time_t)*in_time;
+        struct tm safe_date;
+        GMTIME_R(&safe_time, &safe_date);
+
+        copy_tm_to_TM64(&safe_date, p);
+        assert(check_tm(p));
+
+        return p;
+    }
+
+#ifdef HAS_TM_TM_GMTOFF
+    p->tm_gmtoff = 0;
+#endif
+    p->tm_isdst  = 0;
+
+#ifdef HAS_TM_TM_ZONE
+    p->tm_zone   = "UTC";
+#endif
+
+    v_tm_sec =  (int)(time % 60);
+    time /= 60;
+    v_tm_min =  (int)(time % 60);
+    time /= 60;
+    v_tm_hour = (int)(time % 24);
+    time /= 24;
+    v_tm_tday = time;
+
+    WRAP (v_tm_sec, v_tm_min, 60);
+    WRAP (v_tm_min, v_tm_hour, 60);
+    WRAP (v_tm_hour, v_tm_tday, 24);
+
+    v_tm_wday = (int)((v_tm_tday + 4) % 7);
+    if (v_tm_wday < 0)
+        v_tm_wday += 7;
+    m = v_tm_tday;
+
+    if (m >= CHEAT_DAYS) {
+        year = CHEAT_YEARS;
+        m -= CHEAT_DAYS;
+    }
+
+    if (m >= 0) {
+        /* Gregorian cycles, this is huge optimization for distant
times */
+        cycles = (int)(m / (Time64_T) days_in_gregorian_cycle);
+        if( cycles ) {
+            m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+            year += (cycles * years_in_gregorian_cycle);
+        }
+
+        /* Years */
+        leap = IS_LEAP (year);
+        while (m >= (Time64_T) length_of_year[leap]) {
+            m -= (Time64_T) length_of_year[leap];
+            year++;
+            leap = IS_LEAP (year);
+        }
+
+        /* Months */
+        v_tm_mon = 0;
+        while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
+            m -= (Time64_T) days_in_month[leap][v_tm_mon];
+            v_tm_mon++;
+        }
+    } else {
+        year--;
+
+        /* Gregorian cycles */
+        cycles = (int)((m / (Time64_T) days_in_gregorian_cycle) + 1);
+        if( cycles ) {
+            m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+            year += (cycles * years_in_gregorian_cycle);
+        }
+
+        /* Years */
+        leap = IS_LEAP (year);
+        while (m < (Time64_T) -length_of_year[leap]) {
+            m += (Time64_T) length_of_year[leap];
+            year--;
+            leap = IS_LEAP (year);
+        }
+
+        /* Months */
+        v_tm_mon = 11;
+        while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
+            m += (Time64_T) days_in_month[leap][v_tm_mon];
+            v_tm_mon--;
+        }
+        m += (Time64_T) days_in_month[leap][v_tm_mon];
+    }
+
+    p->tm_year = year;
+    if( p->tm_year != year ) {
+#ifdef EOVERFLOW
+        errno = EOVERFLOW;
+#endif
+        return NULL;
+    }
+
+    /* At this point m is less than a year so casting to an int is safe
*/
+    p->tm_mday = (int) m + 1;
+    p->tm_yday = julian_days_by_month[leap][v_tm_mon] + (int)m;
+    p->tm_sec  = v_tm_sec;
+    p->tm_min  = v_tm_min;
+    p->tm_hour = v_tm_hour;
+    p->tm_mon  = v_tm_mon;
+    p->tm_wday = v_tm_wday;
+    
+    assert(check_tm(p));
+
+    return p;
+}
+
+
+struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
+{
+    time_t safe_time;
+    struct tm safe_date;
+    struct TM gm_tm;
+    Year orig_year;
+    int month_diff;
+
+    assert(local_tm != NULL);
+
+    /* Use the system localtime() if time_t is small enough */
+    if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
+        safe_time = (time_t)*time;
+
+        TIME64_TRACE1("Using system localtime for %lld\n", *time);
+
+        LOCALTIME_R(&safe_time, &safe_date);
+
+        copy_tm_to_TM64(&safe_date, local_tm);
+        assert(check_tm(local_tm));
+
+        return local_tm;
+    }
+
+    if( gmtime64_r(time, &gm_tm) == NULL ) {
+        TIME64_TRACE1("gmtime64_r returned null for %lld\n", *time);
+        return NULL;
+    }
+
+    orig_year = gm_tm.tm_year;
+
+    if (gm_tm.tm_year > (2037 - 1900) ||
+        gm_tm.tm_year < (1970 - 1900)
+       )
+    {
+        TIME64_TRACE1("Mapping tm_year %lld to safe_year\n",
(Year)gm_tm.tm_year);
+        gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year + 1900)) - 1900;
+    }
+
+    safe_time = (time_t)timegm64(&gm_tm);
+    if( LOCALTIME_R(&safe_time, &safe_date) == NULL ) {
+        TIME64_TRACE1("localtime_r(%d) returned NULL\n",
(int)safe_time);
+        return NULL;
+    }
+
+    copy_tm_to_TM64(&safe_date, local_tm);
+
+    local_tm->tm_year = orig_year;
+    if( local_tm->tm_year != orig_year ) {
+        TIME64_TRACE2("tm_year overflow: tm_year %lld, orig_year %lld
\n",
+              (Year)local_tm->tm_year, (Year)orig_year);
+
+#ifdef EOVERFLOW
+        errno = EOVERFLOW;
+#endif
+        return NULL;
+    }
+
+
+    month_diff = local_tm->tm_mon - gm_tm.tm_mon;
+
+    /*  When localtime is Dec 31st previous year and
+        gmtime is Jan 1st next year.
+    */
+    if( month_diff == 11 ) {
+        local_tm->tm_year--;
+    }
+
+    /*  When localtime is Jan 1st, next year and
+        gmtime is Dec 31st, previous year.
+    */
+    if( month_diff == -11 ) {
+        local_tm->tm_year++;
+    }
+
+    /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st 
+       in a non-leap xx00.  There is one point in the cycle
+       we can't account for which the safe xx00 year is a leap
+       year.  So we need to correct for Dec 31st comming out as
+       the 366th day of the year.
+    */
+    if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
+        local_tm->tm_yday--;
+
+    assert(check_tm(local_tm));
+    
+    return local_tm;
+}
+
+
+int valid_tm_wday( const struct TM* date ) {
+    if( 0 <= date->tm_wday && date->tm_wday <= 6 )
+        return 1;
+    else
+        return 0;
+}
+
+int valid_tm_mon( const struct TM* date ) {
+    if( 0 <= date->tm_mon && date->tm_mon <= 11 )
+        return 1;
+    else
+        return 0;
+}
+
+
+char *asctime64_r( const struct TM* date, char *result ) {
+    /* I figure everything else can be displayed, even hour 25, but if
+       these are out of range we walk off the name arrays */
+    if( !valid_tm_wday(date) || !valid_tm_mon(date) )
+        return NULL;
+
+    sprintf(result, TM64_ASCTIME_FORMAT,
+        wday_name[date->tm_wday],
+        mon_name[date->tm_mon],
+        date->tm_mday, date->tm_hour,
+        date->tm_min, date->tm_sec,
+        1900 + date->tm_year);
+
+    return result;
+}
+
+
+char *ctime64_r( const Time64_T* time, char* result ) {
+    struct TM date;
+
+    localtime64_r( time, &date );
+    return asctime64_r( &date, result );
+}
+
+
+/* Non-thread safe versions of the above */
+struct TM *localtime64(const Time64_T *time) {
+    tzset();
+    return localtime64_r(time, &Static_Return_Date);
+}
+
+struct TM *gmtime64(const Time64_T *time) {
+    return gmtime64_r(time, &Static_Return_Date);
+}
+
+char *asctime64( const struct TM* date ) {
+    return asctime64_r( date, Static_Return_String );
+}
+
+char *ctime64( const Time64_T* time ) {
+    tzset();
+    return asctime64(localtime64(time));
+}
diff --git a/dlls/msvcrt/time64.h b/dlls/msvcrt/time64.h
new file mode 100644
index 0000000..c0924b4
--- /dev/null
+++ b/dlls/msvcrt/time64.h
@@ -0,0 +1,81 @@
+#ifndef TIME64_H
+#    define TIME64_H
+
+#include <time.h>
+#include "time64_config.h"
+
+/* Set our custom types */
+typedef INT_64_T        Int64;
+typedef Int64           Time64_T;
+typedef Int64           Year;
+
+
+/* A copy of the tm struct but with a 64 bit year */
+struct TM64 {
+        int     tm_sec;
+        int     tm_min;
+        int     tm_hour;
+        int     tm_mday;
+        int     tm_mon;
+        Year    tm_year;
+        int     tm_wday;
+        int     tm_yday;
+        int     tm_isdst;
+
+#ifdef HAS_TM_TM_GMTOFF
+        long    tm_gmtoff;
+#endif
+
+#ifdef HAS_TM_TM_ZONE
+        char    *tm_zone;
+#endif
+};
+
+
+/* Decide which tm struct to use */
+#ifdef USE_TM64
+#define TM      TM64
+#else
+#define TM      tm
+#endif   
+
+
+/* Declare public functions */
+struct TM *gmtime64_r    (const Time64_T *, struct TM *);
+struct TM *localtime64_r (const Time64_T *, struct TM *);
+struct TM *gmtime64      (const Time64_T *);
+struct TM *localtime64   (const Time64_T *);
+
+char *asctime64          (const struct TM *);
+char *asctime64_r        (const struct TM *, char *);
+
+char *ctime64            (const Time64_T*);
+char *ctime64_r          (const Time64_T*, char*);
+
+Time64_T   timegm64      (const struct TM *);
+Time64_T   mktime64      (struct TM *);
+Time64_T   timelocal64   (struct TM *);
+
+
+/* Not everyone has gm/localtime_r(), provide a replacement */
+#ifdef HAS_LOCALTIME_R
+#    define LOCALTIME_R(clock, result) localtime_r(clock, result)
+#else
+#    define LOCALTIME_R(clock, result) fake_localtime_r(clock, result)
+#endif
+#ifdef HAS_GMTIME_R
+#    define GMTIME_R(clock, result)    gmtime_r(clock, result)
+#else
+#    define GMTIME_R(clock, result)    fake_gmtime_r(clock, result)
+#endif
+
+
+/* Use a different asctime format depending on how big the year is */
+#ifdef USE_TM64
+    #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %lld\n"
+#else
+    #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
+#endif
+
+
+#endif
diff --git a/dlls/msvcrt/time64_config.h b/dlls/msvcrt/time64_config.h
new file mode 100644
index 0000000..77cd9db
--- /dev/null
+++ b/dlls/msvcrt/time64_config.h
@@ -0,0 +1,76 @@
+/* Configuration
+   -------------
+   Define as appropriate for your system.
+   Sensible defaults provided.
+*/
+
+
+#ifndef TIME64_CONFIG_H
+#    define TIME64_CONFIG_H
+
+/* Debugging
+   TIME_64_DEBUG
+   Define if you want debugging messages
+*/
+/* #define TIME_64_DEBUG */
+
+
+/* INT_64_T
+   A 64 bit integer type to use to store time and others.
+   Must be defined.
+*/
+#define INT_64_T                long long
+
+
+/* USE_TM64
+   Should we use a 64 bit safe replacement for tm?  This will
+   let you go past year 2 billion but the struct will be incompatible
+   with tm.  Conversion functions will be provided.
+*/
+/* #define USE_TM64 */
+
+
+/* Availability of system functions.
+
+   HAS_GMTIME_R
+   Define if your system has gmtime_r()
+
+   HAS_LOCALTIME_R
+   Define if your system has localtime_r()
+
+   HAS_TIMEGM
+   Define if your system has timegm(), a GNU extension.
+*/
+#define HAS_GMTIME_R
+#define HAS_LOCALTIME_R
+/* #define HAS_TIMEGM */
+
+
+/* Details of non-standard tm struct elements.
+
+   HAS_TM_TM_GMTOFF
+   True if your tm struct has a "tm_gmtoff" element.
+   A BSD extension.
+
+   HAS_TM_TM_ZONE
+   True if your tm struct has a "tm_zone" element.
+   A BSD extension.
+*/
+/* #define HAS_TM_TM_GMTOFF */
+/* #define HAS_TM_TM_ZONE */
+
+
+/* USE_SYSTEM_LOCALTIME
+   USE_SYSTEM_GMTIME
+   USE_SYSTEM_MKTIME
+   USE_SYSTEM_TIMEGM
+   Should we use the system functions if the time is inside their
range?
+   Your system localtime() is probably more accurate, but our gmtime()
is
+   fast and safe.
+*/
+#define USE_SYSTEM_LOCALTIME
+/* #define USE_SYSTEM_GMTIME */
+#define USE_SYSTEM_MKTIME
+/* #define USE_SYSTEM_TIMEGM */
+
+#endif /* TIME64_CONFIG_H */
diff --git a/dlls/msvcrt/time64_limits.h b/dlls/msvcrt/time64_limits.h
new file mode 100644
index 0000000..fd4455f
--- /dev/null
+++ b/dlls/msvcrt/time64_limits.h
@@ -0,0 +1,95 @@
+/* 
+   Maximum and minimum inputs your system's respective time functions
+   can correctly handle.  time64.h will use your system functions if
+   the input falls inside these ranges and corresponding USE_SYSTEM_*
+   constant is defined.
+*/
+
+#ifndef TIME64_LIMITS_H
+#define TIME64_LIMITS_H
+
+/* Max/min for localtime() */
+#define SYSTEM_LOCALTIME_MAX     2147483647
+#define SYSTEM_LOCALTIME_MIN    -2147483647-1
+
+/* Max/min for gmtime() */
+#define SYSTEM_GMTIME_MAX        2147483647
+#define SYSTEM_GMTIME_MIN       -2147483647-1
+
+/* Max/min for mktime() */
+static const struct tm SYSTEM_MKTIME_MAX = {
+    7,
+    14,
+    19,
+    18,
+    0,
+    138,
+    1,
+    17,
+    0
+#ifdef HAS_TM_TM_GMTOFF
+    ,-28800
+#endif
+#ifdef HAS_TM_TM_ZONE
+    ,"PST"
+#endif
+};
+
+static const struct tm SYSTEM_MKTIME_MIN = {
+    52,
+    45,
+    12,
+    13,
+    11,
+    1,
+    5,
+    346,
+    0
+#ifdef HAS_TM_TM_GMTOFF
+    ,-28800
+#endif
+#ifdef HAS_TM_TM_ZONE
+    ,"PST"
+#endif
+};
+
+/* Max/min for timegm() */
+#ifdef HAS_TIMEGM
+static const struct tm SYSTEM_TIMEGM_MAX = {
+    7,
+    14,
+    3,
+    19,
+    0,
+    138,
+    2,
+    18,
+    0
+    #ifdef HAS_TM_TM_GMTOFF
+        ,0
+    #endif
+    #ifdef HAS_TM_TM_ZONE
+        ,"UTC"
+    #endif
+};
+
+static const struct tm SYSTEM_TIMEGM_MIN = {
+    52,
+    45,
+    20,
+    13,
+    11,
+    1,
+    5,
+    346,
+    0
+    #ifdef HAS_TM_TM_GMTOFF
+        ,0
+    #endif
+    #ifdef HAS_TM_TM_ZONE
+        ,"UTC"
+    #endif
+};
+#endif /* HAS_TIMEGM */
+
+#endif /* TIME64_LIMITS_H */
-- 
1.6.6.1






More information about the wine-patches mailing list