Return Month/Day from Day Number
|
|
Thread rating:  |
Webbiz - 25 Jul 2009 17:47 GMT Could someone help me with this?
How might one convert a day number from 1 to 366 into a Month and Day string?
Most years are 365, I know. But the assumption will be that each year contains 366 (is a leap) by this function.
Day Num as Return Values:
1 = January 1 60 = February 29 366 = December 31
The above must always be true.
Now, I was thinking about doing something like this. Maybe this is the best way to go, maybe not. If you have a better idea, please let me know.
What I thought I'd do in my GetMMDDFromDayNum function is this:
------------------------ Function GetMMDDFromDayNum(ByVal DayNum as long) as String Dim dLeapYearRef as Date
'Ref date is leap year minus 1 day dLeapYearRef = #12/31/1999#
GetMMDDFromDayNum = Format(dLeapYearRef + DayNum, "MMMM dd")
End Function
Any thoughts?
Thanks.
Webbiz
MikeD - 26 Jul 2009 03:19 GMT > Could someone help me with this? > [quoted text clipped - 30 lines] > > Any thoughts? I know I would definately use DateAdd:
Print DateAdd("d",DayNum,#12/31/1999#)
Always better to use date functions than to rely on mathematically adding and subtracting. If you want the date formatted a certain way, then nest that inside a Format function. Of course, you must have a date to base this on or devise a default date to use. For example, a logical choice for a default would be 12/31 of last year...and you could write that as such:
Print DateAdd("d", DayNum, DateSerial(Year(Date) - 1, 12, 31))
(assuming the PC's clock is correct...at least the year)
I don't know if I'd write this into a wrapper function (your GetMMDDFromDayNum). I suppose it depends on how many times I'd need to call it. If only once or twice, I doubt that I would. If a dozen times, probably. If I WERE going to write a wrapper function, I think I'd make the date an optional parameter (mind you, I'm pretty much just winging this). Something like this:
Public Function GetDateFromDayNum(ByVal DayNum As Long, Optional RefDate As Variant) As Date
If Not IsDate(RefDate) Then GetDateFromDayNum = DateAdd("d", DayNum, DateSerial(Year(Date) - 1, 12, 31)) Else GetDateFromDayNum = DateAdd("d", DayNum, RefDate) End If
End Function
(a Variant, in this case, is beneficial)
I would NOT have the function return a specifically formatted string as in your example because that would limit the function's usefulness (it wouldn't be generic anymore). Better to have it return a Date data type and let the calling procedure format it as appropriate.
I'm sure others will have ideas that may be better, but I think this function would give you a bit of flexibility so that you could use it in multiple applications.
-- Mike
Webbiz - 26 Jul 2009 03:35 GMT >> Could someone help me with this? >> [quoted text clipped - 74 lines] >function would give you a bit of flexibility so that you could use it in >multiple applications. Hello Mike-
Since the purpose of my function is to return a date based on a Day Number as if the year is a leap year (1 to 366), one can't just use any date reference for that to happen, right?
So the routine MUST use the date of the last day just prior to a leap year so that by adding 1 to 366, you get January 1 to December 31st, right?
On another note, wouldn't putting DateAdd inside Format just be adding more overhead? Since I'm using an actual Date type and pre-loading it with #December 31, 1999#, using simple addition of 1 to 366 can easily be done without any conversion (such as Datevalue, etc.) and really no need for DateAdd.
I completely agree with the formatting issue in as much as limiting the re-usuability of the function. However, this particular routine resides in a class so it's contained and single-minded. It does one thing. It takes a number from 1 to 366 and returns the verbage of the month and day, such as "March 14", etc. It's for display purposes as one moves the mouse across a picturebox.
Thanks for your comments.
Webbiz
dpb - 26 Jul 2009 03:54 GMT ...
> Since the purpose of my function is to return a date based on a Day > Number as if the year is a leap year (1 to 366), one can't just use > any date reference for that to happen, right? ...
OK, I'll bite...for what earthly reason would you not consider the actual number of days/year?
But, if you're adamant of it always being 366, then sure the lookup table based on accumulated days is as the logical implementation. (Of course, it's also a reasonable implementation w/ the addition of a LeapYear logical input, too...)
--
Webbiz - 26 Jul 2009 21:42 GMT >... >> Since the purpose of my function is to return a date based on a Day [quoted text clipped - 9 lines] >course, it's also a reasonable implementation w/ the addition of a >LeapYear logical input, too...) Because the purpose of function is to cover EVERY SINGLE DAY possible, whether or not that day exists for the current year. Note that it returns Month/Day and not year as it is year neutral. There is really no point in getting too deep into details why. Just trust that it is important. :-)
I don't understand the 'lookup' table. You mean to fill-up a complete table (like an array) that contains every single day, perhaps in strings? January 1, January 2, January 3...
The the DayNum would index this array (lookup table)?
Would you do it this way rather than adding the Day Num to December 31, 1999?
Thanks.
Webbiz
Jim Mack - 27 Jul 2009 00:22 GMT > Because the purpose of function is to cover EVERY SINGLE DAY > possible, whether or not that day exists for the current year. Note > that it returns Month/Day and not year as it is year neutral. There > is really no point in getting too deep into details why. Just trust > that it is important. :-) Then the best answer is the one you already noted: DateAdd the serial day number to a base date of 1999-12-31, and discard the year part of the result. There's no point, and no benefit, in making it any more complicated.
 Signature Jim
