Moment js unexpected behaviour for timezone - momentjs

While using moment js I observed that moment.tz().format() is not returning abbreviation always which breaking regex post this time format.
moment.tz('America/Los_Angeles').format('z') // "PDT" (abbreviation)
moment.tz('Asia/Magadan').format('z') // "+11" (3-char offset)
What is the factor on which this behaviour depends? for which location or is it random?
I see that this zone will be cached by moment js. Is there cached in browser somewhere ?

Related

Date parsing errors when timezone does not exist with Java OpenAPI generator client

I'm working on integrating with an API that (unfortunately) does not always append the timezone offset to their date. I know this isn't optimal but I can't change their behavior.
Example:
2022-06-08T16:07:13.96
Using the java generator and the java8 date library produces a runtime error while parsing the date:
java.time.format.DateTimeParseException: Text '2022-06-08T16:07:13.96' could not be parsed at index 22
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2052)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1954)
at java.base/java.time.OffsetDateTime.parse(OffsetDateTime.java:404)
at com.acme.openapi.JSON$OffsetDateTimeTypeAdapter.read(JSON.java:287)
From my limited understanding, I believe that ISO-8601 dates should be treated as local dates instead of offset dates if the zone offset is omitted. Im unsure if this is something that is supported in the java generator.
I ended up swapping the date library to java8-localdatetime but it gave GSON some problems:
Expected BEGIN_OBJECT but was String
I changed to a jackson based client and that cleared everything up.
I use this nasty wrapper everywhere, where I use a DateTime from the API, to "hack" it in the current timezone.
DateTime utcHack(DateTime dt) {
return DateTime.parse(dt.toLocal().toString() + "Z");
}
I am open for improvements!

How can I get SignalR to stop changing my date/time values?

I have a simple class that contains a DateTime property. When I set the value of this property using DateTime.Now() the value is correct. However, when I pass this class object as a parameter via SignalR, on the receiving end SignalR has changed the DateTime so that it no longer matches the original DateTime. How can I get SignalR to stop manipulating my DateTime values? This issue seems to have started with a recent update to the latest SignalR nuget packages.
This seems to be a problem when the hub and the client are in two different time zones. It seems Microsoft is trying to help, by adjusting the date/time to the local time zone, but I want the original value, not the value Microsoft "thinks" I want.
When you want two systems to communicate with each other I would recommend using always the DateTime in UTC for various reasons. In your case you have two options here:
1 - Send the date as string string date = DateTime.Now.ToString(CultureInfo.InvariantCulture); so on the client side you just need to parse the datetime like DateTime.Parse(date, CultureInfo.InvariantCulture);.
2 - Send the date in UTC like DateTime.UtcNow; so event if the SignalR tries to change the date, it will have the DateTime.Kind as UTC. In this case or you will get the current date correctly, or you just adjust on the client side to the local time like receivedDate.ToLocalTime();
This one was a real head scratcher as I had the exact same issue. The client was storing the correct time but by the time it hit the Hub... it had changed. I'm really glad you posted this as I wouldn't have imagined this issue being possible. #Kiril1512 posted the correct resolution. In my case I used his second option as I didn't want to change my model... although simply converting to a string would have been simpler. While I did follow his suggestion and convert everything to DateTime.UtcNow()... I am thinking this was unnecessary as I noticed even previously stored dates converted correctly. This makes me think that either this isn't necessary to do, or dates are converted to Utc when they hit the Hub automatically which may be what the issue was to begin with?
Either way I posted this as I discovered that converting this date back to Local time was a little more involved. Here is how I ended up doing the conversion which resolved this issue for me that I gathered from this resource:
DateTime convertedDate = DateTime.SpecifyKind(
DateTime.Parse(msg.CreatedOn.ToString()),
DateTimeKind.Utc);
var kind = convertedDate.Kind;
DateTime dt = convertedDate.ToLocalTime();

MomentJS toISOString with local timezone

