How To: Convert Day Number to Month and Day

I needed to convert the day number, that is, what day it is between 1 and 365, into a month and day without using Calendar / DateTime classes and such today, so I knocked up a few simple functions that work assuming it’s not a leap year (i.e. that February has 28 days).

Here they are if they’re of any use to you:

int dateToDayNumber(int month, int day)
{
	// Catch invalid input and return early
	if (month < 1 || month > 12 || day < 1 || day > 31) return 0;
 
	if (month == 1 ) return       day;
	if (month == 2 ) return 31  + day;
	if (month == 3 ) return 59  + day;
	if (month == 4 ) return 90  + day;
	if (month == 5 ) return 120 + day;
	if (month == 6 ) return 151 + day;
	if (month == 7 ) return 181 + day;
	if (month == 8 ) return 212 + day;
	if (month == 9 ) return 243 + day;
	if (month == 10) return 273 + day;
	if (month == 11) return 304 + day;
	return 334 + day;
}
 
int dayNumberToMonth(int dayNumber)
{
	// Catch invalid input and return early
	if (dayNumber < 1 || dayNumber > 365) return 0;
 
	if (dayNumber <= 31 ) return 1;  // Jan
	if (dayNumber <= 59 ) return 2;  // Feb
	if (dayNumber <= 90 ) return 3;  // Mar
	if (dayNumber <= 120) return 4;  // Apr
	if (dayNumber <= 151) return 5;  // May
	if (dayNumber <= 181) return 6;  // Jun
	if (dayNumber <= 212) return 7;  // Jul
	if (dayNumber <= 243) return 8;  // Aug
	if (dayNumber <= 273) return 9;  // Sep
	if (dayNumber <= 304) return 10; // Oct
	if (dayNumber <= 334) return 11; // Nov
	return 12;                       // Dec
}
 
int dayNumberToDayOfMonth(int dayNumber)
{
	// Catch invalid input and return early
	if (dayNumber < 1 || dayNumber > 365) return 0;
 
	if (dayNumber <= 31 ) return dayNumber;       // Jan
	if (dayNumber <= 59 ) return dayNumber - 31;  // Feb
	if (dayNumber <= 90 ) return dayNumber - 59;  // Mar
	if (dayNumber <= 120) return dayNumber - 90;  // Apr
	if (dayNumber <= 151) return dayNumber - 120; // May
	if (dayNumber <= 181) return dayNumber - 151; // Jun
	if (dayNumber <= 212) return dayNumber - 181; // Jul
	if (dayNumber <= 243) return dayNumber - 212; // Aug
	if (dayNumber <= 273) return dayNumber - 243; // Sep
	if (dayNumber <= 304) return dayNumber - 273; // Oct
	if (dayNumber <= 334) return dayNumber - 304; // Nov
	return dayNumber - 334;                       // Dec
}

A touch cheap, but it gets the job done.

How-To: Get human-friendly dates in Java using the Calendar class

ISO-8601 has a PosseAs part of the groundwork for some upcoming Java shenanigans I’ve finished off reading/skimming Head First Java – and it’s a truly excellent book. I wish it covered Connector/J & MySQL, but I can find that knowledge elsewhere so it’s not that big of a deal.

As part of today’s prep I ended up writing something I didn’t think I’d have to – a function to get a nice, human-readable date as a String. I don’t like having to write things like this because surely getting a nice, human-readable version of what’s essentially a timestamp (if the Date class wasn’t deprecated) has been solved. By which I mean that it’s done – let’s never, ever solve that same problem again. But the issue is that if you don’t know where to look for the solution, then it can take you longer to find it than to solve the problem again, and I think that’s what’s happened to me today when I didn’t feel like learning and using the joda-time API to do something I felt core Java should be capable of.

Before we get to the source code I’d just like to direct a quick word to to the Java API Calendar class developers to say:

  • I don’t care who you worship – the first day of the week is Monday, please see ISO-8601, and
  • Chopping and changing between zero-based constants and one-based constants sucks massive d*cks.

Anyways, the getFriendlyDate function gets a day/month/year from a Calendar object and converts it into something a human being might like to read, like this…

$ java FriendlyDate
 
The only-friendly-to-robots date is : java.util.GregorianCalendar[time=1317711607960,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="GMT+10:00",offset=36000000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2011,MONTH=9,WEEK_OF_YEAR=40,WEEK_OF_MONTH=1,DAY_OF_MONTH=4,DAY_OF_YEAR=277,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=5,HOUR_OF_DAY=17,MINUTE=0,SECOND=7,MILLISECOND=960,ZONE_OFFSET=36000000,DST_OFFSET=0]
 
The more-like-a-timestamp date is   : Tue Oct 04 17:00:07 GMT+10:00 2011
 
