moment-timezone change utc to default timezone - javascript

Context:
I need to parse datetimes from UTC into the timezone currently set as default by moment-timezone with moment.tz.setDefault.
Attempt (functional):
const task = {
startDate: '2020-03-24 14:00:00'
};
const startDateMoment = moment.utc(task.startDate).tz(moment().tz());
However, I am creating a new moment object every time I need to set the timezone. Is there a more performant way to converting between utc to the default timezone, or getting the default timezone from moment?
moment.tz.guess() guesses the timezone from the browser, not the one set manually.
console.log(`default timezone: "${moment().tz()}", guessed timezone: "${moment.tz.guess()}"`);
returns
default timezone: "US/Samoa", guessed timezone: "America/Vancouver"

After a bit of digging, I did find the following, undocumented, use-at-your-own-risk, could-disappear-at-any-time property:
moment.defaultZone.name
moment.defaultZone is a Zone Object, of which we really only care about the name property.
Note that it is null if you haven't set a default yet! In the code below, I'm taking advantage of a couple new JavaScript features, optional chaining and the nullish coalescing operator to see if the default zone is set and if not, use the moment.tz.guess() value.
const task = {
startDate: '2020-03-24 14:00:00'
};
console.log('before setting default: ', moment.defaultZone);
moment.tz.setDefault('US/Samoa');
console.log('after setting default: ', moment.defaultZone);
const startDateMoment = moment.utc(task.startDate).tz(moment.defaultZone?.name ?? moment.tz.guess());
console.log(startDateMoment.format());
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<script src="https://momentjs.com/downloads/moment-timezone-with-data.min.js"></script>

Related

Date Range is starting with Plus

I have created a small function which give all dates between two dates.
but this is starting from Plus 1 date.
Here's my code (also on Here is jsfiddle):
function DateRangeArr(to, from) {
console.log(to);
console.log(from);
var Dateresult = [];
var end = new Date(from);
var loop = new Date(to);
while (loop <= end) {
Dateresult.push({ 'total': 0, 'log_date': loop });
var newDate = loop.setDate(loop.getUTCDate() + 1);
loop = new Date(newDate);
}
return Dateresult;
}
console.log(DateRangeArr('2020-12-05','2020-12-12'));
It's giving me results starting from 2020-12-06 to 2020-12-13. Also will it gives result depending upon user time zone ?
There are a couple of things going on that could account for the problem.
(This is probably the thing you're seeing) You're changing the state of the Date instance that you added to the array after adding it. That changes the instance that's in the array.
Since "2020-12-05" is a date-only string, it's parsed as UTC in specification-compliant browsers (see the rules at Date.parse), so the Date objects will represent midnight on that date UTC rather than midnight on that date in your local timezone. If you want them to reliably be midnight local time, add T00:00 to the string. If you want them to be parsed reliably as UTC, you should be able to use them as-is, but the spec moved around on this (ES2015 said they should be parsed as local time, not UTC, before being updated by ES2016) so to get UTC reliably, add T00:00Z to it. (My understanding is that the spec allows just adding a Z, but others read the spec differently, and both Safari's JavaScriptCore and Firefox's SpiderMonkey reject new Date("2020-12-05Z"), so add T00:00Z.) Or use new Date(Date.UTC(year, month - 1, day)) instead.
You're mixing up accessors here: loop.setDate(loop.getUTCDate() + 1). That's using the UTC accessor to get the date, but then the local time accessor to set it. You need to be consistent in order to add a day to the date.
You haven't said how you're using the resulting array of Date objects, but just note that the date they show may seem off if (for instance) you parse them as UTC but then display the result in local time or vice-versa, because of the time zone offset.
You have to and from reversed in your code. You're using them consistently, but it's quite hard to read. :-) When building an array in date order, it goes from the earlier date to the later date.
Here's a version updated to work entirely in UTC; the resulting Date objects represent midnight UTC on each of the dates:
function dateRangeArr(from, to) {
console.log(from);
console.log(to);
var dates = [];
var end = new Date(to + "T00:00Z");
var loop = new Date(from + "T00:00Z");
while (loop <= end) {
dates.push({ "total": 0, "log_date": new Date(+loop) });
loop.setUTCDate(loop.getUTCDate() + 1);
}
return dates;
}
console.log(dateRangeArr("2020-12-05","2020-12-12"));
(I've also updated the function and variable names to be more consistent with standard practice in JavaScript, since the function isn't a constructor function.)
Here's a version that works in local time instead:
function dateRangeArr(from, to) {
console.log(from);
console.log(to);
var dates = [];
var end = new Date(to + "T00:00");
var loop = new Date(from + "T00:00");
while (loop <= end) {
dates.push({ "total": 0, "log_date": new Date(+loop) });
loop.setDate(loop.getDate() + 1);
}
return dates;
}
console.log(dateRangeArr("2020-12-05","2020-12-12"));
Which you use depends on whether you want the Date instances to represent midnight UTC (use the UTC version) or midnight local time (use the local time version).

