Convert a posix style timezone to timezoneinfo in

2020-04-11 02:34发布

I get timezone information from another machine in the format :

"CET-1CEST,M3.5.0/2,M10.5.0/3"

(Posix style timezones)

I need to parse this and convert this into a c# .net TimeZoneInfo class.

Is there a way to achieve this ?

3条回答
三岁会撩人
2楼-- · 2020-04-11 03:16

I would parse it according to its format: http://www.ibm.com/developerworks/aix/library/au-aix-posix/

Maybe you could also consider: http://nodatime.org/ - I currently don't know if they support this.

查看更多
一夜七次
3楼-- · 2020-04-11 03:24

According to this article: http://www.ibm.com/developerworks/aix/library/au-aix-posix/ a POSIX time like "CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00" has the following specifications:

  • CST6CDT is the name of the time zone
  • CST is the abbreviation used when DST is off
  • 6 hours is the time difference from GMT
  • CDT is the abbreviation used when DST is on
  • ,M3 is the third month
  • .2 is the second occurrence of the day in the month
  • .0 is Sunday
  • /2:00:00 is the time
  • ,M11 is the eleventh month
  • .1 is the first occurrence of the day in the month
  • .0 is Sunday
  • /2:00:00 is the time

and the date is in the Mm.n.d format, where:

  • Mm (1-12) for 12 months
  • n (1-5) 1 for the first week and 5 for the last week in the month
  • d (0-6) 0 for Sunday and 6 for Saturday

Well, based on these information and the adjustment rules that could be found in TimeZoneInfo class, you can use this code to do the conversion:

public static TimeZoneInfo ConvertPosixToTimeZoneInfo(string posix)
    {
        string[] tokens = posix.Split(',');
        tokens[0] = tokens[0].Replace("/", ".");
        var match = Regex.Match(tokens[0], @"[-+]?[0-9]*\.?[0-9]+").Value;
        var ticks = (long)(decimal.Parse(match, CultureInfo.InvariantCulture) * 60) * 600000000;
        var baseOffset = new TimeSpan(ticks);

        var systemTimeZones = TimeZoneInfo.GetSystemTimeZones().Where(t => t.BaseUtcOffset == baseOffset).ToList();

        var startRuleTokens = tokens[1].TrimStart('M').Split('/');
        var startDateRuleTokens = startRuleTokens[0].Split('.');
        var startTimeRuleTokens = startRuleTokens[1].Split(':');

        var endRuleTokens = tokens[2].TrimStart('M').Split('/');
        var endDateRuleTokens = endRuleTokens[0].Split('.');
        var endTimeRuleTokens = endRuleTokens[1].Split(':');

        int? targetIndex = null;
        for (int i = 0; i < systemTimeZones.Count; i++)
        {
            var adjustmentRules = systemTimeZones[i].GetAdjustmentRules();
            foreach (var rule in adjustmentRules)
            {
                if (rule.DaylightTransitionStart.Month == int.Parse(startDateRuleTokens[0]) &&
                    rule.DaylightTransitionStart.Week == int.Parse(startDateRuleTokens[1]) &&
                    rule.DaylightTransitionStart.DayOfWeek == (DayOfWeek)int.Parse(startDateRuleTokens[2]) &&
                    rule.DaylightTransitionStart.TimeOfDay.Hour == int.Parse(startTimeRuleTokens[0]) &&
                    rule.DaylightTransitionStart.TimeOfDay.Minute == int.Parse(startTimeRuleTokens[1]) &&
                    rule.DaylightTransitionStart.TimeOfDay.Second == int.Parse(startTimeRuleTokens[2]) &&

                    rule.DaylightTransitionEnd.Month == int.Parse(endDateRuleTokens[0]) &&
                    rule.DaylightTransitionEnd.Week == int.Parse(endDateRuleTokens[1]) &&
                    rule.DaylightTransitionEnd.DayOfWeek == (DayOfWeek)int.Parse(endDateRuleTokens[2]) &&
                    rule.DaylightTransitionEnd.TimeOfDay.Hour == int.Parse(endTimeRuleTokens[0]) &&
                    rule.DaylightTransitionEnd.TimeOfDay.Minute == int.Parse(endTimeRuleTokens[1]) &&
                    rule.DaylightTransitionEnd.TimeOfDay.Second == int.Parse(endTimeRuleTokens[2]))
                {
                    targetIndex = i;
                    break;
                }
            }
        }

        if (targetIndex.HasValue)
            return systemTimeZones[targetIndex.Value];
        return null;
    }
