Noda Time - Create a ZonedDateTime from DateTime and TimeZoneId - c#

Let's say I have the following date, time, and time zone: 2016-10-15, 1:00:00, America/Toronto.
How do I create a ZonedDateTime that represents that exact date and time in the specified zone?
Basically what I need a ZonedDateTime object that represents the exact date and time in the exact time zone.
In case the time is skipped, I would like to add the tick of hour to the new time. Example:
If 00:00 is skipped to 1:00, and I attempt to get the time 00:30 in the zone, I want the result to be 1:30, not only 1:00, which is the first time of the interval.
If 00:00 is skipped to 1:45, and I attempt to get the time 00:20 in the zone, I want the result ot be 2:05.
If a time is ambiguous, i. e., occurs twice, I want the earlir mapping.

What you've described is precisely the behaviour of LocalDateTime.InZoneLeniently in Noda Time 2.0. (Thanks to Matt Johnson's change :) However, as that's still in alpha, here's a solution for 1.3.2. Basically, you just want an appropriate ZoneLocalMappingResolver, which you can build using Resolvers. Here's a complete example.
using NodaTime.TimeZones;
using NodaTime.Text;
class Program
{
static void Main(string[] args)
{
// Paris went forward from UTC+1 to UTC+2
// at 2am local time on March 29th 2015, and back
// from UTC+2 to UTC+1 at 3am local time on October 25th 2015.
var zone = DateTimeZoneProviders.Tzdb["Europe/Paris"];
ResolveLocal(new LocalDateTime(2015, 3, 29, 2, 30, 0), zone);
ResolveLocal(new LocalDateTime(2015, 6, 19, 2, 30, 0), zone);
ResolveLocal(new LocalDateTime(2015, 10, 25, 2, 30, 0), zone);
}
static void ResolveLocal(LocalDateTime input, DateTimeZone zone)
{
// This can be cached in a static field; it's thread-safe.
var resolver = Resolvers.CreateMappingResolver(
Resolvers.ReturnEarlier, ShiftForward);
var result = input.InZone(zone, resolver);
Console.WriteLine("{0} => {1}", input, result);
}
static ZonedDateTime ShiftForward(
LocalDateTime local,
DateTimeZone zone,
ZoneInterval intervalBefore,
ZoneInterval intervalAfter)
{
var instant = new OffsetDateTime(local, intervalBefore.WallOffset)
.WithOffset(intervalAfter.WallOffset)
.ToInstant();
return new ZonedDateTime(instant, zone);
}
}
Output:
29/03/2015 02:30:00 => 2015-03-29T03:30:00 Europe/Paris (+02)
19/06/2015 02:30:00 => 2015-06-19T02:30:00 Europe/Paris (+02)
25/10/2015 02:30:00 => 2015-10-25T02:30:00 Europe/Paris (+02)

Edit
There were problems with the previous solution, suchs as invalid datetimes during DST, etc.
Here's the new solution that accounts for everything, with explanation.
Thanks to #Veeram.
// Transform the "time" in a localized time.
var tzLocalTime = LocalDateTime.FromDateTime(time);
try
{
// To get the exact same time in the specified zone.
zoned = tzLocalTime.InZoneStrictly(zone);
}
catch(SkippedTimeException)
{
// This happens if the time is skipped
// because of daylight saving time.
//
// Example:
// If DST starts at Oct 16 00:00:00,
// then the clock is advanced by 1 hour
// which means Oct 16 00:00:00 is *skipped*
// to Oct 16 01:00:00.
// In this case, it is not possible to convert
// to exact same date, and SkippedTImeException
// is thrown.
// InZoneLeniently will convert the time
// to the start of the zone interval after
// the skipped date.
// For the example above, this would return Oct 16 01:00:00.
// If someone schedules an appointment at a time that
// will not occur, than it is ok to adjust it to what
// will really happen in the real world.
var originalTime = ste.LocalDateTime;
// Correct for the minutes, seconds, and milliseconds.
// This is needed because if someone schedueld an appointment
// as 00:30:00 when 00:00:00 is skipped, we expect the minute information
// to be as expected: 01:30:00, instead of 01:00:00.
var minuteSecondMillisecond = Duration.FromMinutes(originalTime.Minute) + Duration.FromSeconds(originalTime.Second) + Duration.FromMilliseconds(originalTime.Millisecond);
zoned = zLocalTime.InZoneLeniently(zone).Plus(minuteSecondMillisecond);
}
catch(AmbiguousTimeException ate)
{
// This happens when the time is ambiguous.
// During daylight saving time, for example,
// an hour might happen twice.
//
// Example:
// If DST ends on Feb 19 00:00:00, then
// Feb 18 23:00:00 will happen twice:
// once during DST, and once when DST ends
// and the clock is set back.
// In such case, we assume the earlier mapping.
// We could work with the second time that time
// occur with ate.LaterMapping.
zoned = ate.EarlierMapping;
}

