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()
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)
}
}
}
I used for my web app this widget but I encountered an issue:
As I read in official docs, I can only implement this format config
'format' => '%-D %!D:день,дней; %H:%M:%S',
But how can I set new plural rules ?
Looks like there is no such possibility in this lib, but you can rewrite function pluralize in countdown.js like this:
function pluralize(format, count) {
var plural = "s", singular = "", plural_2_4 = "s";
if (format) {
format = format.replace(/(:|;|\s)/gi, "").split(/\,/);
if (format.length === 1) {
plural = format[0];
plural_2_4 = format[0];
} else if (format.length === 2){
singular = format[0];
plural = format[1];
plural_2_4 = format[1];
} else {
singular = format[0];
plural_2_4 = format[1];
plural = format[2];
}
}
if (Math.abs(count) % 10 === 1) {
return singular;
} else if (Math.abs(count) % 10 > 1 && Math.abs(count) % 10 < 5) {
return plural_2_4;
} else {
return plural;
}
}
I just added one more break point for Russian grammar. Now, using %!d:день, дня, дней; you will get день for 1, дня for 2 - 4 and дней for all the rest including 0. Hope this helps.
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'));
I would like to know if there is a way (I hope there is one, or I am in trouble :p), to find a date in any kind of string.
Here are some examples to make you understand what I am looking for :
var string1 = 'blabla-test-20140215.dat'
I would need to have access to '20140215'
var string2 = 'blabla_Test.20141212'
I would need to have access to '20141212'
Well, I would like to be able to find a date in a format yyyymmdd in a string, no matter the string.
Thank you if you have any clue, I haven't found anything on internet yet.
EDIT :
There can be other numbers in the string, but always less than 8.
for instance :
var string3 = 'blabla-2526-20141212'
The date I am looking for is always separated from other numbers. I can't have :
var string4 = 'blabla-252620141212'
I just want to find the numbers representing the date (in a format yyyymmdd, for instance in string3 I want 20141212, which refers to : 12/12/2014)
I'm note sure, but if your string contains digits only for date, you can use RexEx ?
may be like this :
var regex = /[0-9]+/;
var string1 ="blabla-test-20140215.dat";
var found = regex.exec(string1);
alert('Found: ' + found);
It is simple enough to extract a sequence of eight numbers from a string with regex, just use something like the following:
var dateString = (/\d{8}/.exec(string) || [])[0];
This will find the first eight-character-long string of numbers in a given string. If no such sequence exists, it will be undefined. You can then use it to create a Date object if necessary.
You should use Regex for this.
re = /(\d{4})(\d{2})(\d{2})/g //create a regex that matches: 4 digits followed by 2 digits followed by 2 digits
///// yyyy mm dd
result = re.exec(string1) //execute the regex
// now, access the different capture groups according to their indexes
console.log(result[1]) // yyyy
console.log(result[2]) // mm
console.log(result[3]) // dd
This solution will check for valid dates (not just any 8 arbitrary numbers):
function hasDate(input) {
var matches = input.match(/(\d{4})(\d{2})(\d{2})/),
indexOf = function (elem, arr) {
var len = arr.length, i;
for (i = 0; i < len; i++) {
if (arr[i] === elem) {
return i;
}
}
return -1;
},
months31 = [1, 3, 5, 7, 8, 10, 12],
idx, isLeapYear,
year, month, day;
if (matches) {
year = parseInt(matches[1]);
month = parseInt(matches[2]);
day = parseInt(matches[3]);
//Check invalid dates from the start
if (month < 1 || month > 12) {
return false;
}
else if (day < 1 || day > 31) {
return false;
}
idx = indexOf(month, months31);
isLeapYear = ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
//Month has 31 days, we are good
if (idx >= 0) {
return true;
}
//Feb - check for leap year
else if (month === 2 && (day <= 28 || (isLeapYear && day <= 29))) {
return true;
}
//All other months
else if (month !== 2 && day <= 30) {
return true;
}
}
//If we got this far, its a bad date
return false;
}
//return true
hasDate('blabla_Test.20141212');
//return false
hasDate('blabla_Test.20140229');
//return true
hasDate('blah_20140228');
//return true
hasDate('blah_20000229');