Saturday, December 27, 2008

Random Integer

Our PHP programmer friends have a function called randInt() which returns a random integer between two specified integers. It's time to level the playing field.


ASP

  1. function randInt(min, max)
  2.     Randomize
  3.     randInt = Int((max - min + 1) * Rnd + min)
  4. end function

View ASP implementation on Snipplr

Saturday, December 20, 2008

strip_tags()

This week we're recreating a PHP function that is extremely important for sanitizing user input. All HTML/ASP/PHP tags are stripped outright; there is no support for a whitelist of allowed tags. A whitelist can be very dangerous without much more rigorous testing to check for script-related exploits. A safer solution would be to force the user to use something like UBB code or Markdown and convert to HTML on the backend.


ASP

  1. function strip_tags(unsafeString)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .Global = true
  6.         .IgnoreCase = true
  7.         .Pattern = "(\<(/?[^\>]+)\>)"
  8.     end with
  9.     strip_tags = regEx.Replace(unsafeString, "")
  10.     set regEx = nothing
  11. end function

View ASP implementation on Snipplr

Saturday, December 13, 2008

isPostBack()

Today's function comes not from PHP, but ASP.NET. We're going to replicate the isPostBack() function from ASP.NET's Page object.


ASP

  1. function isPostBack()
  2.     if uCase(Request.ServerVariables("REQUEST_METHOD")) = "POST" then
  3.         isPostBack = true
  4.     else
  5.         isPostBack = false
  6.     end if
  7. end function

PHP

  1. function isPostBack()
  2. {
  3.     return ($_SERVER['REQUEST_METHOD'] == 'POST');
  4. }

Saturday, December 6, 2008

Summation

This week's function is summation using the Gauss method.


ASP

  1. function sum(x, y)
  2.     sum = (x + y) * ((y - x + 1) / 2)
  3. end function

PHP

  1. function sum($x, $y)
  2. {
  3.     return ($x + $y) * (($y - $x + 1) / 2);
  4. }

Saturday, November 29, 2008

Swatch Internet Time

Back in 1998, a company called Swatch decided to invent a new method of timekeeping. It didn't catch on, but some people embraced it. If you're one of those people, this is for you. We're going to write a function to convert a time value into a Swatch beat. Our PHP programmer friends already have access to this sort of thing through PHP's date() function.


The following code builds upon two other functions previously published here:

IMPORTANT: To return an accurate result, the time value passed in needs to be UTC/GMT (in other words, without offset). There are two ways to achieve this: set your server's time zone to GMT, or use my UTC time function.


ASP

  1. function swatch(someTime)
  2.     swatch = str_pad(floor(((Hour(someTime) * 3600 * 1000) + (Minute(someTime) * 60 * 1000) + (Second(someTime) * 1000)) / 86400), 3, "0", STR_PAD_LEFT)
  3. end function

View ASP implementation on Snipplr

Saturday, November 22, 2008

Mersenne Numbers

Today we're going to write a function to generate Mersenne numbers. This is useful in searching for Mersenne primes.


ASP

  1. function mersenne(x)
  2.     mersenne = 2^x - 1
  3. end function

PHP

  1. function mersenne($x)
  2. {
  3.     return 2^$x - 1;
  4. }

Saturday, November 15, 2008

UTC and Atomic Time

In this special double issue, I'm going to show you how to obtain time values in the GMT timezone, as well as ultra-precise atomic clock time values.


Our PHP programmer friends have a slight advantage against us. Their language is aware of what time zone the server is located in and is capable of doing various things with that information, including returning time values without any timezone offset. ASP has no clue what time zone the server is set for, but we can use XMLHTTP to retrieve a time value from a NIST time server.


ASP

  1. function utcnow()
  2.     dim xmlhttp
  3.     dim response
  4.     ' Server to query datetime from
  5.     Const TimeServer = "http://time.nist.gov:13"
  6.     ' Use XML HTTP object to request web page content
  7.     Set xmlhttp = Server.CreateObject("Microsoft.XMLHTTP")
  8.     xmlhttp.Open "GET", TimeServer, false, "", ""
  9.     xmlhttp.Send
  10.     response = xmlhttp.ResponseText
  11.     set xmlhttp = nothing
  12.     ' Parse UTC date
  13.     utcnow = cDate(mid(response, 11, 2) & "/" & mid(response, 14, 2) & "/" & mid(response, 8, 2) & " " & mid(response, 16, 9))
  14. end function

If you were to compare the value returned by this function and the value returned by the built-in Now() function, you might notice more than just the hour value is different. This could mean you live in one of those funky half-hour-offset timezones, or it could mean that your server's clock is off by a few minutes. If accurate time values are important to you, you need a better Now() function. We can build one on top of the UTCnow() function we just wrote.


ASP

  1. function atomicnow()
  2.     dim utc
  3.     dim offset
  4.     utc = utcnow()
  5.     ' The order of the dates is important here!
  6.     offset = DateDiff("h", utc, now())
  7.     atomicnow = DateAdd("h", offset, utc)
  8. end function

There is expected to be some lag caused by this function, but the order of magnitude should only be milliseconds.


View implementation on Snipplr

Saturday, November 8, 2008

Sinc function

Today we're going to a Sinc function, both normalized and unnormalized. Apparently it's useful in digital signal processing.


ASP

  1. Const M_PI = 3.14159265358979323846
  2. ' Unnormalized sinc function.
  3. function sinc(x)
  4.     sinc = sin(x) / x
  5. end function
  6. ' Normalized sinc function.
  7. ' REQUIRES: constant M_PI
  8. function nsinc(x)
  9.     sinc = sin(M_PI * x) / (M_PI * x)
  10. end function

PHP

  1. // Unnormalized sinc function.
  2. function sinc($x)
  3. {
  4.     return sin($x) / $x;
  5. }
  6. // Normalized sinc function.
  7. function nsinc($x)
  8. {
  9.     return sin(M_PI * $x) / (M_PI * $x);
  10. }

Saturday, November 1, 2008

Nth Day

This week I want to share with you a function that I personally got a lot of use out of. Several years ago I had a situation where there was a calendar with several hundreds recurring events. It was desired to have these events fall on roughly the same day each year. The person responsible for this was spending two or three days at the end of each year planning all the occurrences for the next year on a giant wet-eraseable calendar.


Here's what all the fuss was about: Last year, November 1 was a Thursday. This year, November 1 is a Saturday. Saturday is not a working day for most people, so these events we are scheduling need to fall on week days. Because it fell on a Thursday last year, we'd prefer to have it on a Thursday again this year. This means either pushing it back to October 30 or forward to November 6.


To make the event occur on the same day of the week, we'll store this information in our database and use it to generate the date for a given year. This is called the Nth Day. In the above example, our Nth Day is the first Thursday in November.


ASP

  1. function nthDay(someYear, someMonth, someWeek, someWeekday)
  2.     dim firstDay
  3.     dim someDay
  4.     firstDay = weekday(dateSerial(someYear, someMonth, 1))
  5.     ' Check if the week day of the first day of the month is before or after the given week day.
  6.     if (someWeekday - firstDay) >= 0 then
  7.         someDay = 1 + (someWeekday - firstDay) + ((someWeek - 1) * 7)
  8.     else
  9.         someDay = 1 + (someWeekday - firstDay) + (someWeek * 7)
  10.     end if
  11.     nthDay = dateSerial(someYear, someMonth, someDay)
  12. end function

If developing a system around this concept like I did, it's important to know that some months have a fifth week, depending on how late in the week the first day of the month is. You can't really schedule anything recurring during the fifth week. We kept it open both to give the people conducting the events a little time off, as well as for a place to move an event from the following month in situations where the customer thought it would be too late in the year otherwise.


