From d26adb9df7d83c7c1280afdc22679978fba3ff90 Mon Sep 17 00:00:00 2001
From: Vadim Zeitlin <vadim@wxwidgets.org>
Date: Sun, 28 Aug 2005 13:06:36 +0000
Subject: [PATCH] Fixed several bugs in wxDateTime timezone handling:  -
 ToTimezone() and MakeTimezone() now work as expected  - added and documented
 FromTimezone() and MakeFromTimezone()  - Set(double jdn) interprets jdn
 always in UTC  - updated ParseRfc822Date() timezone handling  - removed
 workarounds for old bugs from the test

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@35334 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
---
 docs/changes.txt                |  1 +
 docs/latex/wx/datetime.tex      | 32 ++++++++++++++----
 include/wx/datetime.h           | 51 +++++++++++++++++++++-------
 src/common/datetime.cpp         | 59 +++++++++++++++------------------
 tests/datetime/datetimetest.cpp | 30 ++++++++++++-----
 5 files changed, 114 insertions(+), 59 deletions(-)

diff --git a/docs/changes.txt b/docs/changes.txt
index b5941a9ab7..ae373127d7 100644
--- a/docs/changes.txt
+++ b/docs/changes.txt
@@ -16,6 +16,7 @@ All:
 - Fixed compilation with IBM xlC compiler.
 - wxABI_VERSION, see 'Backward Compatibility' topic overview in the manual.
 - Added wxLongLong::ToDouble()
+- Added wxDateTime::[Make]FromTimezone(), fixed several TZ-related bugs
 
 All (GUI):
 
diff --git a/docs/latex/wx/datetime.tex b/docs/latex/wx/datetime.tex
index c77282bc17..ba26086069 100644
--- a/docs/latex/wx/datetime.tex
+++ b/docs/latex/wx/datetime.tex
@@ -406,10 +406,12 @@ provided. You can construct a wxDateTime object from a
 Please see the \helpref{time zone overview}{tdatetimezones} for more
 information about time zones. Normally, these functions should be rarely used.
 
+\helpref{FromTimezone}{wxdatetimefromtimezone}\\
 \helpref{ToTimezone}{wxdatetimetotimezone}\\
 \helpref{MakeTimezone}{wxdatetimemaketimezone}\\
-\helpref{ToGMT}{wxdatetimetogmt}\\
-\helpref{MakeGMT}{wxdatetimemakegmt}\\
+\helpref{MakeFromTimezone}{wxdatetimemakefromtimezone}\\
+\helpref{ToUTC}{wxdatetimetoutc}\\
+\helpref{MakeUTC}{wxdatetimemakeutc}\\
 \helpref{GetBeginDST}{wxdatetimegetbegindst}\\
 \helpref{GetEndDST}{wxdatetimegetenddst}\\
 \helpref{IsDST}{wxdatetimeisdst}
@@ -1461,6 +1463,16 @@ year 1 is Rata Die day 1.
 %%%%%%%%%%%%%%%%%%%%%%%%%%% timezone and DST %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 
+\membersection{wxDateTime::FromTimezone}\label{wxdatetimefromtimezone}
+
+\constfunc{wxDateTime}{FromTimezone}{\param{const TimeZone\& }{tz}, \param{bool }{noDST = false}}
+
+Transform the date from the given time zone to the local one. If {\it noDST} is 
+{\tt true}, no DST adjustments will be made.
+
+Returns the date in the local time zone.
+
+
 \membersection{wxDateTime::ToTimezone}\label{wxdatetimetotimezone}
 
 \constfunc{wxDateTime}{ToTimezone}{\param{const TimeZone\& }{tz}, \param{bool }{noDST = false}}
@@ -1479,17 +1491,25 @@ Modifies the object in place to represent the date in another time zone. If
 {\it noDST} is {\tt true}, no DST adjustments will be made.
 
 
-\membersection{wxDateTime::ToGMT}\label{wxdatetimetogmt}
+\membersection{wxDateTime::MakeFromTimezone}\label{wxdatetimemakefromtimezone}
+
+\func{wxDateTime\&}{MakeFromTimezone}{\param{const TimeZone\& }{tz}, \param{bool }{noDST = false}}
+
+Same as \helpref{FromTimezone}{wxdatetimefromtimezone} but modifies the object
+in place.
+
+
+\membersection{wxDateTime::ToUTC}\label{wxdatetimetoutc}
 
