perl DateTime and non-existent time user input because of DST clock forward - perl

In a scenario where we know the timezone of the user and the date but the time is input by the user in a textbox (i.e. a calendar where an user selects the date but types the time), after it is properly parsed and we know the hours and minutes, how should we deal wih a non-existent time because of DST clock forward (eg. 02:00 does not exist because of clocks are turned forward 1 hour) in order to pass it -at least an existent hour- to DateTime->new();?
use DateTime;
$dt = DateTime->new(
year => $year_userinput, #2016
month => $month_userinput, #03
day => $day_userinput, #27
hour => $hour_userinput, #02
minute => $minute_userinput, #30
second => 0,
time_zone => $timezone_userinput, #Europe/Berlin
);
Error: Invalid local time for date in time zone: Europe/Berlin

As you go into Daylight Savings, there's no problem. Assuming DLS starts at 2am on a given day in the fictitious time zone, ATZ (a timezone) with an offset of N hrs, then the three seconds commencing one second before 2am are timestamped;
1:59:59 ATZ +N
3:00:00 ADZ +(N+1)
3:00:01 ADZ +(N+1)
...and when coming out of Daylight savings ...
2:00:00 ADZ +(N+1)
2:00:01 ADZ +(N+1)
...
... about an hour later
...
2:59:59 ADZ +(N+1)
2:00:00 ATZ +N
2:00:01 ATZ
There appears to have been an earlier "2:00:01" but that included an offset of (N+1) - or was in the timezone ADZ - whereas this one is in ATZ (N). The DateTime module raises the issue like this:
Ambiguous Local Times
Because of Daylight Saving Time, it is possible to specify a local time that is ambiguous. For example, in the US in 2003, the transition from to saving to standard time occurred on October 26, at 02:00:00 local time. The local clock changed from 01:59:59 (saving time) to 01:00:00 (standard time). This means that the hour from 01:00:00 through 01:59:59 actually occurs twice, though the UTC time continues to move forward.
To avoid the issue, you are going to have to always include the timezone or offset when creating time objects. To do that, you need to use the date (you said you had it) to detect that it is end-of-DLS day and, if the user has chosen a time within the critical hour, you're going to have to prompt for "is that 2:30am ADZ or 2:30am ATZ?" or something similar. Likewise, if it's start-of-DLS, your interface has to refuse entries referencing the critical hour.
Earlier in the doco for DateTime, there's the suggestion - for performance reasons - of determining the local timezone once and then using it throughout your app;
our $App::LocalTZ = DateTime::TimeZone->new( name => 'local' );
... # then everywhere else
my $dt = DateTime->new( ..., time_zone => $App::LocalTZ );
... but this is going to leave you vulnerable to the issue again. Since your interface is going to have to know its start-of-DLS day or end-of-DLS day, you can set and use $App::LocalTZ as advised and then override with the specific, prompted-for Timezone if it's end-of-DLS day.

Related

How to set up only time in date-fns and still keep the timezone in javascript

I currently have the following use case:
User receives a date in UTC from the backend
This date is transformed into local time for displaying purposes
The date is displayed in different inputs. One input for date and other for time
User can select time independently
The date should be sent back to the backend in UTC format as well
I'm not very experienced with time zones and I'm getting beaten by trying to allow the user to set up only the time (or date) in a datetime field.
My pseudo code is the following:
When receiving the from backend simply convert the date to show it to the user, making the orignal date stay in UTC
When the user picks the hour and minute (all in one action) use setHours and setMinutes from date-fns library
Use native toISOString() to set in models
Code so far [playground]:
import { utcToZonedTime, format } from "date-fns-tz";
import { setHours, setMinutes } from "date-fns";
const UTCStringdate = "2022-04-06T10:00:00.000000Z";
const userTimezone = "Asia/Bangkok";
const localizedTime = utcToZonedTime(UTCStringdate, userTimezone);
// Prints the correct information
// 10:00 in UTC is equal to 17:00 in Bangkok
console.log(format(localizedTime, "HH:mm"));
// Now I expext to set only the `minutes` and `hours`
// to this `localizedTime` so that I can update the backend
const [hours, minutes] = "10:30".split(":");
// Somewhere over here the `setHours` and `setMinutes`
// is turning the Date object into my own timezone
// and not using `Asia/Bangkok` timezone anymore
let newTime = setHours(localizedTime, hours);
newTime = setMinutes(newTime, minutes);
// Now I expect to print 17:30 since we only
// set up 30 minutes forward than the original one
// but it ends up printing 10:30
console.log(format(newTime, 'HH:mm'));
I understand that somewhere along the way (most likely in setHours and setMinutes) the date-fns library turns back the localizedTime back into my own timezone, completely ruining the idea of turning the Asia/Bangkok time into UTC.
Questions
First, is this the best approach to manipulate only the time part of a date when considering timezones? If not, anyone can point me to articles? I wasn't able to find anything on the topic
Second, how can I use setHours and setMinutes and still maintain the timezone?
There are no multiple time zones in JavaScript. There is UTC and there is your local one. What date-fns-tz does, is adjusting the time to the chosen user time zone. In your example, you can see this when printing both the original and the localized time
const utcTime = new Date(UTCStringdate);
console.log(utcTime.toISOString()); // -> 2022-04-06T10:00:00.000Z
console.log(localizedTime.toISOString()); // -> 2022-04-06T14:00:00.000Z
To solve your issue, convert UTC time to users time and let the user to adjust hours and minutes in his local time zone. After that, convert the time object back to UTC using zonedTimeToUtc
newTime = zonedTimeToUtc(newTime, userTimezone);
and then use newTime.toISOString() to send it back to the server.
console.log(newTime.toISOString());