An idea I had to improve the system, but didn't get around to exploring, was to ignore the months entirely and use the weeks of the year. Nobody wants to schedule anything around Christmas and New Year's anyway, so you don't run into problems around the beginning/end of the year like you do at the beginning/end of a month. Under this system, first Thursday of November becomes Thursday of Week 44. Scheduling a recurring event this way is not only easier, but also more consistent from year to year.


View ASP implementation on Snipplr

Saturday, October 25, 2008

Golden function

The golden function is the upper branch of the hyperbola.


ASP

  1. function gold(x)
  2.     gold = (x + sqr(x^2 + 4)) / 2
  3. end function

PHP

  1. function gold($x)
  2. {
  3.     return ($x + sqrt($x^2 + 4)) / 2;
  4. }

Sorry if this is not exciting stuff. I've got some better stuff coming, but I want to clear out some older stuff that has been waiting a while.


Saturday, October 18, 2008

Levenshtein distance

The Levenshtein distance between two strings is a measurement of similarity. The smaller the distance, the more similar two strings are. Our PHP programmer friends have a function to calculate this distance; we deserve one too.


ASP

  1. function levenshtein(byVal first, byVal second)
  2.     dim distance
  3.     dim truncateLength
  4.     if first = second then
  5.         ' The distance is zero if the strings are identical.
  6.         distance = 0

  7.     else
  8.         ' The distance is at least the difference of the lengths of the two strings.
  9.         distance = abs(len(first) - len(second))
  10.         ' Force the strings to be the same length to prevent overflows.
  11.         truncateLength = ((len(first) + len(second)) - distance) / 2
  12.         first = Left(first, truncateLength)
  13.         second = Left(second, truncateLength)
  14.         ' Compare the corresponding characters in each string.
  15.         for i = 1 to truncateLength
  16.             if Mid(first, i, 1) <> Mid(second, i, 1) then
  17.                 distance = distance + 1
  18.             end if
  19.         next
  20.     end if
  21.     levenshtein = distance
  22. end function

View ASP implementation on Snipplr

Saturday, October 11, 2008

WordCount

PHP has a function called str_word_count() which allows you to count the number of words in a string, and an array or associative array of those words. Unfortunately for us, ASP doesn't support optional parameters, so we can't duplicate all of this functionality in a single function, so we'll just concentrate on the main feature of counting the number of words.


ASP

  1. function WordCount(byVal someString)
  2.     dim position
  3.     dim spaces
  4.     spaces = 1
  5.     someString = trim(someString)
  6.     for position = 1 to len(someString)
  7.         if mid(someString, position, 1) = " " and not mid(someString, position - 1, 1) = " " then
  8.             spaces = spaces + 1
  9.         end if
  10.     next
  11.     WordCount = spaces
  12. end function

View ASP implementation on Snipplr

Saturday, October 4, 2008

Nth Root

Both ASP and PHP have a function that allows you to calculate the square root of a number. What if we wanted the cubic root of a number, or some deeper root? Calculating the root of a number is the same as raising that number to a fractional exponent.


ASP

  1. function root(x, y)
  2.     root = x ^ (1 / y)
  3. end function

PHP

  1. function root($x, $y)
  2. {
  3.     return pow($x, 1/$y);
  4. }

x
the number you want the root of
y
the depth you want to go (2 = square, 3 = cubic, etc.)

Saturday, September 27, 2008

Format a number in scientific notation

Surprisingly, neither ASP nor PHP seem to have a built-in way of formatting a number in scientific notation. Today we're going to change that.


ASP

  1. function formatScientific(someFloat)
  2.     dim power
  3.     power = (someFloat mod 10) - 1
  4.     formatScientific = cStr(cDbl(someFloat) / 10^power) & "e" & cStr(power)
  5. end function

PHP

  1. function formatScientific($someFloat)
  2. {
  3.     $power = ($someFloat % 10) - 1;
  4.     return ($someFloat / pow(10, $power)) . "e" . $power;
  5. }

Sunday, September 21, 2008

Logarithms

ASP's log() function returns the natural logarithm of a number. But what if we want a different base? The same problem exists with handheld calculators, and the same trick we use to get around it there can be used here too.


ASP

  1. function logx(number, base)
  2.     logx = log(number) / log(base)
  3. end function

View ASP implementation on Snipplr

Saturday, September 13, 2008

str_repeat

This week we're duplicating another string function from PHP: str_repeat().


ASP

  1. function str_repeat(input, multiplier)
  2.     dim output
  3.     output = ""
  4.     for i = 1 to multiplier
  5.         output = output & input
  6.     next
  7.     str_repeat = output
  8. end function

View ASP implementation on Snipplr

Saturday, September 6, 2008

str_pad

PHP has a very useful function called str_pad(). It allows you to ensure that a string will conform to a specific length by adding characters of your choice on the end of your choice. A good practical example is when you want a number with leading zeros. ASP doesn't have an equivalent function out of the box, but we're going to write one today; as usual, we'll follow the same syntax to make it easy for those who are already familiar with PHP.


ASP

  1. Const STR_PAD_LEFT = "LEFT"
  2. Const STR_PAD_RIGHT = "RIGHT"
  3. Const STR_PAD_BOTH = "BOTH"
  4. ' Pad a string to a certain length with another string.
  5. function str_pad(input, pad_length, pad_string, pad_type)
  6.     dim output
  7.     dim difference
  8.     output = ""
  9.     difference = pad_length - len(input)
  10.     if difference > 0 then
  11.         select case ucase(pad_type)
  12.         case "LEFT"
  13.             for i = 1 to difference step len(pad_string)
  14.                 output = output & pad_string
  15.             next
  16.             output = right(output & input, pad_length)
  17.         case "BOTH"
  18.             output = input
  19.             for i = 1 to difference step len(pad_string) * 2
  20.                 output = pad_string & output & pad_string
  21.             next
  22.             ' Not sure if it will step far enough when difference is an odd number, so this next block is just in case.
  23.             if len(output) < pad_length then
  24.                 output = output & pad_string
  25.             end if
  26.             output = left(output, pad_length)
  27.         case else
  28.             for i = 1 to difference step len(pad_string)
  29.                 output = output & pad_string
  30.             next
  31.             output = left(input & output, pad_length)
  32.         end select
  33.     else
  34.         output = input
  35.     end if
  36.     str_pad = output
  37. end function

Thinking back to my days as a web developer, this was probably the most reused function out of all I'd ever written.


View ASP implementation on Snipplr

Saturday, August 30, 2008

Roman Numerals, Part 3

Back in March, we wrote a function to turn an arabic number into a Roman numeral. That function had a limitation of numbers smaller than 5000. There are ways of writing Roman numerals larger than 5000, but they are not as well accepted by purists because they evolved during later time periods. Out of respect for the purists, this new function will hinge on the old one, rather than replace it.

In the system we will be using, a bar is placed over the numeral to indicate that it is multipled by 1000:

  • V = 5000
  • X = 10,000
  • L = 50,000
  • C = 100,000
  • D = 500,000
  • M = 1 million

Since there are no Unicode characters for this purpose, we will have to cheat a little bit by using some HTML and CSS.


ASP

  1. function bigroman(ByVal arabic)
  2.     dim thousands
  3.     thousands = Array("", "M", "MM", "MMM", "M(V)", "(V)", "(V)M", "(V)MM", "(V)MMM", "M(X)")
  4.     if arabic >= 10000 then
  5.         bigroman = "(" & roman((arabic - (arabic mod 10000)) / 1000) & ")"
  6.         arabic = arabic mod 10000
  7.     end if
  8.     bigroman = bigroman & thousands((arabic - (arabic mod 1000)) / 1000)
  9.     arabic = arabic mod 1000
  10.     bigroman = bigroman & roman(arabic)
  11.     ' Convert parentheses to <span> tags.
  12.     bigroman = replace(bigroman, "(", "<span style=""text-decoration: overline"">")
  13.     bigroman = replace(bigroman, ")", "</span>")
  14. end function

