Identify second/third saturday using moment.js - javascript

I am trying to implement a method which will return if a date is a holiday or not. For the purpose of this use case, a holiday is naively defined as all sundays, and both second and third saturdays. Here is a snippet of the code,
export const isHoliday = (date: moment.Moment | Date) => {
if (date instanceof Date) {
date = moment(date);
}
const dayOfWeek = date.day();
if (dayOfWeek >= 1 && dayOfWeek < 6) {
// it is a weekday
return false;
}
if (dayOfWeek === 0) {
// it is a sunday
return true;
}
if (dayOfWeek === 6) {
return isSecondSaturday(date) || isThirdSaturday(date);
}
};
And here are the other methods,
const isSecondSaturday = (date: moment.Moment) => {
return isNthSaturday(date, 2);
};
const isThirdSaturday = (date: moment.Moment) => {
return isNthSaturday(date, 3);
};
const isNthSaturday = (today: moment.Moment, n: number) => {
const thisMonth = today.clone().utc().startOf("month");
const firstSaturday = thisMonth.day(6); // <-- culprit?
const nthSaturday = firstSaturday.add(n - 1, "week");
return nthSaturday.date() === today.date();
};
In my testing, I found that the second and third Saturdays are not correctly identified for certain months. I think it could be something minor that I'm missing.