James Hahn - 27 Jul 2009 02:21 GMT That won't work. 2000 was not a leap year.
>> Because the purpose of function is to cover EVERY SINGLE DAY >> possible, whether or not that day exists for the current year. Note [quoted text clipped - 6 lines] > the result. There's no point, and no benefit, in making it any more > complicated. Rick Rothstein - 27 Jul 2009 03:23 GMT > That won't work. 2000 was not a leap year. Yes it was.
 Signature Rick (MVP - Excel)
James Hahn - 27 Jul 2009 10:29 GMT Sorry - you're right. I got my exceptions the wrong way around.
>> That won't work. 2000 was not a leap year. > > Yes it was. Jim Mack - 27 Jul 2009 04:13 GMT > That won't work. 2000 was not a leap year. Sorry, I didn't know. I'll go back and correct all my calendars.
 Signature Jim
>>> Because the purpose of function is to cover EVERY SINGLE DAY >>> possible, whether or not that day exists for the current year. [quoted text clipped - 9 lines] >> -- >> Jim Jimekus - 27 Jul 2009 06:18 GMT http://www.google.co.nz/search?q=365.25+30.61&btnG=Web
These links are for those who want to program date functions from the intrinsic Julian date characteristics rather than rely on Visual Basic date routines.
The 365.25 in these formulae should nowadays be replaced by 365.25-1/200=365.2495 to handle those pesky turn of the even century years.
Jeff Johnson - 29 Jul 2009 01:17 GMT > http://www.google.co.nz/search?q=365.25+30.61&btnG=Web > [quoted text clipped - 5 lines] > 365.25-1/200=365.2495 to handle those pesky turn of the even century > years. !!! It has nothing to do with "even" century years, by which I assume you mean the first two digits of a 4-digit year. By that definition, 2200 would be an "even century" year, but it will NOT be a leap year. It is only if the year if divisible by 400 (FOUR hundred, not the TWO hundred in your calculation) that it will be a leap year.
Jimekus - 29 Jul 2009 02:38 GMT > >http://www.google.co.nz/search?q=365.25+30.61&btnG=Web > [quoted text clipped - 11 lines] > year if divisible by 400 (FOUR hundred, not the TWO hundred in your > calculation) that it will be a leap year. I was put crook and now stand corrected. the constant s/b 365.25-1/400=365.24975 when using 4 digit years.
Jimekus - 29 Jul 2009 02:44 GMT > > "Jimekus" <Jime...@gmail.com> wrote in message > [quoted text clipped - 18 lines] > I was put crook and now stand corrected. the constant s/b > 365.25-1/400=365.24975 when using 4 digit years. oops! s/b 365.2475 - sorry to be out by a significant number of places.
Nobody - 29 Jul 2009 03:13 GMT "Jimekus" <Jimekus@gmail.com> wrote in message news:aa73046e-eb36-4a9c-9dc6-
> I was put crook and now stand corrected. the constant s/b > 365.25-1/400=365.24975 when using 4 digit years. Perhaps it's a bit more complicated:
http://en.wikipedia.org/wiki/Leap_year#Algorithm
dpb - 27 Jul 2009 17:34 GMT ...
>> But, if you're adamant of it always being 366, then sure the lookup >> table based on accumulated days is as the logical implementation. (Of [quoted text clipped - 6 lines] > no point in getting too deep into details why. Just trust that it is > important. :-) Well, it _isn't_ possible unless the year in question is a leap year.
> I don't understand the 'lookup' table. You mean to fill-up a complete > table (like an array) that contains every single day, perhaps in > strings? January 1, January 2, January 3... ...
No, simply the accumulated month beginning day.
--
dpb - 27 Jul 2009 21:54 GMT ...
> No, simply the accumulated month beginning day. OK, I still don't think it makes any sense to not have the LpYear flag, but here's the lookup table solution. I did this in Matlab; you can translate to VB easily enough I'm sure... :)
All you need is the daysum array as the lookup table; I just built it via days of month since I remember them better than days into the year... :)
>> days=[0, 31,29,31, 30,31,30, 31,31,30, 31,30,31] days = 0 31 29 31 30 31 30 31 31 30 31 30 31
>> daysum=cumsum(days) daysum = 0 31 60 91 121 152 182 213 244 274 305 335 366
% the engine for idx=1:13 if day<=daysum(idx) m=idx-1; break end end dom = day-daysum(m)
% some edge case tests...
>> day=1;for idx=1:13, if day<=daysum(idx), m=idx-1;break,end,end >> disp([m day-daysum(m)]) 1 1
>> day=32;for idx=1:13, if day<=daysum(idx),m=idx-1;break,end,end disp([m day-daysum(m)]) 2 1
>> day=365;for idx=1:13, if day<=daysum(idx),m=idx-1;break,end,end disp([m day-daysum(m)]) 12 30
I didn't try to time it in VB; I'd expect it to be faster than the intrinsic date function versions as it doesn't have any generic date function overhead. Adding in a format$() operation of some sort probably would make it quite close if it is indeed a string result required.
Anyway, the general technique is probably of value to demonstrate if nothing else...
--
--
Karl E. Peterson - 27 Jul 2009 22:17 GMT >> I don't understand the 'lookup' table. You mean to fill-up a complete >> table (like an array) that contains every single day, perhaps in >> strings? January 1, January 2, January 3... > > No, simply the accumulated month beginning day. Why? You've already got a lookup table built into OLE.
If you want the leap year date, just do this:
DateSerial(2000, 1, DayOfYear)
If you want the non-leap year date:
DateSerial(2001, 1, DayOfYear)
If you want the current year date:
DateSerial(Year(Now), 1, DayOfYear)
If you want the date in a specific year:
DateSerial(TheYear, 1, DayOfYear)
And so on. No gynmastics required.
 Signature .NET: It's About Trust! http://vfred.mvps.org
