Time-y2038-20100403000755000765000765 011356003401 13736 5ustar00schwernschwern000000000000Time-y2038-20100403/Build.PL000444000765000765 202711356003401 15370 0ustar00schwernschwern000000000000#!/usr/bin/perl -w use Config; unless( defined $Config{quadtype} and length $Config{quadtype} ) { warn "Time::y2038 needs \$Config{quadtype} set to work.\n"; die "OS Unsupported\n"; } use lib 'inc'; use Local::Module::Build; my $build = Local::Module::Build->new( module_name => 'Time::y2038', license => 'perl', include_dirs => ['y2038'], c_source => 'y2038', # extra_compiler_flags => "-g -Wall -ansi -pedantic -Wno-long-long -Wextra -Wdeclaration-after-statement -Wendif-labels -Wconversion", build_requires => { 'Module::Build' => '0.36', 'Test::Warn' => '0.11', 'Test::Exception' => '0.27', 'Test::More' => 0.82, 'ExtUtils::CBuilder' => 0.24, 'JSON' => 2.17, }, configure_requires => { 'Module::Build' => '0.36' }, requires => { perl => '5.6.1' }, ); $build->create_build_script; Time-y2038-20100403/Changes000444000765000765 722211356003401 15371 0ustar00schwernschwern00000000000020100403 Sat Apr 3 20:25:49 PDT 2010 Build * Add a timeout to the system time.h limit check for systems that hang when you try times too large. [rt.cpan.org 40764] Bugs * strict compilers, like Sun's cc, would error because we passed too many elements to the tm struct. [rt.cpan.org #55225] * Ensure localtime() honors the TZ environment variable. 20100225 Thu Feb 25 23:09:15 PST 2010 Bugs * Accidentally left a debugging print in mktime() Tests * Test against 2006, before the American DST rules changed, so as not to break on old machines. 20100223 Tue Feb 23 11:57:23 PST 2010 Build * Fix for JSON.pm not being available for Build.PL 20100218 Thu Feb 18 12:29:49 PST 2010 * No changes since the last alpha Summary of Changes Since The Last Stable Improvements * System mktime() is now probed giving more accurate mktime() on 64 bit systems. Test * Test fixes for 64 bit machines, OS X, BSD, people in the UK Build * Build fixes Versions of Visual C++ lacking long long * Build fixes for Strawberry and any system with a time.h limit of 0. * Build fixes for slightly out of date Module::Build 20100216.1421_04 Tue Feb 16 14:22:08 PST 2010 Build * Switch dependency on JSON::XS to just JSON to eliminate Lehmann-ware dependency. * The test for the limits of the time.h did not work for finding functions. * Work around bug in OS X 10.6 where gmtime() would be wrong before -70546986201600. Test * Add some more fuzz around the future localtime() tests. 20100214.1823_03 Sun Feb 14 18:23:22 PST 2010 Build * Bump up the minimum Module::Build release to fix a bug where Build would not see inc/ and thus couldn't find Local::Module::Build. 20100213.0504_02 Sat Feb 13 05:04:39 PST 2010 Build * The check for the minimum time and date was broken and would only do a single check. 20100213.0000_01 Sat Feb 13 04:32:59 PST 2010 Improvements * mktime() and timelocal() will now make better use of the system functions giving better accuracy. Build * Had too old a version of Module::Build listed as the requirement. * The limit check program was ignoring a limit of 0. Duh. * Fix so the check_max program is not always re-run on Cygwin. Tests * More localtime() tests to track down the 64 bit issues * Failures on 64 bit systems, especially in the UK, should be fixed * More sanity tests for distant dates 20081111 Tue Nov 11 15:36:27 PST 2008 Build Improvements * Moved building the program to check the limits of time.h to the code step to avoid having a configure_requires on ExtUtils::CBuilder. * Also made it quiessent and not rebuild itself unless necessary. * Added some sane min/max limits to the time.h limit check to avoid going wildly out of range and tickling system bugs that cause the whole thing to hang. * Improved the limit check so now it's a real binary search. * Fixed a number of off-by-one and overflow errors in the limit check. * Added debugging information to the limit check program. Doc Improvements * Added the location of the repository. * Gave a more specific location to submit bugs to. * Added an INSTALL file. 20081106 Thu Nov 6 19:01:22 PST 2008 Portability Improvements * Test the system time.h's limits and use them rather than the safe 1970-2038 range. Test Fixes * Compare times using numeric equality instead of string equality to avoid Perl messing up on big numbers. * timelocal() can now take an $isdst argument to disambiguate certain dates which can occur twice (like daylight savings time fall back) 20081020 Mon Oct 20 00:10:51 PDT 2008 First releaseTime-y2038-20100403/check_max.c000444000765000765 1765211356003401 16214 0ustar00schwernschwern000000000000/* A little program to test the limits of your system's time functions */ #include "time64_config.h" #include #include #include #include #include struct tm Test_TM; time_t Time_Max; time_t Time_Min; time_t Time_Zero = 0; char *dump_date(const struct tm *date) { char *dump = malloc(80 * sizeof(char)); sprintf( dump, "{ %d, %d, %d, %d, %d, %d }", date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday, date->tm_mon, date->tm_year ); return dump; } /* Visual C++ 2008's difftime() can't do negative times */ double my_difftime(time_t left, time_t right) { double diff = (double)left - (double)right; return diff; } time_t check_date_max( struct tm * (*date_func)(const time_t *), const char *func_name ) { struct tm *date; time_t time = Time_Max; time_t good_time = 0; time_t time_change = Time_Max; /* Binary search for the exact failure point */ do { printf("# Trying %s(%.0f) max...", func_name, my_difftime(time, Time_Zero)); date = (*date_func)(&time); time_change /= 2; /* date_func() broke or tm_year overflowed or time_t overflowed */ if(date == NULL || date->tm_year < 69 || time < good_time) { printf(" failed\n"); time -= time_change; } else { printf(" success\n"); good_time = time; time += time_change; } } while(time_change > 0 && good_time < Time_Max); return(good_time); } time_t check_date_min( struct tm * (*date_func)(const time_t *), const char *func_name ) { struct tm *date; time_t time = Time_Min; time_t good_time = 0; time_t time_change = Time_Min; /* Binary search for the exact failure point */ do { printf("# Trying %s(%.0f) min...", func_name, my_difftime(time, Time_Zero)); date = (*date_func)(&time); time_change /= 2; /* date_func() broke or tm_year overflowed or time_t overflowed */ if(date == NULL || date->tm_year > 70 || time > good_time) { printf(" failed\n"); time -= time_change; } else { printf(" success\n"); good_time = time; time += time_change; } } while((time_change < 0) && (good_time > Time_Min)); return(good_time); } struct tm * check_to_time_max( time_t (*to_time)(struct tm *), const char *func_name, struct tm * (*to_date)(const time_t *) ) { time_t round_trip; time_t time = Time_Max; time_t good_time = 0; struct tm *date; struct tm *good_date = malloc(sizeof(struct tm)); time_t time_change = Time_Max; /* Binary search for the exact failure point */ do { printf("# Trying %s(%.0f) max...", func_name, my_difftime(time, Time_Zero)); date = (*to_date)(&time); round_trip = (*to_time)(date); time_change /= 2; /* date_func() broke or tm_year overflowed or time_t overflowed */ if(time != round_trip) { printf(" failed\n"); time -= time_change; } else { printf(" success\n"); good_time = time; memcpy(good_date, date, sizeof(struct tm)); time += time_change; } } while(time_change > 0 && good_time < Time_Max); return(good_date); } struct tm * check_to_time_min( time_t (*to_time)(struct tm *), const char *func_name, struct tm * (*to_date)(const time_t *) ) { time_t round_trip; time_t time = Time_Min; time_t good_time = 0; struct tm *date; struct tm *good_date = malloc(sizeof(struct tm)); time_t time_change = Time_Min; /* Binary search for the exact failure point */ do { printf("# Trying %s(%.0f) min...", func_name, my_difftime(time, Time_Zero)); date = (*to_date)(&time); round_trip = (*to_time)(date); time_change /= 2; /* date_func() broke or tm_year overflowed or time_t overflowed */ if(time != round_trip) { printf(" failed\n"); time -= time_change; } else { printf(" success\n"); good_time = time; memcpy(good_date, date, sizeof(struct tm)); time += time_change; } } while((time_change < 0) && (good_time > Time_Min)); return(good_date); } void guess_time_limits_from_types(void) { if( sizeof(time_t) == 4 ) { /* y2038 bug, out to 2**31-1 */ Time_Max = 2147483647; Time_Min = -2147483647 - 1; /* "C90 doesn't have negative constants, only positive ones that have been negated." */ } else if( sizeof(time_t) >= 8 ) { /* The compiler might warn about overflowing in the assignments below. Don't worry, these won't get run in that case */ if( sizeof(Test_TM.tm_year) == 4 ) { /* y2**31-1 bug */ Time_Max = 67768036160140799LL; Time_Min = -67768036191676800LL; } else { /* All the way out to 2**63-1 */ Time_Max = 9223372036854775807LL; Time_Min = -9223372036854775807LL; } } else { printf("Weird sizeof(time_t): %ld\n", sizeof(time_t)); } } /* Dump a tm struct as a json fragment */ char * tm_as_json(const struct tm* date) { char *date_json = malloc(sizeof(char) * 512); #ifdef HAS_TM_TM_ZONE char zone_json[32]; #endif #ifdef HAS_TM_TM_GMTOFF char gmtoff_json[32]; #endif sprintf(date_json, "\"tm_sec\": %d, \"tm_min\": %d, \"tm_hour\": %d, \"tm_mday\": %d, \"tm_mon\": %d, \"tm_year\": %d, \"tm_wday\": %d, \"tm_yday\": %d, \"tm_isdst\": %d", date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday, date->tm_mon, date->tm_year, date->tm_wday, date->tm_yday, date->tm_isdst ); #ifdef HAS_TM_TM_ZONE sprintf(zone_json, ", \"tm_zone\": \"%s\"", date->tm_zone); strcat(date_json, zone_json); #endif #ifdef HAS_TM_TM_GMTOFF sprintf(gmtoff_json, ", \"tm_gmtoff\": %ld", date->tm_gmtoff); strcat(date_json, gmtoff_json); #endif return date_json; } int main(void) { time_t gmtime_max; time_t gmtime_min; time_t localtime_max; time_t localtime_min; #ifdef HAS_TIMEGM struct tm* timegm_max; struct tm* timegm_min; #endif struct tm* mktime_max; struct tm* mktime_min; guess_time_limits_from_types(); gmtime_max = check_date_max(gmtime, "gmtime"); gmtime_min = check_date_min(gmtime, "gmtime"); localtime_max = check_date_max(localtime, "localtime"); localtime_min = check_date_min(localtime, "localtime"); #ifdef HAS_TIMEGM Time_Max = gmtime_max; Time_Min = gmtime_min; timegm_max = check_to_time_max(timegm, "timegm", gmtime); timegm_min = check_to_time_min(timegm, "timegm", gmtime); #endif Time_Max = localtime_max; Time_Min = localtime_min; mktime_max = check_to_time_max(mktime, "mktime", localtime); mktime_min = check_to_time_min(mktime, "mktime", localtime); printf("# system time.h limits, as JSON\n"); printf("{\n"); printf(" \"gmtime\": { \"max\": %.0f, \"min\": %0.f },\n", my_difftime(gmtime_max, Time_Zero), my_difftime(gmtime_min, Time_Zero) ); printf(" \"localtime\": { \"max\": %.0f, \"min\": %0.f },\n", my_difftime(localtime_max, Time_Zero), my_difftime(localtime_min, Time_Zero) ); printf(" \"mktime\": {\n"); printf(" \"max\": { %s },\n", tm_as_json(mktime_max)); printf(" \"min\": { %s }\n", tm_as_json(mktime_min)); printf(" }\n"); #ifdef HAS_TIMEGM printf(" ,\n"); printf(" \"timegm\": {\n"); printf(" \"max\": { %s },\n", tm_as_json(timegm_max)); printf(" \"min\": { %s }\n", tm_as_json(timegm_min)); printf(" }\n"); #endif printf("}\n"); return 0; } Time-y2038-20100403/INSTALL000444000765000765 472511356003401 15134 0ustar00schwernschwern000000000000This is the Time::y2038 Perl module. It is a wrapper around the y2038 C library to use its functions from Perl. The latest version and project information can be had from: http://y2038.googlecode.com/ Run "perldoc Time::y2038" for documentation (before or after installing) or see http://search.cpan.org/perldoc?Time::y2038. SIMPLE INSTALL -------------- Use the CPAN shell. Run "cpan" and type "install Time::y2038". This will resolve all module dependencies and get you the latest version. SLIGHTLY LESS SIMPLE INSTALL ---------------------------- In the Time-y2038 directory, run "cpan .". This will resolve all module dependencies from CPAN and install Time::y2038 from your source directory. MANUAL INSTALL -------------- This module uses Module::Build. To install, you must first have the required prerequisites. Build & testing requirements... perl 5.6.1 Module::Build 0.2808 Test::Warn 0.11 Test::Exception 0.27 Test::More 0.62 ExtUtils::CBuilder 0.24 An ANSI C compiler that ExtUtils::CBuilder knows about. Runtime requirements... perl 5.6.1 The simplest way to resolve the module dependencies is to run "cpan ." in the source directory. The CPAN shell will install all dependencies from CPAN and Time::y2038 from the source directory. Once all dependencies are installed, run these commands: perl Build.PL ./Build ./Build test ./Build install This will install the module as part of your perl installation. If you want to install it for personal use... perl Build.PL --install_base ~ and the rest as normal. This will install the library under ~/lib/perl5/ and the man pages in ~/man/. Set your PERL5LIB and MANPATH environment variables appropriately. PROBLEMS -------- If you encounter any problems building or testing this module, please report it at http://rt.cpan.org/Dist/Display.html?Queue=Time-y2038 Please include the complete output of your build. Don't cut things out to save me trouble, you never know what's important. Also include the output of "perl -V", the type and version of your C compiler, your operating system, the versions of the prerequisite modules and the output of running the bin/check_max program. If you don't have time for all that, just copy & paste what you've got and send it off. Do not wait until you have a patch. Don't assume your bug is known or your platform is too weird to bother with. Report early, report often. Time-y2038-20100403/MANIFEST000444000765000765 56611356003401 15213 0ustar00schwernschwern000000000000Build.PL Changes check_max.c inc/Local/Module/Build.pm INSTALL lib/Time/y2038.pm lib/Time/y2038.xs lib/Time/y2038/Everywhere.pm MANIFEST This list of files MANIFEST.SKIP META.yml munge_config t/everywhere.t t/localtimetz.t t/time.t t/timegm.t typemap y2038/time64.c y2038/time64.h y2038/time64_config.h.in y2038/time64_limits.h.in SIGNATURE Added here by Module::Build Time-y2038-20100403/MANIFEST.SKIP000444000765000765 150011356003401 15765 0ustar00schwernschwern000000000000 #!start included /usr/local/perl/5.10.0/lib/5.10.0/ExtUtils/MANIFEST.SKIP # Avoid version control files. \bRCS\b \bCVS\b \bSCCS\b ,v$ \B\.svn\b \B\.git\b \B\.gitignore\b \b_darcs\b # Avoid Makemaker generated and utility files. \bMANIFEST\.bak \bMakefile$ \bblib/ \bMakeMaker-\d \bpm_to_blib\.ts$ \bpm_to_blib$ \bblibdirs\.ts$ # 6.18 through 6.25 generated this # Avoid Module::Build generated and utility files. \bBuild$ \b_build/ # Avoid temp and backup files. ~$ \.old$ \#$ \b\.# \.bak$ # Avoid Devel::Cover files. \bcover_db\b #!end included /usr/local/perl/5.10.0/lib/5.10.0/ExtUtils/MANIFEST.SKIP # Avoid emacs temp files ^\.# # Avoid object files \.o$ # Avoid generated/compiled files check_max$ lib/Time/y2038\.c y2038/time64_config.h$ y2038/time64_limits.h$ # Avoid our own dists ^Time-y2038- ^MYMETA.yml$ Time-y2038-20100403/META.yml000444000765000765 133211356003401 15343 0ustar00schwernschwern000000000000--- abstract: "Versions of Perl's time functions which work beyond 2038" author: - 'Michael G Schwern ' build_requires: ExtUtils::CBuilder: 0.24 JSON: 2.17 Module::Build: 0.36 Test::Exception: 0.27 Test::More: 0.82 Test::Warn: 0.11 configure_requires: Module::Build: 0.36 generated_by: 'Module::Build version 0.3603' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Time-y2038 provides: Time::y2038: file: lib/Time/y2038.pm version: 20100403 Time::y2038::Everywhere: file: lib/Time/y2038/Everywhere.pm version: 20100403 requires: perl: v5.6.1 resources: license: http://dev.perl.org/licenses/ version: 20100403 Time-y2038-20100403/munge_config000444000765000765 115111356003401 16454 0ustar00schwernschwern000000000000#!/usr/bin/perl -w use Config; use Module::Build; my $build = Module::Build->current; my %config = (%Config, $build->notes); my $input_file = shift; my $output_file = shift; open my $input_fh, $input_file or die "Can't open $input_file: $!"; open my $output_fh, ">$output_file" or die "Can't open $output_file: $!"; print $output_fh <<"END"; /* * !!! DO NOT EDIT THIS FILE!!! * This file was generated from $input_file. * Edit that and rerun make to regenerate. */ END while(<$input_fh>) { my $matched = s{%%(.*)%%}{defined $config{$1} ? $config{$1} : ''}eg; print $output_fh $_; } Time-y2038-20100403/SIGNATURE000644000765000765 372711356003401 15372 0ustar00schwernschwern000000000000This file contains message digests of all files listed in MANIFEST, signed via the Module::Signature module, version 0.61. To verify the content in this distribution, first make sure you have Module::Signature installed, then type: % cpansign -v It will check each file's integrity, as well as the signature's validity. If "==> Signature verified OK! <==" is not displayed, the distribution may already have been compromised, and you should not run its Makefile.PL or Build.PL. -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 SHA1 4d4b9fe7a9836ea619fbc68874efa7d7e6d60cd2 Build.PL SHA1 3b29b365a4cdf942f0eab9d537c0d93dd2007611 Changes SHA1 fb110d0098fa623a2d6566e86a6d630110f60d23 INSTALL SHA1 3170f39a524774094b99997ef580a9760a3d7757 MANIFEST SHA1 b42c7cde42d44221603a84f6bdfeb4a6005604e5 MANIFEST.SKIP SHA1 2df76dbe03a63cc8164fb816b328cf0235239e5e META.yml SHA1 d2f3d11e9f7f0fd886b47f9257352eb8b71bf4a8 check_max.c SHA1 21842315e5b12aac383ea48aa10a41a7ab95787d inc/Local/Module/Build.pm SHA1 f2cee8c7a7fd7e6422c19727ec5258e359b09803 lib/Time/y2038.pm SHA1 ddd3ef1934b37a4fe1644f8045a775a30278df72 lib/Time/y2038.xs SHA1 ad65a95fd786d4c53c453ee42055175a63dc080c lib/Time/y2038/Everywhere.pm SHA1 6c52173a196e81890474ea47f31ba2aeb3b7939c munge_config SHA1 1fa62ec9022c346fa26bf94652114e38b5efc5f0 t/everywhere.t SHA1 76efef2e7c55a7e351c62e44820f0f87b64d85a1 t/localtimetz.t SHA1 53f53a00f22f19b5f16d7801e730e57c173f61c7 t/time.t SHA1 851b9264b863b168ed07d17720e0176a0fe67d7c t/timegm.t SHA1 a915486e0da8713dead77ca21d041c8ae6b6676e typemap SHA1 b84518ded0b1ab0d30107cb4d4002bfa64757bff y2038/time64.c SHA1 0d7cf54304f41d3dbfd803d597c29d62b845ce2a y2038/time64.h SHA1 015c751b69ae7971c626034ad2b27eaabe5a76a7 y2038/time64_config.h.in SHA1 95314ecad677f573bf33bbb3c4b70d8880f15975 y2038/time64_limits.h.in -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.13 (Darwin) iEYEARECAAYFAku4BwEACgkQWMohlhD1QydTmgCg0dkZK9ux6iP9/KLwbhW+G2AA 9pUAn2dkAgtjHD2DN/UQSjAvPnLY6o5a =cNQs -----END PGP SIGNATURE----- Time-y2038-20100403/typemap000444000765000765 22111356003401 15450 0ustar00schwernschwern000000000000const Time64_T T_NV Time64_T T_NV const Year T_NV Year T_NV const int T_IV Time-y2038-20100403/inc000755000765000765 011356003401 14507 5ustar00schwernschwern000000000000Time-y2038-20100403/inc/Local000755000765000765 011356003401 15541 5ustar00schwernschwern000000000000Time-y2038-20100403/inc/Local/Module000755000765000765 011356003401 16766 5ustar00schwernschwern000000000000Time-y2038-20100403/inc/Local/Module/Build.pm000444000765000765 1576611356003401 20557 0ustar00schwernschwern000000000000package Local::Module::Build; use strict; use base qw(Module::Build); use ExtUtils::CBuilder; # A very conservative set of default limits for the system time.h my %Limit_Defaults = ( gmtime => { max => 2147483647, min => 0, }, localtime => { max => 2147483647, min => 0, }, mktime => { max => { tm_gmtoff => 0, tm_hour => 3, tm_isdst => 0, tm_mday => 19, tm_min => 14, tm_mon => 0, tm_sec => 7, tm_wday => 2, tm_yday => 18, tm_year => 138, tm_zone => "UTC", }, min => { tm_gmtoff => 0, tm_hour => 0, tm_isdst => 0, tm_mday => 1, tm_min => 0, tm_mon => 0, tm_sec => 0, tm_wday => 4, tm_yday => 0, tm_year => 70, tm_zone => "UTC", }, }, timegm => { max => { tm_gmtoff => 0, tm_hour => 3, tm_isdst => 0, tm_mday => 19, tm_min => 14, tm_mon => 0, tm_sec => 7, tm_wday => 2, tm_yday => 18, tm_year => 138, tm_zone => "UTC", }, min => { tm_gmtoff => 0, tm_hour => 0, tm_isdst => 0, tm_mday => 1, tm_min => 0, tm_mon => 0, tm_sec => 0, tm_wday => 4, tm_yday => 0, tm_year => 70, tm_zone => "UTC", } } ); sub is_osx_106 { return 0 unless $^O eq 'darwin'; my $version = `sw_vers -productVersion`; return $version =~ m{^10\.6\.}; } sub probe_system_time { my $self = shift; $self->note_time_capabilities; $self->munge("y2038/time64_config.h.in" => "y2038/time64_config.h"); $self->note_time_limits; $self->munge("y2038/time64_limits.h.in" => "y2038/time64_limits.h"); } sub munge { my $self = shift; my($src, $dest) = @_; return if $self->up_to_date(["munge_config",$src] => [$dest]); system $^X, "munge_config", $src, $dest; $self->add_to_cleanup($dest); } sub note_time_capabilities { my $self = shift; return if $self->up_to_date(["Build", "y2038/time64_limits.h.in"], ["y2038/time64_limits.h"]); my %tests = ( HAS_TIMEGM => <<'END', struct tm *date; time_t zero; date = localtime(&zero); zero = timegm(date); END HAS_GMTIME_R => <<'END', struct tm date; time_t zero = 0; (void)gmtime_r(&zero, &date); END HAS_LOCALTIME_R => <<'END', struct tm date; time_t zero = 0; (void)localtime_r(&zero, &date); END HAS_TM_TM_GMTOFF => <<'END', struct tm *date; time_t zero; int offset; date = gmtime(&zero); offset = date->tm_gmtoff; END HAS_TM_TM_ZONE => <<'END', struct tm *date; time_t zero; char *zone; date = gmtime(&zero); zone = date->tm_zone; END ); warn "Probing time.h capabilities.\n"; warn "You may see some C errors, that's ok.\n"; my $cb = ExtUtils::CBuilder->new( quiet => 1 ); for my $test (keys %tests) { my $code = $tests{$test}; $code = < int main() { $code return 0; } END open my $fh, ">", "try.c" or die "Can't write try.c: $!"; print $fh $code; close $fh; my $exe = eval { # Compile AND link to force undefined symbols to error my $obj = $cb->compile(source => "try.c"); my $exe = $cb->link_executable(objects => $obj, exe_file => "try"); unlink $obj; $exe; }; $self->notes($test, $exe ? 1 : 0); unlink $exe if $exe; unlink "try.c"; } } sub note_time_limits { my $self = shift; my $source = "check_max.c"; my $exe = $self->notes("check_max") || "check_max"; unless( $self->up_to_date($source => $exe) ) { warn "Building a program to test the range of your system time functions...\n"; my $cb = $self->cbuilder; my $obj = $cb->compile(source => "check_max.c", include_dirs => ['y2038']); $exe = $cb->link_executable(objects => $obj, exe_file => $exe); $exe = $self->find_real_exe($exe); $self->notes(check_max => $exe); $self->add_to_cleanup($obj, $exe); } return if $self->up_to_date(["y2038/time64_limits.h.in", "munge_config", $exe] => "y2038/time64_limits.h"); warn " and running it...\n"; require JSON; my $limits; my $json = run_in_alarm( 2, sub { return `./$exe`; }, sub { warn " time limit check timed out, using defaults"; return; }); warn " Done.\n"; if( $json ) { $json =~ s{^\#.*\n}{}gm; $limits = JSON::from_json($json); } else { $limits = \%Limit_Defaults; } my %config; for my $key (qw(gmtime localtime)) { for my $limit (qw(min max)) { $config{$key."_".$limit} = $limits->{$key}{$limit}; } } for my $key (qw(mktime timegm)) { for my $limit (qw(min max)) { my $struct = $limits->{$key}{$limit}; for my $tm (keys %$struct) { $config{$key."_".$limit."_".$tm} = $struct->{$tm}; } } } # Windows lies about being able to handle just a little bit of # negative time. for my $key (qw(gmtime_min localtime_min)) { if( -10_000 < $config{$key} && $config{$key} < 0 ) { $config{$key} = 0; } } # OS X 10.6's gmtime is broken before -70546986201600 # See Apple bug 7654647 if( is_osx_106 ) { for my $key (qw(gmtime_min localtime_min)) { if( $config{$key} < -70546986201600 ) { $config{$key} = -70546986201600; } } } for my $key (sort { $a cmp $b } keys %config) { my $val = $config{$key}; warn sprintf "%15s: %s\n", $key, $val; $self->notes($key, "$val"); } return; } sub run_in_alarm { my($timeout, $try, $catch) = @_; my(@ret, $ret); my $pass = eval { local $SIG{ALRM} = sub { die "alarm\n" }; alarm $timeout; wantarray ? @ret = $try->() : $ret = $try->(); alarm 0; 1; }; return wantarray ? @ret : $ret if $pass; return $catch->() if $@ eq "alarm\n"; die $@; } # This is necessary because Cygwin nerotically puts a .exe on # every executable. This appears to be built into gcc. sub find_real_exe { my $self = shift; my $exe = shift; my $real_exe; for ($exe, "$exe.exe") { $real_exe = $_ if -e; } warn "Can't find the executable, thought it was $exe" unless defined $real_exe; return $real_exe; } sub ACTION_code { my $self = shift; $self->probe_system_time; return $self->SUPER::ACTION_code(@_); } 1; Time-y2038-20100403/lib000755000765000765 011356003401 14504 5ustar00schwernschwern000000000000Time-y2038-20100403/lib/Time000755000765000765 011356003401 15402 5ustar00schwernschwern000000000000Time-y2038-20100403/lib/Time/y2038.pm000444000765000765 720611356003401 16667 0ustar00schwernschwern000000000000package Time::y2038; use strict; use warnings; use base qw(Exporter); use XSLoader; our $VERSION = '20100403'; our @EXPORT = qw(localtime gmtime timegm timelocal); XSLoader::load __PACKAGE__, $VERSION; 1; __END__ =head1 NAME Time::y2038 - Versions of Perl's time functions which work beyond 2038 =head1 SYNOPSIS use Time::y2038; print scalar gmtime 2**52; # Sat Dec 6 03:48:16 142715360 =head1 DESCRIPTION On many computers, Perl's time functions will not work past the year 2038. This is a design fault in the underlying C libraries Perl uses. Time::y2038 provides replacements for those functions which will work accurately +/1 142 million years. This only imports the functions into your namespace. To replace it everywhere, see L. Replaces the following functions: =head3 gmtime() See L for details. =head3 localtime() See L for details. =head3 timegm() my $time = timegm($sec, $min, $hour, $month_day, $month, $year); The inverse of C, takes a date and returns the coorsponding $time (number of seconds since Midnight, January 1st, 1970 GMT). All values are the same as C so $month is 0..11 (January is 0) and the $year is years since 1900 (2008 is 108). # June 4, 1906 03:02:01 GMT my $time = timegm(1, 2, 3, 4, 5, 6); timegm() can take two additional arguments which are always ignored. This lets you feed the results from gmtime() back into timegm() without having to strip the arguments off. The following is always true: timegm(gmtime($time)) == $time; =head3 timelocal() my $time = timelocal($sec, $min, $hour, $mday, $month, $year); my $time = timelocal($sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst); Like C, but interprets the date in the current time zone. C will normally figure out if daylight savings time is in effect, but if $isdst is given this will override that check. This is mostly useful to resolve ambiguous times around "fall back" when the hour between 1am and 2am occurs twice. # Sun Nov 4 00:59:59 2007 print timelocal(59, 59, 0, 4, 10, 107); # 1194163199 # Sun Nov 4 01:00:00 2007 DST, one second later print timelocal(0, 0, 1, 4, 10, 107, undef, undef, 1); # 1194163200 # Sun Nov 4 01:00:00 2007 no DST, one hour later print timelocal(0, 0, 1, 4, 10, 107, undef, undef, 0); # 1194166800 $wday and $yday are ignored. They are only there for compatibility with the return value of C. =head1 LIMITATIONS The safe range of times is +/ 2**52 (about 142 million years). Although the underlying time library can handle times from -2**63 to 2**63-1 (about +/- 292 billion years) Perl uses floating point numbers internally and so accuracy degrates after 2**52. =head1 BUGS & FEEDBACK See F to report and view bugs. If you like the module, please drop the author an email. The latest version of this module can be found at F and the repository is at F in F. You have to check out the whole repository because there are symlinks. =head1 AUTHOR Michael G Schwern =head1 LICENSE & COPYRIGHT Copyright 2008-2010 Michael G Schwern This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See F =head1 SEE ALSO L overrides localtime() and gmtime() across the whole program. The y2038 project at F L =cut Time-y2038-20100403/lib/Time/y2038.xs000444000765000765 1331211356003401 16720 0ustar00schwernschwern000000000000#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include #include "time64.h" #define myPUSHi(int) PUSHs(sv_2mortal(newSViv(int))); #define myPUSHn(num) PUSHs(sv_2mortal(newSVnv(num))); #define myPUSHs(str) PUSHs(sv_2mortal(str)); static const char * const dayname[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char * const monname[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; int about_eq(double left, double right, double epsilon) { double diff; if( left == right ) { return 1; } diff = fabs(left - right); if( diff < epsilon ) { return 1; } else { return 0; } } MODULE = Time::y2038 PACKAGE = Time::y2038 PROTOTYPES: ENABLE void gmtime(...) PROTOTYPE: ;$ INIT: Time64_T when; struct TM *err; struct TM date; PPCODE: if( GIMME_V == G_VOID ) { warn("Useless use of gmtime() in void context"); XSRETURN_EMPTY; } if( items == 0 ) { time_t small_when; time(&small_when); when = (Time64_T)small_when; } else { double when_float = SvNV(ST(0)); when = (Time64_T)when_float; /* Check for Time64_T overflow */ if( !about_eq((double)when, when_float, 1024.0) ) { warn("gmtime(%.0f) can not be represented", when_float); XSRETURN_EMPTY; } } err = gmtime64_r(&when, &date); if( err == NULL ) { warn("gmtime(%.0f) can not be represented", (double)when); XSRETURN_EMPTY; } if( GIMME_V == G_ARRAY ) { EXTEND(SP, 9); myPUSHi(date.tm_sec); myPUSHi(date.tm_min); myPUSHi(date.tm_hour); myPUSHi(date.tm_mday); myPUSHi(date.tm_mon); myPUSHn((double)date.tm_year); myPUSHi(date.tm_wday); myPUSHi(date.tm_yday); myPUSHi(date.tm_isdst); } else { SV *tsv; /* XXX newSVpvf()'s %lld type is broken, so cheat with a double */ double year = (double)date.tm_year + 1900; EXTEND(SP, 1); EXTEND_MORTAL(1); tsv = newSVpvf("%s %s %2d %02d:%02d:%02d %.0f", dayname[date.tm_wday], monname[date.tm_mon], date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec, year); myPUSHs(tsv); } void localtime(...) PROTOTYPE: ;$ INIT: Time64_T when; struct TM *err; struct TM date; PPCODE: if( GIMME_V == G_VOID ) { warn("Useless use of localtime() in void context"); XSRETURN_EMPTY; } if( items == 0 ) { time_t small_when; time(&small_when); when = (Time64_T)small_when; } else { double when_float = SvNV(ST(0)); when = (Time64_T)when_float; /* Check for Time64_T overflow */ if( !about_eq((double)when, when_float, 1024.0) ) { warn("localtime(%.0f) can not be represented", when_float); XSRETURN_EMPTY; } } tzset(); err = localtime64_r(&when, &date); if( err == NULL ) { warn("localtime(%.0f) can not be represented", (double)when); XSRETURN_EMPTY; } if( GIMME_V == G_ARRAY ) { EXTEND(SP, 9); myPUSHi(date.tm_sec); myPUSHi(date.tm_min); myPUSHi(date.tm_hour); myPUSHi(date.tm_mday); myPUSHi(date.tm_mon); myPUSHn((double)date.tm_year); myPUSHi(date.tm_wday); myPUSHi(date.tm_yday); myPUSHi(date.tm_isdst); } else { SV *tsv; /* XXX newSVpvf()'s %lld type is broken, so cheat with a double */ double year = (double)date.tm_year + 1900; EXTEND(SP, 1); EXTEND_MORTAL(1); tsv = newSVpvf("%s %s %2d %02d:%02d:%02d %.0f", dayname[date.tm_wday], monname[date.tm_mon], date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec, year); myPUSHs(tsv); } double timegm(...) INIT: struct TM date; Time64_T when; CODE: if( items < 6 ) croak("Usage: timegm($sec, $min, $hour, $mday, $month, $year)"); date.tm_sec = SvIV(ST(0)); date.tm_min = SvIV(ST(1)); date.tm_hour = SvIV(ST(2)); date.tm_mday = SvIV(ST(3)); date.tm_mon = SvIV(ST(4)); date.tm_year = (Year)SvNV(ST(5)); when = timegm64(&date); RETVAL = (double)when; OUTPUT: RETVAL double timelocal(...) INIT: struct TM date; Time64_T when; CODE: if( items < 6 ) croak("Usage: timelocal($sec, $min, $hour, $mday, $month, $year)"); date.tm_sec = SvIV(ST(0)); date.tm_min = SvIV(ST(1)); date.tm_hour = SvIV(ST(2)); date.tm_mday = SvIV(ST(3)); date.tm_mon = SvIV(ST(4)); date.tm_year = (Year)SvNV(ST(5)); date.tm_isdst = items >= 9 ? SvIV(ST(8)) : -1; when = mktime64(&date); RETVAL = (double)when; OUTPUT: RETVAL Time-y2038-20100403/lib/Time/y2038000755000765000765 011356003401 16167 5ustar00schwernschwern000000000000Time-y2038-20100403/lib/Time/y2038/Everywhere.pm000444000765000765 141111356003401 21004 0ustar00schwernschwern000000000000package Time::y2038::Everywhere; use strict; use warnings; our $VERSION = '20100403'; use Time::y2038; *CORE::GLOBAL::localtime = \&localtime; *CORE::GLOBAL::gmtime = \&gmtime; 1; __END__ =head1 NAME Time::y2038::Everywhere - Use Time::y2038's gmtime and localtime everywhere. =head1 SYNOPSIS use Time::y2038::Everywhere; # All uses of localtime() and gmtime() in the whole program # are now using Time::y2038's =head1 DESCRIPTION Time::y2038::Everywhere replaces localtime() and gmtime() with its own functions everywhere. This ensures not just that your code is 2038 safe, but that any modules you use are, too. =head1 NOTES May also override Time::Local::timelocal and Time::Local::timegm in the future. =head1 SEE ALSO L =cut Time-y2038-20100403/t000755000765000765 011356003401 14201 5ustar00schwernschwern000000000000Time-y2038-20100403/t/everywhere.t000444000765000765 34611356003401 16673 0ustar00schwernschwern000000000000#!/usr/bin/perl -w use strict; use warnings; use Test::More 'no_plan'; { package Foo; use Time::y2038::Everywhere; } is gmtime(2**52), "Sat Dec 6 03:48:16 142715360"; like localtime(2**52), qr/Dec .* 142715360/; Time-y2038-20100403/t/localtimetz.t000444000765000765 164511356003401 17060 0ustar00schwernschwern000000000000#!/usr/bin/perl # Ensure localtime() honors the current time zone use strict; use warnings; use Time::y2038; use Test::More 'no_plan'; my $Time = time(); SKIP: { local $ENV{TZ}; # Two time zones, different and likely to exist my $tz1 = "America/Los_Angeles"; my $tz2 = "America/Chicago"; # If the core localtime doesn't respond to TZ, we don't have to. skip "localtime does not respect TZ env", 1 unless do { # check that localtime respects changes to $ENV{TZ} $ENV{TZ} = $tz1; my $hour = (CORE::localtime($Time))[2]; $ENV{TZ} = $tz2; my $hour2 = (CORE::localtime($Time))[2]; $hour != $hour2; }; # check that localtime respects changes to $ENV{TZ} $ENV{TZ} = $tz1; my $hour = (localtime($Time))[2]; $ENV{TZ} = $tz2; my $hour2 = (localtime($Time))[2]; isnt $hour, $hour2, "localtime() honors TZ"; } Time-y2038-20100403/t/time.t000444000765000765 1112511356003401 15501 0ustar00schwernschwern000000000000#!/usr/bin/perl -w use strict; use warnings; use Test::More 'no_plan'; use Test::Warn; BEGIN { use_ok 'Time::y2038'; } # Try to set the time zone so we can reliably test localtime(). local $ENV{TZ} = 'US/Pacific'; my $Test_Localtime = localtime(0) eq 'Wed Dec 31 16:00:00 1969'; # Compare local time +/2 hours. Ignore seconds, minutes and dst my $Epsilon = [undef, undef, 2, 0, 0, 0, 0, 0, undef]; sub date_ok { my($have_date, $want_date, $epsilon, $name) = @_; my $nok = 0; for my $idx (0..$#{$want_date}) { my $have = $have_date->[$idx]; my $want = $want_date->[$idx]; my $ep = $epsilon->[$idx]; next unless defined $ep; $nok ||= abs($have - $want) > $epsilon; } ok( !$nok, $name ); if( $nok ) { diag sprintf < 2**52, 'Mon Jun 19 .* 71358665' => 2**51, 'Tue Sep 25 .* 35680317' => 2**50, 'Mon Oct 25 .* 3058' => 2**35, 'Fri Mar 7 .* 881' => -2**35, 'Thu Apr 7 .* -35676378' => -2**50, 'Sat Jul 14 .* -71354726' => -2**51, 'Mon Jan 25 .* -142711421' => -2**52, 'Sun Oct 19 22:08:36 2008' => 1224479316, ); for my $want (keys %times) { my $time = $times{$want}; like localtime($time), qr/$want/, sprintf "localtime(%.0f)", $time; } } # Some sanity tests for the far, far future and far, far past { my %time2year = ( -2**62 => -146138510344, -2**52 => -142711421, -2**48 => -8917617, -2**46 => -2227927, 2**46 => 2231866, 2**48 => 8921556, 2**52 => 142715360, 2**62 => 146138514283 ); for my $time (sort keys %time2year) { my $want = $time2year{$time}; my $have = (gmtime($time))[5] + 1900; is $have, $want, "year check, gmtime($time)"; $have = (localtime($time))[5] + 1900; is $have, $want, "year check, localtime($time)"; } } for my $name (qw(gmtime localtime)) { my $func = do { no strict 'refs'; \&{$name}; }; # Test in void context #line 132 warning_like { 1; $func->(0); 1; } qr/^\QUseless use of $name() in void context at $0 line 134.\E$/, "void context warning"; # Check the prototype is( prototype($name), prototype("CORE::$name"), "prototype($name)" ); # Test with no args. # Ignore the minutes and seconds in case they get run at a minute/second boundry. is_deeply( [($func->())[2..8]], [($func->(time))[2..8]], "$name()" ); # Test too big or small a time. my $huge_time = sprintf "%.0f", 2**65; #line 152 warning_like { is $func->($huge_time), undef; } qr/^\Q$name($huge_time) can not be represented at $0 line 153\E/; #line 157 warning_like { is $func->(-$huge_time), undef; } qr/^\Q$name(-$huge_time) can not be represented at $0 line 158\E/; } Time-y2038-20100403/t/timegm.t000444000765000765 414111356003401 16005 0ustar00schwernschwern000000000000#!/usr/bin/perl -w use strict; use warnings; use Test::More 'no_plan'; use Test::Exception; BEGIN { use_ok 'Time::y2038'; } # Try to set the time zone so we can reliably test localtime(). local $ENV{TZ} = 'US/Pacific'; my $Test_Localtime = localtime(0) eq 'Wed Dec 31 16:00:00 1969'; for my $time (-2**52, -1, 0, 1, 2**52) { cmp_ok timegm(gmtime($time)), '==', $time, sprintf "timegm(gmtime(%.0f))", $time; cmp_ok timelocal(localtime($time)), '==', $time, sprintf "timelocal(localtime(%.0f))", $time; } # Test DST handling. SKIP: { skip "Tests specific to US/Pacific time zone", 4 unless $Test_Localtime; # 1 second before old American DST spring ahead my $time = 1143971999; cmp_ok timelocal(59, 59, 1, 2, 3, 106), '==', $time, 'DST just before spring ahead'; is localtime($time), 'Sun Apr 2 01:59:59 2006'; $time++; cmp_ok timelocal(0, 0, 3, 2, 3, 106), '==', $time, 'DST just after spring ahead'; is localtime($time), 'Sun Apr 2 03:00:00 2006'; # 1 second before fall back. $time = 1162108799; cmp_ok timelocal(59, 59, 0, 29, 9, 106), '==', $time, 'DST just before fall back'; is localtime($time), 'Sun Oct 29 00:59:59 2006'; # Sun Oct 29 01:00:00 2006 DST, one second later $time++; cmp_ok timelocal(0, 0, 1, 29, 9, 106, undef, undef, 1), '==', $time, 'DST at 1am'; is localtime($time), 'Sun Oct 29 01:00:00 2006'; # After one hour, it's still 1am but no longer DST $time += 3600; cmp_ok timelocal(0, 0, 1, 29, 9, 106, undef, undef, 0), '==', $time, 'not DST at 1am'; is localtime($time), 'Sun Oct 29 01:00:00 2006'; # An hour later it's 2am. $time += 3600; cmp_ok timelocal(0, 0, 2, 29, 9, 106), '==', $time, '3am after DST fall back is 2 hours after 2am'; is localtime($time), 'Sun Oct 29 02:00:00 2006'; } #line 16 throws_ok { timegm(); } qr[^Usage: timegm\(\$sec, \$min, \$hour, \$mday, \$month, \$year\) at \Q$0\E line 17\.$]; #line 24 throws_ok { timelocal(); } qr[^Usage: timelocal\(\$sec, \$min, \$hour, \$mday, \$month, \$year\) at \Q$0\E line 25\.$]; Time-y2038-20100403/y2038000755000765000765 011356003401 14523 5ustar00schwernschwern000000000000Time-y2038-20100403/y2038/time64.c000444000765000765 5407311356003401 16165 0ustar00schwernschwern000000000000/* 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 #include #include #include #include #include #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(const 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); return (Time64_T)mktime(&safe_date); } /* 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); time += seconds_between_years(year, (Year)(safe_date.tm_year + 1900)); return time; } /* Because I think mktime() is a crappy name */ Time64_T timelocal64(const 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)); } Time-y2038-20100403/y2038/time64.h000444000765000765 363611356003401 16151 0ustar00schwernschwern000000000000#ifndef TIME64_H # define TIME64_H #include #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 (const struct TM *); Time64_T timelocal64 (const 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 Time-y2038-20100403/y2038/time64_config.h.in000444000765000765 360411356003401 20076 0ustar00schwernschwern000000000000#ifndef TIME64_CONFIG_H # define TIME64_CONFIG_H /* Configuration ------------- Define as appropriate for your system. Sensible defaults provided. */ /* 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 %%quadtype%% /* 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 #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 /* 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. */ #if %%HAS_GMTIME_R%% #define HAS_GMTIME_R #endif #if %%HAS_LOCALTIME_R%% #define HAS_LOCALTIME_R #endif #if %%HAS_TIMEGM%% #define HAS_TIMEGM #endif /* 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. */ #if %%HAS_TM_TM_GMTOFF%% #define HAS_TM_TM_GMTOFF #endif #if %%HAS_TM_TM_ZONE%% #define HAS_TM_TM_ZONE #endif /* 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 */ Time-y2038-20100403/y2038/time64_limits.h.in000444000765000765 444511356003401 20136 0ustar00schwernschwern000000000000/* 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 %%localtime_max%% #define SYSTEM_LOCALTIME_MIN %%localtime_min%% /* Max/min for gmtime() */ #define SYSTEM_GMTIME_MAX %%gmtime_max%% #define SYSTEM_GMTIME_MIN %%gmtime_min%% /* Max/min for mktime() */ static const struct tm SYSTEM_MKTIME_MAX = { %%mktime_max_tm_sec%%, %%mktime_max_tm_min%%, %%mktime_max_tm_hour%%, %%mktime_max_tm_mday%%, %%mktime_max_tm_mon%%, %%mktime_max_tm_year%%, %%mktime_max_tm_wday%%, %%mktime_max_tm_yday%%, %%mktime_max_tm_isdst%% #ifdef HAS_TM_TM_GMTOFF ,%%mktime_max_tm_gmtoff%% #endif #ifdef HAS_TM_TM_ZONE ,"%%mktime_max_tm_zone%%" #endif }; static const struct tm SYSTEM_MKTIME_MIN = { %%mktime_min_tm_sec%%, %%mktime_min_tm_min%%, %%mktime_min_tm_hour%%, %%mktime_min_tm_mday%%, %%mktime_min_tm_mon%%, %%mktime_min_tm_year%%, %%mktime_min_tm_wday%%, %%mktime_min_tm_yday%%, %%mktime_min_tm_isdst%% #ifdef HAS_TM_TM_GMTOFF ,%%mktime_min_tm_gmtoff%% #endif #ifdef HAS_TM_TM_ZONE ,"%%mktime_min_tm_zone%%" #endif }; /* Max/min for timegm() */ #ifdef HAS_TIMEGM static const struct tm SYSTEM_TIMEGM_MAX = { %%timegm_max_tm_sec%%, %%timegm_max_tm_min%%, %%timegm_max_tm_hour%%, %%timegm_max_tm_mday%%, %%timegm_max_tm_mon%%, %%timegm_max_tm_year%%, %%timegm_max_tm_wday%%, %%timegm_max_tm_yday%%, %%timegm_max_tm_isdst%% #ifdef HAS_TM_TM_GMTOFF ,%%timegm_max_tm_gmtoff%% #endif #ifdef HAS_TM_TM_ZONE ,"%%timegm_max_tm_zone%%" #endif }; static const struct tm SYSTEM_TIMEGM_MIN = { %%timegm_min_tm_sec%%, %%timegm_min_tm_min%%, %%timegm_min_tm_hour%%, %%timegm_min_tm_mday%%, %%timegm_min_tm_mon%%, %%timegm_min_tm_year%%, %%timegm_min_tm_wday%%, %%timegm_min_tm_yday%%, %%timegm_min_tm_isdst%% #ifdef HAS_TM_TM_GMTOFF ,%%timegm_min_tm_gmtoff%% #endif #ifdef HAS_TM_TM_ZONE ,"%%timegm_min_tm_zone%%" #endif }; #endif /* HAS_TIMEGM */ #endif /* TIME64_LIMITS_H */