Related

C# - Handling ranges of prevailing times on DST transition days - The supplied DateTime represents an invalid time

A couple of premises:
By "prevailing time" I mean how it is handled locally (my industry uses this terminology). For example, Eastern Prevailing Time has a UTC offset of -05:00 except during DST when it is -04:00
I find it much cleaner to handle range data by treating the end value as exclusive, rather than the hackish inclusive approach (where you have to subtract an epsilon from the first value beyond the end of your range).
For example, the range of values from 0 (inclusive) to 1 (exclusive), as per interval notation, is [0, 1), which is much more readable than [0, 0.99999999999...] (and is less prone to rounding issues and thus off-by-one errors, because the epsilon value depends on the data type being used).
With these two ideas in mind, how can I represent the final hour time range on the spring DST transition day, when the ending timestamp is invalid (i.e. there is no 2am, it instantly becomes 3am)?
[2019-03-10 01:00, 2019-03-10 02:00) in your time zone of choice that supports DST.
Putting the end time as 03:00 is quite misleading, as it looks like a 2-hour wide time range.
When I run it through this C# sample code, it blows up:
DateTime hourEnd_tz = new DateTime(2019, 3, 10, 0, 0, 0, DateTimeKind.Unspecified);//midnight on the spring DST transition day
hourEnd_tz = hourEnd_tz.AddHours(2);//other code variably computes this offset from business logic
TimeZoneInfo EPT = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");//includes DST rules
DateTime hourEnd_utc = TimeZoneInfo.ConvertTime(//interpret the value from the user's time zone
hourEnd_tz,
EPT,
TimeZoneInfo.Utc);
System.ArgumentException: 'The supplied DateTime represents an invalid time. For example, when the clock is adjusted forward, any time in the period that is skipped is invalid.
Parameter name: dateTime'
How might I handle this case (elsewhere I am already handling the autumn ambiguous times), without having to extensively refactor my time range class library?
Premise 1 is reasonable, though often the word "prevailing" is dropped and it's just called "Eastern Time" - either are fine.
Premise 2 is a best practice. Half-open ranges offer many benefits, such as not having to deal with date math involving an epsilon, or having to determine what precision the epsilon should have.
However, the range you're attempting to describe cannot be done with a date and time alone. It needs to also involve the offset from UTC. For US Eastern Time (using ISO 8601 format), it looks like this:
[2019-03-10T01:00:00-05:00, 2019-03-10T03:00:00-04:00) (spring-forward)
[2019-11-03T02:00:00-04:00, 2019-11-03T02:00:00-05:00) (fall-back)
You said:
Putting the end time as 03:00 is quite misleading, as it looks like a 2-hour wide time range.
Ah, but putting the spring end time as 02:00 would also be misleading, as that local time is not observed on that day. Only by combining the actual local date and time with the offset at that time can one be accurate.
You can use the DateTimeOffset structure in .NET to model these (or the OffsetDateTime structure in Noda Time).
How might I handle this case ... without having to extensively refactor my time range class library?
First, you'll need an extension method that lets you convert from DateTime to a DateTimeOffset for a specific time zone. You'll need this for two reasons:
The new DateTimeOffset(DateTime) constructor assumes that a DateTime with Kind of DateTimeKind.Unspecified should be treated as local time. There's no opportunity to specify a time zone.
The new DateTimeOffset(dt, TimeZoneInfo.GetUtcOffset(dt)) approach isn't good enough, because GetUtcOffset presumes you want the standard time offset in the case of ambiguity or invalidity. That is usually not the case, and thus you have to code the following yourself:
public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
if (dt.Kind != DateTimeKind.Unspecified)
{
// Handle UTC or Local kinds (regular and hidden 4th kind)
DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
return TimeZoneInfo.ConvertTime(dto, tz);
}
if (tz.IsAmbiguousTime(dt))
{
// Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
return new DateTimeOffset(dt, offset);
}
if (tz.IsInvalidTime(dt))
{
// Advance by the gap, and return with the daylight offset (2:30 ET becomes 3:30 EDT)
TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
TimeSpan gap = offsets[1] - offsets[0];
return new DateTimeOffset(dt.Add(gap), offsets[1]);
}
// Simple case
return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}
Now that you have that defined (and put it in a static class somewhere in your project), you can call it where needed in your application.
For example:
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dt = new DateTime(2019, 3, 10, 2, 0, 0, DateTimeKind.Unspecified);
DateTimeOffset dto = dt.ToDateTimeOffset(tz); // 2019-03-10T03:00:00-04:00
or
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dt = new DateTime(2019, 11, 3, 1, 0, 0, DateTimeKind.Unspecified);
DateTimeOffset dto = dt.ToDateTimeOffset(tz); // 2019-11-03T01:00:00-04:00
or
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dt = new DateTime(2019, 3, 10, 0, 0, 0, DateTimeKind.Unspecified);
DateTimeOffset midnight = dt.ToDateTimeOffset(tz); // 2019-03-10T00:00:00-05:00
DateTimeOffset oneOClock = midnight.AddHours(1); // 2019-03-10T01:00:00-05:00
DateTimeOffset twoOClock = oneOClock.AddHours(1); // 2019-03-10T02:00:00-05:00
DateTimeOffset threeOClock = TimeZoneInfo.ConvertTime(twoOClock, tz); // 2019-03-10T03:00:00-04:00
TimeSpan diff = threeOClock - oneOClock; // 1 hour
Note that subtracting two DateTimeOffset values correctly considers their offsets (whereas subtracting two DateTime values completely ignores their Kind).

