Momentjs duration is not parsing positive sign correctly - momentjs

I am trying to add or subtract a duration from UTC 0000 hours. Momentjs lets one create a duration and then pass that into add method. Their documentation mentions that support of mixed negative and positive sign was added in V 2.13.0. But using a positive sign is returning 0. The code is available in this fiddle - https://jsfiddle.net/kshitij/9jqvfk3g/
const m = moment().utcOffset(0);
m.set({hour:0,minute:0,second:0,millisecond:0})
m.utc().format('HH:mm')
let d = moment.duration('+03:30').asHours();
console.log('Positive offset: ', d);
console.log('Positive time: ', m.add(d, 'H').format());
let b = moment.duration('-03:30').asHours();
console.log('Negative offset: ', b);
console.log('Negative time: ', m.add(b, 'H').format());
<script src="https://momentjs.com/downloads/moment.js"></script>
Momentjs duration documentation.
https://momentjs.com/docs/#/durations/

This was a bug in MomentJS. It has been fixed now after my PR was merged - https://github.com/moment/moment/pull/4007
Edit
Corresponding Github issue for this - https://github.com/moment/moment/issues/4002

Related

Correct way to get accurate time in Rust?

I'm trying to get accurate time with:
use chrono::{DateTime, Local, Utc};
use std::time::SystemTime;
fn main() {
println!(
"Local.now() {}",
Local::now().format("%H:%m:%S").to_string()
);
println!("Utc.now() {}", Utc::now().format("%H:%m:%S").to_string());
let system_time = SystemTime::now();
let stime: DateTime<Utc> = system_time.into();
println!("SystemTime.now() {}", stime.format("%H:%m:%S"));
}
However, if I run it:
$ date && target/debug/mybin
Sun Jan 15 04:08:19 PM CET 2023
Local.now() 16:01:19
Utc.now() 15:01:19
SystemTime.now() 15:01:19
I don't know where comes from the shift, but I want to know what's the correct way to get the right time?
The %m token inserts the current month's number, which is 1 because it is January. You probably want %M instead, which inserts the minute number. So you are correctly obtaining the current time, but are incorrectly displaying it by using the month number in the place where you'd expect to see the minute number.
See chrono's strftime documentation for a complete list of formatting codes.

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

Wrong date difference calculated using momentjs

I am trying to calculate the number of days between two dates using moment js.
function (value) {
var expiration= moment(value).format('DDMMYYYY');
var today = moment().format('DDMMYYYY');
var dayToExpiration = moment(expiration- today).format('D[days] ,H[hours]');
console.log(today + " : " + expiration
console.log(dayToExpiration);
The result is:
11102018 : 28102020 //--> 11.10.2018 : 28.10.2018
1 days ,6 hours //why only one day??
Because your dayToExpiration variable should be a moment.Duration object, not a string.
The difference between two datetimes is a duration, not a datetime.
Short answer:
As John Madhavan-Reese stated in his answer, you have to use moment Duration to represent the diffecence between two moments in time.
Issue in the code sample:
In your code you are creating a moment object from the difference between expiration and today. This value is interpreded by moment as the number of milliseconds since the Unix Epoch (see moment(Number)), so you are creating a moment object for a random day around the 1st January 1970 (see the output of moment(expiration- today).format() ). The D token in format() stands for Day of Month, so it gives an "incorrect" output.
My suggested solution:
You can calculate difference using momentjs' diff() then you can create a duration using moment.duration(Number).
Finally you can get your desired output using moment-duration-format plug-in (by John Madhavan-Reese :D)
Here a live sample:
function getDiff(value) {
var expiration= moment(value); // Parse input as momement object
var today = moment(); // get now value (includes current time)
// Calculate diff, create a duration and format it
var dayToExpiration = moment.duration(Math.abs(today.diff(expiration))).format('D[days], H[hours]');
console.log(today.format('DDMMYYYY') + " : " + expiration.format('DDMMYYYY'));
console.log(dayToExpiration);
}
getDiff('2018-10-28');
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-duration-format/2.2.2/moment-duration-format.min.js"></script>
I am getting errors. this one works for me:
moment.duration(expiration.diff(today))._milliseconds / (1000*60*60*24));

moment toISOstring without modifying date

I have a date like "Thu Sep 01 2016 00:00:00 GMT+0530 (IST)" which I need to send to server as ISO-8601 utc time. I tried like :
moment(mydate).toISOString()
moment.utc(mydate).toISOString()
moment(mydate).utcOffset("+00:00").toISOString()
but I am getting the result like
2016-08-31T18:30:00.000Z
which is 1day behind my intended time. So what can I do to make moment ignore my local timezone and see it as UTC?
Edit:
The expected output is
2016-09-01T18:30:00.000Z
And no, the initial input isn't a string rather a javascript "new Date()" value.
Reason this happens:
This happens because .toISOString() returns a timestamp in UTC, even if the moment in question is in local mode. This is done to provide consistency with the specification for native JavaScript Date .toISOString()
Solution:
Use the same function and pass true value to it. This will prevent UTC Conversion.
moment(date).toISOString(true)
const date = new Date("2020-12-17T03:24:00");
const dateISOStringUTC = moment(date).toISOString();
const dateISOString = moment(date).toISOString(true);
console.log("Converted to UTC:" + dateISOStringUTC)
console.log("Actual Date value:" + dateISOString)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
I take the same problem today and find the solution.
Here is the solution: moment(date,moment.ISO_8601)
var date = new Date();
console.log("Original Date");
console.log(date);
console.log("After Moment Format");
console.log(moment(date,moment.ISO_8601));
Test Execution:
Moment Documentation: MomentJs

'clientEvents' decreases start time automatically

Problem with clientEvents
var tempEventWithOlderInfo = calendar.fullCalendar( 'clientEvents' ,'_fc798' )[0];
alert( 'start time: '+ tempEventWithOlderInfo.start.getHours() );
This alerts the wrong time. If the start time of event is 6:30 PM. The above code would alert 5 instead of 6.
I am not sure why clientEvents method isn't returning the correct time.
No need for a bug report here. getHours() returns the hour of the day as indexed from 0.
The full range of values getHours() can return is 0-23. Just increment your result by 1 to get the actual hour.
http://www.w3schools.com/jsref/jsref_gethours.asp

Resources