Convert windows timezone to time.js timezone?

We have an application in ASP.NET that stores all user time zone data in Windows format (by TimeZoneInfo.Id).

We also use the time.js and moment.js TimeZone libraries to convert UTC data to user data on the client side. This is a complex AngularJs application that must do client-side timezone processing.

So far, we have used the NodaTime.NET library to convert the Windows time zone identifier to the Moment.js time zone identifier. It worked well for most time zones. But we must make this conversion 100% compatible.

Currently, it turns out that there is no reliable way to map the Windows time zone identifier to IANA time zone data. There are many discrepancies.

I believe that modern JS applications often use time zones. And sometimes you need to convert TZ exactly on the server side (C #) and on the client side (JS).

Is there a way to strictly display / convert .NET TimeZoneInfo into a Moment.js time-domain object?

+7
javascript timezone c # momentjs
source share
2 answers

TL; DR:

  • Continue to use Noda Time on the server side.
  • Choose whether to use BCL or IANA data; I would personally recommend IANA, but this is your call. (Among other things, IANA data is more clearly versioned.)
  • Use Noda Time to generate moment.js data so that you know exactly what the client will use and that it will match what you do on the server.
  • Develop a strategy for what happens when data changes.

More details:

And sometimes you need to convert TZ exactly on the server side (C #) and on the client side (JS).

You need to get exactly the same time zone data on both sides and equivalent implementations on both sides. This has problems because:

  • IANA time zone data is updated regularly (so you would have to say “use data 2015a,” for example)
  • Windows time zone data is regularly updated.
  • I would not want to bet that each implementation of IANA rules is exactly the same, although they should be
  • I know that the implementation of TimeZoneInfo has changed over time, in part to remove some odd errors and in part to include more data . (.NET 4.6 understands the concept of changing the time zone of a standard bias by history; earlier versions do not)

With Noda Time, you can easily convert BCL or IANA time zone data to moment.js format - and do it more reliably than Evgenyt code, because TimeZoneInfo does not allow you to request transitions. (Due to errors in TimeZoneInfo , there are small pockets where offsets can change only a few hours - they should not, but if you want to exactly match TimeZoneInfo , you will need to find all of those - Eugenit’s code will not always mark them.) Noda Time does not exactly reflect TimeZoneInfo ; it must be self-sufficient.

The moment.js format looks pretty simple, so as long as you don't mind sending data to the client, this is definitely an option. You need to think about what to do when the data changes:

  • How do you pick it up on the server?
  • How do you temporarily deal with a client using old data?

If exact consistency is really important to you, you might want to send time zone data to the client with the time zone data version ... which the client can then return to the server when sending the data. (I assume this is true, of course.) The server can either use this version or reject the client’s request and tell them more recent data.

Here is a sample code to convert Noda timezone data at a time. js - this looks good to me, but I haven’t done much with it. It matches the documentation at momentjs.com ... note that the offset must be canceled, as moment.js for some reason decides to use positive offsets for time zones that are behind UTC.

 using System; using System.Linq; using NodaTime; using Newtonsoft.Json; class Test { static void Main(string[] args) { Console.WriteLine(GenerateMomentJsZoneData("Europe/London", 2010, 2020)); } static string GenerateMomentJsZoneData(string tzdbId, int fromYear, int toYear) { var intervals = DateTimeZoneProviders .Tzdb[tzdbId] .GetZoneIntervals(Instant.FromUtc(fromYear, 1, 1, 0, 0), Instant.FromUtc(toYear + 1, 1, 1, 0, 0)) .ToList(); var abbrs = intervals.Select(interval => interval.Name); var untils = intervals.Select(interval => interval.End.Ticks / NodaConstants.TicksPerMillisecond); var offsets = intervals.Select(interval => -interval.WallOffset.Ticks / NodaConstants.TicksPerMinute); var result = new { name = tzdbId, abbrs, untils, offsets }; return JsonConvert.SerializeObject(result); } } 
+6
source share

UPDATE

John suggested that you need to use NodaTime BCL or IANA data in both moments and .NET. Otherwise, you will receive discrepancies. I have to agree with that.

You cannot 100% reliably convert time to .NET 4.5 using TimeZoneInfo. Even if you convert it using NodaTime , as suggested, or TimeZoneToMomentConverter , as shown below.


ORIGINAL RESPONSE

Updating IANA and Windows time data over time and varying degrees of granularity.

So, if you want the exact same conversion in .NET and moment.js - you have either

  • use IANA everywhere (with NodaTime as Matt suggested)
  • use the Windows time zone all over the world (convert TimeZoneInfo rules to moment.js format).

We went the second way and implemented the converter.

It adds a thread-safe cache to be more efficient, as it basically loops through dates (instead of trying to convert the TimeZoneInfo rules themselves). In our tests, it converts the current Windows time zones with 100% accuracy (see GitHub Tests).

This is the tool code:

 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Script.Serialization; namespace Pranas.WindowsTimeZoneToMomentJs { /// <summary> /// Tool to generates JavaScript that adds MomentJs timezone into moment.tz store. /// As per http://momentjs.com/timezone/docs/ /// </summary> public static class TimeZoneToMomentConverter { private static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero); private static readonly JavaScriptSerializer Serializer = new JavaScriptSerializer(); private static readonly ConcurrentDictionary<Tuple<string, int, int, string>, string> Cache = new ConcurrentDictionary<Tuple<string, int, int, string>, string>(); /// <summary> /// Generates JavaScript that adds MomentJs timezone into moment.tz store. /// It caches the result by TimeZoneInfo.Id /// </summary> /// <param name="tz">TimeZone</param> /// <param name="yearFrom">Minimum year</param> /// <param name="yearTo">Maximum year (inclusive)</param> /// <param name="overrideName">Name of the generated MomentJs Zone; TimeZoneInfo.Id by default</param> /// <returns>JavaScript</returns> public static string GenerateAddMomentZoneScript(TimeZoneInfo tz, int yearFrom, int yearTo, string overrideName = null) { var key = new Tuple<string, int, int, string>(tz.Id, yearFrom, yearTo, overrideName); return Cache.GetOrAdd(key, x => { var untils = EnumerateUntils(tz, yearFrom, yearTo).ToArray(); return string.Format( @"(function(){{ var z = new moment.tz.Zone(); z.name = {0}; z.abbrs = {1}; z.untils = {2}; z.offsets = {3}; moment.tz._zones[z.name.toLowerCase().replace(/\//g, '_')] = z; }})();", Serializer.Serialize(overrideName ?? tz.Id), Serializer.Serialize(untils.Select(u => "-")), Serializer.Serialize(untils.Select(u => u.Item1)), Serializer.Serialize(untils.Select(u => u.Item2))); }); } private static IEnumerable<Tuple<long, int>> EnumerateUntils(TimeZoneInfo timeZone, int yearFrom, int yearTo) { // return until-offset pairs int maxStep = (int)TimeSpan.FromDays(7).TotalMinutes; Func<DateTimeOffset, int> offset = t => (int)TimeZoneInfo.ConvertTime(t, timeZone).Offset.TotalMinutes; var t1 = new DateTimeOffset(yearFrom, 1, 1, 0, 0, 0, TimeSpan.Zero); while (t1.Year <= yearTo) { int step = maxStep; var t2 = t1.AddMinutes(step); while (offset(t1) != offset(t2) && step > 1) { step = step / 2; t2 = t1.AddMinutes(step); } if (step == 1 && offset(t1) != offset(t2)) { yield return new Tuple<long, int>((long)(t2 - UnixEpoch).TotalMilliseconds, -offset(t1)); } t1 = t2; } yield return new Tuple<long, int>((long)(t1 - UnixEpoch).TotalMilliseconds, -offset(t1)); } } } 

You can also get it through NuGet:

 PM> Install-Package Pranas.WindowsTimeZoneToMomentJs 

And browser sources for code and tests on GitHub .

+4
source share

All Articles