The verbose friendly date is        : Tuesday the 4th of October 2011
 
The non-verbose friendly date is    : Tuesday, 4th October 2011

And the source code to perform this scandalous transformation is:

Update 13-Oct-2011: Just realised that the 1st, 2nd, 3rd etc. stuff is broken because I’m checking the last digit in the day of month and using that, so if it ends with 3 I give the suffix “rd” making it “3rd”, which is fine until you get to the 13th, which comes out as “13rd”… I don’t have time to fix it up right now, but I will in the near future. Or do a total re-write using the mighty Shetboy’s formatting advice (see comments).

import java.util.Calendar;
 
public class FriendlyDate
{
	public static void main(String[] args)
	{
		 // Get the current date and time in a Calendar object
		Calendar now = Calendar.getInstance();
 
		System.out.println("The only-friendly-to-robots date is : " + now.toString() );
		System.out.println();	
 
		System.out.println("The more-like-a-timestamp date is   : " + now.getTime() );
		System.out.println();
 
		String verboseFriendlyDate = getFriendlyDate(now, true);
		System.out.println("The verbose friendly date is        : " + verboseFriendlyDate);
		System.out.println();
 
		String friendlyDate = getFriendlyDate(now);
		System.out.println("The non-verbose friendly date is    : " + friendlyDate);
	}
 
	// Overloaded function to cut down on calling arguments, no default parameters in Java =/
	public static String getFriendlyDate(Calendar theDate)
	{
		return getFriendlyDate(theDate, false);
	}
 
	// Function to get a human readable version of a Calendar object
	// If verbose is true we slightly expand the date wording
	public static String getFriendlyDate(Calendar theDate, boolean verbose)
	{
		int year       = theDate.get(Calendar.YEAR);
		int month      = theDate.get(Calendar.MONTH);
		int dayOfMonth = theDate.get(Calendar.DAY_OF_MONTH);
		int dayOfWeek  = theDate.get(Calendar.DAY_OF_WEEK);
 
		// Get the day of the week as a String.
		// Note: The Calendar DAY_OF_WEEK property is NOT zero-based, and Sunday is the first day of week.
		String friendly = "";
		switch (dayOfWeek)
		{
			case 1:
				friendly = "Sunday";
				break;
			case 2:
				friendly = "Monday";
				break;
			case 3:
				friendly = "Tuesday";
				break;
			case 4:
				friendly = "Wednesday";
				break;
			case 5:
				friendly = "Thursday";
				break;
			case 6:
				friendly = "Friday";
				break;
			case 7:
				friendly = "Saturday";
				break;
			default:
				friendly = "BadDayValue";
				break;
		}
 
		// Add padding and the prefix to the day of month
		if (verbose == true)
		{
			friendly += " the " + dayOfMonth;
		}
		else
		{
			friendly += ", " + dayOfMonth;
		}
 
		String dayString = String.valueOf(dayOfMonth);   // Convert dayOfMonth to String using valueOf
 
		// Suffix is "th" for day of day of month values ending in 0, 4, 5, 6, 7, 8, and 9
		if (dayString.endsWith("0") || dayString.endsWith("4") || dayString.endsWith("5") || dayString.endsWith("6") ||
                    dayString.endsWith("7") || dayString.endsWith("8") || dayString.endsWith("9"))
		{
			friendly += "th ";
		}
 
		// Suffix is "st" for day of month values ending in 1
		if (dayString.endsWith("1"))
		{
			friendly += "st ";
		}
 
		// Suffix is "nd" for day of month values ending in 2
		if (dayString.endsWith("2"))
		{
			friendly += "nd ";
		}
 
		// Suffix is "rd" for day of month values ending in 3
		if (dayString.endsWith("3"))
		{
			friendly += "rd ";
		}
 
		// Add more padding if we've been asked to be verbose
		if (verbose == true)
		{
			friendly += "of ";
		}
 
 
		// Get a friendly version of the month.
		// Note: The Calendar MONTH property is zero-based to increase the chance of developers making mistakes.
		switch (month)
		{
			case 0:
				friendly += "January";
				break;
			case 1:
				friendly += "February";
				break;
			case 2:
				friendly += "March";
				break;
			case 3:
				friendly += "April";
				break;
			case 4:
				friendly += "May";
				break;
			case 5:
				friendly += "June";
				break;
			case 6:
				friendly += "July";
				break;
			case 7:
				friendly += "August";
				break;
			case 8:
				friendly += "September";
				break;
			case 9:
				friendly += "October";
				break;
			case 10:
				friendly += "November";
				break;
			case 11:
				friendly += "December";
				break;
			default:
				friendly += "BadMonthValue";
				break;
		}
 
		// Tack on the year and we're done. Phew!
		friendly += " " + year;		
 
		return friendly;
 
	} // End of getFriendlyDate function
 
} // End of FriendlyDate class

