How to Validate date field in Apigee Edge - apigee

I want to validate a date field in Apigee and raise fault if the date is not of specific format.
Example: I want to pass a date and check if the date is of the given format "mm/dd/yyyy". If the date is not if this format I need to raise a fault.

To do this, you'll need to use a script callout. I'd recommend javascript for simplicity's sake. Assuming you already extracted the date using an ExtractVariables policy, a simple validation might look like:
var date = context.getVariable("date").split("/");
date.map(function(d) { return parseInt(d) });
if (date.length < 3) {
// raise fault
} else {
if (date[0] >= 1 && date[0] <= 12 &&
date[1] >= 1 && date[1] <= 31 &&
date[0] >= 1970 && date[0] <= 3000) {
// probably a good date
} else {
//raise fault
}
}
Now, that written, it doesn't actually validate months to number of days or leap years or anything fancy. If you need it pinpoint accurate, I'd recommend using Moment.js as an included resource on the javascript callout.
Keep in mind too that this is a quickly written example, and isn't robust enough to handle an assortment of bad inputs. I'd recommend using a try/catch block at the very least.

Related

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

Time subtraction in Aurelia

I would like to print the duration of an event that occurs between 'startDateTime' and 'endDateTime', expressed in minutes or seconds (if less than 1 minute).
In other words, ${startDateTime | dateFormat:"YYYY-MM-DD HH:mm"} is 2018-09-07 11:57 and ${startDateTime | dateFormat:"YYYY-MM-DD HH:mm"} is 2018-09-07 13:00.
What I would like to print is 63 minutes.
In PHP, I would do ->getTimestamp(), but in Aurelia I have no clue what to even try.
I did test with something like ${endDateTime| dateFormat:"HH:mm:ss" - startDateTime| dateFormat:"HH:mm:ss"} but this can't work as it doesn't convert the entire date time to seconds or minutes...
Therefore, is there a clean solution I can implement in my view?
I solved it using a value converter.
import moment = require("moment");
export class DurationValueConverter {
public toView(startAt, endAt) {
if (!endAt) {
// If end date is missing, use the current date and time.
endAt = moment();
}
const duration = moment.duration(moment(endAt).diff(moment(startAt)));
return duration.humanize();
}
}
Usage: ${startedAt | duration:endedAt}
What you want to have is relative time, It's on its way to browsers, but for now, you will have to use polyfill / library for it. One you can find is from yahoo: https://github.com/yahoo/intl-relativeformat

Get cell types when reading and parsing excel files

I am trying to read and parse and excel and some unclear things come into play as usual for me.
Here is what i have:
while (true)
{
comVariantCell1 = cells.item(row, 1).value().variantType();
comVariantCell2 = cells.item(row, 2).value().variantType();
//if an empty cell is found, processing will stop and user will get an error message in order to solve the inconsistency.
if (comVariantCell1 != COMVariantType::VT_EMPTY && comVariantCell2 != COMVariantType::VT_EMPTY)
{
//both cells have values, check their types.
importedLine = conNull();
progress1.setText(strfmt("Importing row %1", row));
if (cells.item(row, 1).value().variantType() == COMVariantType::VT_BSTR)
{
importedLine += cells.item(row, 1).value().bStr();
}
else
{
importedLine += cells.item(row, 1).value().double();
}
importedLine += cells.item(row, 2).value().double();
importedLinesCollection += [importedLine]; //conIns(importedLinesCollection, row - 1, (importedLine));
row++;
}
else
{
info (strFmt("Empty cell found at line %1 - import will not continue and no records were saved.", row));
break;
}
}
Excel format:
Item number Transfer Qty
a100 50.5
a101 10
a102 25
This worked well to check if the cell type is string: COMVariantType::VT_BSTR
but what should i use to check for a real or integer value ?
I am pretty sure in this case, the quantity will be not contain real values but anyway, it could be useful in the future to make the difference between these two types.
I have to mention that, even if i have an int value and I use cells.item(row, 1).value().int() it won't work. I can't see why.
Why do i want to make the difference? Because if it's forbidden to have real values in the quantity column ( at least in my case ), i want to check that and give the user the opportunity to put a correct value in that place and maybe further investigate why that happened to be there.
Take a look on how it is done in \Classes\SysDataExcelCOM\readRow.
It is basically using switch to test the type. This is really boring!
Also take a look on ExcelIO, a class I made some years ago. It reads Excel and returns each row as a container. This is a more high-level approach.
As a last resort you could save the Excel as a tab separated file. Then use TextIO to read the content. This will be at least 10 times faster than using Excel!