DayJS isValid behaves differently to Moment

I was trying to validate birthday using dayjs but its isValid is returning true for a date that doesn't exist. Funnily enough, the isValid by moment is working just fine.
dayjs('2019/02/31', 'YYYY/MM/DD').isValid() // true
moment('2019/02/31', 'YYYY/MM/DD').isValid() // false
I can't switch to moment because of the lightweightness of dayjs
Any idea how to tackle this?
August 2021: It's now possible to detect with .isValid() method these invalid dates passing a third parameter as true to dayjs constructor:
dayjs('2019/02/31', 'YYYY/MM/DD').isValid() // true
dayjs('2019/02/31', 'YYYY/MM/DD',true).isValid() // false
More information on this strict parsing argument can be found now at official doc.
NOTE: In Node.js I had to load first customParseFormat plugin before calling isValid() to receive expected validity results:
const dayjs = require("dayjs");
var customParseFormat = require("dayjs/plugin/customParseFormat");
dayjs.extend(customParseFormat);
Please look at this thread. Basically isValid doesn't validate if passed date is exists, it just validates that date was parsed correctly.
I'm not exactly sure if this works in all scenarios (especially if you have localization), but you could try something like:
function validate(date, format) {
return dayjs(date, format).format(format) === date;
}
validate('2019/02/31', 'YYYY/MM/DD') // false
Reason for this kind of check is that
dayjs('2019/02/31', 'YYYY/MM/DD').format('YYYY/MM/DD')
returns 2019/03/03. Then when you compare it to your initial date (you should be able because formatting is the same) you should get the same value - and in this case you don't.
How moment.isValid() works (overflow checks)
If the moment that results from the parsed input does not exist, moment#isValid will return false.
How dayjs.isValid() works
If you look at the source code:
src/index.js
this.$d = parseDate(cfg) // Line 75
// ...
isValid() { // Line 96
return !(this.$d.toString() === C.INVALID_DATE_STRING)
}
src/constant.js
export const INVALID_DATE_STRING = 'Invalid Date'
If you look to the source parse is not a straighforward Date.parse() because it take in consideration locale, date format and so on, but it seems that every valid date passed to Date() or Date.parse() are valid.
Date.parse('2019/02/31') // 1551567600000
new Date(2019/02/31) // Thu Jan 01 1970 01:00:00 GMT+0100
You can use customParseFormat plugin from day.js
I do this on my react project and this is work perfectly fine.
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);
dayjs('30-02-2022', 'DD-MM-YYYY', true).isValid() // false
use strict mode
dayjs('2019/02/31', 'YYYY/MM/DD', true).isValid() //false
When strict mode is not enforced
Note: you have to extend customParseFormat plugin as a prerequisite
Unfortunately, we can't rely on isValid() function that is provided in dayjs.
What I did, was to create a file utils called date-utils.ts. This file is a wrapper around the lib and add more validations on top of it.
Essentially, this statement will do the work we need:
// it validates the string date properly
if (Number.isNaN(new Date(date).getTime())) {
return false;
}
Then, you could create a file like the typescript example (feel free to make it js if you want).
// date-utils.ts
import dayjs from 'dayjs';
const isValid = (date: string) => {
if (!date) {
return false;
}
// it validates the string date properly
if (Number.isNaN(new Date(date).getTime())) {
return false;
}
return dayjs(date).isValid();
};
const getDate = (date: string) => {
if (!date) {
throw Error('The date is required.');
}
if (!isValid(date)) {
throw Error(`The date "${date}" is not valid.`);
}
return dayjs(date);
};
export default { isValid, getDate };

Moment js: moment(0) = Wed Dec 31 1969?

