Will moment.js Timezone Account for DST? - momentjs

I'm setting up a simple interface where the user can view the time of a scheduled webinar in their time zone by selecting it from a dropdown menu. The start time of the webinar will update dynamically based on the timezone the user has selected. I'm using this script with moment.js and moment-timezone.js:
var timestamp = $('.overview .date').data('timestamp');
var utcDate = moment.unix(timestamp).utc();
$('.modal.timezone select').on('change', function(){
var timezone = $(this).val();
var tzDate = utcDate.tz(timezone);
var formattedTime = tzDate.format("h:mm A");
var formattedTimezone = tzDate.format("z");
$('.modal.timezone .time').html("<strong>" + formattedTime + " </strong>" + formattedTimezone);
});
This works well; the time is updated to the selected timezone. But what I'm wondering is will this still work when DST kicks in, or will the webinar times display one hour off?

You aren't doing anything here that uses the current time, so there's nothing that would change on its own.
Even then, you are feeding a UTC value into moment-timezone which uses the DST rules corresponding to that time zone - not your own.
So you should be fine. If you are unsure, you can always test it out by changing your local computer's clock to just before the DST change for wherever you're located and see what happens.

Related

Get time format according to spreadsheet locale?

I want to store a Javascript Date() object in a spreadsheet with correct format according to spreadsheet's locale (SpreadsheetApp.getActive().getSpreadsheetLocale()).
Is there a way to get the country specific (date and) time format string from the spreadsheet locale?
E.g. when locale is de_DE, time format string as hh:mm
but when locale is da_DK, time format string as hh.mm
Interesting as well how to get the countries currency format.
BTW when I have date and time in de_DE and than change to da_DK, dates are reformatted (23.01.2020 -> 23/01/2020) but times are not (it stays as 22:59). Is that an error in Spreadsheet?
Dates in JavaScript have the method toLocaleDateString, which return a string formatted according to the specified locale. But this doesn't seem to work in Apps Script.
If you're open to using an Apps Script Web App for this, you could use this toLocaleDateString in your client-side script (that is, in a script tag in your HTML).
If that's not the case, I think your best option would be to create the relationship between formats and locales yourself, because Apps Script doesn't have a built-in method to achieve that. You could, for example, use a switch statement that would check the locale, and then format the date accordingly with Utilities.formatDate, the tool Apps Script uses to format dates. It could be something along the following lines:
var locale = SpreadsheetApp.getActive().getSpreadsheetLocale();
var formattedDate;
switch (locale) {
case 'de_DE':
formattedDate = Utilities.formatDate(yourDate, yourTimeZone, "hh:mm");
break;
case 'da_DK':
formattedDate = Utilities.formatDate(yourDate, yourTimeZone, "hh.mm");
break;
// ...
}
return formattedDate;
Reference:
toLocateDateString
Apps Script Web Apps
Utilities.formatDate
I hope this is of any help.
Sorry for that, however I found a function that would be worth checking out, it's toLocaleDateString() and toLocaleTimeString (), they deliver the local date and time format.
Please check
Formato fechas JavaScript.
I did the test from Google Apps Script and it throws me the following
function pruebafecha() {
var d = new Date();
var n = d.toLocaleDateString();
var h = d.toLocaleTimeString();
Logger.log(n);
Logger.log(h);
}
This is the answer(Colombia):
[20-01-24 16:47:50:286 EST] 24 de enero de 2020
[20-01-24 16:47:50:287 EST] 16:47:50 EST
A JavaScript Date object includes date, time and timezone. When Google Apps Script pass a Date object to the spreadsheet using setValue() / setValues() the value is displayed according to the cell number formatting using the spreadsheet timezone.
If the cell formatting is set to Automatic by default the date will be displayed accordingly to the spreadsheet locale.
If you want to force the cell to display a date in an specific format use Class Range setNumberFormat / setNumberFormats
If you don't want to use the above methods and don't want to rely on the spreadsheet locale and automatic cell format then instead of passing a Date object pass the value as an string prepending it with an ' (apostrophe, single quote character) to prevent that that automatic data type parsing changes the value and it's format.
Related
Javascript in Google Sheets script: help using setNumberFormat
I don't know very well the configuration of the sheet you mention. However, I share a code that I use to print the date and time of data submission of a form.
var d = new Date();
var hour = d.getHours()-1;
var min = d.getMinutes();
var day = d.getDate();
var month = d.getMonth()+1;
var year = d.getFullYear();
if (month<10) {dia = day+"/"+"0"+month+"/"+year;}
else {dia = day+"/"+month+"/"+year;}
if (min<10){time = hour+":"+"0"+min;}
else {time = hour+":"+min;}
What I do in the code is to take the values โ€‹โ€‹of day, month and year, I add 1 to the value of month because it takes values โ€‹โ€‹[0:11] => [Jan, Dec].
Then I build the format I want from date and time, you can notice that I have 1 left to the hours, because when I did the tests I noticed that the time of the script was one hour above.
I use google translate, I hope it is understood.