C# Get specific Date in ToInt64(milliseconds)

I just cannot figure this out....Damnit! Please see the calling method in the second code snippet. You'll see a commented out line that reads //_time = time.ToUTCString(); Go to the first code snippet to see the method ToUTCString(). You can see that it takes the datetime, converts it to Universal Time and subtracts the UnixEpoch to get the TotalSeconds. Then it converts that value to Int64() and finally to a string. I tried calling the methos ToLocalString but that's changing the date as well.
The date that I pass in is the date that I want to be converted to Int64 and eventually to a string. The datetime I pass in. Not changed.
I don't want to change the date that is passed in. I always pass in the date starting with 12:00:00AM (or 00:00:00) and that is the time I always want. Both of these methods change the date and or time. The date I pass in is 06/01/2017 12:00:00AM but sometimes it gets changed to 05/31/2017 04:00:00 or it keeps the date but the time is wrong. Dark Sky requires the date to be a value of Convert.ToInt64(milliseconds) and then converted to a string.
Does anybody know how to get the exact date and time that is passed in converted to Int64 using milliseconds?
I have the following Extensions class:
public static class Extensions
{
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static DateTime ToDateTime(this Int64 _input)
{
return UnixEpoch.AddSeconds(_input);
}
public static string ToLocalString(this DateTime _input)
{
// _input = {6/1/2017 12:00:00 AM} System.DateTime
var milliseconds = _input.ToLocalTime().Subtract(UnixEpoch).TotalSeconds;
return Convert.ToInt64(milliseconds).ToString();
// I want to get the milliseconds for {6/1/2017 12:00:00 AM}
// I don't want the date or time to change
}
public static string ToUTCString(this DateTime _input)
{
// _input = {6/1/2017 12:00:00 AM} System.DateTime
var milliseconds = _input.ToUniversalTime().Subtract(UnixEpoch).TotalSeconds;
return Convert.ToInt64(milliseconds).ToString();
// I want to get the milliseconds for {6/1/2017 12:00:00 AM}
// I don't want the date or time to change
}
}
This is the calling method:
public ForecastIORequest(string apiKey, float latF, float longF, DateTime time, Unit unit, Language? lang = null, Extend[] extend = null, Exclude[] exclude = null)
{
_apiKey = apiKey;
_latitude = latF.ToString(CultureInfo.InvariantCulture);
_longitude = longF.ToString(CultureInfo.InvariantCulture);
//_time = time.ToUTCString();
_time = time.ToLocalString();
//DateTime t = _time.
_unit = Enum.GetName(typeof(Unit), unit);
_extend = (extend != null) ? RequestHelpers.FormatExtendString(extend) : "";
_exclude = (exclude != null) ? RequestHelpers.FormatExcludeString(exclude) : "";
_lang = (lang != null) ? RequestHelpers.FormatLanguageEnum(lang) : Language.en.ToString();
}
There's a fundamental problem with the way you're handling dates here, and if I boil it down to one thing, I think the problem is that ToUniversalTime() doesn't work the way you think it does.
What ToUniversalTime() does is, simply giving the UTC time of a time that's defined in a different time zone. For example, say my local time is UTC-7. So if I define a DateTime object without specifying DateTimeKind and set the value to, say, 2017/6/1 9:00:00, that means, at that time the actual UTC time is 2017/6/1 16:00:00 at that time, and ToUniversalTime() will give you a DateTime object with that value.
Let me change your ToUTCString() method a little bit and show you the problem with it. It's returning a long value instead of string now, and I break down the first line of code into two.
public static long ToUTC(this DateTime _input)
{
var utcTime = _input.ToUniversalTime();
var totalSeconds = utcTime.Subtract(UnixEpoch).TotalSeconds;
return Convert.ToInt64(totalSeconds);
}
And notice that in your Extensions class, the UnixEpoch object's DateTimeKind is set to UTC. I changed the date to 2017/6/1 8:00:00 for the ease of understanding.
private static readonly DateTime UnixEpoch = new DateTime(2017, 6, 1, 8, 0, 0, DateTimeKind.Utc);
public static DateTime ToDateTime(this Int64 _input)
{
return UnixEpoch.AddSeconds(_input);
}
Now let's call that method with a DateTime object whose DateTimeKind is set to UTC.
// dateObj will have time 2017/6/1 9:00:00 _in UTC_.
var dateObj = new DateTime(2017, 6, 1, 9, 0, 0, DateTimeKind.Utc);
// This method converts to UTC, but it's already in UTC, so no actual conversion takes place.
// Then subtracts UnixEpoch from it, which is also in UTC.
long dateInLong = dateObj.ToUTC();
// The difference is one hour, so dateInLong will be 3600.
Console.WriteLine(dateInLong);
// This method adds the above difference to UnixEpoch, and displays the time.
Console.WriteLine(dateInLong.ToDateTime());
Now, here, everything is in UTC and you should see output as expected, like below:
3600
6/1/2017 09:00:00
All good so far.
Now change things a bit, and let's set our dateObj to local instead of UTC, as you do in your example.
// Notice that the object is in local time now.
var dateObj = new DateTime(2017, 6, 1, 9, 0, 0);
long dateInLong = dateObj.ToUTC();
Console.WriteLine(dateInLong);
Console.WriteLine(dateInLong.ToDateTime());
Now, the above dateObj will have time 9:00:00, but in my local time. My actual location is UTC-7 so note that this means 9AM local time for me is 4PM UTC. But note that we haven't changed the UnixEpoch object, which is still in UTC and time is set to 8AM UTC in it. And therefore, dateInLong will be 28,800 (8 hours x 60 mins x 60 seconds). So when your ToDateTime() method is called, it adds 28,000 seconds to 8AM UTC time, and returns as a DateTime object, of which time now is 4PM UTC.
28800
6/1/2017 16:00:00
And that's why depending on the time you set your dateObj to, your output changes time as you said.
Solution
You need to decide which time zone to use, and stick to that. One option would be to get rid of all the UTC conversions and have all times set in local time.
public static class Extensions
{
// NOT set to UTC
private static readonly DateTime UnixEpoch = new DateTime(2017, 6, 1, 8, 0, 0);
public static DateTime ToDateTime(this Int64 _input)
{
return UnixEpoch.AddSeconds(_input);
}
public static long ToUTC(this DateTime _input)
{
// NOT converted to UTC. So... change variable names accordingly.
var utcTime = _input;
var totalSeconds = utcTime.Subtract(UnixEpoch).TotalSeconds;
return Convert.ToInt64(totalSeconds);
}
}
class Program
{
static void Main(string[] args)
{
// Notice that the object is in local time and NOT UTC.
var dateObj = new DateTime(2017, 6, 1, 9, 0, 0);
long dateInLong = dateObj.ToUTC();
Console.WriteLine(dateInLong);
Console.WriteLine(dateInLong.ToDateTime());
Console.ReadLine();
}
}
The other option, set EVERYTHING to UTC, but then you'll have to make sure that the DateTime object on which you call ToUTC() is defined in UTC and not local.
So:
private static readonly DateTime UnixEpoch = new DateTime(2017, 6, 1, 8, 0, 0, DateTimeKind.Utc);
And
var utcTime = _input.ToUniversalTime();
And finally
var dateObj = new DateTime(2017, 6, 1, 9, 0, 0, DateTimeKind.Utc);
BUT...
I see a bigger problem with you code, looking at the second code snippet. In the ForecastIORequest() constructor, you're saving time as a string. And that's not an ideal solution in my opinion. Because as you found the hard way, depending on what time zone the calling object was created, your time difference will be, well, different. And you'd have no way of knowing which.
I'd rather store the DateTime object as it is, and read it and calculate the difference when needed, taking into account time zones.
Hope this helps.
See DateTime.Ticks - there are 10,000 ticks in a millisecond. Simply take DateTime.Ticks / 10000 (ten thousand) and you have your milliseconds.
Here's a simple extension method to get the milliseconds as a long (that's Int64):
public static long ToMilliseconds(this DateTime dateTime)
{
return dateTime.Ticks / 10000;
}