I tried to make null date.
so I tried to make them all 0 when I click the clear button.
this.clear = function () {
_this.newQuarters.forEach(function (_value, index) {
_value.startDate = 0;
_value.endDate = 0;
_value.suppressAllocation = false;
_value.quarters.forEach(function (_childValue, index) {
_childValue.startDate = 0;
_childValue.endDate = 0;
_childValue.suppressAllocation = false;
});
});
};
}
After that, I tried to add 0 at moment from other function.
this.newQuarters.forEach(function (_value, index) {
var startDate = moment(_value.startDate);
but, it display startDate = Wed Dec 31 1969.
Please help me to find to make all date are null.
When passing a number to the moment() function, it interpret it as a unix timestamp.
Which is the number of second since EPOCH time, or 01-01-1970.
So passing 0 to that function result in the very first second of jan 1 1970. As Bergi pointed out, you are probably displaying your dates in your local timezone, which could result in a time before 01-01-1970.
If you want to create a null date, you should set your startDate to null and handle it correctly ( with an if statement).
You could also set the date back to the current time by passing no argument to the moment() function.
Dates can be tricky to work with in an application. There is no such thing as time that is not time. At least not in the world of programming. For this reason we have to create our own understanding of what a lack of time will be.
There is no time without time but there is a lack of value.
You obviously understand this, you had an approach, in your case, you seem to be fine with simply having time be zero, it just broke when working with moment as moment is using EPOCH time. Thus, zero is time.
You are going to have to test like the rest of us.
this.newQuarters.forEach(function (_value, index) {
var startDate = _value.startDate ? moment(_value.startDate) : null;
Lack of time could be whatever you want, undefine, null, a beginning date
Be consistent: Database vendors and languages handle things differently, front end is not always the same time zone, etc.
If you are dealing with momentjs library, there is a function: invalid()
You can easily make your variables invalid moment object and control their validity.
let currentDate = moment(); // Valid moment object
console.log(currentDate.format());
// make the moment object invalid
currentDate = moment.invalid();
console.log(currentDate.isValid()); // Check validity
console.log(currentDate.format()); // Invalid date
// make the moment object valid again
currentDate = moment();
console.log(currentDate.isValid()); // Check validity
console.log(currentDate.format()); // A valid date
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>

Detect an invalid timezone with momentjs

Is there a way I can detect an invalid timezone with momentjs?
If I do this:
const m = moment.tz("01/01/2019 5:30pm", "MM/DD/YYYY h:mma", true, "invalid timezone");
m.isValid(); //true
Is there any way to make isValid() return false, or is there some other way to detect that the timezone is invalid?
It logs the following: Moment Timezone has no data for invalid timezone. See http://momentjs.com/timezone/docs/#/data-loading/ but how can I tell programatically?
If you're working with a fully created Moment object, then the answer given by Jaromanda X is the way to go. However, if you're just testing for the time zone name being valid (and loaded into moment), then you don't need to construct a Moment object to do that. Just test the time zone name itself.
if (!moment.tz.zone('FooBarTimeZone')) {
console.log('Invalid time zone');
} else {
console.log('Valid time zone');
}
Note that this works because the moment.tz.zone function returns either a Zone object (which like any object is truthy), or null (which is falsey).
Also note that there was originally a moment.tz.zoneExists function, which still works - but will now give a deprecation message, describing using the above technique instead.
check if m.tz() === undefined
let m = moment.tz("01/01/2019 5:30pm", "MM/DD/YYYY h:mma", true, "invalid timezone");
console.log(`"invalid timezone" results in .tz() == ${m.tz()}`)
m = moment.tz("01/01/2019 5:30pm", "MM/DD/YYYY h:mma", true, "Australia/Melbourne");
console.log(`"Australia/Melbourne" results in .tz() == ${m.tz()}`)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data.min.js"></script>
Of course you can also check if a zone exists
console.log(`"invalid zone name is ${moment.tz.zone("invalid zone name")}`);
console.log(`"australia/nsw" is ${JSON.stringify(moment.tz.zone("australia/nsw"), null, 4)}`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data.min.js"></script>

Maximum call stack exceeded trying to set date in paper-date-picker

With https://github.com/bendavis78/paper-date-picker (based on moment.js)
I am trying to set the date (optionally) offset months in the future.
The element:
<paper-date-picker date="{{date}}"></paper-date-picker>
The following property works:
properties: {
date: {
type: Date
}
}
But this floods the console with "Uncaught RangeError: Maximum call stack size exceeded." from polymer:1258 & polymer-mini:964
properties: {
date: {
type: Date,
value: function() {
var myDate = new Date();
var offset = this.attributes.offset ? parseInt(this.attributes.offset.value) : 0;
myDate.setMonth(myDate.getMonth() + offset);
return myDate;
}
}
}
It doesn't seem to matter what the date transform is, if the "value" block is there it fails.
Logging the values in the function shows the above parses date as I'd expect, but I guess my expectations differ from the component?
Can anyone spot where I'm going wrong?
You need to set the date of the calendar object rather than the date-picker object, e.g. this.$.datepicker-id.$.calendar.date = new Date();
That worked in terms of resetting the date for the picker, but I actually encountered a different issue afterwards related to not being able to reselect the same date that I previously selected after closing the dialog. I had to remove the logic in calendar._updateSelection to not set the selected.width to 0px.
I can reproduce the error by plugging in a new Date() (as reported in console):
<paper-date-picker date="Tue Mar 15 2016 17:32:36 GMT+0000 (GMT Standard Time)"></paper-date-picker>
So even though this is a valid datetime, I guess the date picker is not happy about the format of myDate for one reason or another. I think you should try converting that date to a string in the format mm/dd/yyyy. As a quick proof-of-concept, try:
properties: {
date: {
type: Date,
value: function() {
return '03/15/2016';
}
}
}

Categories

Resources