-\constfunc{wxDateTime}{ToGMT}{\param{bool }{noDST = false}}
+\constfunc{wxDateTime}{ToUTC}{\param{bool }{noDST = false}}
 
 This is the same as calling \helpref{ToTimezone}{wxdatetimetotimezone} with
 the argument {\tt GMT0}.
 
 
-\membersection{wxDateTime::MakeGMT}\label{wxdatetimemakegmt}
+\membersection{wxDateTime::MakeUTC}\label{wxdatetimemakeutc}
 
-\func{wxDateTime\&}{MakeGMT}{\param{bool }{noDST = false}}
+\func{wxDateTime\&}{MakeUTC}{\param{bool }{noDST = false}}
 
 This is the same as calling \helpref{MakeTimezone}{wxdatetimemaketimezone} with
 the argument {\tt GMT0}.
diff --git a/include/wx/datetime.h b/include/wx/datetime.h
index 16bf40e690..5e6f63d698 100644
--- a/include/wx/datetime.h
+++ b/include/wx/datetime.h
@@ -757,26 +757,42 @@ public:
         //      religious holidays (Easter...) or moon/solar eclipses? Some
         //      algorithms can be found in the calendar FAQ
 
-    // timezone stuff: a wxDateTime object constructed using given
-    // day/month/year/hour/min/sec values correspond to this moment in local
-    // time. Using the functions below, it may be converted to another time
-    // zone (for example, the Unix epoch is wxDateTime(1, Jan, 1970).ToGMT())
+
+    // Timezone stuff: a wxDateTime object constructed using given
+    // day/month/year/hour/min/sec values is interpreted as this moment in
+    // local time. Using the functions below, it may be converted to another
+    // time zone (e.g., the Unix epoch is wxDateTime(1, Jan, 1970).ToGMT()).
     //
-    // these functions try to handle DST internally, but there is no magical
+    // These functions try to handle DST internally, but there is no magical
     // way to know all rules for it in all countries in the world, so if the
     // program can handle it itself (or doesn't want to handle it at all for
     // whatever reason), the DST handling can be disabled with noDST.
-    //
-    // Converting to the local time zone doesn't do anything.
     // ------------------------------------------------------------------------
 
         // transform to any given timezone
     inline wxDateTime ToTimezone(const TimeZone& tz, bool noDST = false) const;
     wxDateTime& MakeTimezone(const TimeZone& tz, bool noDST = false);
 
-        // transform to GMT/UTC
-    wxDateTime ToGMT(bool noDST = false) const { return ToTimezone(GMT0, noDST); }
-    wxDateTime& MakeGMT(bool noDST = false) { return MakeTimezone(GMT0, noDST); }
+#if wxABI_VERSION >= 20602
+        // interpret current value as being in another timezone and transform
+        // it to local one
+    inline wxDateTime FromTimezone(const TimeZone& tz, bool noDST = false) const;
+    wxDateTime& MakeFromTimezone(const TimeZone& tz, bool noDST = false);
+#endif // ABI >= 2.6.2
+
+        // transform to/from GMT/UTC
+    wxDateTime ToUTC(bool noDST = false) const { return ToTimezone(UTC, noDST); }
+    wxDateTime& MakeUTC(bool noDST = false) { return MakeTimezone(UTC, noDST); }
+
+    wxDateTime ToGMT(bool noDST = false) const { return ToUTC(noDST); }
+    wxDateTime& MakeGMT(bool noDST = false) { return MakeUTC(noDST); }
+
+#if wxABI_VERSION >= 20602
+    wxDateTime FromUTC(bool noDST = false) const
+        { return FromTimezone(UTC, noDST); }
+    wxDateTime& MakeFromUTC(bool noDST = false)
+        { return MakeFromTimezone(UTC, noDST); }
+#endif // ABI >= 2.6.2
 
         // is daylight savings time in effect at this moment according to the
         // rules of the specified country?
@@ -785,6 +801,7 @@ public:
         // the information is not available (this is compatible with ANSI C)
     int IsDST(Country country = Country_Default) const;
 
+
     // accessors: many of them take the timezone parameter which indicates the
     // timezone for which to make the calculations and the default value means
     // to do it for the current timezone of this machine (even if the function
@@ -1839,12 +1856,22 @@ inline wxDateTime& wxDateTime::operator+=(const wxDateSpan& diff)
 // wxDateTime and timezones
 // ----------------------------------------------------------------------------
 