PHP

  1. function bigroman($arabic)
  2. {
  3.     $thousands = Array("", "M", "MM", "MMM", "M(V)", "(V)", "(V)M", "(V)MM", "(V)MMM", "M(X)");
  4.     if ($arabic >= 10000)
  5.     {
  6.         $bigroman = "(" . roman(($arabic - fmod($arabic, 10000)) / 1000) . ")";
  7.         $arabic = fmod($arabic, 10000);
  8.     }
  9.     $bigroman .= $thousands[($arabic - fmod($arabic, 1000)) / 1000];
  10.     $arabic = fmod($arabic, 1000);
  11.     $bigroman .= roman($arabic);
  12.     // Convert parentheses to <span> tags.
  13.     $bigroman = str_replace("(", "<span style=""text-decoration: overline"">", $bigroman);
  14.     $bigroman = str_replace(")", "</span>", $bigroman);
  15.     return $bigroman;
  16. }

Saturday, August 23, 2008

isValidPostCode

In this final installment of the postal code trilogy, we turn our attention to the United Kingdom. Postal codes in the UK are called postcodes. They are similar to postal codes in Canada in that they contain both letters and numbers, but unlike Canadian postal codes, they are variable in length.


A postcode can have any of the following formats:

  • A9 9AA
  • A99 9AA
  • A9A 9AA
  • AA9 9AA
  • AA99 9AA
  • AA9A 9AA

To match all of these formats, we'll use the following regular expression: [A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))|[0-9][A-HJKS-UW])\ [0-9][ABD-HJLNP-UW-Z]{2}. You may notice that, like Canadian postal codes, certain letters are only allowed in certain positions, or not at all.


There are also a few special cases that are valid postcodes but deviate from the regular format:

  • Girobank - (GIR\ 0AA)
  • Father Christmas - (SAN\ TA1)
  • British Forces Post Office - (BFPO\ (C\/O\ )?[0-9]{1,4})
  • Overseas territories - ((ASCN|BBND|[BFS]IQQ|PCRN|STHL|TDCU|TKCA)\ 1ZZ)

ASP

  1. function isValidPostCode(postCode)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .IgnoreCase = true
  6.         .Global = true
  7.         .Pattern = "^([A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))|[0-9][A-HJKS-UW])\ [0-9][ABD-HJLNP-UW-Z]{2}|(GIR\ 0AA)|(SAN\ TA1)|(BFPO\ (C\/O\ )?[0-9]{1,4})|((ASCN|BBND|[BFS]IQQ|PCRN|STHL|TDCU|TKCA)\ 1ZZ))$"
  8.     end with
  9.     if regEx.Test(trim(CStr(postCode))) then
  10.         isValidPostCode = true
  11.     else
  12.         isValidPostCode = false
  13.     end if
  14.     set regEx = nothing
  15. end function

PHP

  1. function isValidPostCode($postCode)
  2. {
  3.     $pattern = "/^([A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))|[0-9][A-HJKS-UW])\ [0-9][ABD-HJLNP-UW-Z]{2}|(GIR\ 0AA)|(SAN\ TA1)|(BFPO\ (C\/O\ )?[0-9]{1,4})|((ASCN|BBND|[BFS]IQQ|PCRN|STHL|TDCU|TKCA)\ 1ZZ))$/i";
  4.     return (preg_match($pattern, trim($postCode)) > 0) ? true : false;
  5. }

Saturday, August 16, 2008

isValidPostalCode

This week we're turning our attention to my own country, Canada, and writing a function to validate postal codes. Unlike a ZIP code, a postal code contains letters too; the format is A1A 1A1. The simplest regular expression to validate this would be:

^[A-Z]{1}[\d]{1}[A-Z]{1}[ ]?[\d]{1}[A-Z]{1}[\d]{1}$

But not all letters are used, and some letters can only appear in certain positions. Taking this into account gives us a slightly more complex pattern of:

^[ABCEGHJ-NPRSTVXY]{1}[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$


But I want to take this a step further and check the validity of the combination of the first three characters. The postal code C2B 4S3 has a valid format, but the code itself is not valid because there is no C2B postal area. I also want to check the postal code against the province to ensure that they match.


The full source code is too long to post here, but is available in its entirety from Snipplr in the language of your choice:


Alternatively, if you wanted the extended validation, but didn't care about the province matching, you could combine everything into one gigantic regular expression:


^(A(0[ABCEGHJ-NPR]|1[ABCEGHK-NSV-Y]|2[ABHNV]|5[A]|8[A])|B(0[CEHJ-NPRSTVW]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGHJNRSTV-Z]|3[ABEGHJ-NPRSTVZ]|4[ABCEGHNPRV]|5[A]|6[L]|9[A])|C(0[AB]|1[ABCEN])|E(1[ABCEGHJNVWX]|2[AEGHJ-NPRSV]|3[ABCELNVYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTV]|6[ABCEGHJKL]|7[ABCEGHJ-NP]|8[ABCEGJ-NPRST]|9[ABCEGH])|G(0[ACEGHJ-NPRSTV-Z]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGJ-N]|3[ABCEGHJ-NZ]|4[ARSTVWXZ]|5[ABCHJLMNRTVXYZ]|6[ABCEGHJKLPRSTVWXZ]|7[ABGHJKNPSTXYZ]|8[ABCEGHJ-NPTVWYZ]|9[ABCHNPRTX])|H(0[HM]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRSTV-Z]|4[ABCEGHJ-NPRSTV-Z]|5[AB]|7[ABCEGHJ-NPRSTV-Y]|8[NPRSTYZ]|9[ABCEGHJKPRSWX])|J(0[ABCEGHJ-NPRSTV-Z]|1[ACEGHJ-NRSTXZ]|2[ABCEGHJ-NRSTWXY]|3[ABEGHLMNPRTVXYZ]|4[BGHJ-NPRSTV-Z]|5[ABCJ-MRTV-Z]|6[AEJKNRSTVWYXZ]|7[ABCEGHJ-NPRTV-Z]|8[ABCEGHLMNPRTVXYZ]|9[ABEHJLNTVXYZ])|K(0[ABCEGHJ-M]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-MPRSTVW]|4[ABCKMPR]|6[AHJKTV]|7[ACGHK-NPRSV]|8[ABHNPRV]|9[AHJKLV])|L(0[[ABCEGHJ-NPRS]]|1[ABCEGHJ-NPRSTV-Z]|2[AEGHJMNPRSTVW]|3[BCKMPRSTVXYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTVW]|6[ABCEGHJ-MPRSTV-Z]|7[ABCEGJ-NPRST]|8[EGHJ-NPRSTVW]|9[ABCGHK-NPRSTVWYZ])|M(1[BCEGHJ-NPRSTVWX]|2[HJ-NPR]|3[ABCHJ-N]|4[ABCEGHJ-NPRSTV-Y]|5[ABCEGHJ-NPRSTVWX]|6[ABCEGHJ-NPRS]|7[AY]|8[V-Z]|9[ABCLMNPRVW])|N(0[ABCEGHJ-NPR]|1[ACEGHKLMPRST]|2[ABCEGHJ-NPRTVZ]|3[ABCEHLPRSTVWY]|4[BGKLNSTVWXZ]|5[ACHLPRV-Z]|6[ABCEGHJ-NP]|7[AGLMSTVWX]|8[AHMNPRSTV-Y]|9[ABCEGHJKVY])|P(0[ABCEGHJ-NPRSTV-Y]|1[ABCHLP]|2[ABN]|3[ABCEGLNPY]|4[NPR]|5[AEN]|6[ABC]|7[ABCEGJKL]|8[NT]|9[AN])|R(0[ABCEGHJ-M]|1[ABN]|2[CEGHJ-NPRV-Y]|3[ABCEGHJ-NPRSTV-Y]|4[AHJKL]|5[AGH]|6[MW]|7[ABCN]|8[AN]|9[A])|S(0[ACEGHJ-NP]|2[V]|3[N]|4[AHLNPRSTV-Z]|6[HJKVWX]|7[HJ-NPRSTVW]|9[AHVX])|T(0[ABCEGHJ-MPV]|1[ABCGHJ-MPRSV-Y]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRZ]|4[ABCEGHJLNPRSTVX]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTVWX]|7[AENPSVXYZ]|8[ABCEGHLNRSVWX]|9[ACEGHJKMNSVWX])|V(0[ABCEGHJ-NPRSTVWX]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NRSTV-Y]|4[ABCEGK-NPRSTVWXZ]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTV-Z]|7[ABCEGHJ-NPRSTV-Y]|8[ABCGJ-NPRSTV-Z]|9[ABCEGHJ-NPRSTV-Z])|X(0[ABCGX]|1[A])|Y(0[AB]|1[A]))[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$

Saturday, August 9, 2008

isValidZipCode

Another week, another validation function. Last week was a little long, so this week we'll do a bit shorter one: validating a United States ZIP code. The pattern is simple enough that I don't think an explanation is warranted.


ASP

  1. function isValidZIPCode(zipCode)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .IgnoreCase = True
  6.         .Global = True
  7.         .Pattern = "^[0-9]{5}(-[0-9]{4})?$"
  8.     end with
  9.     if regEx.Test(trim(CStr(zipCode))) then
  10.         isValidZipCode = True
  11.     else
  12.         isValidZipCode = False
  13.     end if
  14.     set regEx = nothing
  15. end function

PHP

  1. function isValidZIPCode($zipCode)
  2. {
  3.     return (preg_match("/^[0-9]{5}(-[0-9]{4})?$/i", trim($zipCode)) > 0) ? true : false;
  4. }

Saturday, August 2, 2008

isValidEmail

This week we're going to build on the regular expression we wrote last week to validate e-mail addresses. What do IP addresses have to do with e-mail addresses? Just like domain names map to IP addresses, so also the domain part of an e-mail address can be substituted with an IP address, so instead of person@example.com you could have person@192.168.1.1


ASP

  1. function isValidEmail(email)
  2.     dim regEx
  3.     dim result
  4.     set regEx = new RegExp
  5.     with regEx
  6.         .IgnoreCase = True
  7.         .Global = True
  8.         .Pattern = "^[^@]{1,64}@[^@]{1,255}$"
  9.     end with
  10.     result = false
  11.     ' Test length.
  12.     if regEx.Test(email) then
  13.         regEx.Pattern = "^((([\w\+\-]+)(\.[\w\+\-]+)*)|(\"[^(\\|\")]{0,62}\"))@(([a-zA-Z0-9\-]+\.)+([a-zA-Z0-9]{2,})|\[?([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})(\.([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})){3}\]?)$"
  14.         ' Test syntax.
  15.         if regEx.Test(email) then
  16.             result = true
  17.         end if
  18.     end if
  19.     isValidEmail = result
  20.     set regEx = nothing
  21. end function

PHP

  1. function isValidEmail($email)
  2. {
  3.     $lengthPattern = "/^[^@]{1,64}@[^@]{1,255}$/";
  4.     $syntaxPattern = "/^((([\w\+\-]+)(\.[\w\+\-]+)*)|(\"[^(\\|\")]{0,62}\"))@(([a-zA-Z0-9\-]+\.)+([a-zA-Z0-9]{2,})|\[?([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})(\.([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})){3}\]?)$/";
  5.     return ((preg_match($lengthPattern, $email) > 0) && (preg_match($syntaxPattern, $email) > 0)) ? true : false;
  6. }

The validation is broken down into two steps: checking the length of each part, and checking the syntax of each part.


^[^@]{1,64}@[^@]{1,255}$


The part before the @ symbol is called the local part, and cannot exceed 64 characters. The part after the @ symbol is called the domain part, and cannot exceed 255 characters.


^((([\w\+\-]+)(\.[\w\+\-]+)*)|(\"[^(\\|\")]{0,62}\"))@(([a-zA-Z0-9\-]+\.)+([a-zA-Z0-9]{2,})|\[?([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})(\.([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})){3}\]?)$


In the check for syntax, the local part is validated by ((([\w\+\-]+)(\.[\w\+\-]+)*)|(\"[^(\\|\")]{0,62}\")). This is actually two patterns separated by the pipe character. The first, (([\w\+\-]+)(\.[\w\+\-]+)*), allows letters, numbers, the plus sign, and the hyphen (or minus sign if you prefer). It also allows periods, but not as the first or last character. The second, (\"[^(\\|\")]{0,62}\"), allows just about anything, provided the local part is enclosed in quotation marks (which is valid, but you'll probably never encounter it).


The domain part is validated by (([a-zA-Z0-9\-]+\.)+([a-zA-Z0-9]{2,})|\[?([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})(\.([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})){3}\]?). Once again, this is two different patterns separated by the pipe character. The second pattern is our IP address checker from last week with optional enclosure in square brackets. The first pattern, ([a-zA-Z0-9\-]+\.)+([a-zA-Z0-9]{2,}), allows a slightly smaller range of characters (no plus signs or underscores), any number of subdomains, and a top-level domain of at least 2 characters (the minimum). Some regular expressions will impose a maximum of six characters on the top-level domain (the longest at the moment is .museum), but that wouldn't allow for longer top-level domains that could be created in the future.


Saturday, July 26, 2008

isValidIP

As promised, more regular expression fun. This week we're going to validate IP addresses. An IP address consists of four octets separated by periods. A lazy person might be inclined to use a regular expression of \d{1,3} for each octet, but that would allow numbers larger than 255. A more complex expression is needed: ([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1}).


This expression consists of three parts separated by the pipe character "|". The first part, [1]?\d{1,2}, matches numbers between 0 and 199. The second part, 2[0-4]{1}\d{1}, matches numbers between 200 and 249. The third part, 25[0-5]{1}, matches numbers between 250 and 255. We will repeat this pattern four times, once for each octet, and separate with periods.


ASP

  1. function isValidIP(ip)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .IgnoreCase = True
  6.         .Global = True
  7.         .Pattern = "^([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})(\.([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})){3}$"
  8.     end with
  9.     if regEx.Test(trim(CStr(ip))) then
  10.         isValidIP = true
  11.     else
  12.         isValidIP = false
  13.     end if
  14.     set regEx = nothing
  15. end function

PHP

  1. function isValidIP($ip)
  2. {
  3.     $pattern = "/^([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})(\.([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})){3}$/";
  4.     return (preg_match($pattern, $ip) > 0) ? true : false;
  5. }

Next week we're going to build on this pattern to validate something else. I wonder what that could be?


Saturday, July 19, 2008

isAlpha(Numeric)

We're going to take a break from math-related functions for a few weeks (yay!) and play with regular expressions. Regular expressions are more powerful and faster than old-fashioned string parsing.


Both ASP and PHP have a function for checking if something is numeric. How about a function for checking if something is alphabetical?


ASP

  1. function isAlpha(someString)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .Global = true
  6.         .IgnoreCase = true
  7.         .Pattern = "[A-Z\s_]"
  8.     end with
  9.     if regEx.test(someString) then
  10.         isAlpha = true
  11.     else
  12.         isAlpha = false
  13.     end if
  14.     set regEx = nothing
  15. end function

PHP

  1. function is_alpha($someString)
  2. {
  3.     return (preg_match("/[A-Z\s_]/i", $someString) > 0) ? true : false;
  4. }

The test pattern we are using above will allow letters of the alphabet, the underscore character, and whitespace characters. With a small tweak to the test pattern, we can also write a function to check if a string is alphanumeric.


ASP

  1. function isAlphaNumeric(someString)
  2.     dim regEx
  3.     set regEx = new RegExp
  4.     with regEx
  5.         .Global = true
  6.         .IgnoreCase = true
  7.         .Pattern = "[\w\s.]"
  8.     end with
  9.     if regEx.test(someString) then
  10.         isAlphaNumeric = true
  11.     else
  12.         isAlphaNumeric = false
  13.     end if
  14.     set regEx = nothing
  15. end function

PHP

  1. function is_alphanumeric($someString)
  2. {
  3.     return (preg_match("/[\w\s.]/i", $someString) > 0) ? true : false;
  4. }

The \w switch in the pattern includes the 26 letters of the alphabet plus the numbers zero through nine.


More regular expression fun next week!


Saturday, July 12, 2008

Fibonacci numbers

The Fibonacci numbers have many applications in computer programming. Today we're going to write a function that returns individual numbers from the Fibonacci sequence.


ASP

  1. function fib(x)
  2.     dim fibArray()
  3.     redim fibArray(x)
  4.     fibArray(0) = 0
  5.     fibArray(1) = 1
  6.     for i = 2 to x
  7.         fibArray(i) = fibArray(i - 1) + fibArray(i - 2)
  8.     next
  9.     fib = fibArray(x)
  10. end function

PHP

  1. function fib($x)
  2. {
  3.     $fibArray[0] = 0;
  4.     $fibArray[1] = 1;
  5.     for($i = 2; $i <= $x; $i++)
  6.     {
  7.         $fibArray[$i] = $fibArray[$i - 1] + $fibArray[$i - 2];
  8.     }
  9.     return $fibArray[$x];
  10. }

So, for example, fib(8) will return 21, because the Fibonacci sequence is 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...


Saturday, July 5, 2008

Fermat numbers

Today we're going to write a function to generate Fermat numbers. This could be useful if you want to write your own pseudo-random number generator.


ASP

  1. function fermat(x)
  2.     fermat = 2^2^x + 1
  3. end function

PHP

  1. function fermat($x)
  2. {
  3.     return 2^2^$x + 1;
  4. }

Saturday, June 21, 2008

AM/PM

To those of you who have been bored by the recent string of math-related articles, I apologize. I still have a lot of math-related functions to share with you, but I do have other things to share with you as well, such as validation functions, string functions, and datetime functions. This week we'll take a look at one of those datetime functions.


In ASP, time values are normally in the format HH:MM:SS followed by AM or PM. Quite often, we don't want to include the seconds portion, so we extract the hours and minutes using the Hour() and Minute() functions respectively. Unfortunately, there is no quick function for extracting the AM/PM. Let's do something about that right now.


  1. function ampm(someTime)
  2.     if hour(someTime) < 12 then
  3.         ampm = "AM"
  4.     else
  5.         ampm = "PM"
  6.     end if
  7. end function

Now we can do stuff like this:
Hour(someTime) & ":" & Minute(someTime) & " " & ampm(someTime)


Those of you who are PHP programmers are hopefully aware that the same result can be achieved in PHP using the built-in date() function and specifying the letter A or a in the format string for uppercase or lowercase respectively:
date("g:i A", $timestamp)


View this code on Snipplr

Saturday, June 14, 2008

Factorial

This week we're going to delve into some discrete math, starting with factorial. The factorial of a number is the product of that number and all the numbers smaller than it. For example, the factorial of 3 is 1 x 2 x 3 = 6.


ASP

  1. function factorial(x)
  2.     dim result
  3.     result = 1
  4.     if x > 1 then
  5.         for i = 2 to x
  6.             result = result * i
  7.         next
  8.     end if
  9.     factorial = result
  10. end function

PHP

  1. function factorial($x)
  2. {
  3.     $result = 1;
  4.     if ($x > 1)
  5.     {
  6.         for ($i = 2; $i <= $x; $i++)
  7.         {
  8.             $result *= $i;
  9.         }
  10.     }
  11.     return $result;
  12. }

Now that we have a function for factorial, we can also do combinatorial. Combinatorial tells us the number of combinations, without regard to order, of y items that can be made from a pool of x items.


ASP

  1. function combinatorial(x, y)
  2.     if (x >= y) and (y > 0) then
  3.         combinatorial = factorial(x) / factorial(y) / factorial(x - y)
  4.     else
  5.         combinatorial = 0
  6.     end if
  7. end function

PHP

  1. function combinatorial($x, $y)
  2. {
  3.     return (($x >= $y) && ($y > 0)) ? factorial($x) / factorial($y) / factorial($x - $y) : 0;
  4. }

We can also get the number of permutations of y items that can be made from a pool of x items.


ASP

  1. function permutations(x, y)
  2.     permutations = factorial(x) / factorial(x - y)
  3. end function

PHP

  1. function permutations($x, $y)
  2. {
  3.     return factorial($x) / factorial($x - $y);
  4. }

Saturday, June 7, 2008

Pythagorean Theorem

When you're dealing with right-angled triangles, trigonometry is not required to calculate the length of the hypotenuse. Our PHP programmers friends have a function called hypot() which solves for c in the equation: a^2 + b^2 = c^2


  1. function hypot(a, b)
  2.     hypot = sqr(a^2 + b^2)
  3. end function

Using the classic 3-4-5 triangle as an example, for a = 3 and b = 4, the function will return 5.


View this code on Snipplr

Saturday, May 31, 2008

Degrees and Radians

When you were learning trigonometry, you probably measured angles in degrees. But in calculus, angles are measured in radians (search for "degrees vs. radians" on Google for the reasons). Being able to convert between these two units might be handy. PHP provides two functions for this purpose, but no such luck in ASP. As usual, we're going to write our own.


Radians is heavily based on my favorite number, pi. We're going to need this number in our calculations, so make sure to define a constant for it.


Const M_PI = 3.14159265358979323846


And now the functions themselves...


  1. function deg2rad(x)
  2.     deg2rad = x * M_PI / 180
  3. end function
  4. function rad2deg(x)
  5.     rad2deg = x * 180 / M_PI
  6. end function

View this code on Snipplr

Saturday, May 24, 2008

Euclid's Algorithm

Euclid's algorithm is one of the oldest, known by ancient Greeks like Aristotle. You might remember it from elementary school when you had to find things like greatest common factor/divisor and least common multiple. Greatest common factor/divisor allowed you to reduce fractions like 4/12 to 1/3. Least common multiple allowed you to add and subtract fractions that had different denominators.


There is more than one way to implement this algorithm. The original involved iterative subtraction. This was later improved upon with iterative modulo division. Another method, the one I'll be using here, is recursion.


ASP

  1. function gcd(byVal a, byVal b)
  2.     a = abs(a)
  3.     b = abs(b)
  4.     if a = 0 then
  5.         gcd = b
  6.     elseif b = 0 then
  7.         gcd = a
  8.     elseif a > b then
  9.         gcd = gcd(b, a mod b)
  10.     else
  11.         gcd = gcd(a, b mod a)
  12.     end if
  13. end function
  14. function lcm(byVal a, byVal b)
  15.     a = abs(a)
  16.     b = abs(b)
  17.     if a > b then
  18.         lcm = (b / gcd(a, b)) * a
  19.     else
  20.         lcm = (a / gcd(a, b)) * b
  21.     end if
  22. end function

PHP

  1. function gcd($a, $b)
  2. {
  3.     $a = abs($a);
  4.     $b = abs($b);
  5.     if ($a == 0)
  6.     {
  7.         return $b;
  8.     }
  9.     elseif ($b == 0)
  10.     {
  11.         return $a;
  12.     }
  13.     elseif ($a > $b)
  14.     {
  15.         return gcd($b, $a % $b);
  16.     }
  17.     else
  18.     {
  19.         return gcd($a, $b % $a);
  20.     }
  21. }
  22. function lcm($a, $b)
  23. {
  24.     $a = abs($a);
  25.     $b = abs($b);
  26.     if ($a > $b)
  27.     {
  28.         return ($b / gcd($a, $b)) * $a;
  29.     }
  30.     else
  31.     {
  32.         return ($a / gcd($a, $b)) * $b;
  33.     }
  34. }

Euclid's algorithm is not always the fastest, but it is the simplest. A better algorithm was devised by 20th century mathematician Dick Lehmer. Another 20th century mathematician, Josef Stein, devised a specialized algorithm for computers which uses bit shifting. Note, however, that the performance improvement of these more modern algorithms varies depending on the CPU and size of the numbers involved. In some cases, Euclid is actually faster.


Monday, May 19, 2008

Update

I've updated my Leap Years article with more elegant PHP solutions thanks to Jim Mayes.

I discovered his feedback via a web site called Snipplr, where I've been uploading all the code examples from this blog. All the previous code examples are still available here on the blog, but I'm considering posting future code examples on Snipplr only and linking to them from the blog, because it takes a ridiculous amount of time to format the code examples to display nicely on the blog. I think it will depend on the length of the example; I don't mind reformatting short examples, but I've got some longer ones that I'd rather not.

Anyways, you can view all my code snippets from my Snipplr profile. There are also some bonus things in there which are outside the scope of this blog (eg. Windows registry tweaks). I've also added this link to the sidebar so that it will be easy to find in the future when this post disappears off the front page.

Saturday, May 17, 2008

Ceiling and Floor

In mathematics, there are two elementary special functions called ceiling() and floor() which allow us to round up or down, respectively, to the nearest whole number. These functions exist natively in PHP, but not in ASP. They are handy in situations where you want to force a number like 15.8 to round down to 15, but the round() function rounds it up to 16 according to the standard rules for rounding.


ASP

  1. function floor(x)
  2.     dim temp
  3.     temp = round(x)
  4.     if temp > x then
  5.         temp = temp - 1
  6.     end if
  7.     floor = temp
  8. end function
  9. function ceil(x)
  10.     dim temp
  11.     temp = round(x)
  12.     if temp < x then
  13.         temp = temp + 1
  14.     end if
  15.     ceil = temp
  16. end function

Saturday, May 10, 2008

Tractive Effort

This week is another short, fun snippet of code. We're going to calculate the tractive effort of a steam locomotive. Useless to most people, but I've got a large project where I'll be using it myself.


ASP

  1. function TractiveEffort(cylinderDiameter, stroke, pressure, wheelDiameter)
  2.     TractiveEffort = cylinderDiameter ^ 2 * stroke * pressure * 0.85 / wheelDiameter
  3. end function

PHP

  1. function $tractiveEffort($cylinderDiameter, $stroke, $pressure, $wheelDiameter)
  2. {
  3.     return $cylinderDiameter ^ 2 * $stroke * $pressure * 0.85 / $wheelDiameter;
  4. }

It should be noted that the formula only applies to non-compound steam locomotives. Locomotives with multiple cylinders are more complex.

Saturday, May 3, 2008

Base Conversion

Most of the modern world uses a base-10 number system. It's the easiest to work with. However, when it comes to computer systems, we use a few different number systems too. Base-2, or binary, is the basis of data storage and transmission; a bit is either on or off. We also use Base-8, or octal; each byte contains 8 bits. Last but not least is Base-16, or hexadecimal.


Under some circumstances, we might need to convert between these different number systems. ASP provides a pathetic two functions for this purpose: hex(), which converts from base-10 (decimal) to base-16 (hexadecimal), and oct() which converts from decimal to octal. PHP provides equivalent functions dechex() and decoct(), as well as additional functions decbin() for converting from decimal to binary, bindec() for converting from binary to decimal, hexdec() for converting from hexadecimal to decimal, and octdec() for converting from octal to decimal.


It would be nice to extend ASP to support these four additional functions. When I started doing that, I noticed a pattern in the formulae. All the functions that converted from decimal were fairly similar, and all the functions that converted to decimal were also fairly similar. So I abstracted the calculations out into their own functions and simplified my code.


ASP

  1. ' Convert from binary to decimal.
  2. function bindec(bin)
  3.     bindec = toDecimal(bin, 2)
  4. end function
  5. ' Convert from decimal to binary
  6. function decbin(dec)
  7.     decbin = fromDecimal(dec, 2)
  8. end function
  9. ' Convert from decimal to hexadecimal.
  10. function dechex(dec)
  11.     ' Assume that built-in hex() function is faster.
  12.     dechex = hex(dec)
  13. end function
  14. ' Convert from decimal to octal.
  15. function decoct(dec)
  16.     ' Assume that built-in oct() function is faster.
  17.     decoct = oct(dec)
  18. end function
  19. ' Convert from hexadecimal to decimal.
  20. function hexdec(hex)
  21.     hexdec = toDecimal(hex, 16)
  22. end function
  23. ' Convert from octal to decimal.
  24. function octdec(oct)
  25.     octdec = toDecimal(oct, 8)
  26. end function

Before we get into the underlying fromDecimal() and toDecimal() functions, some things need to be said. The lowest possible base is 2, so if the user tries to specify a base smaller than 2, we will change it to 2. Likewise, there is a practical upper limit of base 36. Beyond that, things get more complicated, so any base higher than 36 will be changed to 36 to prevent the function from going out of bounds and returning bad data.


ASP

  1. function toDecimal(value, radix)
  2.     dim result
  3.     dim digit
  4.     result = 0
  5.     ' Prevent radix from going out of bounds.
  6.     if radix < 2 then
  7.         radix = 2
  8.     elseif radix > 36 then
  9.         radix = 36
  10.     end if
  11.     for i = 1 to Len(value)
  12.         digit = Mid(value, i, 1)
  13.         ' Convert letters to numbers.
  14.         if not isNumeric(digit) then
  15.             ' The letter A in any base is equal to 10 in decimal.
  16.             ' The ASCII value of A is 65, so subtract 55 from the ASCII value to obtain the decimal value.
  17.             digit = Asc(UCase(digit)) - 55
  18.         else
  19.             digit = CInt(digit)
  20.         end if
  21.         ' Return zero if any digit is out of bounds for the radix.
  22.         if digit >= radix then
  23.             result = 0
  24.             exit for
  25.         end if
  26.         result = result + (digit * radix ^ (Len(value) - i))
  27.     next
  28.     toDecimal = result
  29. end function
  30. function fromDecimal(value, radix)
  31.     dim result
  32.     dim digit
  33.     result = 0
  34.     ' Prevent radix from going out of bounds.
  35.     if radix < 2 then
  36.         radix = 2
  37.     elseif radix > 36 then
  38.         radix = 36
  39.     end if
  40.     ' Check for invalid input.
  41.     if isNumeric(value) then
  42.         ' Inputted value appears to be base 10. OK to proceed.
  43.         do until value = 0
  44.             digit = value Mod radix
  45.             if digit > 9 then
  46.                 digit = Chr(digit + 55)
  47.             end if
  48.             result = CStr(digit) & result
  49.             value = value \ radix
  50.         loop
  51.     else
  52.         ' Inputted value was NOT base 10.
  53.         result = 0
  54.     end if
  55.     fromDecimal = result
  56. end function

It would be even better if we could easily convert between two bases where neither one is decimal. Also, it would be nice to have a unified interface. We can achieve both with one function, base_convert(), which is also present in PHP.


ASP

  1. function base_convert(value, sourceRadix, targetRadix)
  2.     if sourceRadix = targetRadix then
  3.         ' If source radix and target radix are equal, don't waste time converting.
  4.         baseConv = value
  5.     elseif sourceRadix = 10 then
  6.         ' If source radix is decimal, skip converting to decimal.
  7.         baseConv = fromDecimal(value, targetRadix)
  8.     elseif targetRadix = 10 then
  9.         ' If target radix is decimal, skip converting from decimal.
  10.         baseConv = toDecimal(value, sourceRadix)
  11.     else
  12.         ' Convert to decimal, and then from decimal.
  13.         baseConv = fromDecimal(toDecimal(value, sourceRadix), targetRadix)
  14.     end if
  15. end function

I leave you with the following potential exercises:

  • From base 37 to 62, the lowercase letters of the Latin alphabet are used, but special handling would need to be added for them. The ASCII value for Z is 90. The ASCII value for a is 97. There are six other characters in between.
  • Base64 encoding begins with the uppercase letters A-Z, followed by a-z, followed by 0-9, followed by + and /. If you want to add Base64 support, separate functions would be wise, but could still be tied into the base_convert() wrapper.

Saturday, April 26, 2008

Immediate If

Visual Basic programmers have a function at their disposal called IIf(), which is an abbreviation for Immediate If.

Example

result = IIf(2 + 2 = 5, "Correct", "Wrong")

The iif() function is sometimes more convenient than a full-blown if...then...else... control structure. Oddly enough, the function does not exist in VBScript.

Our PHP programmer friends have a superior equivalent, the ternary operator.

Example

$result = (2 + 2 = 5) ? 'Correct' : 'Wrong';

Unfortunately we can't recreate the ternary operator in ASP, but we can recreate the iif() function.

ASP

  1. function IIf(expression, truecondition, falsecondition)
  2.     if isNull(expression) then
  3.         IIf = falseCondition
  4.     elseif cbool(expression) then
  5.         IIf = truecondition
  6.     else
  7.         IIf = falsecondition
  8.     end if
  9. end function

Saturday, April 19, 2008

Proper Case

Both ASP and PHP have handy functions for forcing characters in a string to be all uppercase or all lowercase, but what about proper case? Proper case is used for names of people, titles of songs or movies, etc. The first letter in each word is capitalized, and the other letters are lowercase.


PHP has a built-in function for this, but the usage is slightly different and the name of the function is inconsistent, so we're going to write a facade function.


PHP

  1. function strtoproper($someString)
  2. {
  3.     return ucwords(strtolower($someString));
  4. }

ASP doesn't have a function for this, though full-blown Visual Basic does. We're going to duplicate that functionality, and make a few improvements. Consider names like McDonald and O'Brien. An ordinary proper case function would not handle these properly. We're going to use a regular expression to find these situations and handle them appropriately.


ASP

  1. function PCase(someString)
  2.     dim position
  3.     dim space
  4.     dim result
  5.     dim regEx
  6.     position = 1
  7.     set regEx = new RegExp
  8.     regEx.Pattern = "^(Mc[A-Z]{1}[A-Za-z]|O\'[A-Z]{1}[A-Za-z]|Mac[A-Z]{1}[A-Za-z])"
  9.     ' Loop through the string checking for spaces.
  10.     do while InStr(position, someString, " ", 1) <> 0
  11.         ' Find the position of the next space.
  12.         space = InStr(position, someString, " ", 1)
  13.         ' Capitalize (and append to our output) the first character after the space which was handled by the previous run through the loop.
  14.         result = result & UCase(Mid(someString, position, 1))
  15.         ' Check for situations like McDonald or O'Brien.
  16.         if not regEx.Test(Mid(someString, position, space - position)) then
  17.             ' Lowercase (and append to our output) the rest of the string up to and including the current space.
  18.             result = result & LCase(Mid(someString, position + 1, space - position))
  19.         else
  20.             if Left(Mid(someString, position), 3) = "Mac" then
  21.                 ' Leave the next three characters intact.
  22.                 result = result & Mid(someString, position + 1, 3)
  23.                 ' Append the rest of the string.
  24.                 result = result & LCase(Mid(someString, position + 4, space - position + 4))
  25.             else
  26.                 ' Leave the next two characters intact.
  27.                 result = result & Mid(someString, position + 1, 2)
  28.                 ' Append the rest of the string.
  29.                 result = result & LCase(Mid(someString, position + 3, space - position + 3))
  30.             end if
  31.         end if
  32.         position = space + 1
  33.     loop
  34.     ' Capitalize the first character of the last word after the final space (or the only word if there were no spaces).
  35.     result = result & UCase(Mid(someString, position, 1))
  36.     ' Check for situations like McDonald or O'Brien.
  37.     if not regEx.Test(Mid(someString, position)) then
  38.         ' Lowercase (and append to our output) the rest of the string up to and including the current space.
  39.         result = result & LCase(Mid(someString, position + 1))
  40.     else
  41.         if Left(Mid(someString, position), 3) = "Mac" then
  42.             ' Leave the next three characters intact.
  43.             result = result & Mid(someString, position + 1, 3)
  44.             ' Append the rest of the string.
  45.             result = result & LCase(Mid(someString, position + 4))
  46.         else
  47.             ' Leave the next two characters intact.
  48.             result = result & Mid(someString, position + 1, 2)
  49.             ' Append the rest of the string.
  50.             result = result & LCase(Mid(someString, position + 3))
  51.         end if
  52.     end if
  53.     set regEx = Nothing
  54.     PCase = result
  55. end function

Saturday, April 12, 2008

Ordinal Numbers

This week we're going to write a function to turn a cardinal integer into an ordinal number (eg. 4 becomes 4th). This is mostly useful when generating a page to display to a visitor.


ASP

  1. function ordinal(ByVal cardinal)
  2.     cardinal = CStr(cardinal)
  3.     if Right(cardinal, 1) = "1" and Right(cardinal, 2) <> "11" then
  4.         ordinal = cardinal & "st"
  5.     elseif Right(cardinal, 1) = "2" and Right(cardinal, 2) <> "12" then
  6.         ordinal = cardinal & "nd"
  7.     elseif Right(cardinal, 1) = "3" and Right(cardinal, 2) <> "13" then
  8.         ordinal = cardinal & "rd"
  9.     else
  10.         ordinal = cardinal & "th"
  11.     end if
  12. end function

PHP

  1. function ordinal($cardinal)
  2. {
  3.     if (substr($cardinal, -1, 1) == 1 && substr($cardinal, -2, 2) != 11)
  4.     {
  5.         return ($cardinal . 'st');
  6.     }
  7.     elseif (substr($cardinal, -1, 1) == 2 && substr($cardinal, -2, 2) != 12)
  8.     {
  9.         return ($cardinal . 'nd');
  10.     }
  11.     elseif (substr($cardinal, -1, 1) == 3 && substr($cardinal, -2, 2) != 13)
  12.     {
  13.         return ($cardinal . 'rd');
  14.     }
  15.     else
  16.     {
  17.         return ($cardinal . 'th');
  18.     }
  19. }

Saturday, April 5, 2008

Force SSL

If your web site requires visitors to enter private information, like their credit card number, or even just a username and password, you're hopefully using SSL to secure the transmission. But providing SSL is not enough, because visitors can still access your site without SSL. When a visitor navigates to a page where they are entering private information, we want to enforce that their data is protected by SSL.


Our PHP programmer friends can do this via an htaccess file:


  1. RewriteEngine On
  2. RewriteCond %{HTTPS} off
  3. RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

But for those of us stuck using ASP, we don't have this luxury. We can achieve the same result with some code.


  1. sub forceSSL()
  2.     dim secureURL
  3.     if UCase(Request.ServerVariables("HTTPS")) = "OFF" then
  4.         secureURL = "https://" & Request.ServerVariables("SERVER_NAME") & Request.ServerVariables("HTTP_URL")
  5.         if Request.ServerVariables("QUERY_STRING") <> "" then
  6.             secureURL = secureURL & "?" & Request.ServerVariables("QUERY_STRING")
  7.         end if
  8.         Response.Redirect secureURL
  9.     end if
  10. end sub

There are some things here worth noting. In both the htaccess example and the ASP example, we are checking if HTTPS is off. Some people will instead check if traffic is coming from port 80, the standard HTTP port, or not coming from 443, the standard HTTPS port. Checking port numbers is not the best solution because the server administrator can set up HTTP and HTTPS to run on different ports. It's also worth noting that the above ASP example preserves QueryString variables, if any are being passed. Most other examples I've seen on the Internet ignore the QueryString variables, which leads to navigation problems.


With this subroutine in your arsenal, just call it at the top of any page you want to secure.

Saturday, March 29, 2008

Finding your Star Wars name

It's high time we did something fun. We're going to write a function that determines a person's Star Wars name.


ASP

  1. function starwarsname(firstName, lastName, maidenName, city)
  2.     starwarsname = Left(firstName, 3) & LCase(Left(lastName, 2)) & " " & Left(maidenName, 2) & LCase(Left(city, 3))
  3. end function

PHP

  1. function starwarsname($firstName, $lastName, $maidenName, $city)
  2. {
  3.     return substr($firstName, 0, 3) . strtolower(substr($lastName, 0, 2)) . " " . substr($maidenName, 0, 2) . strtolower(substr($city, 0, 3));
  4. }

Saturday, March 22, 2008

Calculating Easter

It's Easter weekend, and as promised we're going to write a function to calculate when Easter occurs for a given year. Easter is a strange holiday in that it jumps around the calendar between March and April. This is because it is tied to the lunar cycle. Easter occurs on the first Sunday after the Paschal full moon.


ASP

  1. function dateEaster(someYear)
  2.     Dim goldenNumber
  3.     Dim solarCorrection
  4.     Dim lunarCorrection
  5.     Dim paschalFullMoon
  6.     Dim dominicalNumber
  7.     Dim difference
  8.     Dim dayEaster
  9.     goldenNumber = (someYear Mod 19) + 1
  10.     if someYear <= 1752 then
  11.         ' Julian calendar
  12.         dominicalNumber = (someYear + (someYear / 4) + 5) Mod 7
  13.         paschalFullMoon = (3 - (11 * goldenNumber) - 7) Mod 30
  14.     else
  15.         ' Gregorian calendar
  16.         dominicalNumber = (someYear + (someYear / 4) - (someYear / 100) + (someYear / 400)) Mod 7
  17.         solarCorrection = (someYear - 1600) / 100 - (someYear - 1600) / 400
  18.         lunarCorrection = (((someYear - 1400) / 100) * 8) / 25
  19.         paschalFullMoon = (3 - 11 * goldenNumber + solarCorrection - lunarCorrection) Mod 30
  20.     end if
  21.     do until dominicalNumber > 0
  22.         dominicalNumber = dominicalNumber + 7
  23.     loop
  24.     do until paschalFullMoon > 0
  25.         paschalFullMoon = paschalFullMoon + 30
  26.     loop
  27.     if paschalFullMoon = 29 or (paschalFullMoon = 28 and goldenNumber > 11) then
  28.         paschalFullMoon = paschalFullMoon - 1
  29.     end if
  30.     difference = (4 - paschalFullMoon - dominicalNumber) Mod 7
  31.     if difference < 0 then
  32.         difference = difference + 7
  33.     end if
  34.     dayEaster = paschalFullMoon + difference + 1
  35.     if dayEaster < 11 then
  36.         ' Easter occurs in March.
  37.         dateEaster = DateSerial(someYear, 3, dayEaster + 21)
  38.     else
  39.         ' Easter occurs in April.
  40.         dateEaster = DateSerial(someYear, 4, dayEaster - 10)
  41.     end if
  42. end function

PHP

  1. function dateEaster($someYear)
  2. {
  3.     $goldenNumber = fmod($someYear, 19) + 1;
  4.     if ($someYear <= 1752)
  5.     {
  6.         // Julian calendar
  7.         $dominicalNumber = fmod($someYear + ($someYear / 4) + 5, 7);
  8.         $paschalFullMoon = fmod(3 - (11 * $goldenNumber) - 7, 30);
  9.     }
  10.     else
  11.     {
  12.         // Gregorian calendar
  13.         $dominicalNumber = fmod($someYear + ($someYear / 4) - ($someYear / 100) + ($someYear / 400), 7);
  14.         $solarCorrection = ($someYear - 1600) / 100 - ($someYear - 1600) / 400;
  15.         $lunarCorrection = ((($someYear - 1400) / 100) * 8) / 25;
  16.         $paschalFullMoon = fmod(3 - 11 * $goldenNumber + $solarCorrection - $lunarCorrection, 30);
  17.     }
  18.     while ($dominicalNumber < 0)
  19.     {
  20.         $dominicalNumber += 7;
  21.     }
  22.     while ($paschalFullMoon < 0)
  23.     {
  24.         $paschalFullMoon += 30;
  25.     }
  26.     if ($paschalFullMoon == 29 || ($paschalFullMoon == 28 && $goldenNumber > 11))
  27.     {
  28.         $paschalFullMoon--;
  29.     }
  30.     $difference = fmod(4 - $paschalFullMoon - $dominicalNumber, 7);
  31.     if ($difference < 0)
  32.     {
  33.         $difference += 7;
  34.     }
  35.     $dayEaster = $paschalFullMoon + $difference + 1;
  36.     if ($dayEaster < 11)
  37.     {
  38.         // Easter occurs in March.
  39.         $dateEaster = mktime(0, 0, 0, 3, $dayEaster + 21, $someYear);
  40.     }
  41.     else
  42.     {
  43.         // Easter occurs in April.
  44.         $dateEaster = mktime(0, 0, 0, 4, $dayEaster - 10, $someYear);
  45.     }
  46.     return $dateEaster;
  47. }

We can calculate the other ecclesiastical holidays by offsetting the date of Easter with the number of days between Easter and the holiday we're looking for.


ASP

  1. function dateGoodFriday(someYear)
  2.     dateGoodFriday = DateAdd("d", -2, dateEaster(someYear))
  3. end function
  4. function datePalmSunday(someYear)
  5.     datePalmSunday = DateAdd("d", -7, dateEaster(someYear))
  6. end function
  7. function dateAshWednesday(someYear)
  8.     dateAshWednesday = DateAdd("d", -46, dateEaster(someYear))
  9. end function
  10. function dateAscensionDay(someYear)
  11.     dateAscensionDay = DateAdd("d", 39, dateEaster(someYear))
  12. end function
  13. function datePentecost(someYear)
  14.     datePentecost = DateAdd("d", 49, dateEaster(someYear))
  15. end function

PHP

  1. function dateGoodFriday($someYear)
  2. {
  3.     $easter = getDate(dateEaster($someYear));
  4.     return mktime(0, 0, 0, $easter[mon], $easter[mday] - 2, $easter[year]);
  5. }
  6. function datePalmSunday($someYear)
  7. {
  8.     $easter = getDate(dateEaster($someYear));
  9.     return mktime(0, 0, 0, $easter[mon], $easter[mday] - 7, $easter[year]);
  10. }
  11. function dateAshWednesday($someYear)
  12. {
  13.     $easter = getDate(dateEaster($someYear));
  14.     return mktime(0, 0, 0, $easter[mon], $easter[mday] - 46, $easter[year]);
  15. }
  16. function dateAscensionDay($someYear)
  17. {
  18.     $easter = getDate(dateEaster($someYear));
  19.     return mktime(0, 0, 0, $easter[mon], $easter[mday] + 39, $easter[year]);
  20. }
  21. function datePentecost($someYear)
  22. {
  23.     $easter = getDate(dateEaster($someYear));
  24.     return mktime(0, 0, 0, $easter[mon], $easter[mday] + 49, $easter[year]);
  25. }