Comparing two different timezone timespans using NodaTime

I have a requirement which I'm getting a little confused about. I started using NodaTime which I think is the best way to go.
I have two users, User1 and User2 both in two different timezones. They are available to meet between 2pm and 5pm for example, in their local timezones. If User2 has an offset of +2 hours from User1, then the overlap is just 1 hour. What I want to get the number of hours overlap (the actual time for User1 and User2 would be a bonus.)
All I have got so far is:
var user1TimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(user1timezone);
var user2TimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(user2timeZone);
Any thoughts on how I should even start tackling this problem?
Thanks,
Firstly, be aware that it could change each day: don't treat a time zone as a fixed offset.
Secondly, be aware that the local time specified (for each of start/end) may not even happen, or may happen twice. Work out how you want to handle ambiguous and skipped times.
For any particular day, I would just convert they users' start/end times to Instant (via ZonedDateTime) and then you can find the overlap. This does assume that any overlap happens on the same day, however... that isn't the case in reality. I'm having a meeting soon where one of the attendees is in New Zealand - it's March 14th here, but March 15th there. Accounting for that is rather trickier...
Here's code for the relatively simple case though:
using NodaTime;
using System;
class Test
{
static void Main()
{
// My availability: 4pm-7pm in London
var jon = new Availability(
DateTimeZoneProviders.Tzdb["Europe/London"],
new LocalTime(16, 0, 0),
new LocalTime(19, 0, 0));
// My friend Richard's availability: 12pm-4pm in New York
var richard = new Availability(
DateTimeZoneProviders.Tzdb["America/New_York"],
new LocalTime(12, 0, 0),
new LocalTime(16, 0, 0));
// Let's look through all of March 2017...
var startDate = new LocalDate(2017, 3, 1);
var endDate = new LocalDate(2017, 4, 1);
for (LocalDate date = startDate; date < endDate; date = date.PlusDays(1))
{
var overlap = GetAvailableOverlap(date, jon, richard);
Console.WriteLine($"{date:yyyy-MM-dd}: {overlap:HH:mm}");
}
}
static Duration GetAvailableOverlap(
LocalDate date,
Availability avail1,
Availability avail2)
{
// TODO: Check that the rules of InZoneLeniently are what you want.
// Be careful, as you could end up with an end before a start...
var start1 = (date + avail1.Start).InZoneLeniently(avail1.Zone);
var end1 = (date + avail1.End).InZoneLeniently(avail1.Zone);
var start2 = (date + avail2.Start).InZoneLeniently(avail2.Zone);
var end2 = (date + avail2.End).InZoneLeniently(avail2.Zone);
var latestStart = Instant.Max(start1.ToInstant(), start2.ToInstant());
var earliestEnd = Instant.Min(end1.ToInstant(), end2.ToInstant());
// Never return a negative duration... return zero of there's no overlap.
// Noda Time should have Duration.Max really...
var overlap = earliestEnd - latestStart;
return overlap < Duration.Zero ? Duration.Zero : overlap;
}
}
public sealed class Availability
{
public DateTimeZone Zone { get; }
public LocalTime Start { get; }
public LocalTime End { get; }
public Availability(DateTimeZone zone, LocalTime start, LocalTime end)
{
Zone = zone;
Start = start;
End = end;
}
}
If you have a server where you do that, you have to send UTC and then compare it. When you get the time on the client side you have to convert it into local. It means, that when first user wants to arrange a meeting, he sends his time into UTC to server, then when second user gets this time, he will convert it into his local time.
// First user sends UTC.
DateTime firstUserTime = DateTime.UtcNow;
// Second user gets time in his time zone.
DateTime secondUserTime = firstUserTime.ToLocalTime();