Could you please provide for which month the following implementation does not work? I've put your implementation to the console and checked first four months - it works fine.
function isHoliday(date) {
const dayOfWeek = date.day();
if (date instanceof Date) {
date = moment(date);
}
if (dayOfWeek >= 1 && dayOfWeek < 6) {
// it is a weekday
return false;
}
if (dayOfWeek === 0) {
// it is a sunday
return true;
}
if (dayOfWeek === 6) {
return isSecondSaturday(date) || isThirdSaturday(date);
}
};
function isSecondSaturday(date) {
return isNthSaturday(date, 2);
};
function isThirdSaturday(date) {
return isNthSaturday(date, 3);
};
function isNthSaturday(today, n){
const thisMonth = today.clone().utc().startOf("month");
const firstSaturday = thisMonth.day(6); // <-- culprit?
const nthSaturday = firstSaturday.add(n - 1, "week");
return nthSaturday.date() === today.date();
};
function checkHolidaysInMonth(monthIndex) {
var thisYear = moment().utc().startOf("year");
thisYear.add(monthIndex,'months');
for(var i = 0; i < 31; i++) {
if(isHoliday(thisYear) === true) {
console.log("Is 2nd or 3rd Sat or Sun: " + thisYear.format())
}
thisYear.add(1,'days');
}
}
checkHolidaysInMonth(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.27.0/moment.min.js"></script>

Okay, I have figured out the solution. I had been using another method in my code which would find the last working date. This involved some subtraction of date. This should have been isolated to the method, but since momentjs mutates the original moment, it caused a side effect whenever that function was called.
So if ever you are manipulating an argument, please use the clone() method (see docs) on it before you do the operation.
e.g. moment().clone()

Related

given a date and a cron expression can you validate that the date is part of the cron expression

I have a cron expression and i need to check if a certain Date is part of it.(meaning the cron would trigger at that Date)
(using nodejs)
You can use cron-parser package and do something like this:
const isDateMatchesCronExpression = (expression, date, scope = 'second') => {
scope = ['second', 'minute', 'hour', 'day', 'month', 'weekday'].indexOf(scope.toLowerCase());
try {
const data = cronParser.parseExpression(expression).fields;
if (scope <= 0 && !data.second.includes(date.getSeconds())) return false;
if (scope <= 1 && !data.minute.includes(date.getMinutes())) return false;
if (scope <= 2 && !data.hour.includes(date.getHours())) return false;
if (scope <= 3 && !data.dayOfMonth.includes(date.getDate())) return false;
if (scope <= 4 && !data.month.includes(date.getMonth() + 1)) return false;
if (scope <= 5 && !data.dayOfWeek.includes(date.getDay())) return false;
return true;
} catch (e) {
throw new Error(`isDateMatchesCronExpression error: ${e}`);
}
};

facing typescript issue var editIndex: any Binding element 'editIndex' implicitly has an 'any' type

Here I am showing some error on my datepicker file this is react application and i am trying to using typescript but few error I am getting
Property 'contains' does not exist on type 'ReactInstance'.
var editIndex: any Binding element 'editIndex' implicitly has an 'any' type
findClosestSeparatorFieldIndex({ value, editIndex }) {
const partialValue = value.substr(0, editIndex + 1);
let numSeparators = partialValue.match(new RegExp(SEPARATOR, "g"));
if (numSeparators) {
// FIELD index from zero (['DD', 'MM', 'YYYY'])
return numSeparators.length - 1;
}
Error showing
any Binding element 'editIndex' implicitly has an 'any' type
return null;
}
let resolvedDate = null;
if (day && month && year) {
resolvedDate = moment([year, month - 1, day]);
if (!resolvedDate.isValid()) {
errors.push("Invalid");
// console.log(resolvedDate);
}
}
Error showing
let month: string
The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.ts(2362)
this.setState({ value, hint, errors });
this.props.onChange({
day: parseInt(day, 10),
month: parseInt(month, 10) - 1,
year: parseInt(year, 10),
value,
resolvedDate
});
Error Showing
(property) resolvedDate: string
Type 'Moment | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.ts(2322)
DateField.tsx(31, 3): The expected type comes from property 'resolvedDate' which is declared here on type 'OnChangePropType'
let resolvedDate = null;
if (day && month && year) {
resolvedDate = moment([year, month - 1, day]);
if (!resolvedDate.isValid()) {
errors.push("Invalid");
// console.log(resolvedDate);
}
}
error Showing
let month: string
The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
here I am showing my file code
code
import _ from "lodash";
import moment from "moment";
import React, { Component } from "react";
import "./DateField.scss";
const SEPARATOR = "-";
const POSSIBLE_SEPARATORS = ["-", "/", " ", String.fromCharCode(13)];
const FIELDS = [
{
label: "DD",
pad: "0"
},
{
label: "MM",
pad: "0"
},
{
label: "YYYY",
pad: moment().year()
}
];
const HINT = FIELDS.map((field) => field.label).join(SEPARATOR);
const MAX_LENGTH = HINT.length;
type OnChangePropType = {
day: number;
month: number;
year: number;
value: string;
resolvedDate: string;
};
interface IProps {
value: string;
onChange: (param: OnChangePropType) => void;
}
interface IState {
value: string;
errors: Array<Object>;
hint: string;
}
interface Dimensions { value: any, editIndex: any }
export class DateField extends Component<IProps, IState> {
static defaultProps = {
onChange: () => {}
};
constructor(props:IProps) {
super(props);
this.state = {
value: props.value || "",
hint: HINT,
errors: []
};
}
/**
* Find difference between words (i.e. insertion or edit point)
* #param {*} value
* #param {*} lastValue
*/
findDifference(value:any, lastValue:any) {
for (let i = 0; i < value.length && i < lastValue.length; i++) {
if (value[i] !== lastValue[i]) {
return i;
}
}
return value.length - 1;
}
findClosestSeparatorFieldIndex({ value, editIndex }) {
const partialValue = value.substr(0, editIndex + 1);
let numSeparators = partialValue.match(new RegExp(SEPARATOR, "g"));
if (numSeparators) {
// FIELD index from zero (['DD', 'MM', 'YYYY'])
return numSeparators.length - 1;
}
return null;
}
componentDidUpdate(prevProp:IProps){
const { value } = this.props;
if(prevProp.value !== value && value) {
this.handleDate(value);
}
}
handleDate(value: string) {
const inputField = this.refs.input;
// const caretStart = inputField.selectionStart;
// const caretEnd = inputField.selectionEnd;
// e.preventDefault();
let { hint } = this.state;
// let value = val;
console.log("value", value);
const errors = [];
// swap all possible separators for correct one
value = value.replace(
new RegExp(`[${POSSIBLE_SEPARATORS.join("")}]`, "g"),
SEPARATOR
);
// remove non-valid chars (not sep or digit)
value = value.replace(new RegExp(`[^${SEPARATOR}0-9]`, ""), "");
let editIndex = this.findDifference(value, this.state.value);
let fieldToCompleteIndex = null;
// find attempts at splitting
if (value.charAt(editIndex) === SEPARATOR) {
// const allSeparators = new RegExp(SEPARATOR, 'g').exec(value);
// console.log('all', allSeparators);
// const closestSeparator = _.find(allSeparators, (match, i) => {
// return editIndex < match.index ? i : false;
// });
fieldToCompleteIndex = this.findClosestSeparatorFieldIndex({
value,
editIndex
});
// console.log(fieldToCompleteIndex);
// if (editIndex >
// if (editIndex < 2) {
// completeComponent = 'day';
// }
// console.log(editIndex, 'YES');
}
// fix value by removing non-digits
value = value.replace(/[^0-9]/g, "");
const maxLength = HINT.replace(SEPARATOR, "").length;
// size limit
if (value.length > maxLength) {
value = value.substr(0, maxLength);
}
// split into fields
let day = value.substr(0, 2);
let month = value.substr(2, 2);
let year = value.substr(4, 4);
// const resolvedDate = this.resolveDate({ day, month, year })
// console.log(resolvedDate);
if (fieldToCompleteIndex === 0) {
day = this.completeField(day, fieldToCompleteIndex);
}
if (fieldToCompleteIndex === 1) {
month = this.completeField(month, fieldToCompleteIndex);
}
if (fieldToCompleteIndex === 2) {
year = this.completeField(year, fieldToCompleteIndex);
}
// editIndex++;
let resolvedDate = null;
if (day && month && year) {
resolvedDate = moment([year, month - 1, day]);
if (!resolvedDate.isValid()) {
errors.push("Invalid");
// console.log(resolvedDate);
}
}
value =
day +
(month || fieldToCompleteIndex === 0 ? SEPARATOR + month : "") +
(year || fieldToCompleteIndex === 1 ? SEPARATOR + year : "");
// edit hint to remove replaced chars
hint = HINT.substr(value.length);
this.setState({ value, hint, errors });
this.props.onChange({
day: parseInt(day, 10),
month: parseInt(month, 10) - 1,
year: parseInt(year, 10),
value,
resolvedDate
});
console.log(
"caretStart",
caretStart,
"caretEnd",
caretEnd,
"editIndex",
editIndex
);
}
change(e: React.ChangeEvent<HTMLInputElement>) {
this.handleDate(e.target.value.toString());
// requestAnimationFrame(() => {
// inputField.selectionStart = editIndex;
// inputField.selectionEnd = editIndex;
// });
}
// resolveDate({ day, month, year }) {
// const today = moment();
// day = parseInt(day) || 1;
// month = parseInt(month) || 0;
// year = parseInt(year) || today.year();
// let resolvedDate = moment([year, month, day]);
// console.log(resolvedDate);
// // if (parseInt(day) > ) {
// // day = 1;
// // }
// if (!month || parseInt(month) === 0) {
// month = today.month();
// }
// return {
// day,
// month,
// year,
// };
// }
completeField(value, fieldIndex) {
return _.padStart(
value,
FIELDS[fieldIndex].label.length,
FIELDS[fieldIndex].pad
);
}
render() {
const { value, hint, errors } = this.state;
return (
<div>
<div className="field">
<div className="field-hint">
<span className="hint-filled">{value}</span>
{hint}
</div>
<input
className="field-input"
onChange={(e) => this.change(e)}
value={value}
ref="input"
/>
</div>
<div className="field-errors">
{errors.map((error: React.ReactNode, i: number) => (
<div className="field-errors-item" key={i}>
{error}
</div>
))}
</div>
</div>
);
}
}
Project link https://codesandbox.io/s/cold-night-nqvyy

Regex to extract date range from a long string

This is the whole string
Now that your RV has been booked, we will deposit $152.00 into your account 24 hours after #### picks up your RV.
We will also hold the $500.00 security deposit to cover any unforseen costs.
Booking Details
Tab Teardrop - Hip Modern Travel Trailer
Trailer - Sleeps 2
Friday, July 31, 2020 - Sunday, August 2, 2020
Pickup Details
Friday, July 31, 2020
I want to extract only this part using regular expression.
Tab Teardrop - Hip Modern Travel Trailer
Trailer - Sleeps 2
Friday, July 31, 2020 - Sunday, August 2, 2020
Sometimes you just gotta write your own parser.
A regular expression would be too complicated for this task.
const originalText = document.querySelector('.hidden').value.trim();
const parseInvoice = (input, target) => {
const lines = input.split('\n'), buffer = [];
let isActive = false, line;
for (let i = 0; i < lines.length; i++) {
line = lines[i];
if (line === target) {
isActive = true;
continue;
}
if (isActive && line.trim().length === 0) {
break;
}
if (isActive) {
buffer.push(line);
}
}
return buffer.join('\n');
};
console.log(parseInvoice(originalText, 'Booking Details'));
.hidden { display: none; }
.as-console-wrapper { top: 0; max-height: 100% !important; }
<textarea class="hidden">
Now that your RV has been booked, we will deposit $152.00 into your account 24 hours after #### picks up your RV.
We will also hold the $500.00 security deposit to cover any unforeseen costs.
Booking Details
Tab Teardrop - Hip Modern Travel Trailer
Trailer - Sleeps 2
Friday, July 31, 2020 - Sunday, August 2, 2020
Pickup Details
Friday, July 31, 2020
</textarea>
You could simplify the expression to this.
const parseInvoice = (input, target) => {
let isActive = false;
return input.split('\n').filter(line => {
if (line === target) { isActive = true; return false; }
if (isActive && line.trim().length === 0) { isActive = false; return false; }
return isActive;
}).join('\n');
};
If you want to only scan until the first empty line, you will need a another flag, but keep in mind that filter will scan every line. If you only want to scan the minimum number of lines, you should use Array.prototype.some or Array.prototype.every.
const parseInvoice = (input, target) => {
let isActive = false, done = false;
return input.split('\n').filter(line => {
if (done) return false;
if (line === target) {
isActive = true; return false;
}
if (isActive && line.trim().length === 0) {
isActive = false; done = true; return false;
}
return isActive;
}).join('\n');
};
If you want to match using a regular expression, you could modify the function to take start and an end expression. There are so many ways you could implement this. It's up to you to decide.
const parseInvoice = (input, startExpr, endExpr, keepMatches) => {
const lines = input.split('\n'), buffer = [];
let isActive = false, line;
for (let i = 0; i < lines.length; i++) {
line = lines[i];
if (line.match(startExpr)) {
isActive = true;
if (keepMatches) buffer.push(line);
continue;
}
if (isActive && line.match(endExpr)) {
if (keepMatches) buffer.push(line);
break;
}
if (isActive) {
buffer.push(line);
}
}
return buffer.join('\n');
};
console.log(parseInvoice(originalText, /^Booking Details$/, /^$/, true));

Convert JSON data into new JSON output

I'm trying to write a script to output JSON according to these constraints.
So far I think my logic is correct.
Any help is appreciated.
CURRENT ISSUES:
[working now]I can't figure out why duration continues to return 0
[working now]how to tackle setting the max/min
tackling how to handle when two excursions of different types occur back to back (“hot” ⇒ “cold” or “cold” ⇒ “hot”)
This is how each new object should appear
let current_excursion = {
'device_sensor' : '',
'start_at' : [],
'stop_at' : 0,
'duration' : 0,
'type': '',
'max/min':0
}
device_sensor
The sId this excursion was detected on.
start_at
The date and time the temperature first is out of range in ISO_8601 format.
stop_at
The date and time the temperature is back in range in ISO_8601 format.
duration
The total time in seconds the temperature was out of range.
type
Either the string “hot” or “cold” depending on the type of excursion.
max/min
The temperature extreme for the excursion. For a “hot” excursion this will be the max and for a “cold” excursion the min.
A temperature excursion event starts
when the temperature goes out of range and ends when the temperature returns
to the range.
For a “hot” excursion this is when the temperature is greater than 8 °C,
and for a “cold” excursion this is when the temperature is less than 2 °C.
If two excursions of different types occur back to back
(“hot” ⇒ “cold” or “cold” ⇒ “hot”) please take the midpoint of the two
timestamps as the end of the first excursion and the start of the second.
If an excursion is occurring at the end of the temperature readings
please end the excursion at the last reading (duration = 0)
Here is the link to the test data
Test Case Data
Here is what I've written so far:
const tempTypeTernary = (num) =>{
if(num < 2){
return 'cold'
} else if(num > 8){
return 'hot'
}
}
const excursion_duration = (x,y) =>{
let start = new Date(x) / 1000
let end = new Date(y) / 1000
return end - start
}
const reset_excursion = (obj) => {
Object.keys(obj).map(key => {
if (obj[key] instanceof Array) obj[key] = []
else obj[key] = ''
})
}
const list_excursion = (array) =>{
let result = [];
let max_min_excursion = 0;
let current_excursion = {
'device_sensor' : '',
'start_at' : [],
'stop_at' : 0,
'duration' : 0,
'type': '',
'max/min':0
}
for(let k = 0; k < array.length;k++){
if( array[k]['tmp'] < 2 || array[k]['tmp'] > 8){
current_excursion['device_sensor'] = array[k]['sId'];
current_excursion['start_at'] = [new Date(array[k]['time']).toISOString(),array[k]['time']];
current_excursion['type'] = tempTypeTernary(array[k]['tmp']);
if( array[k]['tmp'] > 2 || array[k]['tmp'] < 8){
current_excursion['stop_at'] = new Date(array[k]['time']).toISOString();
current_excursion['duration'] = excursion_duration(current_excursion['start_at'][1],array[k]['time'])
}
result.push(current_excursion)
reset_excursion(current_excursion)
}
}
return result
}
list_excursion(json)
Let me be bold and try to answer on just eyeballing the code; please tryout this:
const tempTypeTernary = (num) =>{
if(num < 2){
return 'cold'
} else if(num > 8){
return 'hot'
}
}
const excursion_duration = (x,y) =>{
let start = new Date(x) / 1000
let end = new Date(y) / 1000
return end - start
}
const reset_excursion = (obj) => {
Object.keys(obj).map(key => {
if (obj[key] instanceof Array) obj[key] = []
else obj[key] = ''
})
}
const list_excursion = (array) =>{
let result = [];
let max_min_excursion = 0;
let current_excursion = {
'device_sensor' : '',
'start_at' : [],
'stop_at' : 0,
'duration' : 0,
'type': '',
'max/min':0
}
for(let k = 0; k < array.length;k++){
if( array[k]['tmp'] < 2 || array[k]['tmp'] > 8)
{
if (current_excursion['type']==null)
{
current_excursion['device_sensor'] = array[k]['sId'];
current_excursion['start_at'] = [new Date(array[k]['time']).toISOString(),array[k]['time']];
current_excursion['type'] = tempTypeTernary(array[k]['tmp'])
}
}
else // this is where the second 'if' was
{
if (current_excursion['type']!=null)
{
current_excursion['stop_at'] = new Date(array[k]['time']).toISOString();
current_excursion['duration'] = excursion_duration(current_excursion['start_at'][1],array[k]['time'])
result.push(current_excursion)
reset_excursion(current_excursion)
}
}
}

Getting Total Seconds From Date Timestamps

I am trying to get the total seconds from 2 timestamps. I am currently getting the total days, hours and minutes, but I am trying to get all the way to the second. I am unable to find the formula...
Can someone point me in the right direction of getting the seconds too, along with the days, hours and minutes that I have already accomplished.
Here is my code thus far:
//gets timestamps
var clockedIn = "2017-03-02 09:45:25";
var clockedOut = "2017-03-04 09:49:06";
//sets timestamps to vars
var now = clockedIn;
var then = clockedOut;
var diff = moment.duration(moment(then).diff(moment(now)));
//parses out times
var days = parseInt(diff.asDays());
var hours = parseInt(diff.asHours());
hours = (hours - days * 24);
var minutes = parseInt(diff.asMinutes());
minutes = minutes - (days * 24 * 60 + hours * 60);
//I am looking to get seconds here...
Any help would be appreciated, even if it is just a link.
Thanks
One simple solution is to create two Date objects and get the difference between them.
var clockedIn = new Date("2017-03-02 09:45:25");
var clockedOut = new Date("2017-03-04 09:49:06");
var seconds = (clockedOut-clockedIn)/1000
// divide by 1000 because the difference we get will be in milliseconds.
Do you mean this function? seconds()
There's a nice plugin for momentjs available on github that makes this kind of formating very nice, as you can simply specify a format for your duration. https://github.com/jsmreese/moment-duration-format
var clockedIn = "2017-03-02 09:45:25";
var clockedOut = "2017-03-04 09:49:06";
//sets timestamps to vars
var now = clockedIn;
var then = clockedOut;
var diff = moment.duration(moment(then).diff(moment(now)));
console.log(diff.format("d [days], H [hours], m [minutes] [and] s [seconds] "));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js"></script>
<script>
/*! Moment Duration Format v1.3.0
* https://github.com/jsmreese/moment-duration-format
* Date: 2014-07-15
*
* Duration format plugin function for the Moment.js library
* http://momentjs.com/
*
* Copyright 2014 John Madhavan-Reese
* Released under the MIT license
*/
(function (root, undefined) {
// repeatZero(qty)
// returns "0" repeated qty times
function repeatZero(qty) {
var result = "";
// exit early
// if qty is 0 or a negative number
// or doesn't coerce to an integer
qty = parseInt(qty, 10);
if (!qty || qty < 1) { return result; }
while (qty) {
result += "0";
qty -= 1;
}
return result;
}
// padZero(str, len [, isRight])
// pads a string with zeros up to a specified length
// will not pad a string if its length is aready
// greater than or equal to the specified length
// default output pads with zeros on the left
// set isRight to `true` to pad with zeros on the right
function padZero(str, len, isRight) {
if (str == null) { str = ""; }
str = "" + str;
return (isRight ? str : "") + repeatZero(len - str.length) + (isRight ? "" : str);
}
// isArray
function isArray(array) {
return Object.prototype.toString.call(array) === "[object Array]";
}
// isObject
function isObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
// findLast
function findLast(array, callback) {
var index = array.length;
while (index -= 1) {
if (callback(array[index])) { return array[index]; }
}
}
// find
function find(array, callback) {
var index = 0,
max = array.length,
match;
if (typeof callback !== "function") {
match = callback;
callback = function (item) {
return item === match;
};
}
while (index < max) {
if (callback(array[index])) { return array[index]; }
index += 1;
}
}
// each
function each(array, callback) {
var index = 0,
max = array.length;
if (!array || !max) { return; }
while (index < max) {
if (callback(array[index], index) === false) { return; }
index += 1;
}
}
// map
function map(array, callback) {
var index = 0,
max = array.length,
ret = [];
if (!array || !max) { return ret; }
while (index < max) {
ret[index] = callback(array[index], index);
index += 1;
}
return ret;
}
// pluck
function pluck(array, prop) {
return map(array, function (item) {
return item[prop];
});
}
// compact
function compact(array) {
var ret = [];
each(array, function (item) {
if (item) { ret.push(item); }
});
return ret;
}
// unique
function unique(array) {
var ret = [];
each(array, function (_a) {
if (!find(ret, _a)) { ret.push(_a); }
});
return ret;
}
// intersection
function intersection(a, b) {
var ret = [];
each(a, function (_a) {
each(b, function (_b) {
if (_a === _b) { ret.push(_a); }
});
});
return unique(ret);
}
// rest
function rest(array, callback) {
var ret = [];
each(array, function (item, index) {
if (!callback(item)) {
ret = array.slice(index);
return false;
}
});
return ret;
}
// initial
function initial(array, callback) {
var reversed = array.slice().reverse();
return rest(reversed, callback).reverse();
}
// extend
function extend(a, b) {
for (var key in b) {
if (b.hasOwnProperty(key)) { a[key] = b[key]; }
}
return a;
}
// define internal moment reference
var moment;
if (typeof require === "function") {
try { moment = require('moment'); }
catch (e) {}
}
if (!moment && root.moment) {
moment = root.moment;
}
if (!moment) {
throw "Moment Duration Format cannot find Moment.js";
}
// moment.duration.format([template] [, precision] [, settings])
moment.duration.fn.format = function () {
var tokenizer, tokens, types, typeMap, momentTypes, foundFirst, trimIndex,
args = [].slice.call(arguments),
settings = extend({}, this.format.defaults),
// keep a shadow copy of this moment for calculating remainders
remainder = moment.duration(this);
// add a reference to this duration object to the settings for use
// in a template function
settings.duration = this;
// parse arguments
each(args, function (arg) {
if (typeof arg === "string" || typeof arg === "function") {
settings.template = arg;
return;
}
if (typeof arg === "number") {
settings.precision = arg;
return;
}
if (isObject(arg)) {
extend(settings, arg);
}
});
// types
types = settings.types = (isArray(settings.types) ? settings.types : settings.types.split(" "));
// template
if (typeof settings.template === "function") {
settings.template = settings.template.apply(settings);
}
// tokenizer regexp
tokenizer = new RegExp(map(types, function (type) {
return settings[type].source;
}).join("|"), "g");
// token type map function
typeMap = function (token) {
return find(types, function (type) {
return settings[type].test(token);
});
};
// tokens array
tokens = map(settings.template.match(tokenizer), function (token, index) {
var type = typeMap(token),
length = token.length;
return {
index: index,
length: length,
// replace escaped tokens with the non-escaped token text
token: (type === "escape" ? token.replace(settings.escape, "$1") : token),
// ignore type on non-moment tokens
type: ((type === "escape" || type === "general") ? null : type)
// calculate base value for all moment tokens
//baseValue: ((type === "escape" || type === "general") ? null : this.as(type))
};
}, this);
// unique moment token types in the template (in order of descending magnitude)
momentTypes = intersection(types, unique(compact(pluck(tokens, "type"))));
// exit early if there are no momentTypes
if (!momentTypes.length) {
return pluck(tokens, "token").join("");
}
// calculate values for each token type in the template
each(momentTypes, function (momentType, index) {
var value, wholeValue, decimalValue, isLeast, isMost;
// calculate integer and decimal value portions
value = remainder.as(momentType);
wholeValue = (value > 0 ? Math.floor(value) : Math.ceil(value));
decimalValue = value - wholeValue;
// is this the least-significant moment token found?
isLeast = ((index + 1) === momentTypes.length);
// is this the most-significant moment token found?
isMost = (!index);
// update tokens array
// using this algorithm to not assume anything about
// the order or frequency of any tokens
each(tokens, function (token) {
if (token.type === momentType) {
extend(token, {
value: value,
wholeValue: wholeValue,
decimalValue: decimalValue,
isLeast: isLeast,
isMost: isMost
});
if (isMost) {
// note the length of the most-significant moment token:
// if it is greater than one and forceLength is not set, default forceLength to `true`
if (settings.forceLength == null && token.length > 1) {
settings.forceLength = true;
}
// rationale is this:
// if the template is "h:mm:ss" and the moment value is 5 minutes, the user-friendly output is "5:00", not "05:00"
// shouldn't pad the `minutes` token even though it has length of two
// if the template is "hh:mm:ss", the user clearly wanted everything padded so we should output "05:00"
// if the user wanted the full padded output, they can set `{ trim: false }` to get "00:05:00"
}
}
});
// update remainder
remainder.subtract(wholeValue, momentType);
});
// trim tokens array
if (settings.trim) {
tokens = (settings.trim === "left" ? rest : initial)(tokens, function (token) {
// return `true` if:
// the token is not the least moment token (don't trim the least moment token)
// the token is a moment token that does not have a value (don't trim moment tokens that have a whole value)
return !(token.isLeast || (token.type != null && token.wholeValue));
});
}
// build output
// the first moment token can have special handling
foundFirst = false;
// run the map in reverse order if trimming from the right
if (settings.trim === "right") {
tokens.reverse();
}
tokens = map(tokens, function (token) {
var val,
decVal;
if (!token.type) {
// if it is not a moment token, use the token as its own value
return token.token;
}
// apply negative precision formatting to the least-significant moment token
if (token.isLeast && (settings.precision < 0)) {
val = (Math.floor(token.wholeValue * Math.pow(10, settings.precision)) * Math.pow(10, -settings.precision)).toString();
} else {
val = token.wholeValue.toString();
}
// remove negative sign from the beginning
val = val.replace(/^\-/, "");
// apply token length formatting
// special handling for the first moment token that is not the most significant in a trimmed template
if (token.length > 1 && (foundFirst || token.isMost || settings.forceLength)) {
val = padZero(val, token.length);
}
// add decimal value if precision > 0
if (token.isLeast && (settings.precision > 0)) {
decVal = token.decimalValue.toString().replace(/^\-/, "").split(/\.|e\-/);
switch (decVal.length) {
case 1:
val += "." + padZero(decVal[0], settings.precision, true).slice(0, settings.precision);
break;
case 2:
val += "." + padZero(decVal[1], settings.precision, true).slice(0, settings.precision);
break;
case 3:
val += "." + padZero(repeatZero((+decVal[2]) - 1) + (decVal[0] || "0") + decVal[1], settings.precision, true).slice(0, settings.precision);
break;
default:
throw "Moment Duration Format: unable to parse token decimal value.";
}
}
// add a negative sign if the value is negative and token is most significant
if (token.isMost && token.value < 0) {
val = "-" + val;
}
foundFirst = true;
return val;
});
// undo the reverse if trimming from the right
if (settings.trim === "right") {
tokens.reverse();
}
return tokens.join("");
};
moment.duration.fn.format.defaults = {
// token definitions
escape: /\[(.+?)\]/,
years: /[Yy]+/,
months: /M+/,
weeks: /[Ww]+/,
days: /[Dd]+/,
hours: /[Hh]+/,
minutes: /m+/,
seconds: /s+/,
milliseconds: /S+/,
general: /.+?/,
// token type names
// in order of descending magnitude
// can be a space-separated token name list or an array of token names
types: "escape years months weeks days hours minutes seconds milliseconds general",
// format options
// trim
// "left" - template tokens are trimmed from the left until the first moment token that has a value >= 1
// "right" - template tokens are trimmed from the right until the first moment token that has a value >= 1
// (the final moment token is not trimmed, regardless of value)
// `false` - template tokens are not trimmed
trim: "left",
// precision
// number of decimal digits to include after (to the right of) the decimal point (positive integer)
// or the number of digits to truncate to 0 before (to the left of) the decimal point (negative integer)
precision: 0,
// force first moment token with a value to render at full length even when template is trimmed and first moment token has length of 1
forceLength: null,
// template used to format duration
// may be a function or a string
// template functions are executed with the `this` binding of the settings object
// so that template strings may be dynamically generated based on the duration object
// (accessible via `this.duration`)
// or any of the other settings
template: function () {
var types = this.types,
dur = this.duration,
lastType = findLast(types, function (type) {
return dur._data[type];
});
// default template strings for each duration dimension type
switch (lastType) {
case "seconds":
return "h:mm:ss";
case "minutes":
return "d[d] h:mm";
case "hours":
return "d[d] h[h]";
case "days":
return "M[m] d[d]";
case "weeks":
return "y[y] w[w]";
case "months":
return "y[y] M[m]";
case "years":
return "y[y]";
default:
return "y[y] M[m] d[d] h:mm:ss";
}
}
};
})(this);
</script>
You don't have to use any complex method to calculate the difference of the two moment (or Date) objects. Simply calculating the difference gives you the total difference between the two dates in milliseconds.
var now = moment();
var then = moment().add(1231, 'second'); // just to have an example different date
var milliseconds = +then - +now;
var seconds = milliseconds / 1000;
var minutes = seconds / 60;
var hours = minutes / 60;
If you would like to get the difference formatted like HH:mm:ss, just convert the diff milliseconds back to moment object and use .format():
var now = moment();
var then = moment().add(1231, 'second'); // just to have an example different date
var diff_m = moment(+then - +now);
console.log(diff_m.format('HH:mm:ss'));

Categories