December 24, 2020
Christmas portrait 2020

A year for the books. Now let's never speak of it again.


December 19, 2020
New glasses, new restrictions

1 My new glasses arrived today! I'm super excited about them, especially because these prescriptions are a bit weaker than previously which means my sight has improved, at least for now. The optometrist said don't get used to it as soon it will all start going downhill. ๐Ÿ˜„ Thanks for the dose of reality.

Ontario will be implementing more restrictions starting on Monday to combat the spike in the spread of COVID-19. Just in time for Christmas. I wish they'd just shut the whole province down for a few months and fine people who still willy-nilly jump from house to house or have parties and get-togethers. Lock us down like New Zealand did. They have their act together. They're back to living their normal lives which seems so out of reach for us.

Anyway, Christmas is in a few days and Nikki and I were talking yesterday that it's crazy that we're already done shopping. Usually we go right till the 24th. It's definitely a different kind of year in many ways.


December 1, 2020
Found my winter trees

found-trees

Today we got a dusting of snow, resulting in some beautiful landscapes around the Niagara Peninsula. Also today John and I got a COVID-19 test because we both have a cold and these days everything is six degrees from Kevin Bacon COVID-19.

Anyway these trees are my favourite and it was a great feeling after all these years to be standing in front of them again.


October 19, 2020
Mr. Monday brings dagh.net to life

1

I'm going to bring dagh.net's on this day quotes to life through Mr. Monday.

This will allow me to improve on my drawing skills and at the same time bring to light some of the funny things we've said over the years.

My next goal is to run these comics through Illustrator to make them look clean and so that they're scalable.