dpb - 28 Jul 2009 01:00 GMT >>> I don't understand the 'lookup' table. You mean to fill-up a complete >>> table (like an array) that contains every single day, perhaps in >>> strings? January 1, January 2, January 3... >> No, simply the accumulated month beginning day. > > Why? You've already got a lookup table built into OLE. ...
Just another way to skin a cat, basically.
--
Karl E. Peterson - 28 Jul 2009 01:10 GMT >>>> I don't understand the 'lookup' table. You mean to fill-up a complete >>>> table (like an array) that contains every single day, perhaps in [quoted text clipped - 6 lines] > > Just another way to skin a cat, basically. Okay, sure, the more the merrier! :-)
 Signature .NET: It's About Trust! http://vfred.mvps.org
argusy - 28 Jul 2009 15:28 GMT > ... >>> But, if you're adamant of it always being 366, then sure the lookup [quoted text clipped - 9 lines] > > Well, it _isn't_ possible unless the year in question is a leap year. Yes it is.
All the OP wants is an indication of month/day when his mouse moves over a picture. I'm assuming the "Daynum" is buried with all pictures displayed, and he's extracting that with the conversion. That's why "February 29" has to be included - not that it's showing a leap year, but that it is a valid date (that only comes around every four years)
So if his mouse moves over picture 1, and "January 12" comes up, then he's happy.
If the mouse moves over picture 33 and "March 01" comes up ("Yellow sticky" or a textbox somewhere on the screen), then it's indicating that particular date, whether it's "March 01" for a leap year, or "March 01" for a non-leap year. Get it?
At least that's what I deduce from the info he's been handing out bit by bit with his follow up emails. (Like extracting teeth from a snake <g>)
argusy
>> I don't understand the 'lookup' table. You mean to fill-up a complete >> table (like an array) that contains every single day, perhaps in [quoted text clipped - 4 lines] > > -- dpb - 28 Jul 2009 15:33 GMT ...
>> Well, it _isn't_ possible unless the year in question is a leap year. > > Yes it is. No it isn't... :) (unless)
> All the OP wants is an indication of month/day when his mouse moves over > a picture. I'm assuming the "Daynum" is buried with all pictures > displayed, and he's extracting that with the conversion. That's why > "February 29" has to be included - not that it's showing a leap year, > but that it is a valid date (that only comes around every four years) In that case, a random number is just as good as far as I can see...
> So if his mouse moves over picture 1, and "January 12" comes up, then > he's happy. [quoted text clipped - 3 lines] > that particular date, whether it's "March 01" for a leap year, or "March > 01" for a non-leap year. Get it? No, can't say as I do...I'd need a lot more firm context than this to follow the point (but then again, I'm an engineer by training... :) )...
--
Larry Serflaten - 26 Jul 2009 04:29 GMT > On another note, wouldn't putting DateAdd inside Format just be adding > more overhead? Since I'm using an actual Date type and pre-loading it > with #December 31, 1999#, using simple addition of 1 to 366 can easily > be done without any conversion (such as Datevalue, etc.) and really no > need for DateAdd. Au contrare.
The Date and Long are two different data types. One must be coersed into the other before they can be added. I'd guess the Long get converted to a Date type (a widening operation) in the process.
Even if you don't include a conversion, the VB compiler will put one in there.
Since you pass in a Long, you could do the addition as Longs, and then the formatting:
Function GetMMDDFromDayNum(ByVal DayNum As Long) As String 'Ref date is #12/31/1999# GetMMDDFromDayNum = Format$(36525 + DayNum, "MMMM dd") End Function
LFS
Webbiz - 26 Jul 2009 21:48 GMT >> On another note, wouldn't putting DateAdd inside Format just be adding >> more overhead? Since I'm using an actual Date type and pre-loading it [quoted text clipped - 19 lines] > >LFS Okay! Now we're talking.
I get the conversion part, and your example does remove that one step. I didn't see an advantage to using a function (DateAdd) within another function (Format) within my main procedure function as a step up to what I was doing. As you imply, both produced conversions. Yours apparently does not as you have already provided the converted form of the date and a value. I like it.
This is why I ask these kinds of questions!
Thanks Larry.
Webbiz
Bob Butler - 27 Jul 2009 00:04 GMT <cut>
> I didn't see an advantage to using a function (DateAdd) within another > function (Format) making the code more self-documenting, easier to read and understand, and therefore easier to maintain
Larry Serflaten - 27 Jul 2009 00:19 GMT > > I didn't see an advantage to using a function (DateAdd) within another > > function (Format) > > making the code more self-documenting, easier to read and understand, and > therefore easier to maintain Yet another way to skin that cat....
Function DOY(ByVal DayOfYear As Long) As String DOY = Format$(DateSerial(1999, 12, 31 + Num), "mmmm dd") End Function
LFS
Larry Serflaten - 27 Jul 2009 00:24 GMT Doh! Typing code in the newsreader again....
> Function DOY(ByVal DayOfYear As Long) As String > DOY = Format$(DateSerial(1999, 12, 31 + Num), "mmmm dd") > End Function Num S/B DayOfYear
Oh well... LFS
Karl E. Peterson - 27 Jul 2009 20:25 GMT >> > I didn't see an advantage to using a function (DateAdd) within another >> > function (Format) [quoted text clipped - 7 lines] > DOY = Format$(DateSerial(1999, 12, 31 + Num), "mmmm dd") > End Function I prefer:
Function DOY(ByVal DayOfYear As Long) As String ' Pick a leap year, any leap year... DOY = Format$(DateSerial(2000, 1, DayOfYear), "mmmm dd") End Function
Not sure what could be cleaner. :-)
 Signature .NET: It's About Trust! http://vfred.mvps.org