I've been using toISOString to serialize momentJS date before sending to the server via jQuery. It works fine for me except it converts date to UTC but I need to keep the local timezone. Does momentJS have a method for this?
You can either call .format() without any parameters, or you can call .toISOString(true). Both are in ISO 8601 extended format, the difference is only in whether milliseconds are included or not.
// get the current moment, in local mode
const m = moment();
// format without parameters will give the ISO string including offset
console.log(`moment().format() === "${ m.format() }"`);
// if you want to include milliseconds, you can use toISOString(true) instead
console.log(`moment().toISOString(true) === "${ m.toISOString(true) }"`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
Added in 2.20.0:
moment().toISOString(true)
Output:
"2018-05-13T16:16:13.507+03:00"
Found answer here.
Documentation.

moment.js from now is reporting different results in firefox/ie then chrome

We and foolishly assumed a block of momentjs code would work in all browsers. Right now its correctly working in chrome, but all other browsers its not applying the UTC offset. How do I make this code work consistently in other browsers? Right now chrome is working, all others are not.
new moment(new Date(date)).fromNow();
//below shows an example of an exact date.
var now = new moment(new Date("2013-09-30T23:33:36.937")).fromNow();
In chrome you would see something like "now", all other browsers you would see "in 4 hours"
The way to get it to work:
var now = moment.utc("2013-10-01T13:15:30.937").fromNow();
Note that if you turn it into a "Date" and call moment.utc
var now = moment.utc(new Date("2013-10-01T13:15:30.937")).fromNow();
It will not work. It makes sense now that I think of it.
Original Answer
Try this:
var now = moment(date).fromNow();
But rutter is correct that you should specify either a Z or an offset like -07:00. From .net, you should make sure you use either a DateTime with .Kind set to Utc, or use a DateTimeOffset field.
Expanded Answer
You should understand that when you send a string of 2013-10-01T13:15:30.937, you are not sending any context with it. There is no way to know from that string alone whether the time is at UTC, or in your server's time zone, or in the browser's time zone.
If you pass it directly to moment("2013-10-01T13:15:30.937"), it is going to assume the context of the browser's local time zone.
As you discovered, you can tell moment explicitly that this time is in UTC by using the .utc function, such as moment.utc("2013-10-01T13:15:30.937").
While this will work, there are good reasons not to rely on that alone. For example, what if you ever use the same server API for another application, or perhaps for third-parties to consume? Unless you tell them separately that the timestamp is meant to represent UTC, then there is no way to know that.
These strings are in the ISO 8601 / RFC 3339 format. Part of that specification describes how to indicate that a timestamp is in UTC. You simply add a Z at the end. If you supply the Z then any consumer of this timestamp will know that the time should be interpreted as UTC. And sure enough, if you pass that into moment directly, such as moment("2013-10-01T13:15:30.937Z"), it will give the result you expected.
You said in comments that you were generating these values from ASP.Net Web API. Run your application in debug mode and set a breakpoint so you can examine the output of your controller. When you look at the specific DateTime property in question, you will see that it has its own .Kind property. It is probably set to DateTimeKind.Unspecified.
Since you said specifically that your application uses UTC, then these values should have DateTimeKind.Utc instead. Once that is set, then WebAPI will properly emit the Z at the end of the timestamp.
Somewhere in your server-side code, you should do something like this:
dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
You should do this as early as possible. For example, in your data access layer when you retrieve the value from the database. If that's not possible, then at least you should do it in your API Controller so the value gets emitted properly.
See also the MSDN reference for DateTimeKind and DateTime.SpecifyKind.
Also - the reason you were getting browser inconsistencies is because you were using the Date object's constructor instead of the parsing functions built in to moment. While moment will accept a Date, there are several known problems and inconsistencies with how browsers support parsing a Date from a string. Some of those inconsistencies are documented here.
Use this:
var lastLoginTime = moment(user.lastLoginTime).fromNow();

getTimezoneOffset does not work all the time

I register the following javascript on my web page. When the user posts back, SOMETIMES the txtTimeZoneOffSet hidden field comes back as blank. When I test on my computer, I always get a value. It happens on different browsers (I.E, FireFox, and Safari).
I am trying to get the users offset, so when they enter a date, I can convert it to UTC on the server and deal with everything in the same timezone, then when I render dates back out to them, I make sure it is in their timezone.
<script type='text/javascript'>
$(window).load(function () {
var d = new Date();
var tz = d.getTimezoneOffset();
$('#txtTimeZoneOffSet').val(tz / 60);
});
</script>
Does anyone see why this would not work 100% of the time?
The jQuery looks fine to me, so I don't see why it wouldn't work unless JavaScript was disabled or you didn't load jQuery.
However, you are worked on a flawed assumption that the user's current offset will be the same for all dates. In any time zone that has daylight saving time, it could be wrong.
The better approach would be to convert to UTC on the browser. Or at least use the date/time in question in the call to getTimezoneOffset.
UPDATED ANSWER
In response to your question, the code you have now will get the current offset already. You are just not guaranteed that it will be the same offset for any date they might enter. For example:
<input type="text" id="someDate" />
var someDateString = $('#someDate').val();
var dt = new Date(someDateString);
var offset = dt.getTimezoneOffset();
Since the value of the text input is variable, in many time zones you will get a different offset for January than you will for June. So the better thing would be to just convert it to UTC on the browser:
var someDateString = $('#someDate').val();
var dt = new Date(someDateString);
var s = dt.toISOString();
The string s will be in ISO8601 format, and will already be converted to UTC.
Of course, if it were me I would do this using moment.js - since it gives you much better control of how the input string is parsed.
This approach is usually good enough. HOWEVER, if there is any chance you will have to do this conversion on the server, perhaps because the user is not present, then you must know their time zone. Not just the offset, but the actual time zone. See "Time Zone != Offset" in the timezone tag wiki.
The best way is to ask them for it. A map-based picker like this one is a nice touch. You can even guess at a default selection using jsTimezoneDetect. Ultimately you are after a string identifier like America/New_York that you can use on your server. In .Net you can use these with the TZDB provider in Noda Time.
If you don't want any fancy libraries, you can stick with Windows time zones, and present a dropdown list from TimeZoneInfo.GetSystemTimeZones(), where the .Id is the key and the .DisplayName is the value.

Resources