Regex verification correct birth date and check age

I need a regex which takes the string YYYY-MM-DD-XXXX (The last 4 are just for purpose of gender/area) It's mostly important to check the first 8 Digits for a valid birth date.
So far i have this:
/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})\-([0-9]{4})$/
Also i want to check so the input age is at least 18 years old. Would appreciate if somone had some input on how to achieve this.
Edit: The regex above was tested in JS, but should work fine in ASP as well?
I have changed your regex a bit to make it look more authentic
^([1-2]\d{3})\-([0-1][1-9])\-([0-3][0-9])\-([0-9]{4})$
years like 3012 will not pass.
Now you want to find whether a person is 18 years or not.
One approach could be to find the difference between the years of dates provided like this
var str = '1990-09-12-5555';
var res = /^([1-2]\d{3})\-([0-1][1-9])\-([0-3][0-9])\-([0-9]{4})$/.exec(str);
var year_now = new Date().getFullYear();
console.log(year_now-res[1]);
a second approach will be more precise one :
var str = '1990-09-12-5555';
var res = /^([1-2]\d{3})\-([0-1][1-9])\-([0-3][0-9])\-([0-9]{4})$/.exec(str);
var todays_date = new Date();
var birth_date = new Date(res[1],res[2],res[3]);
console.log(todays_date-birth_date);
will output the result in milliseconds. You can do the math to convert it into year
Cheers , Hope that helps !
I suggest using moment.js which provides an easy to use method for doing this.
interactive demo
function validate(date){
var eighteenYearsAgo = moment().subtract("years", 18);
var birthday = moment(date);
if (!birthday.isValid()) {
return "invalid date";
}
else if (eighteenYearsAgo.isAfter(birthday)) {
return "okay, you're good";
}
else {
return "sorry, no";
}
}
To include moment in your page, you can use CDNJS:
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.4.0/moment.min.js"></script>
Source
The following will match any year with a valid day/month combination, but won't do validation such as checking you've not entered 31 days for February.
^[0-9]{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])\-[0-9]{4}$
Not sure exactly what you're trying to achieve but I'd suggest using a date library for this sort of thing. You could return a message to the user somehow if the entered date fails to parse into an object.
In order to do age validation, you will certainly need to use a library so a regex should only be used for date validation purposes

Javascript Date Localization

I'm working with an ASP.NET app with localization and globalization. I'm having some difficulty understanding how to get the Date() function in javascript to work properly given the user's environment. My user base is split between Mexico (spanish) and the US (english). Since the Mexico date format is dd/mm/yyyy and the english format is mm/dd/yyyy, the standard Date(strDate) javascript constructor does not work for me.
Does anyone know the best way to handle globalization/localization of a javascript Date value? I have some business rules to enforce like dateA must be 90 days prior to dateB and dateB cannot exceed today.
Take a look at datejs, it handles localization very nicely. It comes with a lot of globalization setups. You just load the globalization setup of your current CultureInfo and datejs takes care of the rest.
Matt Kruse developed a really interesting date library which should help with your particular case.
Here's a snippet of the method you should use for the issue you mentioned:
// ------------------------------------------------------------------
// parseDate( date_string [, prefer_euro_format] )
//
// This function takes a date string and tries to match it to a
// number of possible date formats to get the value. It will try to
// match against the following international formats, in this order:
// y-M-d MMM d, y MMM d,y y-MMM-d d-MMM-y MMM d
// M/d/y M-d-y M.d.y MMM-d M/d M-d
// d/M/y d-M-y d.M.y d-MMM d/M d-M
// A second argument may be passed to instruct the method to search
// for formats like d/M/y (european format) before M/d/y (American).
// Returns a Date object or null if no patterns match.
// ------------------------------------------------------------------
function parseDate(val) {
var preferEuro=(arguments.length==2)?arguments[1]:false;
generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');
monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
var d=null;
for (var i=0; i<checkList.length; i++) {
var l=window[checkList[i]];
for (var j=0; j<l.length; j++) {
d=getDateFromFormat(val,l[j]);
if (d!=0) { return new Date(d); }
}
}
return null;
}
You could use: var a = Date.parseLocale(value, formats);
If you provide no custom formats, this function uses the Sys.CultureInfo.CurrentCulture property to determine the culture value.
You can take a look on: http://msdn.microsoft.com/en-us/library/bb397521.aspx
I wrote an answer to this here. It uses the toLocalString to determine MM/DD/YYY, DD/MM/YYYY,...
https://stackoverflow.com/a/18154195/119741

Resources