Getting Daylight Savings Time Start and End in NodaTime

How can I get the starting and ending dates for Daylight Savings Time using Noda Time?
The function below accomplishes this task but it is horribly unwieldy and is begging for a simpler solution.
/// <summary>
/// Gets the start and end of daylight savings time in a given time zone
/// </summary>
/// <param name="tz">The time zone in question</param>
/// <returns>A tuple indicating the start and end of DST</returns>
/// <remarks>Assumes this zone has daylight savings time</remarks>
private Tuple<LocalDateTime, LocalDateTime> GetZoneStartAndEnd(DateTimeZone tz)
{
int thisYear = TimeUtils.SystemLocalDateTime.Year; // Get the year of the current LocalDateTime
// Get January 1, midnight, of this year and next year.
var yearStart = new LocalDateTime(thisYear, 1, 1, 0, 0).InZoneLeniently(tz).ToInstant();
var yearEnd = new LocalDateTime(thisYear + 1, 1, 1, 0, 0).InZoneLeniently(tz).ToInstant();
// Get the intervals that we experience in this year
var intervals = tz.GetZoneIntervals(yearStart, yearEnd).ToArray();
// Assuming we are in a US-like daylight savings scheme,
// we should see three intervals:
// 1. The interval that January 1st sits in
// 2. At some point, daylight savings will start.
// 3. At some point, daylight savings will stop.
if (intervals.Length == 1)
throw new Exception("This time zone does not use daylight savings time");
if (intervals.Length != 3)
throw new Exception("The daylight savings scheme in this time zone is unexpected.");
return new Tuple<LocalDateTime,LocalDateTime>(intervals[1].IsoLocalStart, intervals[1].IsoLocalEnd);
}
There's not a single built-in function that I am aware of, but the data is all there, so you can certainly create your own.
You're on the right track with what you've shown, but there are a few things to consider:
Normally people are interested in the end points of the intervals. By returning the start and stop of only the middle interval, you are likely getting values different than you expect. For example, if you use one of the US time zones, such as "America/Los_Angeles", your function returns the transitions as 3/9/2014 3:00:00 AM and 11/2/2014 2:00:00 AM, where you are probably expecting 2:00 AM for both.
Time zones south of the equator that use DST will start it towards the end of the year, and end it towards the beginning of the next year. So sometimes the items in the tuple might be reversed from what you expect them to be.
There are quite a lot of time zones that don't use daylight saving time, so throwing an exception isn't the best idea.
There are at least two time zones that presently have four transitions in a single year ("Africa/Casablanca" and "Africa/Cairo") - having a "break" in their DST periods for Ramadan. And occasionally, there are non-DST-related transitions, such as when Samoa changed its standard offset in 2011, which gave it three transitions in a single year.
Taking all of this into account, it would seem better to return a list of single transition points, rather than a tuple of pairs of transitions.
Also, this is minor, but it would be better form to not bind the method to the system clock at all. The year can easily be passed by parameter. Then you can use this method for non-current years if necessary.
public IEnumerable<LocalDateTime> GetDaylightSavingTransitions(DateTimeZone timeZone, int year)
{
var yearStart = new LocalDateTime(year, 1, 1, 0, 0).InZoneLeniently(timeZone).ToInstant();
var yearEnd = new LocalDateTime(year + 1, 1, 1, 0, 0).InZoneLeniently(timeZone).ToInstant();
var intervals = timeZone.GetZoneIntervals(yearStart, yearEnd);
return intervals.Select(x => x.IsoLocalEnd).Where(x => x.Year == year);
}
Also note at the end, it's important to filter just the values that are in the current year because the intervals may very well extend into the following year, or go on indefinitely.
This snippet code also help you to check a time is in daylightsavingstime or not
public static bool IsDaylightSavingsTime(this DateTimeOffset dateTimeOffset)
{
var timezone = "Europe/London"; //https://nodatime.org/TimeZones
ZonedDateTime timeInZone = dateTimeOffset.DateTime.InZone(timezone);
var instant = timeInZone.ToInstant();
var zoneInterval = timeInZone.Zone.GetZoneInterval(instant);
return zoneInterval.Savings != Offset.Zero;
}
how to use it
var testDate = DateTimeOffset.Now;
var isDst = testDate.IsDaylightSavingsTime();
Depend on your situation, you can modify it a bit

