Skip to content

Instantly share code, notes, and snippets.

@kg
Created May 17, 2018 01:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kg/b93c022f2d48cae0665068a6f55b915c to your computer and use it in GitHub Desktop.
Save kg/b93c022f2d48cae0665068a6f55b915c to your computer and use it in GitHub Desktop.
private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
{
//Reading the header. 4 bytes for magic, 16 are reserved
int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
int leapcnt = ReadBigEndianInt32 (buffer, 28);
int timecnt = ReadBigEndianInt32 (buffer, 32);
int typecnt = ReadBigEndianInt32 (buffer, 36);
int charcnt = ReadBigEndianInt32 (buffer, 40);
if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
throw new InvalidTimeZoneException ();
Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
if (time_types.Count == 0)
throw new InvalidTimeZoneException ();
if (time_types.Count == 1 && time_types[0].IsDst)
throw new InvalidTimeZoneException ();
TimeSpan baseUtcOffset = new TimeSpan (0);
TimeSpan dstDelta = new TimeSpan (0);
string standardDisplayName = null;
string daylightDisplayName = null;
bool dst_observed = false;
DateTime dst_start = DateTime.MinValue;
List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
bool storeTransition = false;
for (int i = 0; i < transitions.Count; i++) {
var pair = transitions [i];
DateTime ttime = pair.Key;
TimeType ttype = pair.Value;
if (!ttype.IsDst) {
if (standardDisplayName != ttype.Name)
standardDisplayName = ttype.Name;
if (baseUtcOffset.TotalSeconds != ttype.Offset) {
baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
storeTransition = true;
adjustmentRules = new List<AdjustmentRule> ();
dst_observed = false;
}
if (dst_observed) {
//FIXME: check additional fields for this:
//most of the transitions are expressed in GMT
dst_start += baseUtcOffset;
DateTime dst_end = ttime + baseUtcOffset + dstDelta;
//some weird timezone (America/Phoenix) have end dates on Jan 1st
if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
dst_end -= new TimeSpan (24, 0, 0);
/*
* AdjustmentRule specifies a DST period that starts and ends within a year.
* When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
* Thus we fallback to the transitions.
*/
if (dst_start.AddYears (1) < dst_end)
storeTransition = true;
DateTime dateStart, dateEnd;
if (dst_start.Month < 7)
dateStart = new DateTime (dst_start.Year, 1, 1);
else
dateStart = new DateTime (dst_start.Year, 7, 1);
if (dst_end.Month >= 7)
dateEnd = new DateTime (dst_end.Year, 12, 31);
else
dateEnd = new DateTime (dst_end.Year, 6, 30);
TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
}
dst_observed = false;
} else {
if (daylightDisplayName != ttype.Name)
daylightDisplayName = ttype.Name;
if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
// Round to nearest minute, since it's not possible to create an adjustment rule
// with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
// This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
}
dst_start = ttime;
dst_observed = true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment