zhao
2021-07-19 8347f2fbddbd25369359dcb2da1233ac48a19fdc
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
/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) Under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for Additional information regarding copyright ownership.
   The ASF licenses this file to You Under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at
 
       http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing, software
   distributed Under the License is distributed on an "AS Is" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations Under the License.
==================================================================== */
 
 
 
namespace HH.WMS.Utils.NPOI.SS.UserModel
{
    using System.Globalization;
    using System;
    using System.Text.RegularExpressions;
    using System.Text;
 
    /// <summary>
    /// Contains methods for dealing with Excel dates.
    /// @author  Michael Harhen
    /// @author  Glen Stampoultzis (glens at apache.org)
    /// @author  Dan Sherman (dsherman at Isisph.com)
    /// @author  Hack Kampbjorn (hak at 2mba.dk)
    /// @author  Alex Jacoby (ajacoby at gmail.com)
    /// @author  Pavel Krupets (pkrupets at palmtreebusiness dot com)
    /// </summary>
    public class DateUtil
    {
        private static int SECONDS_PER_MINUTE = 60;
        private static int MINUTES_PER_HOUR = 60;
        private static int HOURS_PER_DAY = 24;
        private static int SECONDS_PER_DAY = (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE);
 
        private static int BAD_DATE =
            -1;   // used to specify that date Is invalid
        private static long DAY_MILLISECONDS = 24 * 60 * 60 * 1000;
        private static char[] TIME_SEPARATOR_PATTERN = new char[] { ':' };
 
        /// <summary>
        /// Given a Calendar, return the number of days since 1899/12/31.
        /// </summary>
        /// <param name="cal">the date</param>
        /// <param name="use1904windowing">if set to <c>true</c> [use1904windowing].</param>
        /// <returns>number of days since 1899/12/31</returns>
        public static int AbsoluteDay(DateTime cal, bool use1904windowing)
        {
            int daynum = (cal - new DateTime(1899, 12, 31)).Days;
            if (cal > new DateTime(1900, 3, 1) && use1904windowing)
            {
                daynum++;
            }
            return daynum;
        }
        /// <summary>
        /// Given a Date, Converts it into a double representing its internal Excel representation,
        /// which Is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
        /// </summary>
        /// <param name="date">Excel representation of Date (-1 if error - test for error by Checking for less than 0.1)</param>
        /// <returns>the Date</returns>
        public static double GetExcelDate(DateTime date)
        {
            return GetExcelDate(date, false);
        }
        /// <summary>
        /// Gets the excel date.
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="month">The month.</param>
        /// <param name="day">The day.</param>
        /// <param name="hour">The hour.</param>
        /// <param name="minute">The minute.</param>
        /// <param name="second">The second.</param>
        /// <param name="use1904windowing">Should 1900 or 1904 date windowing be used?</param>
        /// <returns></returns>
        public static double GetExcelDate(int year, int month, int day, int hour, int minute, int second, bool use1904windowing)
        {
            if ((!use1904windowing && year < 1900)  //1900 date system must bigger than 1900
                || (use1904windowing && year < 1904))   //1904 date system must bigger than 1904
            {
                return BAD_DATE;
            }
 
            DateTime startdate;
            if (use1904windowing)
            {
                startdate = new DateTime(1904, 1, 1);
            }
            else
            {
                startdate = new DateTime(1900, 1, 1);
            }
            int nextyearmonth = 0;
            if (month > 12)
            {
                nextyearmonth = month - 12;
                month = 12;
            }
            int nextmonthday = 0;
 
            if ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12))
            {
                //big month
                if (day > 31)
                {
                    nextmonthday = day - 31;
                    day = 31;
                }
            }
            else if ((month == 4 || month == 6 || month == 9 || month == 11))
            {
                //small month
                if (day > 30)
                {
                    nextmonthday = day - 30;
                    day = 30;
                }
            }
            else if (DateTime.IsLeapYear(year))
            {
                //Feb. with leap year
                if (day > 29)
                {
                    nextmonthday = day - 29;
                    day = 29;
                }
            }
            else
            {
                //Feb without leap year
                if (day > 28)
                {
                    nextmonthday = day - 28;
                    day = 28;
                }
            }
 
            if(day<=0)
            {
                nextmonthday = day - 1;
                day = 1;
            }
            
            DateTime date = new DateTime(year, month, day, hour, minute, second);
            date = date.AddMonths(nextyearmonth);
            date = date.AddDays(nextmonthday);
            double value = (date - startdate).TotalDays + 1;
 