-inline wxDateTime wxDateTime::ToTimezone(const wxDateTime::TimeZone& tz,
-                                         bool noDST) const
+inline wxDateTime
+wxDateTime::ToTimezone(const wxDateTime::TimeZone& tz, bool noDST) const
 {
     MODIFY_AND_RETURN( MakeTimezone(tz, noDST) );
 }
 
+#if wxABI_VERSION >= 20602
+
+inline wxDateTime
+wxDateTime::FromTimezone(const wxDateTime::TimeZone& tz, bool noDST) const
+{
+    MODIFY_AND_RETURN( MakeFromTimezone(tz, noDST) );
+}
+
+#endif // ABI >= 2.6.2
+
 // ----------------------------------------------------------------------------
 // wxTimeSpan construction
 // ----------------------------------------------------------------------------
diff --git a/src/common/datetime.cpp b/src/common/datetime.cpp
index b09fe1ef75..7ee3917095 100644
--- a/src/common/datetime.cpp
+++ b/src/common/datetime.cpp
@@ -692,7 +692,7 @@ wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
 
         case wxDateTime::A_CST:
             // Central Standard Time in use in Australia = UTC + 9.5
-            m_offset = 60l*(9*60 + 30);
+            m_offset = 60l*(9*MIN_PER_HOUR + MIN_PER_HOUR/2);
             break;
 
         default:
@@ -1405,20 +1405,9 @@ wxDateTime& wxDateTime::Set(double jdn)
     // EPOCH_JDN + 0.5
     jdn -= EPOCH_JDN + 0.5;
 
-    jdn *= MILLISECONDS_PER_DAY;
+    m_time.Assign(jdn*MILLISECONDS_PER_DAY);
 
-    m_time.Assign(jdn);
-
-    // JDNs always suppose an UTC date, so bring it back to local time zone
-    // (also see GetJulianDayNumber() implementation)
-    long tzDiff = GetTimeZone();
-    if ( IsDST() == 1 )
-    {
-        // FIXME: again, we suppose that DST is always one hour
-        tzDiff -= 3600;
-    }
-
-    m_time += tzDiff*1000; // tzDiff is in seconds
+    // JDNs always are in UTC, so we don't need any adjustments for time zone
 
     return *this;
 }
@@ -1637,14 +1626,14 @@ wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
     timeOnly -= tm.msec;
     timeOnly /= 1000;               // now we have time in seconds
 
-    tm.sec = (wxDateTime_t)(timeOnly % 60);
+    tm.sec = (wxDateTime_t)(timeOnly % SEC_PER_MIN);
     timeOnly -= tm.sec;
-    timeOnly /= 60;                 // now we have time in minutes
+    timeOnly /= SEC_PER_MIN;        // now we have time in minutes
 
-    tm.min = (wxDateTime_t)(timeOnly % 60);
+    tm.min = (wxDateTime_t)(timeOnly % MIN_PER_HOUR);
     timeOnly -= tm.min;
 
-    tm.hour = (wxDateTime_t)(timeOnly / 60);
+    tm.hour = (wxDateTime_t)(timeOnly / MIN_PER_HOUR);
 
     return tm;
 }
@@ -2110,16 +2099,7 @@ wxDateTime& wxDateTime::SetToYearDay(wxDateTime::wxDateTime_t yday)
 
 double wxDateTime::GetJulianDayNumber() const
 {
-    // JDN are always expressed for the UTC dates
-    Tm tm(ToTimezone(UTC).GetTm(UTC));
-
-    double result = GetTruncatedJDN(tm.mday, tm.mon, tm.year);
-
-    // add the part GetTruncatedJDN() neglected
-    result += 0.5;
-
-    // and now add the time: 86400 sec = 1 JDN
-    return result + ((double)(60*(60*tm.hour + tm.min) + tm.sec)) / 86400;
+    return m_time.ToDouble() / MILLISECONDS_PER_DAY + EPOCH_JDN + 0.5;
 }
 
 double wxDateTime::GetRataDie() const
@@ -2173,6 +2153,21 @@ wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
         secDiff -= 3600;
     }
 
