summaryrefslogtreecommitdiff
path: root/src/mscorlib/shared/System/TimeZone.cs
blob: 88e2e218641edd9e015ea65c4c84414c7a33fb64 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/*============================================================
**
**
**
** Purpose: 
** This class is used to represent a TimeZone.  It
** has methods for converting a DateTime to UTC from local time
** and to local time from UTC and methods for getting the 
** standard name and daylight name of the time zone.  
**
** The only TimeZone that we support in version 1 is the 
** CurrentTimeZone as determined by the system timezone.
**
**
============================================================*/

using System;
using System.Text;
using System.Threading;
using System.Collections;
using System.Globalization;

namespace System
{
    [Serializable]
    [Obsolete("System.TimeZone has been deprecated.  Please investigate the use of System.TimeZoneInfo instead.")]
    public abstract class TimeZone
    {
        private static volatile TimeZone currentTimeZone = null;

        // Private object for locking instead of locking on a public type for SQL reliability work.
        private static Object s_InternalSyncObject;
        private static Object InternalSyncObject
        {
            get
            {
                if (s_InternalSyncObject == null)
                {
                    Object o = new Object();
                    Interlocked.CompareExchange<Object>(ref s_InternalSyncObject, o, null);
                }
                return s_InternalSyncObject;
            }
        }


        protected TimeZone()
        {
        }

        public static TimeZone CurrentTimeZone
        {
            get
            {
                //Grabbing the cached value is required at the top of this function so that
                //we don't incur a race condition with the ResetTimeZone method below.
                TimeZone tz = currentTimeZone;
                if (tz == null)
                {
                    lock (InternalSyncObject)
                    {
                        if (currentTimeZone == null)
                        {
                            currentTimeZone = new CurrentSystemTimeZone();
                        }
                        tz = currentTimeZone;
                    }
                }
                return (tz);
            }
        }

        //This method is called by CultureInfo.ClearCachedData in response to control panel
        //change events.  It must be synchronized because otherwise there is a race condition 
        //with the CurrentTimeZone property above.
        internal static void ResetTimeZone()
        {
            if (currentTimeZone != null)
            {
                lock (InternalSyncObject)
                {
                    currentTimeZone = null;
                }
            }
        }

        public abstract String StandardName
        {
            get;
        }

        public abstract String DaylightName
        {
            get;
        }

        public abstract TimeSpan GetUtcOffset(DateTime time);

        //
        // Converts the specified datatime to the Universal time base on the current timezone 
        //
        public virtual DateTime ToUniversalTime(DateTime time)
        {
            if (time.Kind == DateTimeKind.Utc)
            {
                return time;
            }
            long tickCount = time.Ticks - GetUtcOffset(time).Ticks;
            if (tickCount > DateTime.MaxTicks)
            {
                return new DateTime(DateTime.MaxTicks, DateTimeKind.Utc);
            }
            if (tickCount < DateTime.MinTicks)
            {
                return new DateTime(DateTime.MinTicks, DateTimeKind.Utc);
            }
            return new DateTime(tickCount, DateTimeKind.Utc);
        }

        //
        // Convert the specified datetime value from UTC to the local time based on the time zone.
        //
        public virtual DateTime ToLocalTime(DateTime time)
        {
            if (time.Kind == DateTimeKind.Local)
            {
                return time;
            }
            Boolean isAmbiguousLocalDst = false;
            Int64 offset = ((CurrentSystemTimeZone)(TimeZone.CurrentTimeZone)).GetUtcOffsetFromUniversalTime(time, ref isAmbiguousLocalDst);
            return new DateTime(time.Ticks + offset, DateTimeKind.Local, isAmbiguousLocalDst);
        }

        // Return an array of DaylightTime which reflects the daylight saving periods in a particular year.
        // We currently only support having one DaylightSavingTime per year.
        // If daylight saving time is not used in this timezone, null will be returned.
        public abstract DaylightTime GetDaylightChanges(int year);

        public virtual bool IsDaylightSavingTime(DateTime time)
        {
            return (IsDaylightSavingTime(time, GetDaylightChanges(time.Year)));
        }

        // Check if the specified time is in a daylight saving time.  Allows the user to
        // specify the array of Daylight Saving Times.
        public static bool IsDaylightSavingTime(DateTime time, DaylightTime daylightTimes)
        {
            return CalculateUtcOffset(time, daylightTimes) != TimeSpan.Zero;
        }