            if (!use1904windowing && value >= 60)
            {
                value++;
            }
            else if (use1904windowing)
            {
                value--;
            }
            return value;
        }
        /// <summary>
        /// Given a Date, Converts it into a double representing its internal Excel representation,
        /// which Is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds.
        /// </summary>
        /// <param name="date">The date.</param>
        /// <param name="use1904windowing">Should 1900 or 1904 date windowing be used?</param>
        /// <returns>Excel representation of Date (-1 if error - test for error by Checking for less than 0.1)</returns>
        public static double GetExcelDate(DateTime date, bool use1904windowing)
        {
            if ((!use1904windowing && date.Year < 1900)  //1900 date system must bigger than 1900
                || (use1904windowing && date.Year < 1904))   //1904 date system must bigger than 1904
            {
                return BAD_DATE;
            }
 
            DateTime startdate;
            if (use1904windowing)
            {
                startdate = new DateTime(1904, 1, 1);
            }
            else
            {
                startdate = new DateTime(1900, 1, 1);
            }
 
            double value = (date - startdate).TotalDays + 1;
 
            if (!use1904windowing && value >= 60)
            {
                value++;
            }
            else if (use1904windowing)
            {
                value--;
            }
            return value;
        }
 
        /// <summary>
        /// Given an Excel date with using 1900 date windowing, and
        /// Converts it to a Date.
        /// </summary>
        /// <param name="date">The Excel date.</param>
        /// <returns>Java representation of the date, or null if date Is not a valid Excel date</returns>
        /// <remarks>
        /// NOTE: If the default 
        /// <c>TimeZone</c>
        ///  in Java uses Daylight
        /// Saving Time then the conversion back to an Excel date may not give
        /// the same value, that Is the comparison
        /// <CODE>excelDate == GetExcelDate(GetJavaDate(excelDate,false))</CODE>
        /// Is not always true. For example if default timezone Is
        /// <c>Europe/Copenhagen</c>
        /// , on 2004-03-28 the minute after
        /// 01:59 CET Is 03:00 CEST, if the excel date represents a time between
        /// 02:00 and 03:00 then it Is Converted to past 03:00 summer time
        /// </remarks>        
        public static DateTime GetJavaDate(double date)
        {
            return GetJavaDate(date, false);
        }
        /**
         *  Given an Excel date with either 1900 or 1904 date windowing,
         *  Converts it to a Date.
         *
         *  NOTE: If the default <c>TimeZone</c> in Java uses Daylight
         *  Saving Time then the conversion back to an Excel date may not give
         *  the same value, that Is the comparison
         *  <CODE>excelDate == GetExcelDate(GetJavaDate(excelDate,false))</CODE>
         *  Is not always true. For example if default timezone Is
         *  <c>Europe/Copenhagen</c>, on 2004-03-28 the minute after
         *  01:59 CET Is 03:00 CEST, if the excel date represents a time between
         *  02:00 and 03:00 then it Is Converted to past 03:00 summer time
         *
         *  @param date  The Excel date.
         *  @param use1904windowing  true if date uses 1904 windowing,
         *   or false if using 1900 date windowing.
         *  @return Java representation of the date, or null if date Is not a valid Excel date
         *  @see TimeZone
         */
        public static DateTime GetJavaDate(double date, bool use1904windowing)
        {
            if (!IsValidExcelDate(date))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Invalid Excel date double value: {0}", date));
            }
            int startYear = 1900;
            int dayAdjust = -1; // Excel thinks 2/29/1900 Is a valid date, which it Isn't
            int wholeDays = (int)Math.Floor(date);
            if (use1904windowing)
            {
                startYear = 1904;
                dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day
            }
            else if (wholeDays < 61)
            {
                // Date Is prior to 3/1/1900, so adjust because Excel thinks 2/29/1900 exists
                // If Excel date == 2/29/1900, will become 3/1/1900 in Java representation
                dayAdjust = 0;
            }
            DateTime startdate = new DateTime(startYear, 1, 1);
            startdate = startdate.AddDays(wholeDays + dayAdjust - 1);
            double millisecondsInDay = (int)((date - wholeDays) *
                                          DAY_MILLISECONDS + 0.5);
            return startdate.AddMilliseconds(millisecondsInDay);
        }
        /// <summary>
        /// Converts a string of format "HH:MM" or "HH:MM:SS" to its (Excel) numeric equivalent
        /// </summary>
        /// <param name="timeStr">The time STR.</param>
        /// <returns> a double between 0 and 1 representing the fraction of the day</returns>
        public static double ConvertTime(String timeStr)
        {
            try
            {
                return ConvertTimeInternal(timeStr);
            }
            catch (FormatException e)
            {
                String msg = "Bad time format '" + timeStr
                    + "' expected 'HH:MM' or 'HH:MM:SS' - " + e.Message;
                throw new ArgumentException(msg);
            }
        }
        /// <summary>
        /// Converts the time internal.
        /// </summary>
        /// <param name="timeStr">The time STR.</param>
        /// <returns></returns>
        private static double ConvertTimeInternal(String timeStr)
        {
            int len = timeStr.Length;
            if (len < 4 || len > 8)
            {
                throw new FormatException("Bad length");
            }
            String[] parts = timeStr.Split(TIME_SEPARATOR_PATTERN);
 
            String secStr;
            switch (parts.Length)
            {
                case 2: secStr = "00"; break;
                case 3: secStr = parts[2]; break;
                default:
                    throw new FormatException("Expected 2 or 3 fields but got (" + parts.Length + ")");
            }
            String hourStr = parts[0];
            String minStr = parts[1];
            int hours = ParseInt(hourStr, "hour", HOURS_PER_DAY);
            int minutes = ParseInt(minStr, "minute", MINUTES_PER_HOUR);
            int seconds = ParseInt(secStr, "second", SECONDS_PER_MINUTE);
 
            double totalSeconds = seconds + (minutes + (hours) * 60) * 60;
            return totalSeconds / (SECONDS_PER_DAY);
        }
        /// <summary>
        /// Given a format ID and its format String, will Check to see if the
        /// format represents a date format or not.
        /// Firstly, it will Check to see if the format ID corresponds to an
        /// internal excel date format (eg most US date formats)
        /// If not, it will Check to see if the format string only Contains
        /// date formatting Chars (ymd-/), which covers most
        /// non US date formats.
        /// </summary>
        /// <param name="formatIndex">The index of the format, eg from ExtendedFormatRecord.GetFormatIndex</param>
        /// <param name="formatString">The format string, eg from FormatRecord.GetFormatString</param>
        /// <returns>
        ///     <c>true</c> if [is A date format] [the specified format index]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsADateFormat(int formatIndex, String formatString)
        {
            // First up, Is this an internal date format?
            if (IsInternalDateFormat(formatIndex))
            {
                return true;
            }
 
            // If we didn't Get a real string, it can't be
            if (formatString == null || formatString.Length == 0)
            {
                return false;
            }
 
            String fs = formatString;
 
            // Translate \- into just -, before matching
            fs = Regex.Replace(fs, "\\\\-", "-");
            // And \, into ,
            fs = Regex.Replace(fs, "\\\\,", ",");
            // And \. into .
            fs = Regex.Replace(fs,"\\\\.", ".");
            // And '\ ' into ' '
            fs = Regex.Replace(fs, "\\\\ ", " ");
 
            // If it end in ;@, that's some crazy dd/mm vs mm/dd
            //  switching stuff, which we can ignore
            fs = Regex.Replace(fs, ";@", "");
            StringBuilder sb = new StringBuilder(fs.Length);
            for (int i = 0; i < fs.Length; i++)
            {
                char c = fs[i];
                if (i < fs.Length - 1)
                {
                    char nc = fs[i + 1];
                    if (c == '\\')
                    {
                        switch (nc)
                        {
                            case '-':
                            case ',':
                            case '.':
                            case ' ':
                            case '\\':
                                // skip current '\' and continue to the next char
                                continue;
                        }
                    }
                    else if (c == ';' && nc == '@')
                    {
                        i++;
                        // skip ";@" duplets
                        continue;
                    }
                }
                sb.Append(c);
            }
            fs = sb.ToString();
 
 
            // short-circuit if it indicates elapsed time: [h], [m] or [s]
            if (Regex.IsMatch(fs, "^\\[([hH]+|[mM]+|[sS]+)\\]"))
            {
                return true;
            }
 
            // If it starts with [$-...], then could be a date, but
            //  who knows what that starting bit Is all about
            fs = Regex.Replace(fs, "^\\[\\$\\-.*?\\]", "");
 
            // If it starts with something like [Black] or [Yellow],
            //  then it could be a date
            fs = Regex.Replace(fs, "^\\[[a-zA-Z]+\\]", "");
            // You're allowed something like dd/mm/yy;[red]dd/mm/yy
            //  which would place dates before 1900/1904 in red
            // For now, only consider the first one
            if (fs.IndexOf(';') > 0 && fs.IndexOf(';') < fs.Length - 1)
            {
                fs = fs.Substring(0, fs.IndexOf(';'));
            }
            // Otherwise, Check it's only made up, in any case, of:
            //  y m d h s - / , . :
            // optionally followed by AM/PM
            if (Regex.IsMatch(fs, @"^[\[\]yYmMdDhHsS\-/,. :\""\\]+0*[ampAMP/]*$"))
            {
                return true;
            }
 
            return false;
        }
        /// <summary>
        /// Converts a string of format "YYYY/MM/DD" to its (Excel) numeric equivalent
        /// </summary>
        /// <param name="dateStr">The date STR.</param>
        /// <returns>a double representing the (integer) number of days since the start of the Excel epoch</returns>
        public static DateTime ParseYYYYMMDDDate(String dateStr)
        {
            try
            {
                return ParseYYYYMMDDDateInternal(dateStr);
            }
            catch (FormatException e)
            {
                String msg = "Bad time format " + dateStr
                    + " expected 'YYYY/MM/DD' - " + e.Message;
                throw new ArgumentException(msg);
            }
        }
        /// <summary>
        /// Parses the YYYYMMDD date internal.
        /// </summary>
        /// <param name="timeStr">The time string.</param>
        /// <returns></returns>
        private static DateTime ParseYYYYMMDDDateInternal(String timeStr)
        {
            if (timeStr.Length != 10)
            {
                throw new FormatException("Bad length");
            }
 
            String yearStr = timeStr.Substring(0, 4);
            String monthStr = timeStr.Substring(5, 2);
            String dayStr = timeStr.Substring(8, 2);
            int year = ParseInt(yearStr, "year", short.MinValue, short.MaxValue);
            int month = ParseInt(monthStr, "month", 1, 12);
            int day = ParseInt(dayStr, "day", 1, 31);
 
            DateTime cal = new DateTime(year, month, day, 0, 0, 0);
            return cal;
        }
        /// <summary>
        /// Parses the int.
        /// </summary>
        /// <param name="strVal">The string value.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="rangeMax">The range max.</param>
        /// <returns></returns>
        private static int ParseInt(String strVal, String fieldName, int rangeMax)
        {
            return ParseInt(strVal, fieldName, 0, rangeMax - 1);
        }
        /// <summary>
        /// Parses the int.
        /// </summary>
        /// <param name="strVal">The STR val.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="lowerLimit">The lower limit.</param>
        /// <param name="upperLimit">The upper limit.</param>
        /// <returns></returns>
        private static int ParseInt(String strVal, String fieldName, int lowerLimit, int upperLimit)
        {
            int result;
            try
            {
                result = int.Parse(strVal, CultureInfo.InvariantCulture);
            }
            catch (FormatException)
            {
                throw new FormatException("Bad int format '" + strVal + "' for " + fieldName + " field");
            }
            if (result < lowerLimit || result > upperLimit)
            {
                throw new FormatException(fieldName + " value (" + result
                        + ") is outside the allowable range(0.." + upperLimit + ")");
            }
            return result;
        }
        /// <summary>
        /// Given a format ID this will Check whether the format represents an internal excel date format or not.
        /// </summary>
        /// <param name="format">The format.</param>
        public static bool IsInternalDateFormat(int format)
        {
            bool retval = false;
 
            switch (format)
            {
                // Internal Date Formats as described on page 427 in
                // Microsoft Excel Dev's Kit...
                case 0x0e:
                case 0x0f:
                case 0x10:
                case 0x11:
                case 0x12:
                case 0x13:
                case 0x14:
                case 0x15:
                case 0x16:
                case 0x2d:
                case 0x2e:
                case 0x2f:
                    retval = true;
                    break;
 
                default:
                    retval = false;
                    break;
            }
            return retval;
        }
 
        /// <summary>
        /// Check if a cell Contains a date
        /// Since dates are stored internally in Excel as double values
        /// we infer it Is a date if it Is formatted as such.
        /// </summary>
        /// <param name="cell">The cell.</param>
        public static bool IsCellDateFormatted(ICell cell)
        {
            if (cell == null) return false;
            bool bDate = false;
 
            double d = cell.NumericCellValue;
            if (DateUtil.IsValidExcelDate(d))
            {
                ICellStyle style = cell.CellStyle;
                if (style == null)
                    return false;
                int i = style.DataFormat;
                String f = style.GetDataFormatString();
                bDate = IsADateFormat(i, f);
            }
            return bDate;
        }
        /// <summary>
        /// Check if a cell contains a date, Checking only for internal excel date formats.
        /// As Excel stores a great many of its dates in "non-internal" date formats, you will not normally want to use this method.
        /// </summary>
        /// <param name="cell">The cell.</param>
        public static bool IsCellInternalDateFormatted(ICell cell)
        {
            if (cell == null) return false;
            bool bDate = false;
 
            double d = cell.NumericCellValue;
            if (DateUtil.IsValidExcelDate(d))
            {
                ICellStyle style = cell.CellStyle;
                int i = style.DataFormat;
                bDate = IsInternalDateFormat(i);
            }
            return bDate;
        }
 
 
        /// <summary>
        /// Given a double, Checks if it Is a valid Excel date.
        /// </summary>
        /// <param name="value">the double value.</param>
        /// <returns>
        ///     <c>true</c> if [is valid excel date] [the specified value]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsValidExcelDate(double value)
        {
            //return true;
            return value > -Double.Epsilon;
        }
 
    }
}