查看更多
可以哭但决不认输i
4楼-- · 2020-04-11 03:37

The following code should do the trick.

Do keep in mind that while this will give you a valid TimeZoneInfo object, it does not map the information to the existing Windows time zones. You can use the various conversion functions such as TimeZoneInfo.ConvertTime, but don't expect it to magically know that PST8PDT should align to "Pacific Standard Time".

public static TimeZoneInfo PosixToTzi(string posixTz)
{
    var parts = posixTz.Split(',');
    var zoneparts = Regex.Split(parts[0], @"([0-9\+\-\.]+)");

    double baseOffsetHours;
    if (zoneparts.Length > 1)
    {
        if (!Double.TryParse(zoneparts[1], out baseOffsetHours))
            throw new FormatException();
    }
    else
    {
        // recognize RFC822 time zone abbreviations
        switch (zoneparts[0].ToUpper())
        {
            case "UT":
            case "UTC":
            case "GMT":
                baseOffsetHours = 0;
                break;
            case "EDT":
                baseOffsetHours = 4;
                break;
            case "EST":
            case "CDT":
                baseOffsetHours = 5;
                break;
            case "CST":
            case "MDT":
                baseOffsetHours = 6;
                break;
            case "MST":
            case "PDT":
                baseOffsetHours = 7;
                break;
            case "PST":
                baseOffsetHours = 8;
                break;
            default:
                throw new FormatException();
        }
    }

    double dstOffsetHours = baseOffsetHours - 1;
    if (zoneparts.Length == 4)
    {
        if (!Double.TryParse(zoneparts[3], out dstOffsetHours))
            throw new FormatException();
    }

    var baseOffset = TimeSpan.FromHours(-baseOffsetHours);
    var dstDelta = TimeSpan.FromHours(baseOffsetHours - dstOffsetHours);

    var rules = new List<TimeZoneInfo.AdjustmentRule>();
    if (parts.Length == 3)
    {
        var rule = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(
            DateTime.MinValue.Date, DateTime.MaxValue.Date, dstDelta,
            ParsePosixTransition(parts[1]), ParsePosixTransition(parts[2]));

        rules.Add(rule);
    }

    return TimeZoneInfo.CreateCustomTimeZone(posixTz, baseOffset, parts[0], zoneparts[0],
        zoneparts[zoneparts.Length == 3 ? 2 : 0], rules.ToArray());
}

private static TimeZoneInfo.TransitionTime ParsePosixTransition(string transition)
{
    var parts = transition.Split('/');
    if (parts.Length > 2)
            throw new FormatException();

    double hours = 0;
    if (parts.Length == 2)
    {
        if (!Double.TryParse(parts[1], out hours))
            throw new FormatException();
    }
    var time = DateTime.MinValue.AddHours(hours);

    if (transition.StartsWith("M", StringComparison.OrdinalIgnoreCase))
    {
        var dateParts = parts[0].Substring(1).Split('.');
        if (dateParts.Length > 3)
            throw new FormatException();

        int month;
        if (!Int32.TryParse(dateParts[0], out month))
            throw new FormatException();

        int week;
        if (!Int32.TryParse(dateParts[1], out week))
            throw new FormatException();

        int dow;
        if (!Int32.TryParse(dateParts[2], out dow))
            throw new FormatException();

        return TimeZoneInfo.TransitionTime.CreateFloatingDateRule(time, month, week, (DayOfWeek) dow);
    }

    if (transition.StartsWith("J", StringComparison.OrdinalIgnoreCase))
    {
        int dayNum;
        if (!Int32.TryParse(parts[0].Substring(1), out dayNum))
            throw new FormatException();
        var date = DateTime.MinValue.AddDays(dayNum);

        return TimeZoneInfo.TransitionTime.CreateFixedDateRule(time, date.Month, date.Day);
    }

    throw new FormatException();
}
查看更多
登录 后发表回答