Generate ics with dynamic VTIMEZONE using moment js

Trying to create a .ics file which has a VTIMEZONE component, which based on the supplied timezone sets the Standard time and Daylight Savings time dynamically.
Just a sample:
BEGIN:VTIMEZONE
TZID:America/New_York
LAST-MODIFIED:20050809T050000Z
BEGIN:STANDARD
DTSTART:20071104T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20070311T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
END:VTIMEZONE
In my attempt to solve this I created a moment.tz.zone(timezone)Object which based on the documentation of moment https://momentjs.com/timezone/docs/#/zone-object/ I assume holds the necessary data untils(should be TZOFFSETFROM, TZOFFSETTO) and offsets(DTSTART).
Yet I can't find a clear documentation on how to extract these data.
Was wondering if there's anyway that one can extract the DTSTART, TZOFFSETFROM and TZOFFSETTO for Standard time and Daylight in moment-timezone.js
You can download pre-made VTIMEZONE components here:
http://tzurl.org/
As you already mentioned in the question, you can use the moment.tz.zone(name) method. This will give you a Zone object that contains a list of timestamps in the untils property, then you can apply your logic to get the timestamps you want in the VTIMEZONE (I've used the first timestamps of the untils array in my code sample).
You can use moment.tz and format() on a timestamp to get DTSTART. You can pass ZZ token to format() to get offset for TZOFFSETFROM and TZOFFSETTO.
You can use abbrs values to get TZNAME.
Here a live sample:
const MAX_OCCUR = 2;
const getVtimezoneFromMomentZone = (tzName) => {
const zone = moment.tz.zone(tzName);
const header = `BEGIN:VTIMEZONE\nTZID:${tzName}`;
const footer = 'END:VTIMEZONE';
let zTZitems = '';
for(let i=0; i<MAX_OCCUR && i+1<zone.untils.length; i++){
const type = i%2 == 0 ? 'STANDARD' : 'DAYLIGHT';
const momDtStart = moment.tz(zone.untils[i], tzName);
const momNext = moment.tz(zone.untils[i+1], tzName);
const item =
`BEGIN:${type}
DTSTART:${momDtStart.format('YYYYMMDDTHHmmss')}
TZOFFSETFROM:${momDtStart.format('ZZ')}
TZOFFSETTO:${momNext.format('ZZ')}
TZNAME:${zone.abbrs[i]}
END:${type}\n`;
zTZitems += item;
}
const result = `${header}\n${zTZitems}${footer}\n`;
return result;
};
console.log(getVtimezoneFromMomentZone('America/New_York'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.23.0/moment-with-locales.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.23/moment-timezone-with-data-2012-2022.min.js"></script>
This is a bit challenging to do in a robust way.
Summary
Use RRULE to avoid bloating your ics and support long-running or open-ended recurring events.
moment-timezone doesn't expose underlying zoneinfo data any way which would make it easy to build RRULE for a given zone (as far as I can tell).
For one-off events with fixed date, you can just pick the correct interval(s) to include in the ics from moment.tz.zone('America/New_York').untils based on the event date.
Details
As an example: moment.tz.zone('America/New_York').untils includes 235 intervals (DAYLIGHT or STANDARD over the years) from 1918 to 2037.
You don't want to include them all in your ics.
If you only include the first two in your VTIMEZONE, it won't be valid except for some events in 1918/1919.
var timezoneName = 'America/New_York',
{untils, abbrs, offsets} = moment.tz.zone(timezone);
console.log(untils.length);
// 236
console.log(moment.tz(untils[0], timezoneName).format('YYYY-MM-DD HH:mm:ss'));
// 1918-03-31 03:00:00
console.log(moment.tz(untils[untils.length-2], timezoneName).format('YYYY-MM-DD HH:mm:ss'));
// 2037-11-01 01:00:00
console.log(untils[untils.length-1]);
// Infinity
You could put all 235 of these intervals into an ICS but it would be really bloated.
The RFC section on VTIMEZONE includes some examples...
This is an example showing time zone information for New York City
using only the "DTSTART" property. Note that this is only
suitable for a recurring event that starts on or later than March
11, 2007 at 03:00:00 EDT (i.e., the earliest effective transition
date and time) and ends no later than March 9, 2008 at 01:59:59 EST (i.e., latest valid date and time for EST in this scenario).
For example, this can be used for a recurring event that occurs
every Friday, 8:00 A.M.-9:00 A.M., starting June 1, 2007, ending
December 31, 2007,
BEGIN:VTIMEZONE
TZID:America/New_York
LAST-MODIFIED:20050809T050000Z
BEGIN:STANDARD
DTSTART:20071104T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20070311T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
END:VTIMEZONE
The point is that the VTIMEZONE in the example is using only the "DTSTART" property...and in this case the VTIMEZONE is only valid for event dates covered by the STANDARD and DAYLIGHT intervals explicitly listed in the VTIMEZONE.
Another example from the RFC...
This is a simple example showing the current time zone rules for
New York City using a "RRULE" recurrence pattern. Note that there
is no effective end date to either of the Standard Time or
Daylight Time rules. This information would be valid for a
recurring event starting today and continuing indefinitely.
BEGIN:VTIMEZONE
TZID:America/New_York
LAST-MODIFIED:20050809T050000Z
TZURL:http://zones.example.com/tz/America-New_York.ics
BEGIN:STANDARD
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
END:VTIMEZONE
Note that in this case the presence of an RRULE which explains when these STANDARD and DAYLIGHT intervals reoccur means that we don't have to explicitly add all the specific intervals over the years. You would just need the most recent (before your event) interval where the RRULE changed. If your event is recurring and spans across rule changes, then you have to include a couple more intervals with corresponding rules to cover the events BEFORE the rule change as well as the events AFTER the rule change.
Indeed, inspecting an ICS generated by Apple's macOS calendar app for an event on August 19, 2021 in timezone Europe/Berlin includes the following VTIMEZONE (indented for readability)...
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
DTSTART:19810329T020000
TZNAME:GMT+2
TZOFFSETTO:+0200
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
DTSTART:19961027T030000
TZNAME:GMT+1
TZOFFSETTO:+0100
END:STANDARD
END:VTIMEZONE
Note that STANDARD has a DTSTART in 1996 and DAYLIGHT has a DTSTART in 1981 despite the event being in 2021. The presence of the RRULE allows them to avoid including many more STANDARD/DAYLIGHT intervals.
Best solution
...is probably to generate RRULE. This allows you to minimize the size of your ics file while supporting recurring events far into the future.
Downside: I can't find any easy way to generate RRULE with moment-timezone... but there appears to be some other libs around that might help (haven't played with them yet).
If anyone has some tips/experience generating RRULEs, it would be great to hear your experience.
Option 2: Work-around for specific use-cases
If you are dynamically generating an ICS file for a single or recurring event where you know the event date (or date range for recurring event), then you can just filter the moment.tz.zone('America/New_York').untils to make sure that you have ALL the STANDARD and DAYLIGHT intervals you need to cover your event date/range.
Downside: for long-running or open-ended recurring events this may not be a good option because too many intervals will have to be included in the ics file (bloat).
However for single, fixed-date events this is probably a fine option.
Quick example for option 2...
I only did a cursory scan of the RFC and to be safe I included the transition FOLLOWING the end date so you will always have at least 2 transitions even when you have an event at a single timestamp. One transition that occurs before the event date and one that occurs after. This may not be necessary.
function generateVTimezone (timezoneName, tsRangeStart, tsRangeEnd) {
var zone = moment.tz.zone(timezoneName),
{untils, abbrs, offsets} = zone,
i, dtStart, utcOffsetBefore, utcOffsetDuring, periodType,
vtz = [
`BEGIN:VTIMEZONE`,
`TZID:${timezoneName}`,
];
tsRangeStart = tsRangeStart || 0;
tsRangeEnd = tsRangeEnd || Math.pow(2,31)-1;
// https://momentjs.com/timezone/docs/#/data-formats/unpacked-format/
// > between `untils[n-1]` and `untils[n]`, the `abbr` should be
// > `abbrs[n]` and the `offset` should be `offsets[n]`
for (i=0; i<untils.length - 1; i++) {
// filter to intervals that include our start/end range timestamps
if (untils[i+1] < tsRangeStart) continue; // interval ends before our start, skip
if (i>0 && untils[i-1] > tsRangeEnd) break; // interval starts after interval we end in, break
utcOffsetBefore = formatUtcOffset(offsets[i]); // offset BEFORE dtStart
dtStart = moment.tz(untils[i], timezoneName).format('YYYYMMDDTHHmmss');
utcOffsetDuring = formatUtcOffset(offsets[i+1]); // offset AFTER dtStart
periodType = offsets[i+1] < offsets[i] ? 'DAYLIGHT' : 'STANDARD'; // spring-forward, DAYLIGHT, fall-back: STANDARD.
vtz.push(`BEGIN:${periodType}`);
vtz.push(`DTSTART:${dtStart}`); // local date-time when change
vtz.push(`TZOFFSETFROM:${utcOffsetBefore}`); // utc offset BEFORE DTSTART
vtz.push(`TZOFFSETTO:${utcOffsetDuring}`); // utc offset AFTER DTSTART
vtz.push(`TZNAME:${abbrs[i+1]}`);
vtz.push(`END:${periodType}`);
}
vtz.push(`END:VTIMEZONE`);
return vtz.join('\r\n'); // rfc5545 says CRLF
}
function formatUtcOffset(minutes) {
var hours = Math.floor(Math.abs(minutes) / 60).toString(),
mins = (Math.abs(minutes) % 60).toString(),
sign = minutes > 0 ? '-' : '+', // sign inverted, see https://momentjs.com/timezone/docs/#/zone-object/offset/
output = [sign];
// zero-padding
if (hours.length < 2) output.push('0');
output.push(hours);
if (mins.length < 2) output.push('0');
output.push(mins);
return output.join('');
}
function test() {
var timezone = 'America/New_York',
startTS = moment.tz('2013-11-18 11:55', timezone).unix()*1000,
endTS = moment.tz('2013-11-18 11:55', timezone).unix()*1000;
console.log(generateVTimezone(timezone, startTS, endTS));
}
test();
produces output...
BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:STANDARD
DTSTART:20131103T010000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20140309T030000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
END:VTIMEZONE

ASP -Set Current Date and time

When I run the ASP project in local host, show correct local time (Sri Lanka) . But when host the project show incorrect date, Time..
lable_date_time.Text = string.Format("{0}", DateTime.Today.ToShortDateString());
Hosting server in United states (www.smarterasp.net)
How to Fix it?
Thank You...
You might find the FindSystemTimeZoneById method useful, along with ConvertTimeFromUtc method. Both are methods of the System.TimeZoneInfo class. Example I used to pass the time in a time zone passed in from a user and converting that to stock market time in NYC. After you do the calculation you can adjust by getting the difference of the offset:
TimeZoneInfo tradeTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tradeTimeZone);
var tradetz = TimeZoneInfo.ConvertTimeFromUtc(myexactexetime, tradeTimeZoneInfo);
TimeZoneInfo nyTZI = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var marketTimezone = TimeZoneInfo.ConvertTimeFromUtc(dateTimeInUsersZone, nyTZI);
TimeSpan diff = marketTimezone - tradetz;
double hours = diff.TotalHours;
dateTimeInUsersZone= dateTimeInUsersZone.AddHours(hours);

Why is my datetime-local input not being processed correctly by google apps script?

I'm trying to process a form with a datetime-local field to create a calendar event in a public google calendar. But when I run the program, all my dates default to December 31st 1969 at 4pm. Anyone have any ideas?
My script (which takes "form" as a parameter):
var event = cal.createEvent(form.myTitle, new Date(form.startTime+".000Z"), new Date(form.endTime+".000Z"));
Logger.log(form.startTime+" and "+form.endTime);
I added ".000Z" as per this solution, but I ran into the same problem even without adding it: Why does my Date object in Google Apps Script return NaN
What startTime and endTime are logged as:
2016-03-15T17:30 and 2016-03-15T19:30
But this is in the execution transcript:
Calendar.createEvent([NEW EVENT, Wed Dec 31 16:00:00 PST 1969, Wed Dec 31 16:00:00 PST 1969])
There are lots of ways to set a date object in JavaScript, but to set the correct date for a Google Calendar, you must do it in a very specific way. You must get the calendar time zone. For many people, if the users of their script are all in the same time zone, then the code will work. The problem comes when you have users across different time zones, or the time zone of the script is different than the time zone of the calendar. The code must construct a valid date string first, and then use the date string to create the date object. You can create a date object without a date string, and that would be preferable in most cases, because people can mess up the code to create the date string, but in this situation, you have no other choice (That I know of). Why? It's because of the time zone offset setting. The time zone offset setting is included in the date string. That is the key piece of information, that makes sure your dates will get set correctly, including for daylight savings.
function setCalendarEvent(){
var startTime = "2016-03-15T17:30";
var endTime = "2016-03-15T19:30";
//Always get the time zone of the calendar. If you don't do that, users accross different times zones will write bad dates
var calTimeZone = CalendarApp.getDefaultCalendar().getTimeZone();
//Construct a valid date string from the data
var startYr = startTime.slice(0,4);
var endYr = endTime.slice(0,4);
var startMonth = startTime.slice(5,7);
var endMonth = endTime.slice(5,7);
var startDay = startTime.slice(8,10);
var endDay = endTime.slice(8,10);
var startHrAndMin = startTime.slice(11,17);
var endHrAndMin = endTime.slice(11,17);
var startDateString = startMonth + "/" + startDay + "/" + startYr + " " + startHrAndMin + ":00 ";
var timeZoneOffset = Utilities.formatDate(new Date(startDateString),calTimeZone, "Z");
var startDateAsDate = new Date(startDateString + " " + timeZoneOffset);
Logger.log('startDateAsDate: ' + startDateAsDate)
};

formatDate() gives correct date -1 day (Google Apps Script)

I'm using a Google Spreadsheet to log some things on a day-to-day basis. To make it user-friendly to my colleagues I've made the spreadsheet as "interface-ish" as possible, basically it resembles a form.
This "form" has a submit button that saves the sheet and creates a new sheet (copy of template).
The problem is that the sheet that is saved should be saved with the date from a cell. BUT it saves with the date one day before the actual date... (!) I'm going nuts trying to figure out why.
Here's the code from the Google Apps Script I'm calling when the submit button is pressed:
function renameSheet() {
var ShootName = SpreadsheetApp.getActiveSheet( ).getRange("G8").getValue();
var DateName1 = SpreadsheetApp.getActiveSheet( ).getRange("A8").getValue();
var newdate = new Date(SpreadsheetApp.getActiveSheet( ).getRange("A8").getValue());
var Datename2 = Utilities.formatDate(newdate, "PST", "yyyy-MM-dd");
var NewName = Datename2 + " - " + ShootName;
SpreadsheetApp.getActiveSpreadsheet().renameActiveSheet(NewName);
var oldSheet = ss.getActiveSheet();
// create a duplicate of the template sheet
ss.setActiveSheet(ss.getSheetByName("Original0"));
var newSheet = ss.duplicateActiveSheet();
newSheet.activate();
ss.moveActiveSheet(1);
newSheet.setName("NewLog");
}
If cell A8 has the value "12.25.16" - the sheet will be named "12.24.16".
If anyone has a proper or even a dirty quickfix to this, I'd love to hear it.
You are hardcoding the timezone in this line :
var Datename2 = Utilities.formatDate(newdate, "PST", "yyyy-MM-dd");
but this does not take into account the daylight saving and date only values in spreadsheets are always at 00:00:00 hours so one hour shift can change the date...
Replace with an automated value like this :
var Datename2 = Utilities.formatDate(newdate, Session.getScriptTimeZone(), "yyyy-MM-dd");

Resources