Cheers!

How to: Create and insert SQL DATE or DATETIME objects using PHP

I was helping a student with a bit of project work the other day and had to create a properly formatted Date object from input provided by some dropdown menus, so in the spirit of only ever solving the same problem once, this is how you go about it… I’ll use MySQL for this example, but if the DATEs or DATETIMEs or whatever you’re using have a slightly different format in your DBMS of choice, then the principle’s the same to generate a compatible object.

MySQL Time Formats

The MySQL DATE type has the format YYYY-MM-DD, so for example today (or at least when I’m writing this post) would be 2011-08-19.

The MySQL DATETIME type has the format YYYY-MM-DD HH:ii:SS, so if it’s 9:36am and 53 seconds into the 19th of August 2011, that would be 2011-08-19 09:36:53.

The formatting of the numbers (i.e. the sequence of digits) is super important, but the dashes and colons are optional. This means that when you’re creating a PHP object to represent a DATE or DATETIME, you could create an object which contains 2011-08-19 or 20110819 etc. and MySQL would accept them as the exact same thing.

Constructing a suitable object in PHP

To create our PHP object, we’re going to use the PHP date and mktime functions. For this example I’m just going to create some variables which hold any date or time values, but in the real world you’d probably get them from a dropdown menu or calendar control.

<?PHP
	// Create variables to store 1:15pm and 28 seconds on the 20th of August 2011
	// NOTE: If you don't enclose your values with apostrophes or quotes, then things will go wrong because
	// single digit values need the leading zeroes, for example the 08 below will turn into 8, and then
	// the whole thing's broken. See the bottom of this post for an alternative padding method for numbers.
	$theYear   = '2011'; // YYYY
	$theMonth  = '08';   // MM
	$theDay    = '20';   // DD
	$theHour   = '13';   // HH
	$theMinute = '15';   // ii
	$theSecond = '28';   // SS
 
	// Create an object suitable for insertion into a MySQL DATE field
	// The date 'YmD' parameters mean: year as YYYY, month as MM w/ leading zeroes, day as DD w/ leading zeroes
	// Full PHP date parameters can be found at: http://php.net/manual/en/function.date.php
	// The format of mktime is: hour, minute, second, month, day, year
	// Full PHP mktime parameters can be found at: http://php.net/manual/en/function.mktime.php
	$sqlDate = date('Ymd', mktime(0, 0, 0, $theMonth, $theDay, $theYear));
 
	// Create an object suitable for insertion into a MySQL DATETIME field
	// The date 'YmdHis' means: Ymd as above, hours as HH w/ leading zeroes, minutes as ii w/ leading zeroes, ss as seconds w/ leading zeroes
	$sqlDateTime = date('YmdHis', mktime($theHour, $theMinute, $theSecond, $theMonth, $theDay, $theYear));
 
	// Now you've got your DATE or DATETIME object, sling 'em in your database with something like this...
	$dbConn = mysql_connect("localhost", "root", "") or die("It is a good day to die!: " . mysql_error()); // Default WAMP settings
 
	// Choose the database to work with
	mysql_select_db("MyLovelyDatabase", $dbConn) or die("It is a good day to fail to connect to your DB!: " . mysql_error());
 
	// Create the insert statement
	$sqlStatement = "INSERT INTO TestTable (someDate, someDateTime) VALUES ('$sqlDate', '$sqlDateTime')";
 
	// Execute the statement
	$result = mysql_query($sqlStatement, $dbConn);
 
	// Display result of the statement. If this is 1 then the insertion was successful, otherwise it wasn't
	echo "Statement executed and gave the following result: " . $result;
 
	// Shut down the connection to the database
	mysql_close($dbConn);
?>

Which when we then take a look at the database shows us:

DateTime Database Record

Result!

Automatically padding out numerical data with leading zeroes

If you need to work directly with numerical data (as opposed to strings), then you can use the following function (written by matrebatre over on the str_pad page) to pad out your data:

<?PHP
	function padNumber($theNumber, $numDigits)
	{
		// str_pad format: value, desired number of characters, what to pad with, where to place the padding
		return str_pad((int) $theNumber, $numDigits, "0", STR_PAD_LEFT);
	}
 
	$value = 5;
	$paddedValue = padNumber($value, 1); echo $paddedValue . "<br/>"; // 5
	$paddedValue = padNumber($value, 2); echo $paddedValue . "<br/>"; // 05
	$paddedValue = padNumber($value, 3); echo $paddedValue . "<br/>"; // 005
?>

Done & dusted.