Timezone conversion from MST to target timezone not working in Perl

my $apptStartDateTime = "20210401100000";
my $formatter = DateTime::Format::Strptime->new(pattern => "%Y%m%d%H%M%S", time_zone => "MST");
my $dt_obj = $formatter->parse_datetime($apptStartDateTime);
$dt_obj->strftime("%Y%m%d%H%M%S"), "\n"; #prints 20210401100000
# to convert to a different zone:
$dt_obj->set_time_zone("America/Los_Angeles");
$dt_obj->strftime("%Y%m%d%H%M%S"), "\n";#prints 20210401100000
The above does not convert from MST to America/Los_Angeles. Can someone please help? I am new to Perl. Also, would the above code take care of DST?
I think you meant to use US Mountain Time, which is identified by America/Denver. But even if the date-time is in US Mountain Time, that won't always give you the right answer.
If you refer to this list of tz database time zones, you'll see that
US Mountain Standard Time (MST) is UTC-7.
US Pacific Time (America/Los_Angeles) is UTC-8 in the winter, and UTC-7 in the summer.
The correct conversion of
2021-04-01 10:00:00 -07:00 (MST)
is therefore
2021-04-01 10:00:00 -07:00 (America/Los_Angeles)
DateTime properly converted the date-time.
Maybe you meant to use US Mountain Time, which is identified by America/Denver (UTC-7 in winter, UTC-6 in summer). However, that introduces an ambiguity. For one hour every year, you'll get the wrong answer due to the overlap during the DST change.
For example,
2020-11-01 02:30:00 (America/Denver)
could refer to both
2020-11-01 02:30:00 -05:00 (America/Denver) Before "fall back"
2020-11-01 02:30:00 -06:00 (America/Denver) After "fall back"
Date-times should be transmitted in UTC or provide the offset from UTC of the date-time. For example, using the standard RFC3339 format, you could use any of
2020-11-01T02:30:00-05:00
2020-11-01T07:30:00+00:00
2020-11-01T07:30:00Z
for the former and any of
2020-11-01T02:30:00-06:00
2020-11-01T08:30:00+00:00
2020-11-01T08:30:00Z
for the latter. Then, you could keep using DateTime::Format::Strptime (with %z), or use DateTime::Format::RFC3339.

DateTimes.toUtc() not working as expected

void main() {
var foo = DateTime(2000).toUtc();
var bar = DateTime.utc(2000);
assert (foo != bar);
}
foo and bar should be equal but they aren't, can anyone tell me what am I doing wrong with toUtc(). As far as I know DateTime(2000) returns the DateTime in local time zone and toUtc() converts that to UTC, but it doesn't do that.
Thanks to someone who deleted the answer after posting this link. And thanks to #lrn for answering it:
DateTime(y, m, d).toUtc() finds the y-m-d time in the local time-zone and then creates a UTC date at the same point in time. So, if I'm in the UTC+2 time zone, and I do DateTime(2019, 04, 02).toUtc(), I start with the local time 2019-04-02T00:00:00+02:00, and then find that the corresponding UTC-time is 2019-04-01T22:00:00Z. That's the same point in time in a different time zone, it answers the question "when the local time is 2019-04-02T00:00:00, what is the time in the UTC time zone".
When you do DateTime.utc(2019, 04, 02), you get the point in time where the UTC clock showed 2019-04-02T00:00:00Z. If I do toLocal() on that, I'd get the local time 2019-04-02T02:00:00+02:00.

Same date in different time zone