(Note: I realize the photo in this entry is not a dagh.net quote but it's a great quote anyway.)


October 16, 2020
Screenshot from 2004

Screenshot from 2004

Oh man, this screenshot of my blog from 2004 brings back memories. Everything from the Van Gogh header image -- which was randomized, no less -- to the Coldfusion chart displaying the number of comments. Pure gold!


October 4, 2020
Lost in the Daze

I created a copy of this site for my friend Bob, who was looking for a simple/basic journal which allows him to include photos. ๐Ÿ™Œ

Go check it out: lostinthedaze.com ๐Ÿ‘ˆ


October 4, 2020
Harnessing the power of Open Graph

Here's another good example of how out of tune I've been with programming for a world filled with social media.

I've recently noticed that when I was pasting a link to one of my articles on Instagram or Twitter, the social media sites have been grabbing whatever they can to generate a preview. In my case, it usually ended up being my logo, the title of the post, and the permalink to the post. Twitter didn't even create a preview at all. Here's what it looked like when I pasted it into a direct message on Instagram:

Before Open Graph

So, like all old people, I turned to Google and found out about the Open Graph Protocol. This internet protocol was created by Facebook, introduced in 2010, and it is basically meant for promoting integration between Facebook and other social media sites (like Instagram, Twitter, etc) by making them into "graph" objects with the same functionality as Facebook objects.

I've integrated this now into my site--I'll explain below--and as you can see, the preview to the same article is now displaying exactly what I want it to. The first image found in the article, followed by the title and the first 100 characters of the article body:

After Open Graph

Here's how I did it. First, I found a piece of script to extract the first image from a body of HTML, in my case, from the body of my article. If an image is not found in the article, it will revert to displaying the logo.

$html_entry = $converter->ConvertToHtml($entries[0]['Body']);
preg_match('/<img.+src=[\'"](?P<src>.+?)[\'"].*>/i', $html_entry, $image);
$first_img = $image['src'];
if ($first_img)
	$og_img = $first_img;
else
	$og_img = $page->logo_url();

I then create the Open Graph object and pass it to the templating system (Smarty):

if ($first_img)
{
	$og = array(
		"type" => "entry",
		"image" => $og_img,
		"title" => $entries[0]['Title'],
		"description" => substr(strip_tags($html_entry),0,100) . "...",
		"url" => $page->base_url() . $log->log_base() . "/" . $entries[0]['PermaLink']
	);
	$smarty->assign("og",$og);
}

The page header template waits for an Open Graph object to come in, and when it does, it populates the meta tags. These meta tags are what social media sites look for to create the previews. If they exist, they'll be used. If they don't exist, the media sites will auto-generate them.

{if $og}
	<meta property="og:type" content="{$og['type']}" />
	<meta property="og:image" content="{$og['image']}" />
	<meta property="og:title" content="{$og['title']}" />
	<meta property="og:description" content="{$og['description']}" />
	<meta property="og:url" content="{$og['url']}" />
{/if}

There you have it. Another thing you've most likely never thought twice about. Now when you look at a preview created after you paste a link, you will know that it is because the social media platform you're pasting it to is going out to grab these Open Graph objects in the site you're linking from. ๐Ÿ˜„


October 3, 2020
Perspective visual: Ontario to Europe

on-eu

Digging around in the archives this morning. Here's a visual to show you how big the province of Ontario, Canada really is compared to Europe. From Toronto to Thunder Bay is roughly the same distance as Budapest to Hannover!


October 2, 2020
Love your job, not your company

A wise man once told me "no matter how good of a job you do at a company, two weeks after you're gone, you'll be forgotten. Business will go on, the company will survive."

I've kept that in the back of my mind ever since. I give my company 100% during work hours.

img1


October 1, 2020
Photography: Winter trees bring back memories

tree-doodle

I admire people that can draw a few simple lines and make it look awesome. I, cannot. I have this photo on the wall in my home office which I decided to draw today as I find myself wanting to draw more and more these days.

Revisiting my old photoblog tonight while writing this post I realized that

I've looked for those trees recently but couldn't spot them as we drove by. That area might be well overgrown now. If I do see them, I'll be sure to take a photo and update this post.

Update: Found them!


September 30, 2020
Simple charts and a simple pen

doodle

My friend Bob sent me a bunch of Field Notes notebooks along with a wide variety of pens because that's just the kind of cool guy he is.

The package arrived today and I tried out one of the Sarasa Dry 0.4mm gel pen he's been touting for the past while. He was right in that it dries quickly so there's no smudging. It's also super light so it's pretty effortless to write with. I'll have to use it more before I can see whether my wrist tires after prolonged use like it does sometimes with my Parker Jotter.

Also today the AI ad overlords suggested an article about learning to draw (which I obviously I fell for because here we are). There was a chart doodle by Saskia Keultjes that made me chuckle which also ended up as the subject of my testing of the new Sarasa Dry.

I found this doodle a great example to illustrate that when you're building charts or even dashboards they should be simple and concise. You don't need sensory overload to get your point across.


September 28, 2020
New logo feat. Mr. Monday

New logo feat. Mr. Monday

A new logo appeared on the site today featuring a character I've been doodling in my notebook from time to time called Mr. Monday. ๐Ÿ˜„ He'll be making appearances on here and probably on his own Tumblr account.


September 28, 2020
Alteryx saves the day

Alteryx workflow example

I've been using Alteryx for about a year and a half now and it's been an invaluable resource to prepare data from any inputs, be it SQL, Excel, Access or even flat files.

If you need to crunch lots of data and are spending hours writing queries or massaging excel spreadsheets and have the means to obtain an Alteryx license, I strongly suggest you give it a go. ๐Ÿ‘

๐Ÿ“บ Getting Started with a Tour of Alteryx Designer


September 27, 2020
Composer require league/commonmark

While I love Michel Fortin's Markdown script it does have a few limitations the more and more I learn about Markdown. So I have switched to Commonmark.

I have never used Composer so I asked the developer of Commonmark how to install his parser and he responded very promptly giving me some great pointers. At the same time I also found this resource which explained installing Composer on my host. I am now up and running and my previously converted posts work just as well with Commonmark.

I've been so out of the loop with open-source development but I am once again starting to have a profound appreciation for the community. These developers I've contacted recently have been great. Very friendly and knowledgeable.


September 24, 2020
PHP Markdown by Michel Fortin

Today I implemented Michel Fortin's PHP Markdown utility. This will help to drastically speed up writing these posts when I don't have to worry about embedding HTML for proper spacing of paragraphs, emphasizing text or inserting links. All posts on this site have now been converted to Markdown syntax. They're still being loaded from a database but all the syntax is Markdown.

I've been toying with the idea of converting the site to "static content" whereas all entries are .md files and eliminating the database, but have not convinced myself that's where I want to go just yet. If I were to do that I'd also have to get more creative with the cool search feature I talked about in the previous post.

If you're like me and have never used Markdown before, or are wondering whether it would be beneficial to you, here are some articles about the benefits of Markdown and here's a cool tutorial and a nice getting started guide.

I want to give a big shout out to Michel for developing the PHP utility and also for responding to my email within 10 minutes when I had questions about it. And of course a big thanks to Tim--from my wife most of all, I'm sure--for keeping my attention focused on my website at night instead of anything else useful. :D


September 23, 2020
Walkthrough of the search feature of this site

This site is my playground and I'm getting a kick out of building on it every day. Last night I made a search feature and showed Tim. He asked me about how it's done and then referred to me as "very data programmer" when I said I use stored procedures. :) I'll take it as a compliment. (I later learned about "static site generators" which is what his is and that's why he was wondering. His site has no database! What a concept!)

