a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
415 lines
11 KiB
C#
415 lines
11 KiB
C#
/*
|
|
* System.DateTimeUtils
|
|
*
|
|
* Copyright (C) 2007 Novell, Inc (http://www.novell.com)
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace System {
|
|
internal static class DateTimeUtils {
|
|
public static int CountRepeat (string fmt, int p, char c)
|
|
{
|
|
int l = fmt.Length;
|
|
int i = p + 1;
|
|
while ((i < l) && (fmt [i] == c))
|
|
i++;
|
|
|
|
return i - p;
|
|
}
|
|
|
|
public static unsafe void ZeroPad (StringBuilder output, int digits, int len)
|
|
{
|
|
// more than enough for an int
|
|
char* buffer = stackalloc char [16];
|
|
int pos = 16;
|
|
|
|
do {
|
|
buffer [-- pos] = (char) ('0' + digits % 10);
|
|
digits /= 10;
|
|
len --;
|
|
} while (digits > 0);
|
|
|
|
while (len -- > 0)
|
|
buffer [-- pos] = '0';
|
|
|
|
output.Append (new string (buffer, pos, 16 - pos));
|
|
}
|
|
|
|
static int ParseQuotedString (string fmt, int pos, StringBuilder output)
|
|
{
|
|
// pos == position of " or '
|
|
|
|
int len = fmt.Length;
|
|
int start = pos;
|
|
char quoteChar = fmt [pos++];
|
|
|
|
while (pos < len) {
|
|
char ch = fmt [pos++];
|
|
|
|
if (ch == quoteChar)
|
|
return pos - start;
|
|
|
|
if (ch == '\\') {
|
|
// C-Style escape
|
|
if (pos >= len)
|
|
throw new FormatException("Un-ended quote");
|
|
|
|
if (output != null)
|
|
output.Append (fmt [pos++]);
|
|
} else {
|
|
if (output != null)
|
|
output.Append (ch);
|
|
}
|
|
}
|
|
|
|
throw new FormatException("Un-ended quote");
|
|
}
|
|
|
|
public static string GetStandardPattern (char format, DateTimeFormatInfo dfi, out bool useutc, out bool use_invariant)
|
|
{
|
|
return GetStandardPattern (format, dfi, out useutc, out use_invariant, false);
|
|
}
|
|
|
|
public static string GetStandardPattern (char format, DateTimeFormatInfo dfi, out bool useutc, out bool use_invariant, bool date_time_offset)
|
|
{
|
|
String pattern;
|
|
|
|
useutc = false;
|
|
use_invariant = false;
|
|
|
|
switch (format)
|
|
{
|
|
case 'd':
|
|
pattern = dfi.ShortDatePattern;
|
|
break;
|
|
case 'D':
|
|
pattern = dfi.LongDatePattern;
|
|
break;
|
|
case 'f':
|
|
pattern = dfi.LongDatePattern + " " + dfi.ShortTimePattern;
|
|
break;
|
|
case 'F':
|
|
pattern = dfi.FullDateTimePattern;
|
|
break;
|
|
case 'g':
|
|
pattern = dfi.ShortDatePattern + " " + dfi.ShortTimePattern;
|
|
break;
|
|
case 'G':
|
|
pattern = dfi.ShortDatePattern + " " + dfi.LongTimePattern;
|
|
break;
|
|
case 'm':
|
|
case 'M':
|
|
pattern = dfi.MonthDayPattern;
|
|
break;
|
|
case 'o':
|
|
case 'O':
|
|
pattern = dfi.RoundtripPattern;
|
|
use_invariant = true;
|
|
break;
|
|
case 'r':
|
|
case 'R':
|
|
pattern = dfi.RFC1123Pattern;
|
|
if (date_time_offset)
|
|
useutc = true;
|
|
use_invariant = true;
|
|
break;
|
|
case 's':
|
|
pattern = dfi.SortableDateTimePattern;
|
|
use_invariant = true;
|
|
break;
|
|
case 't':
|
|
pattern = dfi.ShortTimePattern;
|
|
break;
|
|
case 'T':
|
|
pattern = dfi.LongTimePattern;
|
|
break;
|
|
case 'u':
|
|
pattern = dfi.UniversalSortableDateTimePattern;
|
|
if (date_time_offset)
|
|
useutc = true;
|
|
use_invariant = true;
|
|
break;
|
|
case 'U':
|
|
if (date_time_offset)
|
|
pattern = null;
|
|
else {
|
|
// pattern = dfi.LongDatePattern + " " + dfi.LongTimePattern;
|
|
pattern = dfi.FullDateTimePattern;
|
|
useutc = true;
|
|
}
|
|
break;
|
|
case 'y':
|
|
case 'Y':
|
|
pattern = dfi.YearMonthPattern;
|
|
break;
|
|
default:
|
|
pattern = null;
|
|
break;
|
|
// throw new FormatException (String.Format ("Invalid format pattern: '{0}'", format));
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
public static string ToString (DateTime dt, string format, DateTimeFormatInfo dfi)
|
|
{
|
|
return ToString (dt, null, format, dfi);
|
|
}
|
|
|
|
public static string ToString (DateTime dt, TimeSpan? utc_offset, string format, DateTimeFormatInfo dfi)
|
|
{
|
|
// the length of the format is usually a good guess of the number
|
|
// of chars in the result. Might save us a few bytes sometimes
|
|
// Add + 10 for cases like mmmm dddd
|
|
StringBuilder result = new StringBuilder (format.Length + 10);
|
|
|
|
int i = 0;
|
|
bool saw_day_specifier = false;
|
|
|
|
while (i < format.Length) {
|
|
int tokLen;
|
|
bool omitZeros = false;
|
|
char ch = format [i];
|
|
|
|
switch (ch) {
|
|
|
|
//
|
|
// Time Formats
|
|
//
|
|
case 'h':
|
|
// hour, [1, 12]
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
|
|
int hr = dt.Hour % 12;
|
|
if (hr == 0)
|
|
hr = 12;
|
|
|
|
DateTimeUtils.ZeroPad (result, hr, tokLen == 1 ? 1 : 2);
|
|
break;
|
|
case 'H':
|
|
// hour, [0, 23]
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
DateTimeUtils.ZeroPad (result, dt.Hour, tokLen == 1 ? 1 : 2);
|
|
break;
|
|
case 'm':
|
|
// minute, [0, 59]
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
DateTimeUtils.ZeroPad (result, dt.Minute, tokLen == 1 ? 1 : 2);
|
|
break;
|
|
case 's':
|
|
// second [0, 29]
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
DateTimeUtils.ZeroPad (result, dt.Second, tokLen == 1 ? 1 : 2);
|
|
break;
|
|
case 'F':
|
|
omitZeros = true;
|
|
goto case 'f';
|
|
case 'f':
|
|
// fraction of second, to same number of
|
|
// digits as there are f's
|
|
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
if (tokLen > 7)
|
|
throw new FormatException ("Invalid Format String");
|
|
|
|
int dec = (int)((long)(dt.Ticks % TimeSpan.TicksPerSecond) / (long) Math.Pow (10, 7 - tokLen));
|
|
int startLen = result.Length;
|
|
DateTimeUtils.ZeroPad (result, dec, tokLen);
|
|
|
|
if (omitZeros) {
|
|
while (result.Length > startLen && result [result.Length - 1] == '0')
|
|
result.Length--;
|
|
// when the value was 0, then trim even preceding '.' (!) It is fixed character.
|
|
if (dec == 0 && startLen > 0 && result [startLen - 1] == '.')
|
|
result.Length--;
|
|
}
|
|
|
|
break;
|
|
case 't':
|
|
// AM/PM. t == first char, tt+ == full
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
string desig = dt.Hour < 12 ? dfi.AMDesignator : dfi.PMDesignator;
|
|
|
|
if (tokLen == 1) {
|
|
if (desig.Length >= 1)
|
|
result.Append (desig [0]);
|
|
}
|
|
else
|
|
result.Append (desig);
|
|
|
|
break;
|
|
case 'z':
|
|
// timezone. t = +/-h; tt = +/-hh; ttt+=+/-hh:mm
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
TimeSpan offset =
|
|
utc_offset ??
|
|
TimeZone.CurrentTimeZone.GetUtcOffset (dt);
|
|
|
|
if (offset.Ticks >= 0)
|
|
result.Append ('+');
|
|
else
|
|
result.Append ('-');
|
|
|
|
switch (tokLen) {
|
|
case 1:
|
|
result.Append (Math.Abs (offset.Hours));
|
|
break;
|
|
case 2:
|
|
result.Append (Math.Abs (offset.Hours).ToString ("00"));
|
|
break;
|
|
default:
|
|
result.Append (Math.Abs (offset.Hours).ToString ("00"));
|
|
result.Append (':');
|
|
result.Append (Math.Abs (offset.Minutes).ToString ("00"));
|
|
break;
|
|
}
|
|
break;
|
|
case 'K': // 'Z' (UTC) or zzz (Local)
|
|
tokLen = 1;
|
|
|
|
if (utc_offset != null || dt.Kind == DateTimeKind.Local) {
|
|
offset = utc_offset ?? TimeZone.CurrentTimeZone.GetUtcOffset (dt);
|
|
if (offset.Ticks >= 0)
|
|
result.Append ('+');
|
|
else
|
|
result.Append ('-');
|
|
result.Append (Math.Abs (offset.Hours).ToString ("00"));
|
|
result.Append (':');
|
|
result.Append (Math.Abs (offset.Minutes).ToString ("00"));
|
|
} else if (dt.Kind == DateTimeKind.Utc)
|
|
result.Append ('Z');
|
|
break;
|
|
//
|
|
// Date tokens
|
|
//
|
|
case 'd':
|
|
// day. d(d?) = day of month (leading 0 if two d's)
|
|
// ddd = three leter day of week
|
|
// dddd+ full day-of-week
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
|
|
if (tokLen <= 2)
|
|
DateTimeUtils.ZeroPad (result, dfi.Calendar.GetDayOfMonth (dt), tokLen == 1 ? 1 : 2);
|
|
else if (tokLen == 3)
|
|
result.Append (dfi.GetAbbreviatedDayName (dfi.Calendar.GetDayOfWeek (dt)));
|
|
else
|
|
result.Append (dfi.GetDayName (dfi.Calendar.GetDayOfWeek (dt)));
|
|
|
|
saw_day_specifier = true;
|
|
break;
|
|
case 'M':
|
|
// Month.m(m?) = month # (with leading 0 if two mm)
|
|
// mmm = 3 letter name
|
|
// mmmm+ = full name
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
int month = dfi.Calendar.GetMonth(dt);
|
|
if (tokLen <= 2)
|
|
DateTimeUtils.ZeroPad (result, month, tokLen);
|
|
else if (tokLen == 3)
|
|
result.Append (dfi.GetAbbreviatedMonthName (month));
|
|
else {
|
|
// Handles MMMM dd format
|
|
if (!saw_day_specifier) {
|
|
for (int ii = i + 1; ii < format.Length; ++ii) {
|
|
ch = format [ii];
|
|
if (ch == 'd') {
|
|
saw_day_specifier = true;
|
|
break;
|
|
}
|
|
|
|
if (ch == '\'' || ch == '"') {
|
|
ii += ParseQuotedString (format, ii, null) - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: .NET ignores quoted 'd' and reads it as day specifier but I think
|
|
// that's wrong
|
|
result.Append (saw_day_specifier ? dfi.GetMonthGenitiveName (month) : dfi.GetMonthName (month));
|
|
}
|
|
|
|
break;
|
|
case 'y':
|
|
// Year. y(y?) = two digit year, with leading 0 if yy
|
|
// yyy+ full year with leading zeros if needed.
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
|
|
if (tokLen <= 2)
|
|
DateTimeUtils.ZeroPad (result, dfi.Calendar.GetYear (dt) % 100, tokLen);
|
|
else
|
|
DateTimeUtils.ZeroPad (result, dfi.Calendar.GetYear (dt), tokLen);
|
|
break;
|
|
|
|
case 'g':
|
|
// Era name
|
|
tokLen = DateTimeUtils.CountRepeat (format, i, ch);
|
|
result.Append (dfi.GetEraName (dfi.Calendar.GetEra (dt)));
|
|
break;
|
|
|
|
//
|
|
// Other
|
|
//
|
|
case ':':
|
|
result.Append (dfi.TimeSeparator);
|
|
tokLen = 1;
|
|
break;
|
|
case '/':
|
|
result.Append (dfi.DateSeparator);
|
|
tokLen = 1;
|
|
break;
|
|
case '\'': case '"':
|
|
tokLen = ParseQuotedString (format, i, result);
|
|
break;
|
|
case '%':
|
|
if (i >= format.Length - 1)
|
|
throw new FormatException ("% at end of date time string");
|
|
if (format [i + 1] == '%')
|
|
throw new FormatException ("%% in date string");
|
|
|
|
// Look for the next char
|
|
tokLen = 1;
|
|
break;
|
|
case '\\':
|
|
// C-Style escape
|
|
if (i >= format.Length - 1)
|
|
throw new FormatException ("\\ at end of date time string");
|
|
|
|
result.Append (format [i + 1]);
|
|
tokLen = 2;
|
|
|
|
break;
|
|
default:
|
|
// catch all
|
|
result.Append (ch);
|
|
tokLen = 1;
|
|
break;
|
|
}
|
|
i += tokLen;
|
|
}
|
|
return result.ToString ();
|
|
}
|
|
|
|
}
|
|
}
|