        //
        // NOTENOTE: Implementation detail
        // In the transition from standard time to daylight saving time, 
        // if we convert local time to Universal time, we can have the
        // following (take PST as an example):
        //      Local               Universal       UTC Offset
        //      -----               ---------       ----------
        //      01:00AM             09:00           -8:00
        //      02:00 (=> 03:00)    10:00           -8:00   [This time doesn't actually exist, but it can be created from DateTime]
        //      03:00               10:00           -7:00
        //      04:00               11:00           -7:00
        //      05:00               12:00           -7:00
        //      
        //      So from 02:00 - 02:59:59, we should return the standard offset, instead of the daylight saving offset.
        //
        // In the transition from daylight saving time to standard time,
        // if we convert local time to Universal time, we can have the
        // following (take PST as an example):
        //      Local               Universal       UTC Offset
        //      -----               ---------       ----------
        //      01:00AM             08:00           -7:00
        //      02:00 (=> 01:00)    09:00           -8:00   
        //      02:00               10:00           -8:00
        //      03:00               11:00           -8:00
        //      04:00               12:00           -8:00
        //      
        //      So in this case, the 02:00 does exist after the first 2:00 rolls back to 01:00. We don't need to special case this.
        //      But note that there are two 01:00 in the local time.

        //
        // And imagine if the daylight saving offset is negative (although this does not exist in real life)
        // In the transition from standard time to daylight saving time, 
        // if we convert local time to Universal time, we can have the
        // following (take PST as an example, but the daylight saving offset is -01:00):
        //      Local               Universal       UTC Offset
        //      -----               ---------       ----------
        //      01:00AM             09:00           -8:00
        //      02:00 (=> 01:00)    10:00           -9:00
        //      02:00               11:00           -9:00
        //      03:00               12:00           -9:00
        //      04:00               13:00           -9:00
        //      05:00               14:00           -9:00
        //      
        //      So in this case, the 02:00 does exist after the first 2:00 rolls back to 01:00. We don't need to special case this.
        //
        // In the transition from daylight saving time to standard time,
        // if we convert local time to Universal time, we can have the
        // following (take PST as an example, daylight saving offset is -01:00):
        //
        //      Local               Universal       UTC Offset
        //      -----               ---------       ----------
        //      01:00AM             10:00           -9:00
        //      02:00 (=> 03:00)    11:00           -9:00
        //      03:00               11:00           -8:00
        //      04:00               12:00           -8:00
        //      05:00               13:00           -8:00
        //      06:00               14:00           -8:00
        //      
        //      So from 02:00 - 02:59:59, we should return the daylight saving offset, instead of the standard offset.
        //
        internal static TimeSpan CalculateUtcOffset(DateTime time, DaylightTime daylightTimes)
        {
            if (daylightTimes == null)
            {
                return TimeSpan.Zero;
            }
            DateTimeKind kind = time.Kind;
            if (kind == DateTimeKind.Utc)
            {
                return TimeSpan.Zero;
            }

            DateTime startTime;
            DateTime endTime;

            // startTime and endTime represent the period from either the start of DST to the end and includes the 
            // potentially overlapped times
            startTime = daylightTimes.Start + daylightTimes.Delta;
            endTime = daylightTimes.End;

            // For normal time zones, the ambiguous hour is the last hour of daylight saving when you wind the 
            // clock back. It is theoretically possible to have a positive delta, (which would really be daylight
            // reduction time), where you would have to wind the clock back in the begnning.
            DateTime ambiguousStart;
            DateTime ambiguousEnd;
            if (daylightTimes.Delta.Ticks > 0)
            {
                ambiguousStart = endTime - daylightTimes.Delta;
                ambiguousEnd = endTime;
            }
            else
            {
                ambiguousStart = startTime;
                ambiguousEnd = startTime - daylightTimes.Delta;
            }

            Boolean isDst = false;
            if (startTime > endTime)
            {
                // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year.
                // Note, the summer in the southern hemisphere begins late in the year.
                if (time >= startTime || time < endTime)
                {
                    isDst = true;
                }
            }
            else if (time >= startTime && time < endTime)
            {
                // In northern hemisphere, the daylight saving time starts in the middle of the year.
                isDst = true;
            }

            // If this date was previously converted from a UTC date and we were able to detect that the local
            // DateTime would be ambiguous, this data is stored in the DateTime to resolve this ambiguity. 
            if (isDst && time >= ambiguousStart && time < ambiguousEnd)
            {
                isDst = time.IsAmbiguousDaylightSavingTime();
            }

            if (isDst)
            {
                return daylightTimes.Delta;
            }
            return TimeSpan.Zero;
        }
    }
}