I'll give you an outline of how I'm doing the search here. Although the Smarty template system integration is new (I like Smarty; it just works), some classes are still using code I wrote over a decade ago.

First I create the stored procedure in the database. Along with search, this stored procedure will take care of displaying the entries as well as filtering and paging records, etc.

CREATE PROCEDURE `up_GetLog2Entries`(EID VARCHAR(255), OST INT, LMT INT, iST VARCHAR(25))
BEGIN
	SET @o = OST * LMT;
	SET @l = LMT;
	SET @s = iST;
	IF iST != '' THEN
		-- commented out the LIMIT clause to show all entries (paging disabled on the site, to mimic Tim's template. Might put back later.
		SET @sql = CONCAT("SELECT * FROM vw_GetLog2Entries WHERE MATCH(Body,Title) AGAINST('",@s,"*' IN BOOLEAN MODE)"); -- LIMIT ",@o,",",@l);
		PREPARE qs FROM @sql;
		EXECUTE qs;
		DROP PREPARE qs;
	ELSE
		IF EID = '' THEN
			IF @o = 0 and @l = 0 THEN -- once again, no limit clause; show all entries
				PREPARE qs FROM 'SELECT * FROM vw_GetLog2Entries';
              			EXECUTE qs;
      			ELSE 
				PREPARE qs FROM 'SELECT * FROM vw_GetLog2Entries LIMIT ?,?'; -- set limit and offset, ie: front page shows top 5 entries
				EXECUTE qs USING @o,@l;
			END IF;
			DROP PREPARE qs;
		ELSE
			SELECT * FROM vw_GetLog2Entries WHERE `Key` = EID OR PermaLink = EID; -- single entry
		END IF;
	END IF;
END

In PHP we start with an SQLWrapper class which sets up the connection and prepares the procedure caller. (This is the entire file, you're welcome to it.)

class SQLWrapper 
{
    private $mysqli;
    private $rs;
	protected $params = array();
    
    function __construct() {
        $this->mysqli = @new mysqli ("host","user","pass","database");
        if (mysqli_connect_errno()) {
			printf("Yes, I'm missing one database. Return it immediately!");
			exit();
        }
		$this->mysqli->set_charset('utf8');
	}

	function __destruct() {
		@$this->mysqli->close();
	}
	
	public function AddParameter($param) {
		$this->params[] = $param;
	}
	
	public function RunDBProcedure($p,$force_quote=false) {
		$pnum = 0;
		$ptot = count($this->params);
		$proc = "SET NAMES UTF8; ";
		$proc .= "CALL " . $p . "(";

		if ($ptot) {
			foreach ($this->params as $key=>$val) {
				if (!is_numeric($val) || $force_quote === true) {
					$quote = chr(34);
				}
				$v = (get_magic_quotes_gpc()==1?$val:addslashes($val));
				$proc .= $quote . $v . $quote;
				if ($pnum != $ptot-1)
				{
					$proc .= ",";
				}
				$pnum++;
			}
		}
		$proc .= ");";
				
		$arr = array();
		
		if ($this->mysqli->multi_query($proc)) {
			do {
				if ($result = $this->mysqli->store_result()) {
					while ($row = $result->fetch_array()) {
						$arr[] = $row;
					}
					$result->close();
				}
			} while ($this->mysqli->more_results() && $this->mysqli->next_result());
		}
		$this->params = array();
		return $arr;
	}
}

Next up, the Log class that uses the SQLWrapper to make calls to the database to retrieve the entries. Most of this class is reused from my old log.

require_once "SQLWrapper.class.php";

class Log
{
	
	/* PRIVATE VARS */
	private $title = "";
	private $base;
	private $sql = "";

	function __construct() {
		$this->base = "/writing";
		$this->sql = new SQLWrapper();
	}

	/* RETREIVE METHODS */
	function get($id='',$page=1,$limit=20,$searchterm='') {
		$this->sql->AddParameter(($id==0?'':$id));
		$this->sql->AddParameter($page-1);
		$this->sql->AddParameter($limit);
		$this->sql->AddParameter($searchterm);
		return $this->sql->RunDBProcedure("up_GetLog2Entries");
	}

There are additional functions in there to save, delete, add entries, etc., which I won't bore you with.

Next, in index.php I call the Log class and the template engine.

require_once "Smarty.class.php";
require_once "Log.class.php";

$smarty = new Smarty();
$log = new Log();

$search_term = strlen($_GET['q'])>2 ? $_GET['q'] : "";

/* WRITING */
if (isset($_GET['writing'])) {
	$smarty->assign("page","writing");
	/* SINGLE ENTRY */
	if (isset($_GET['id'])) {
		if ($search_term)
			$smarty->assign("search_term",$search_term);
		$smarty->assign("next_link",$log->get_next($_GET['id']));
		$smarty->assign("prev_link",$log->get_prev($_GET['id']));
		$smarty->assign("single",1);
		$entries = $log->get($_GET['id']);
		$smarty->assign("site_title", $entries[0]['Title'] . " - " . $page->title());
	/* LIST ENTRIES */
	} else {
		if ($search_term) {
			$smarty->assign("search_term",$search_term);
			$smarty->assign("site_title","Entries containing '" . $search_term . "' - " . $page->title());
		}
		$entries = $log->get('',0,0,$search_term);
	}
	$smarty->assign("entries_array",$entries);

/* so on and so forth */

Finally, the template file to display it all, which is a simple Smarty {section} to loop through the entries.

{if $search_term}
<p class='text-success mt-2'>Entries containing '{$search_term}'</p>
{/if}
{section name=sec1 loop=$entries_array}
<div class='mt-3'><b><a href='/writing/{$entries_array[sec1]['PermaLink']}{if $search_term}/{$search_term}{/if}'>{$entries_array[sec1]['Title']}</a></b> ยท {$entries_array[sec1]['TimeAdded']|relative_time}</div>
{/section}

That concludes the walk-through of how I do the search on this site. :) Drop me a line if you want to share your thoughts or comments.


September 20, 2020
Code to Learn with Lynx Coding

Back in the summer John participated in Code to Learn, an online course consisting of 5 one-hour sessions where he learned some very valuable skills, not only about programming logic but social skills, language and maths.

They used a platform called Lynx Coding which at the time was funded by the Canadian Government and available to students for free. I found it very intuitive and well-laid-out and there was plenty of documentation available to support.

I am happy we were able to participate in this course. John really enjoyed it as well and wanted to share his newfound skills with the world. ๐Ÿ˜Š

๐Ÿ“บ John demonstrates Code to Learn with Lynx Coding


September 19, 2020
I hit a milestone and achieved a goal

Update: 6 months later, another milestone! ๐Ÿ˜ƒ

30lbs lost in 4 months

Sidetracking for a second here to share some exciting news. Today I both hit a milestone and achieved a goal. The goal was to have my website back up and running by the time I reach the "30lbs lost" milestone.

Today both of these things were realized.

This for me is a monumental personal achievement! Today I weigh 30lbs less than back when I started this journey 4 months ago. I have not been this weight in over a decade.

If you're wondering how, this article will outline what worked for me. Please note that I am not a nutritionist or a dietary expert so take my advice with a grain of salt (hah).

The secret is very simple: I write down everything I eat now. I don't imagine I'll do this forever, as months go on I have a better grasp of what to eat and how much to eat but it's helped me keep on track. I use MyFitnessPal Premium. This app allows me to visualize macro-nutrients (carbs, fats, proteins), see my history, and keeps me pretty motivated day after day. I'm not being 100% accurate at tracking every single calorie and I suggest you don't either if you decide to do this, as it's not good for your mental health. It's easy to fall into a trap and develop a disorder if you become obsessed. Do your best and try to be as accurate as possible without sweating the small details.

The best way to get started is setting a daily limit (macros or calories). The app will help you set a goal (which you can adjust as you go along). For example let's say you set yourself on a "2000 calories a day" limit, your goal is to consume as much healthy foods as you can in a day to add up to ~2000 calories. I say healthy as you will soon learn a lot about how you eat, what kind of crap is in your food but most of all, how much you have been overeating every day. That's it. Stick to that and you'll reach your weight goal.

My wife and I have also been "meat-free" for nearly a year now. For me this decision was personal as my cholesterol was high and my doctor said--in these words--"change something or I will have to put you on a pill".

We tried the plant-based approach, which I have to tell you has worked out really well. We still eat fish and eggs, and some cheese/dairy (so it's more of a pescatarian lifestyle) but have been replacing for example a juicy, fatty beef burger with a form of this amazing black bean burger recipe. There's tons of options for non-meat-lovers out there these days.

I'm not saying you should stop eating meat, even though there's a lot of resources out there that may sway you to do so for a lot of different reasons, but I am saying be conscious of what you put in your body.

Cutting meat out of our diet did not reduce my weight and it barely reduced my cholesterol, though that's hard to judge as I found myself snacking on junk more, which probably contributed to the elevated cholesterol still.

The only thing that has worked so far was setting a limit and sticking to it. Like I said, if you do this you'll soon find out how to pack yourself with nutrients to keep you full and feeling good. Your appetite will also change and over time the cravings for crap go away.

Our plant-based diet (lifestyle, not diet) also consists of eating very minimal fried foods and processed foods. I consider "Beyond Meat" as processed food too so we don't use it as a daily alternate to meat. We are limiting pastas, breads and junk foods (chips, cookies etc). We still indulge every now and then, but stick to the recommended serving size--which for chips (crisps) is a bowl of chips, not half the bag ๐Ÿ˜„. Indulge, but indulge responsibly. It keeps good mental balance as well as physical balance.

These are the main contributors to where I am at today. Cholesterol has now dropped by 30% and I'm 30lbs lighter. The magic number 30!

That's all for now. Hope you have a great weekend!


September 19, 2020
Calculating Good Friday in T-SQL

A few years back I had to generate a table using T-SQL that lists Canadian holidays for the upcoming x number of years. This was part of our migration from our old Nortel phone system to Skype for Business Public Telephony. This requirement was for programming the after-hours and holiday menus for the upcoming number of years. I think back to this task every now and then because it was a fun little challenge.

Most of the holidays are simple to calculate because they fall on the same day every year but Easter or Good Friday are based on the Paschal Full Moon following the spring equinox, which changes year to year. So here's a script based on this post, which is based on a script from NOAA, to calculate Good Friday:

CREATE FUNCTION dbo.uf_GetGoodFriday (@Year INT)
RETURNS datetime 
WITH EXECUTE AS CALLER
AS 
BEGIN 
	DECLARE @intYear INT, @EpactCalc INT, @PaschalDaysCalc INT, @NumOfDaysToSunday INT, @EasterMonth INT, @EasterDay INT 

	SET @EpactCalc = (24 + 19 * (@Year % 19)) % 30 
	SET @PaschalDaysCalc = @EpactCalc - (@EpactCalc / 28) 
	SET @NumOfDaysToSunday = @PaschalDaysCalc - ((@Year + @Year / 4 + @PaschalDaysCalc - 13) % 7) 

	SET @EasterMonth = 3 + (@NumOfDaysToSunday + 40) / 44 
	SET @EasterDay = @NumOfDaysToSunday + 28 - (31 * (@EasterMonth / 4)) 

	RETURN ( 
		SELECT DATEADD(dd,-2,CONVERT(DATETIME, CONVERT(VARCHAR(2),@EasterMonth) + '/' + CONVERT(VARCHAR(2),@EasterDay) + '/' + CONVERT(VARCHAR(4), @Year)))
	) 
END
GO

Trying it out for next year:

SELECT uf_GetGoodFriday (2021)

Returns:

2021-04-02 00:00:00.000

September 17, 2020
Hello, world!

Welcome to the newest rendition of my website. This is version 4--or maybe 24 by now, it's hard to keep track--and if you're saying to yourself "hey, this layout looks familiar" you must've seen my friend Tim's page and realized that this is pretty much a copy of his site. And you'd be right, the layout definitely is a direct copy (the back end, not so much). His site is actually available at GitHub if you're cool enough to be in the Ruby on Rails crowd. I am unfortunately not so it's just PHP in the back end, Smarty in the middle, and Bootstrap in the front here.

I plan to build on this in the future (some more functionality, etc.) but as a foundation, this pretty much suits what I wanted to get out of this space perfectly. Thanks Tim!

If you want, you can find the previous iteration of my site, now frozen in time, over at 2020.miklos.ca

I can't believe it's been over 7 years since I've done anything in this space. So much has happened! The last I left it, I got married, we then had a baby, bought a new car, moved to a new house, yadda yadda yadda and here we are.

Anyway, I hope you enjoy your stay.