+    return Add(wxTimeSpan::Seconds(secDiff));
+}
+
+wxDateTime& wxDateTime::MakeFromTimezone(const TimeZone& tz, bool noDST)
+{
+    long secDiff = GetTimeZone() + tz.GetOffset();
+
+    // we need to know whether DST is or not in effect for this date unless
+    // the test disabled by the caller
+    if ( !noDST && (IsDST() == 1) )
+    {
+        // FIXME we assume that the DST is always shifted by 1 hour
+        secDiff -= 3600;
+    }
+
     return Subtract(wxTimeSpan::Seconds(secDiff));
 }
 
@@ -2771,7 +2766,7 @@ const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
         }
 
         // hours
-        offset = 60*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
+        offset = MIN_PER_HOUR*(10*(*p - _T('0')) + (*(p + 1) - _T('0')));
 
         p += 2;
 
@@ -2851,12 +2846,12 @@ const wxChar *wxDateTime::ParseRfc822Date(const wxChar* date)
         }
 
         // make it minutes
-        offset *= 60;
+        offset *= MIN_PER_HOUR;
     }
 
-    // the spec was correct
+    // the spec was correct, construct the date from the values we found
     Set(day, mon, year, hour, min, sec);
-    MakeTimezone((wxDateTime_t)(60*offset));
+    MakeFromTimezone(offset*SEC_PER_MIN);
 
     return p;
 }
diff --git a/tests/datetime/datetimetest.cpp b/tests/datetime/datetimetest.cpp
index 44924b868d..29209eef6c 100644
--- a/tests/datetime/datetimetest.cpp
+++ b/tests/datetime/datetimetest.cpp
@@ -257,7 +257,9 @@ void DateTimeTestCase::TestTimeJDN()
     {
         const Date& d = testDates[n];
         wxDateTime dt(d.day, d.month, d.year, d.hour, d.min, d.sec);
-        double jdn = dt.GetJulianDayNumber();
+
+        // JDNs must be computed for UTC times
+        double jdn = dt.FromUTC().GetJulianDayNumber();
 
         CPPUNIT_ASSERT( jdn == d.jdn );
 
@@ -662,7 +664,7 @@ void DateTimeTestCase::TestTimeTicks()
         long ticks = (dt.GetValue() / 1000).ToLong();
         CPPUNIT_ASSERT( ticks == d.ticks );
 
-        dt = d.DT().ToTimezone(wxDateTime::GMT0);
+        dt = d.DT().FromTimezone(wxDateTime::GMT0);
         ticks = (dt.GetValue() / 1000).ToLong();
         CPPUNIT_ASSERT( ticks == d.gmticks );
     }
@@ -674,12 +676,25 @@ void DateTimeTestCase::TestTimeParse()
     static const struct ParseTestData
     {
         const wxChar *format;
-        Date date;
+        Date date;              // NB: this should be in UTC
         bool good;
     } parseTestDates[] =
     {
-        { _T("Sat, 18 Dec 1999 00:46:40 +0100"), { 18, wxDateTime::Dec, 1999, 00, 46, 40, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, true },
-        { _T("Wed, 1 Dec 1999 05:17:20 +0300"),  {  1, wxDateTime::Dec, 1999, 03, 17, 20, 0.0, wxDateTime::Inv_WeekDay, 0, 0 }, true },
+        {
+            _T("Sat, 18 Dec 1999 00:46:40 +0100"),
+            { 17, wxDateTime::Dec, 1999, 23, 46, 40, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
+            true
+        },
+        {
+            _T("Wed, 1 Dec 1999 05:17:20 +0300"),
+            {  1, wxDateTime::Dec, 1999, 2, 17, 20, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
+            true
+        },
+        {
+            _T("Sun, 28 Aug 2005 03:31:30 +0200"),
+            {  28, wxDateTime::Aug, 2005, 1, 31, 30, 0.0, wxDateTime::Inv_WeekDay, 0, 0 },
+            true
+        },
     };
 
     for ( size_t n = 0; n < WXSIZEOF(parseTestDates); n++ )
@@ -691,10 +706,7 @@ void DateTimeTestCase::TestTimeParse()
         {
             CPPUNIT_ASSERT( parseTestDates[n].good );
 
-            wxDateTime dtReal = parseTestDates[n].date.DT();
-            //RN:  We need this because the tests are based on 
-            //a non-GMT time zone
-            dtReal.MakeTimezone(wxDateTime::WEST, true);
+            wxDateTime dtReal = parseTestDates[n].date.DT().FromUTC();
             CPPUNIT_ASSERT( dt == dtReal );
         }
         else // failed to parse
-- 
2.50.0