Larry Serflaten - 27 Jul 2009 00:12 GMT > >Function GetMMDDFromDayNum(ByVal DayNum As Long) As String > > 'Ref date is #12/31/1999# > > GetMMDDFromDayNum = Format$(36525 + DayNum, "MMMM dd") > >End Function
> Okay! Now we're talking. Since we're talking, can I ask why you'd use such a long name for a simple function? Using long names requires people to remember all the words in the name (less Intellisence).
It may help to remember that everything you add; classes, properties, functions, and subs, are extensions to the VB language. You don't see that kind of explicit naming in VB.
For examples, the UCase function is not called: GetUpperCaseOfString(). Abs function is not called GetAbsoluteValueFromNumber(). Do you see what I'm saying?
Also, it pays to incorporate knowlege of the VB language when deciding what names to use. You have a function that returns a string from numerical input. Functions in VB that do that include; Str(), CStr(), and Format(). Names that might be considered for your own function could include; DOY(), DOYStr(), and FormatDOY(). If they have no option to change the format, you would select one of the first two (or similar names). If you give them an option to change the format you would use the last one (or something similar) because that is how VB responds for those functions:
Function DOY(ByVal DayOfYear As Long) As String DOY = VBA.Format$(36525 + DayOfYear, "mmmm dd") End Function
Function FormatDOY(ByVal DayOfYear As Long, Optional Format As String = "mmmm dd") As String FormatDOY = VBA.Format$(36525 + DayOfYear, Format) End Function
DOY would be something entirely new to the programmer, but it is defined by expanding the abbreviation in the parameter. That makes it 'discoverable', easy to know what it does. VB programmers are already familir with Format, so including that name would help to indicate what the function does. And so on....
Note that I used DayOfYear to describe the input rather than DayNum. DayNum may be your own variable name, but DayOfYear is more descriptive, and is somewhat defined in VB Help. (See Format - User defined dates/times; "y").
Just some talk about your super long naming convention.... <g> LFS
Webbiz - 27 Jul 2009 00:29 GMT The reason I use such long names is that I tend to forget the name or what it does without having to go hunt it down and read all the comments again (assuming I remembered to add those ;).
Although not an excuse, my code is eyes only. Since I'm not part of a programming team nor will my code then be handed over to another to expand it in some way, I name everything to mean something to me.
If I can remember a name such as "GetDateFromX" as I'm zipping along, time is saved. It tells me something, that if I supply "X", I'll get a date! (Don't tell the Mrs, eh?)
The other reason is that I simply seem to have a problem coming up with intelligent short names that make sense to me. LOL!
But I do get the DayOfYear as opposed to DayNum. Yes, it does make more sense. It simply didn't come to me.
Oh, the struggles...
Thanks!
Webbiz
>> >Function GetMMDDFromDayNum(ByVal DayNum As Long) As String >> > 'Ref date is #12/31/1999# [quoted text clipped - 45 lines] >Just some talk about your super long naming convention.... <g> >LFS mayayana - 27 Jul 2009 01:50 GMT > Although not an excuse, my code is eyes only. Since I'm not part of a > programming team nor will my code then be handed over to another to > expand it in some way, I name everything to mean something to me. I'm with you. I might have even named it GetMonthDayStringFromDayNumber. If you're not calling it so many times that the typing is tedious, and you work alone, then why not just do what's most clear for you?
I'm glad that GetFileVersionInfoSize is not GetFVIS and that GetPrivateProfileSectionNames is not GetPPSNames. Very short names are mainly leftover standards from the days of command line.
James Hahn - 26 Jul 2009 05:06 GMT Just use DateAdd and any year that is a leap year, or rather the day before the first day of any year that is a leap year.
GetDateFromDayNum = DateAdd("d",DayNum,#12/31/2007#)
>>> Could someone help me with this? >>> [quoted text clipped - 108 lines] > > Webbiz
|
|
|