Computing milliseconds since 1970 in C# yields different date than JavaScript

I need to compute the JavaScript getTime method in C#.
For simplicity, I chose a fixed date in UTC and compared the C#:
C#
DateTime e = new DateTime(2011, 12, 31, 0, 0, 0, DateTimeKind.Utc);
DateTime s = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
TimeSpan t = (e - s);
var x = t.TotalMilliseconds.ToString();
=> 1325289600000
and the JavaScript results:
JavaScript
var d = new Date(2011, 12, 31, 0, 0, 0)
var utcDate = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds());
utcDate.getTime()
=> 1327960800000
Any hints on what I'm doing wrong?
Thanks!
Javascript months are zero-based.
12 means January of next year.
You want 11.
If you meant for the input to be at UTC, you should be doing this instead:
var ts = Date.UTC(2011,11,31,0,0,0);
As SLaks pointed out, months run 0-11, but even then - you must initialize the date as UTC if you want the response in UTC. In your code, you were initializing a local date, and then converting it to UTC. The result would be different depending on the time zone of the computer where the code is running. With Date.UTC, you get back a timestamp - not a Date object, and it will be the same result regardless of where it runs.
From Chrome's debugging console:
This is the same value returned from your .NET code, which looks just fine, except I would return a long, not a string.
The date JS is wrong I believe. Omit the var utcDate line and output just d.getTime()
The time between two dates is the same, regardless of timezone and offset. Timezones are relative to an ACTUAL point in time, so whether you call .getTime() on the UTC or EST or PST date, it will be the same relative to 1970-1-1 of the same timezone.
2011-12-31 EST - 1970-1-1 EST
== 2011-12-31 PST - 1970-1-1 PST
== 2011-12-31 UTC - 1970-1-1 UTC
EDIT: Per #Slaks above, you also are not using the 0-based month (which I also had no idea about).

Categories

Resources