summaryrefslogtreecommitdiff
path: root/android_icu4j/src/main/java/android/icu/util/UniversalTimeScale.java
blob: 6b4f7fdc33d095a4c9c46e66054d63ac6561e891 (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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
/* GENERATED SOURCE. DO NOT MODIFY. */
/*
 *********************************************************************************
 * Copyright (C) 2004-2016, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                  *
 *********************************************************************************
 *
 */

package android.icu.util;

import android.icu.math.BigDecimal;

/** 
 * There are quite a few different conventions for binary datetime, depending on different
 * platforms and protocols. Some of these have severe drawbacks. For example, people using
 * Unix time (seconds since Jan 1, 1970, usually in a 32-bit integer)
 * think that they are safe until near the year 2038.
 * But cases can and do arise where arithmetic manipulations causes serious problems. Consider
 * the computation of the average of two datetimes, for example: if one calculates them with
 * <code>averageTime = (time1 + time2)/2</code>, there will be overflow even with dates
 * beginning in 2004. Moreover, even if these problems don't occur, there is the issue of
 * conversion back and forth between different systems.
 *
 * <p>Binary datetimes differ in a number of ways: the datatype, the unit,
 * and the epoch (origin). We refer to these as time scales.
 *
 * <p>ICU implements a universal time scale that is similar to the
 * .NET framework's System.DateTime. The universal time scale is a
 * 64-bit integer that holds ticks since midnight, January 1st, 0001.
 * (One tick is 100 nanoseconds.)
 * Negative values are supported. This has enough range to guarantee that
 * calculations involving dates around the present are safe.
 *
 * <p>The universal time scale always measures time according to the
 * proleptic Gregorian calendar. That is, the Gregorian calendar's
 * leap year rules are used for all times, even before 1582 when it was
 * introduced. (This is different from the default ICU calendar which
 * switches from the Julian to the Gregorian calendar in 1582.
 * See GregorianCalendar.setGregorianChange() and ucal_setGregorianChange().)
 *
 * ICU provides conversion functions to and from all other major time
 * scales, allowing datetimes in any time scale to be converted to the
 * universal time scale, safely manipulated, and converted back to any other
 * datetime time scale.
 *
 * <p>For more details and background, see the
 * <a href="http://www.icu-project.org/userguide/universalTimeScale.html">Universal Time Scale</a>
 * chapter in the ICU User Guide.
 *
 * @hide Only a subset of ICU is exposed in Android
 */

public final class UniversalTimeScale
{
    /**
     * Used in the JDK. Data is a <code>long</code>. Value
     * is milliseconds since January 1, 1970.
     */
    public static final int JAVA_TIME = 0;

    /**
     * Used in Unix systems. Data is an <code>int</code> or a <code>long</code>. Value
     * is seconds since January 1, 1970.
     */
    public static final int UNIX_TIME = 1;

    /**
     * Used in the ICU4C. Data is a <code>double</code>. Value
     * is milliseconds since January 1, 1970.
     */
    public static final int ICU4C_TIME = 2;

    /**
     * Used in Windows for file times. Data is a <code>long</code>. Value
     * is ticks (1 tick == 100 nanoseconds) since January 1, 1601.
     */
    public static final int WINDOWS_FILE_TIME = 3;

    /**
     * Used in the .NET framework's <code>System.DateTime</code> structure.
     * Data is a <code>long</code>. Value is ticks (1 tick == 100 nanoseconds) since January 1, 0001.
     */
    public static final int DOTNET_DATE_TIME = 4;

    /**
     * Used in older Macintosh systems. Data is an <code>int</code>. Value
     * is seconds since January 1, 1904.
     */
    public static final int MAC_OLD_TIME = 5;

    /**
     * Used in the JDK. Data is a <code>double</code>. Value
     * is milliseconds since January 1, 2001.
     */
    public static final int MAC_TIME = 6;

    /**
     * Used in Excel. Data is a <code>?unknown?</code>. Value
     * is days since December 31, 1899.
     */
    public static final int EXCEL_TIME = 7;

    /**
     * Used in DB2. Data is a <code>?unknown?</code>. Value
     * is days since December 31, 1899.
     */
    public static final int DB2_TIME = 8;

    /**
     * Data is a <code>long</code>. Value is microseconds since January 1, 1970.
     * Similar to Unix time (linear value from 1970) and struct timeval
     * (microseconds resolution).
     */
    public static final int UNIX_MICROSECONDS_TIME = 9;
    
    /**
     * This is the first unused time scale value.
     */
    public static final int MAX_SCALE = 10;
    
    /**
     * The constant used to select the units value
     * for a time scale.
     */
    public static final int UNITS_VALUE = 0;
    
    /**
     * The constant used to select the epoch offset value
     * for a time scale.
     * 
     * @see #getTimeScaleValue
     */
    public static final int EPOCH_OFFSET_VALUE = 1;
    
    /**
     * The constant used to select the minimum from value
     * for a time scale.
     * 
     * @see #getTimeScaleValue
     */
    public static final int FROM_MIN_VALUE = 2;
    
    /**
     * The constant used to select the maximum from value
     * for a time scale.
     * 
     * @see #getTimeScaleValue
     */
    public static final int FROM_MAX_VALUE = 3;
    
    /**
     * The constant used to select the minimum to value
     * for a time scale.
     * 
     * @see #getTimeScaleValue
     */
    public static final int TO_MIN_VALUE = 4;
    
    /**
     * The constant used to select the maximum to value
     * for a time scale.
     * 
     * @see #getTimeScaleValue
     */
    public static final int TO_MAX_VALUE = 5;
    
    /**
     * The constant used to select the epoch plus one value
     * for a time scale.
     * 
     * NOTE: This is an internal value. DO NOT USE IT. May not
     * actually be equal to the epoch offset value plus one.
     * 
     * @see #getTimeScaleValue
     */
    public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6;
    
    /**
     * The constant used to select the epoch offset minus one value
     * for a time scale.
     * 
     * NOTE: This is an internal value. DO NOT USE IT. May not
     * actually be equal to the epoch offset value minus one.
     * 
     * @see #getTimeScaleValue
     *
     * @deprecated This API is ICU internal only.
     * @hide draft / provisional / internal are hidden on Android
     */
    @Deprecated
    public static final int EPOCH_OFFSET_MINUS_1_VALUE = 7;
    
    /**
     * The constant used to select the units round value
     * for a time scale.
     * 
     * NOTE: This is an internal value. DO NOT USE IT.
     * 
     * @see #getTimeScaleValue
     *
     * @deprecated This API is ICU internal only.
     * @hide draft / provisional / internal are hidden on Android
     */
    @Deprecated
    public static final int UNITS_ROUND_VALUE = 8;
    
    /**
     * The constant used to select the minimum safe rounding value
     * for a time scale.
     * 
     * NOTE: This is an internal value. DO NOT USE IT.
     * 
     * @see #getTimeScaleValue
     *
     * @deprecated This API is ICU internal only.
     * @hide draft / provisional / internal are hidden on Android
     */
    @Deprecated
    public static final int MIN_ROUND_VALUE = 9;
    
    /**
     * The constant used to select the maximum safe rounding value
     * for a time scale.
     * 
     * NOTE: This is an internal value. DO NOT USE IT.
     * 
     * @see #getTimeScaleValue
     *
     * @deprecated This API is ICU internal only.
     * @hide draft / provisional / internal are hidden on Android
     */
    @Deprecated
    public static final int MAX_ROUND_VALUE = 10;
    
    /**
     * The number of time scale values.
     * 
     * NOTE: This is an internal value. DO NOT USE IT.
     * 
     * @see #getTimeScaleValue
     *
     * @deprecated This API is ICU internal only.
     * @hide draft / provisional / internal are hidden on Android
     */
    @Deprecated
    public static final int MAX_SCALE_VALUE = 11;
    
    private static final long ticks        = 1;
    private static final long microseconds = ticks * 10;
    private static final long milliseconds = microseconds * 1000;
    private static final long seconds      = milliseconds * 1000;
    private static final long minutes      = seconds * 60;
    private static final long hours        = minutes * 60;
    private static final long days         = hours * 24;
    
    /**
     * This class holds the data that describes a particular
     * time scale.
     */
    private static final class TimeScaleData
    {
        TimeScaleData(long theUnits, long theEpochOffset,
                       long theToMin, long theToMax,
                       long theFromMin, long theFromMax)
        {
            units      = theUnits;
            unitsRound = theUnits / 2;
            
            minRound = Long.MIN_VALUE + unitsRound;
            maxRound = Long.MAX_VALUE - unitsRound;
                        
            epochOffset   = theEpochOffset / theUnits;
            
            if (theUnits == 1) {
                epochOffsetP1 = epochOffsetM1 = epochOffset;
            } else {
                epochOffsetP1 = epochOffset + 1;
                epochOffsetM1 = epochOffset - 1;
            }
            
            toMin = theToMin;
            toMax = theToMax;
            
            fromMin = theFromMin;
            fromMax = theFromMax;
        }
        
        long units;
        long epochOffset;
        long fromMin;
        long fromMax;
        long toMin;
        long toMax;
        
        long epochOffsetP1;
        long epochOffsetM1;
        long unitsRound;
        long minRound;
        long maxRound;
    }
    
    private static final TimeScaleData[] timeScaleTable = {
        new TimeScaleData(milliseconds, 621355968000000000L, -9223372036854774999L, 9223372036854774999L, -984472800485477L,         860201606885477L), // JAVA_TIME
        new TimeScaleData(seconds,      621355968000000000L, -9223372036854775808L, 9223372036854775807L, -984472800485L,               860201606885L), // UNIX_TIME
        new TimeScaleData(milliseconds, 621355968000000000L, -9223372036854774999L, 9223372036854774999L, -984472800485477L,         860201606885477L), // ICU4C_TIME
        new TimeScaleData(ticks,        504911232000000000L, -8718460804854775808L, 9223372036854775807L, -9223372036854775808L, 8718460804854775807L), // WINDOWS_FILE_TIME
        new TimeScaleData(ticks,        000000000000000000L, -9223372036854775808L, 9223372036854775807L, -9223372036854775808L, 9223372036854775807L), // DOTNET_DATE_TIME
        new TimeScaleData(seconds,      600527520000000000L, -9223372036854775808L, 9223372036854775807L, -982389955685L,               862284451685L), // MAC_OLD_TIME
        new TimeScaleData(seconds,      631139040000000000L, -9223372036854775808L, 9223372036854775807L, -985451107685L,               859223299685L), // MAC_TIME
        new TimeScaleData(days,         599265216000000000L, -9223372036854775808L, 9223372036854775807L, -11368793L,                        9981605L), // EXCEL_TIME
        new TimeScaleData(days,         599265216000000000L, -9223372036854775808L, 9223372036854775807L, -11368793L,                        9981605L), // DB2_TIME
        new TimeScaleData(microseconds, 621355968000000000L, -9223372036854775804L, 9223372036854775804L, -984472800485477580L,   860201606885477580L)  // UNIX_MICROSECONDS_TIME
    };
    
    
    /*
     * Prevent construction of this class.
     */
    ///CLOVER:OFF
    private UniversalTimeScale()
    {
        // nothing to do
    }
    ///CLOVER:ON
    
    /**
     * Convert a <code>long</code> datetime from the given time scale to the universal time scale.
     *
     * @param otherTime The <code>long</code> datetime
     * @param timeScale The time scale to convert from
     * 
     * @return The datetime converted to the universal time scale
     */
    public static long from(long otherTime, int timeScale)
    {
        TimeScaleData data = fromRangeCheck(otherTime, timeScale);
                
        return (otherTime + data.epochOffset) * data.units;
    }

    /**
     * Convert a <code>double</code> datetime from the given time scale to the universal time scale.
     * All calculations are done using <code>BigDecimal</code> to guarantee that the value
     * does not go out of range.
     *
     * @param otherTime The <code>double</code> datetime
     * @param timeScale The time scale to convert from
     * 
     * @return The datetime converted to the universal time scale
     */
    public static BigDecimal bigDecimalFrom(double otherTime, int timeScale)
    {
        TimeScaleData data     = getTimeScaleData(timeScale);
        BigDecimal other       = new BigDecimal(String.valueOf(otherTime));
        BigDecimal units       = new BigDecimal(data.units);
        BigDecimal epochOffset = new BigDecimal(data.epochOffset);
        
        return other.add(epochOffset).multiply(units);
    }

    /**
     * Convert a <code>long</code> datetime from the given time scale to the universal time scale.
     * All calculations are done using <code>BigDecimal</code> to guarantee that the value
     * does not go out of range.
     *
     * @param otherTime The <code>long</code> datetime
     * @param timeScale The time scale to convert from
     * 
     * @return The datetime converted to the universal time scale
     */
    public static BigDecimal bigDecimalFrom(long otherTime, int timeScale)
    {
        TimeScaleData data     = getTimeScaleData(timeScale);
        BigDecimal other       = new BigDecimal(otherTime);
        BigDecimal units       = new BigDecimal(data.units);
        BigDecimal epochOffset = new BigDecimal(data.epochOffset);
        
        return other.add(epochOffset).multiply(units);
    }

    /**
     * Convert a <code>BigDecimal</code> datetime from the given time scale to the universal time scale.
     * All calculations are done using <code>BigDecimal</code> to guarantee that the value
     * does not go out of range.
     *
     * @param otherTime The <code>BigDecimal</code> datetime
     * @param timeScale The time scale to convert from
     * 
     * @return The datetime converted to the universal time scale
     */
    public static BigDecimal bigDecimalFrom(BigDecimal otherTime, int timeScale)
    {
        TimeScaleData data = getTimeScaleData(timeScale);
        
        BigDecimal units = new BigDecimal(data.units);
        BigDecimal epochOffset = new BigDecimal(data.epochOffset);
        
        return otherTime.add(epochOffset).multiply(units);
    }

    /**
     * Convert a datetime from the universal time scale stored as a <code>BigDecimal</code> to a
     * <code>long</code> in the given time scale.
     *
     * Since this calculation requires a divide, we must round. The straight forward
     * way to round by adding half of the divisor will push the sum out of range for values
     * within have the divisor of the limits of the precision of a <code>long</code>. To get around this, we do
     * the rounding like this:
     * 
     * <p><code>
     * (universalTime - units + units/2) / units + 1
     * </code>
     * 
     * <p>
     * (i.e. we subtract units first to guarantee that we'll still be in range when we
     * add <code>units/2</code>. We then need to add one to the quotent to make up for the extra subtraction.
     * This simplifies to:
     * 
     * <p><code>
     * (universalTime - units/2) / units - 1
     * </code>
     * 
     * <p>
     * For negative values to round away from zero, we need to flip the signs:
     * 
     * <p><code>
     * (universalTime + units/2) / units + 1
     * </code>
     * 
     * <p>
     * Since we also need to subtract the epochOffset, we fold the <code>+/- 1</code>
     * into the offset value. (i.e. <code>epochOffsetP1</code>, <code>epochOffsetM1</code>.)
     * 
     * @param universalTime The datetime in the universal time scale
     * @param timeScale The time scale to convert to
     * 
     * @return The datetime converted to the given time scale
     */
    public static long toLong(long universalTime, int timeScale)
    {
        TimeScaleData data = toRangeCheck(universalTime, timeScale);
        
        if (universalTime < 0) {
            if (universalTime < data.minRound) {
                return (universalTime + data.unitsRound) / data.units - data.epochOffsetP1;
            }
            
            return (universalTime - data.unitsRound) / data.units - data.epochOffset;
        }
        
        if (universalTime > data.maxRound) {
            return (universalTime - data.unitsRound) / data.units - data.epochOffsetM1;
        }
        
        return (universalTime + data.unitsRound) / data.units - data.epochOffset;
    }
    
    /**
     * Convert a datetime from the universal time scale to a <code>BigDecimal</code> in the given time scale.
     *
     * @param universalTime The datetime in the universal time scale
     * @param timeScale The time scale to convert to
     * 
     * @return The datetime converted to the given time scale
     */
    public static BigDecimal toBigDecimal(long universalTime, int timeScale)
    {
        TimeScaleData data     = getTimeScaleData(timeScale);
        BigDecimal universal   = new BigDecimal(universalTime);
        BigDecimal units       = new BigDecimal(data.units);
        BigDecimal epochOffset = new BigDecimal(data.epochOffset);
        
        return universal.divide(units, BigDecimal.ROUND_HALF_UP).subtract(epochOffset);
    }
    
    /**
     * Convert a datetime from the universal time scale to a <code>BigDecimal</code> in the given time scale.
     *
     * @param universalTime The datetime in the universal time scale
     * @param timeScale The time scale to convert to
     * 
     * @return The datetime converted to the given time scale
     */
    public static BigDecimal toBigDecimal(BigDecimal universalTime, int timeScale)
    {
        TimeScaleData data     = getTimeScaleData(timeScale);
        BigDecimal units       = new BigDecimal(data.units);
        BigDecimal epochOffset = new BigDecimal(data.epochOffset);
        
        return universalTime.divide(units, BigDecimal.ROUND_HALF_UP).subtract(epochOffset);
    }
    
    /**
     * Return the <code>TimeScaleData</code> object for the given time
     * scale.
     * 
     * @param scale - the time scale
     * @return the <code>TimeScaleData</code> object for the given time scale
     */
    private static TimeScaleData getTimeScaleData(int scale)
    {
        if (scale < 0 || scale >= MAX_SCALE) {
            throw new IllegalArgumentException("scale out of range: " + scale);
        }
        
        return timeScaleTable[scale];
    }
    
    /**
     * Get a value associated with a particular time scale.
     * 
     * @param scale - the time scale
     * @param value - a constant representing the value to get
     * 
     * @return - the value.
     */
    public static long getTimeScaleValue(int scale, int value)
    {
        TimeScaleData data = getTimeScaleData(scale);
        
        switch (value)
        {
        case UNITS_VALUE:
            return data.units;
            
        case EPOCH_OFFSET_VALUE:
            return data.epochOffset;
        
        case FROM_MIN_VALUE:
            return data.fromMin;
            
        case FROM_MAX_VALUE:
            return data.fromMax;
            
        case TO_MIN_VALUE:
            return data.toMin;
            
        case TO_MAX_VALUE:
            return data.toMax;
            
        case EPOCH_OFFSET_PLUS_1_VALUE:
            return data.epochOffsetP1;
            
        case EPOCH_OFFSET_MINUS_1_VALUE:
            return data.epochOffsetM1;
            
        case UNITS_ROUND_VALUE:
            return data.unitsRound;
        
        case MIN_ROUND_VALUE:
            return data.minRound;
            
        case MAX_ROUND_VALUE:
            return data.maxRound;
            
        default:
            throw new IllegalArgumentException("value out of range: " + value);
        }
    }
    
    private static TimeScaleData toRangeCheck(long universalTime, int scale)
    {
        TimeScaleData data = getTimeScaleData(scale);
          
        if (universalTime >= data.toMin && universalTime <= data.toMax) {
            return data;
        }
        
        throw new IllegalArgumentException("universalTime out of range:" + universalTime);
    }
    
    private static TimeScaleData fromRangeCheck(long otherTime, int scale)
    {
        TimeScaleData data = getTimeScaleData(scale);
          
        if (otherTime >= data.fromMin && otherTime <= data.fromMax) {
            return data;
        }
        
        throw new IllegalArgumentException("otherTime out of range:" + otherTime);
    }
    
    /**
     * Convert a time in the Universal Time Scale into another time
     * scale. The division used to do the conversion rounds down.
     * 
     * NOTE: This is an internal routine used by the tool that
     * generates the to and from limits. Use it at your own risk.
     * 
     * @param universalTime the time in the Universal Time scale
     * @param timeScale the time scale to convert to
     * @return the time in the given time scale
     * 
     * @deprecated This API is ICU internal only.
     * @hide draft / provisional / internal are hidden on Android
     */
    @Deprecated
    public static BigDecimal toBigDecimalTrunc(BigDecimal universalTime, int timeScale)
    {
        TimeScaleData data = getTimeScaleData(timeScale);
        BigDecimal units = new BigDecimal(data.units);
        BigDecimal epochOffset = new BigDecimal(data.epochOffset);
        
        return universalTime.divide(units, BigDecimal.ROUND_DOWN).subtract(epochOffset);
    }
}