My question is how can I get the same day, month, year, hour, minutes, seconds in a different time zone, for example:
var now = moment().valueOf();
var result1 = moment(now).format('DD-MM-YYYY HH:mm:SS Z');
In my time zone I get some this like this:
18-02-2015 21:08:34 +01:00
So how can I change only time zone without changing other values (days, months, ..., minutes, ...)
I want to get some thing like this:
result2: 18-02-2015 21:08:34 +01:00
result3: 18-02-2015 21:08:34 +10:00
result4: 18-02-2015 21:08:34 +05:00
result5: 18-02-2015 21:08:34 -06:00
result6: 18-02-2015 21:08:34 -11:00
Thanks in advance
Here's how you could do what you are asking:
// get a moment representing the current time
var now = moment();
// create a new moment based on the original one
var another = now.clone();
// change the offset of the new moment - passing true to keep the local time
another.utcOffset('+05:30', true);
// log the output
console.log(now.format()); // "2016-01-15T11:58:07-08:00"
console.log(another.format()); // "2016-01-15T11:58:07+05:30"
However, you must recognize two important things:
The another object no longer represents the current time - even in the target time zone. It's a completely different moment in time. (The world does not synchronize local clocks. If it did, we'd have no need for time zones!).
For this reason, even though the above code satisfies the question that was asked, I strongly recommend against using it. Instead, re-evaluate your requirements, as it's likely they are misunderstanding the nature of time and time zones.
A time zone cannot be fully represented by an offset alone. Read "Time Zone != Offset" in the timezone tag wiki. While some time zones have fixed offsets (such as +05:30 used by India), many time zones change their offsets at different points throughout the year to accommodate daylight saving time.
If you wanted to account for this, you could use moment-timezone instead of calling utcOffset(...). However, the issue in my first bullet would still apply.
// get a moment representing the current time
var now = moment();
// create a new moment based on the original one
var another = now.clone();
// change the time zone of the new moment - passing true to keep the local time
another.tz('America/New_York', true); // or whatever time zone you desire
// log the output
console.log(now.format()); // "2016-01-15T11:58:07-08:00"
console.log(another.format()); // "2016-01-15T11:58:07-05:00"
The most-voted answer is messy IMO. Here's a cleaner solution - similar to BlueSam's answer, but safer:
const myTime = moment.tz('2016-08-30T22:00:00', moment.ISO_8601, 'America/Denver')
myTime.format() //2016-08-30T22:00:00-06:00
const sameTimeDifferentZone = moment.tz(myTime.format('YYYY-MM-DDTHH:mm:ss.SSS'), moment.ISO_8601, 'America/New_York')
sameTimeDifferentZone.format() //2016-08-30T22:00:00-04:00
After reading the above comments, I thought I'd add in based on Joao's answer.
In my case I was trying to use a preexisting moment date with a timezone and converting it to another timezone while retaining the original date value (as asked in the question).
var newTimezone = 'America/Denver';
//date - contains existing moment with timezone i.e 'America/New_York'
moment.tz(date.format('YYYY-MM-DDTHH:mm:ss'), 'YYYY-MM-DDTHH:mm:ss', newTimezone);
From the moment docs: http://momentjs.com/timezone/docs/
reference moment-timezone-with-data.js and specify which timezone to go to, like so:
moment(date).tz("America/Los_Angeles").format()

BlackBerry date parsing and an hour's difference

Due to the limitation of date parsing on BlackBerry I'm trying to roll my own parse/deparse methods, however I seem to be falling foul of an hour's difference somewhere, somehow.
I do this:
long nowLong = System.currentTimeMillis();
String nowString = DateParser.longToString(nowLong);
Date nowDateFromString = DateParser.stringToDate(nowString);
Date nowDateFromLong = DateParser.longToDate(nowLong);
When outputted in order it produces this in console:
[139.46] 1369132556831
[139.46] 21 May 2013 11:35:56 Europe/Dublin
[139.46] Tue May 21 12:35:56 Europe/Dublin 2013
[139.46] Tue May 21 11:35:56 Europe/Dublin 2013
My simulator's time is set to 11:35 so the third statement - DateParser.stringToDate() - seems to be failing somewhere.
Here is my implementation:
public static Date stringToDate(String date) {
long l = HttpDateParser.parse(date);
Date d = new Date(l);
return d;
}
As my nowString includes the time zone I'd expect HttpDateParser.parse() to take this in to account but it seems not to be.
How can I correct this?
HttpDateParser.parse() is documented to handle "GMT" or a "TZD" which I assume to be a "time zone designator". I suspect this is expected to be the (horrible, ambiguous) abbreviation format - so for example, it might be worth trying to parse
21 May 2013 11:35:56 BST
and seeing what you get out. That would at least take you further in terms of diagnosing the behaviour of HttpDateParser. Keeping the time zone's TZDB ID is a better idea in my view, but you may well need to write your own parsing code. You still need to handle local time ambiguity though, where a particular local time occurs twice due to DST transitions.
It's not entirely clear what the input or expected output are in your case - how much control you have over the format. I'd try to use ISO-8601 as far as possible, with a time zone identifer as well if you need one. (If you're only trying to represent an instant in time, I'd use an ISO-8601 representation of the UTC instant, complete with a Z suffix to indicate UTC.)