Reputation: 1552
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.
Upvotes: 2
Views: 2394
Reputation: 1500215
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)
Upvotes: 6
Reputation: 1552
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;
}
Upvotes: 1