Find the next monthly expiration dateSearching database, filtering results, adding longsParsing dates from an...
Can you 'upgrade' leather armor to studded leather armor without purchasing the new armor directly?
Is there a better way to make addon working on both blender 2.80 and 2.79?
When should a commit not be version tagged?
How to add multiple differently colored borders around a node?
Creature spells vs. ability to convert a permanent into a creature
How can I find an Adventure or Adventure Path I need that meets certain criteria?
Can you use a beast's innate abilities while polymorphed?
How to speed up a process
If nine coins are tossed, what is the probability that the number of heads is even?
How to mitigate "bandwagon attacking" from players?
Why might Google Analytics report a sudden, but persistent, drop in bounce rate (70% to 12%)
Find the next monthly expiration date
Why proton concentration is divided by 10⁻⁷?
Did 5.25" floppies undergo a change in magnetic coating?
What are these green text/line displays shown during the livestream of Crew Dragon's approach to dock with the ISS?
Why does Starman/Roadster have radial acceleration?
I am on the US no-fly list. What can I do in order to be allowed on flights which go through US airspace?
Use comma instead of & in table
What can I substitute for soda pop in a sweet pork recipe?
If a druid in Wild Shape swallows a creature whole, then turns back to her normal form, what happens?
Waiting for the gravy at a dinner table
How would we write a misogynistic character without offending people?
When was drinking water recognized as crucial in marathon running?
When does inspiration across artforms become plagiarism
Find the next monthly expiration date
Searching database, filtering results, adding longsParsing dates from an OCR applicationDetermining the next payment renewal dateFind the next 7 o'clock NSDateChecking day number in a yearJavaScript date validationMenu system to manage contactsImplementing a week schedule class in C#Given a date and a duration, calculate the future dateUpdate SQL database, similar-but-different queries for each month
$begingroup$
I have an agreement with a customer. When the agreement ends, the customer needs to pay. The amount which the customer needs to pay increases for each month that has passed since the agreement was initiated. The maximum duration is 3 years, so I have a final expiration date. From this final expiration date, I must be able to find the periodic expiration date (the date when the payment increases). In other words, I need a function which does this:
DateTime GetNextExpirationDate(finalExpirationDate)
One of the issues which I am struggling with is that the algorithm needs to consider whether the expiration date in the current month has already passed (there will be a date each month). It also needs to consider that the day of the month for the final expiration date, might not exist for the periodic expiration dates (e.g. if the final expiration date is the 31st of something).
I have some code which is working, but it appears somewhat redundant and with many steps. I was wondering, if perhaps it could be simplified (either a little or a lot).
public DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
int daysInCurrentMonth = DateTime.DaysInMonth(currentYear, currentMonth);
// If the month has less days that the day of the final expiration date,
// then we need to use the maximum date of the month.
int expirationDayThisMonth = Math.Min(expirationDay, daysInCurrentMonth);
DateTime expirationDateThisMonth = new DateTime(currentYear, currentMonth, expirationDayThisMonth);
// If the periodic expiration date in the current month is already surpassed,
// then we need to the date in the next month.
if (expirationDateThisMonth < DateTime.Now)
{
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
int nextMonthMonth = dateInOneMonth.Month;
int nextMonthYear = dateInOneMonth.Year;
int daysInNextMonth = DateTime.DaysInMonth(nextMonthYear, nextMonthMonth);
int expirationDayNextMonth = Math.Min(expirationDay, daysInNextMonth);
DateTime expirationDateNextMonth = new DateTime(nextMonthYear, nextMonthMonth, expirationDayNextMonth);
return expirationDateNextMonth;
}
else
{
return expirationDateThisMonth;
}
}
Any ways to improve this?
Input/output examples:
Final expiration date: 15.12.2019
DateTime.Now: 12.03.2019
Output: 15.03.2019
Final expiration date 11.10.2019
DateTime.Now: 16.04.2019
Output: 11.05.2019
(I know that I cannot change DateTime.Now
but it does work as an pseudo-input, which is why I included it).
c# .net datetime
New contributor
$endgroup$
add a comment |
$begingroup$
I have an agreement with a customer. When the agreement ends, the customer needs to pay. The amount which the customer needs to pay increases for each month that has passed since the agreement was initiated. The maximum duration is 3 years, so I have a final expiration date. From this final expiration date, I must be able to find the periodic expiration date (the date when the payment increases). In other words, I need a function which does this:
DateTime GetNextExpirationDate(finalExpirationDate)
One of the issues which I am struggling with is that the algorithm needs to consider whether the expiration date in the current month has already passed (there will be a date each month). It also needs to consider that the day of the month for the final expiration date, might not exist for the periodic expiration dates (e.g. if the final expiration date is the 31st of something).
I have some code which is working, but it appears somewhat redundant and with many steps. I was wondering, if perhaps it could be simplified (either a little or a lot).
public DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
int daysInCurrentMonth = DateTime.DaysInMonth(currentYear, currentMonth);
// If the month has less days that the day of the final expiration date,
// then we need to use the maximum date of the month.
int expirationDayThisMonth = Math.Min(expirationDay, daysInCurrentMonth);
DateTime expirationDateThisMonth = new DateTime(currentYear, currentMonth, expirationDayThisMonth);
// If the periodic expiration date in the current month is already surpassed,
// then we need to the date in the next month.
if (expirationDateThisMonth < DateTime.Now)
{
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
int nextMonthMonth = dateInOneMonth.Month;
int nextMonthYear = dateInOneMonth.Year;
int daysInNextMonth = DateTime.DaysInMonth(nextMonthYear, nextMonthMonth);
int expirationDayNextMonth = Math.Min(expirationDay, daysInNextMonth);
DateTime expirationDateNextMonth = new DateTime(nextMonthYear, nextMonthMonth, expirationDayNextMonth);
return expirationDateNextMonth;
}
else
{
return expirationDateThisMonth;
}
}
Any ways to improve this?
Input/output examples:
Final expiration date: 15.12.2019
DateTime.Now: 12.03.2019
Output: 15.03.2019
Final expiration date 11.10.2019
DateTime.Now: 16.04.2019
Output: 11.05.2019
(I know that I cannot change DateTime.Now
but it does work as an pseudo-input, which is why I included it).
c# .net datetime
New contributor
$endgroup$
add a comment |
$begingroup$
I have an agreement with a customer. When the agreement ends, the customer needs to pay. The amount which the customer needs to pay increases for each month that has passed since the agreement was initiated. The maximum duration is 3 years, so I have a final expiration date. From this final expiration date, I must be able to find the periodic expiration date (the date when the payment increases). In other words, I need a function which does this:
DateTime GetNextExpirationDate(finalExpirationDate)
One of the issues which I am struggling with is that the algorithm needs to consider whether the expiration date in the current month has already passed (there will be a date each month). It also needs to consider that the day of the month for the final expiration date, might not exist for the periodic expiration dates (e.g. if the final expiration date is the 31st of something).
I have some code which is working, but it appears somewhat redundant and with many steps. I was wondering, if perhaps it could be simplified (either a little or a lot).
public DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
int daysInCurrentMonth = DateTime.DaysInMonth(currentYear, currentMonth);
// If the month has less days that the day of the final expiration date,
// then we need to use the maximum date of the month.
int expirationDayThisMonth = Math.Min(expirationDay, daysInCurrentMonth);
DateTime expirationDateThisMonth = new DateTime(currentYear, currentMonth, expirationDayThisMonth);
// If the periodic expiration date in the current month is already surpassed,
// then we need to the date in the next month.
if (expirationDateThisMonth < DateTime.Now)
{
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
int nextMonthMonth = dateInOneMonth.Month;
int nextMonthYear = dateInOneMonth.Year;
int daysInNextMonth = DateTime.DaysInMonth(nextMonthYear, nextMonthMonth);
int expirationDayNextMonth = Math.Min(expirationDay, daysInNextMonth);
DateTime expirationDateNextMonth = new DateTime(nextMonthYear, nextMonthMonth, expirationDayNextMonth);
return expirationDateNextMonth;
}
else
{
return expirationDateThisMonth;
}
}
Any ways to improve this?
Input/output examples:
Final expiration date: 15.12.2019
DateTime.Now: 12.03.2019
Output: 15.03.2019
Final expiration date 11.10.2019
DateTime.Now: 16.04.2019
Output: 11.05.2019
(I know that I cannot change DateTime.Now
but it does work as an pseudo-input, which is why I included it).
c# .net datetime
New contributor
$endgroup$
I have an agreement with a customer. When the agreement ends, the customer needs to pay. The amount which the customer needs to pay increases for each month that has passed since the agreement was initiated. The maximum duration is 3 years, so I have a final expiration date. From this final expiration date, I must be able to find the periodic expiration date (the date when the payment increases). In other words, I need a function which does this:
DateTime GetNextExpirationDate(finalExpirationDate)
One of the issues which I am struggling with is that the algorithm needs to consider whether the expiration date in the current month has already passed (there will be a date each month). It also needs to consider that the day of the month for the final expiration date, might not exist for the periodic expiration dates (e.g. if the final expiration date is the 31st of something).
I have some code which is working, but it appears somewhat redundant and with many steps. I was wondering, if perhaps it could be simplified (either a little or a lot).
public DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
int daysInCurrentMonth = DateTime.DaysInMonth(currentYear, currentMonth);
// If the month has less days that the day of the final expiration date,
// then we need to use the maximum date of the month.
int expirationDayThisMonth = Math.Min(expirationDay, daysInCurrentMonth);
DateTime expirationDateThisMonth = new DateTime(currentYear, currentMonth, expirationDayThisMonth);
// If the periodic expiration date in the current month is already surpassed,
// then we need to the date in the next month.
if (expirationDateThisMonth < DateTime.Now)
{
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
int nextMonthMonth = dateInOneMonth.Month;
int nextMonthYear = dateInOneMonth.Year;
int daysInNextMonth = DateTime.DaysInMonth(nextMonthYear, nextMonthMonth);
int expirationDayNextMonth = Math.Min(expirationDay, daysInNextMonth);
DateTime expirationDateNextMonth = new DateTime(nextMonthYear, nextMonthMonth, expirationDayNextMonth);
return expirationDateNextMonth;
}
else
{
return expirationDateThisMonth;
}
}
Any ways to improve this?
Input/output examples:
Final expiration date: 15.12.2019
DateTime.Now: 12.03.2019
Output: 15.03.2019
Final expiration date 11.10.2019
DateTime.Now: 16.04.2019
Output: 11.05.2019
(I know that I cannot change DateTime.Now
but it does work as an pseudo-input, which is why I included it).
c# .net datetime
c# .net datetime
New contributor
New contributor
edited 7 hours ago
200_success
130k16153417
130k16153417
New contributor
asked 11 hours ago
NoceoNoceo
1264
1264
New contributor
New contributor
add a comment |
add a comment |
4 Answers
4
active
oldest
votes
$begingroup$
When working with DateTime
it's usually a good idea to not use a hardcoded DateTime.Now
. This makes testing such methods very difficult because you cannot mock it so you have to write very tricky tests all with Now
. You allso cannot easily switch to UtcNow
etc. and you have to know which now it uses internally.
A much better solution would be to use another parameter like DateTime referenceDate
. This way you can use any combinations of the two values and create more predictable and easier to understand tests.
Additionally I'd make this method static because it does not access any class fields etc.
Oh, I also need to add that you use excelent variable names! This is exactly how it should be done! ;-)
$endgroup$
$begingroup$
Very good points. Especially theDateTime.Now
one, since my primary personal goal is to get better at writing testable code :-). Thanks!
$endgroup$
– Noceo
10 hours ago
add a comment |
$begingroup$
I would try to clean it up by extracting the tricky and repetitive day truncation logic into a reusable function. This would result in something like:
/* Creates a DateTime instance for the given year, month and day.
* It will truncate the possiblyTruncatedDay down to the largest number possible
* for the given month in case if it's out of bounds.
* Eg. trying to create an illegal Feb 31 instance will fallback to Feb 28 or 29) */
private static DateTime CreateTruncating(int year, int month, int possiblyTruncatedDay)
{
return new DateTime(
year,
month,
Math.Min(possiblyTruncatedDay, DateTime.DaysInMonth(year, month)));
}
public static DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
DateTime expirationDateThisMonth = CreateTruncating(currentYear, currentMonth, expirationDay);
if (expirationDateThisMonth >= DateTime.Now)
{
return expirationDateThisMonth;
}
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
return CreateTruncating(dateInOneMonth.Year, dateInOneMonth.Month, expirationDay);
}
$endgroup$
$begingroup$
Not a bad idea. Would it make sense to nest theCreateTruncating()
method insideGetNextExpirationDate
, since it is exclusively used by this method?
$endgroup$
– Noceo
1 hour ago
add a comment |
$begingroup$
Naming
Expiration
vs Final Expiration
seems kind of confusing, imo. Might I suggest PenaltyDate
for when amount they owe increases, and ExpirationDate
for that final date that they must pay.
Testability
As others have mentioned, passing in a referenceDate
will allow you test more easily test outcomes.
Modularity
Breaking the redundant part into a separate function like others have suggested.
Bringing it all together
public static DateTime GetNextPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
var penaltyDate = GetPenaltyDate(expirationDate, referenceDate);
if (penaltyDate < referenceDate)
{
var nextPenaltyDate = GetPenaltyDate(expirationDate, referenceDate.AddMonths(1));
return nextPenaltyDate;
}
else
{
return penaltyDate;
}
}
private static DateTime GetPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
int daysInMonth = DateTime.DaysInMonth(referenceDate.Year, referenceDate.Month);
int penaltyDay = Math.Min(expirationDate.Day, daysInMonth);
return new DateTime(referenceDate.Year, referenceDate.Month, penaltyDay);
}
Testing Mine via Console
class Program
{
static void Main(string[] args)
{
Print(
expirationDate: new DateTime(2019, 12, 15),
referenceDate: new DateTime(2019, 03, 12)
);
Print(
expirationDate: new DateTime(2019, 10, 11),
referenceDate: new DateTime(2019, 04, 16)
);
Console.ReadKey();
}
private static void Print(DateTime expirationDate, DateTime referenceDate)
{
Console.WriteLine("Final Expiration Date: {0}", expirationDate);
Console.WriteLine("Reference Date: {0}", referenceDate);
Console.WriteLine("Output: {0}", Expiration.GetNextPenaltyDate(expirationDate, referenceDate));
}
}
My Results
My output is formatted differently, but if I'm not mistaken the data matches.
Final Expiration Date: 12/15/2019 12:00:00 AM
Reference Date: 3/12/2019 12:00:00 AM
Output: 3/15/2019 12:00:00 AM
Final Expiration Date: 10/11/2019 12:00:00 AM
Reference Date: 4/16/2019 12:00:00 AM
Output: 5/11/2019 12:00:00 AM
$endgroup$
add a comment |
$begingroup$
Potentially simpler version?
/// <summary>
/// Number of calendar months between two DateTimes, rounding down partial months.
/// Will return x such that startDate.AddMonths(x) < endDate and
/// startDate.AddMonths(x+1) >= endDate.
/// Ignores time components.
/// </summary>
/// <param name="startDate">Start Date</param>
/// <param name="endDate">End Date</param>
/// <returns>Number of Months between Start Date and End Date</returns>
public static int MonthsBetween(DateTime startDate, DateTime endDate)
{
return (endDate.Year - startDate.Year) * 12 +
(endDate.Month - startDate.Month) +
((endDate.Day >= startDate.Day) ? 0 : -1); //rounding down for partial months
}
/// <summary>
/// Gets the next monthly expiration date after the Reference Date
/// with day component aligned with Final Expiration Date
/// Ignores time components.
/// </summary>
/// <param name="finalExpirationDate">Final Expiration Date</param>
/// <param name="referenceDate">Reference Date</param>
/// <returns>Next monthly expiration date after Reference Date</returns>
public DateTime GetNextExpirationDate(DateTime finalExpirationDate, DateTime referenceDate)
{
return finalExpirationDate.AddMonths(-MonthsBetween(referenceDate, finalExpirationDate));
}
It's unclear from your description what you want to do if today is an expiration date. The code above will generate today's date in that scenario, which is what your code does. If you want to generate next month's expiration date in that scenario, change the >=
to a >
in MonthsBetween
(and adjust the comments as appropriate, of course).
New contributor
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Noceo is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214700%2ffind-the-next-monthly-expiration-date%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
When working with DateTime
it's usually a good idea to not use a hardcoded DateTime.Now
. This makes testing such methods very difficult because you cannot mock it so you have to write very tricky tests all with Now
. You allso cannot easily switch to UtcNow
etc. and you have to know which now it uses internally.
A much better solution would be to use another parameter like DateTime referenceDate
. This way you can use any combinations of the two values and create more predictable and easier to understand tests.
Additionally I'd make this method static because it does not access any class fields etc.
Oh, I also need to add that you use excelent variable names! This is exactly how it should be done! ;-)
$endgroup$
$begingroup$
Very good points. Especially theDateTime.Now
one, since my primary personal goal is to get better at writing testable code :-). Thanks!
$endgroup$
– Noceo
10 hours ago
add a comment |
$begingroup$
When working with DateTime
it's usually a good idea to not use a hardcoded DateTime.Now
. This makes testing such methods very difficult because you cannot mock it so you have to write very tricky tests all with Now
. You allso cannot easily switch to UtcNow
etc. and you have to know which now it uses internally.
A much better solution would be to use another parameter like DateTime referenceDate
. This way you can use any combinations of the two values and create more predictable and easier to understand tests.
Additionally I'd make this method static because it does not access any class fields etc.
Oh, I also need to add that you use excelent variable names! This is exactly how it should be done! ;-)
$endgroup$
$begingroup$
Very good points. Especially theDateTime.Now
one, since my primary personal goal is to get better at writing testable code :-). Thanks!
$endgroup$
– Noceo
10 hours ago
add a comment |
$begingroup$
When working with DateTime
it's usually a good idea to not use a hardcoded DateTime.Now
. This makes testing such methods very difficult because you cannot mock it so you have to write very tricky tests all with Now
. You allso cannot easily switch to UtcNow
etc. and you have to know which now it uses internally.
A much better solution would be to use another parameter like DateTime referenceDate
. This way you can use any combinations of the two values and create more predictable and easier to understand tests.
Additionally I'd make this method static because it does not access any class fields etc.
Oh, I also need to add that you use excelent variable names! This is exactly how it should be done! ;-)
$endgroup$
When working with DateTime
it's usually a good idea to not use a hardcoded DateTime.Now
. This makes testing such methods very difficult because you cannot mock it so you have to write very tricky tests all with Now
. You allso cannot easily switch to UtcNow
etc. and you have to know which now it uses internally.
A much better solution would be to use another parameter like DateTime referenceDate
. This way you can use any combinations of the two values and create more predictable and easier to understand tests.
Additionally I'd make this method static because it does not access any class fields etc.
Oh, I also need to add that you use excelent variable names! This is exactly how it should be done! ;-)
edited 7 hours ago
answered 10 hours ago
t3chb0tt3chb0t
34.9k751122
34.9k751122
$begingroup$
Very good points. Especially theDateTime.Now
one, since my primary personal goal is to get better at writing testable code :-). Thanks!
$endgroup$
– Noceo
10 hours ago
add a comment |
$begingroup$
Very good points. Especially theDateTime.Now
one, since my primary personal goal is to get better at writing testable code :-). Thanks!
$endgroup$
– Noceo
10 hours ago
$begingroup$
Very good points. Especially the
DateTime.Now
one, since my primary personal goal is to get better at writing testable code :-). Thanks!$endgroup$
– Noceo
10 hours ago
$begingroup$
Very good points. Especially the
DateTime.Now
one, since my primary personal goal is to get better at writing testable code :-). Thanks!$endgroup$
– Noceo
10 hours ago
add a comment |
$begingroup$
I would try to clean it up by extracting the tricky and repetitive day truncation logic into a reusable function. This would result in something like:
/* Creates a DateTime instance for the given year, month and day.
* It will truncate the possiblyTruncatedDay down to the largest number possible
* for the given month in case if it's out of bounds.
* Eg. trying to create an illegal Feb 31 instance will fallback to Feb 28 or 29) */
private static DateTime CreateTruncating(int year, int month, int possiblyTruncatedDay)
{
return new DateTime(
year,
month,
Math.Min(possiblyTruncatedDay, DateTime.DaysInMonth(year, month)));
}
public static DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
DateTime expirationDateThisMonth = CreateTruncating(currentYear, currentMonth, expirationDay);
if (expirationDateThisMonth >= DateTime.Now)
{
return expirationDateThisMonth;
}
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
return CreateTruncating(dateInOneMonth.Year, dateInOneMonth.Month, expirationDay);
}
$endgroup$
$begingroup$
Not a bad idea. Would it make sense to nest theCreateTruncating()
method insideGetNextExpirationDate
, since it is exclusively used by this method?
$endgroup$
– Noceo
1 hour ago
add a comment |
$begingroup$
I would try to clean it up by extracting the tricky and repetitive day truncation logic into a reusable function. This would result in something like:
/* Creates a DateTime instance for the given year, month and day.
* It will truncate the possiblyTruncatedDay down to the largest number possible
* for the given month in case if it's out of bounds.
* Eg. trying to create an illegal Feb 31 instance will fallback to Feb 28 or 29) */
private static DateTime CreateTruncating(int year, int month, int possiblyTruncatedDay)
{
return new DateTime(
year,
month,
Math.Min(possiblyTruncatedDay, DateTime.DaysInMonth(year, month)));
}
public static DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
DateTime expirationDateThisMonth = CreateTruncating(currentYear, currentMonth, expirationDay);
if (expirationDateThisMonth >= DateTime.Now)
{
return expirationDateThisMonth;
}
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
return CreateTruncating(dateInOneMonth.Year, dateInOneMonth.Month, expirationDay);
}
$endgroup$
$begingroup$
Not a bad idea. Would it make sense to nest theCreateTruncating()
method insideGetNextExpirationDate
, since it is exclusively used by this method?
$endgroup$
– Noceo
1 hour ago
add a comment |
$begingroup$
I would try to clean it up by extracting the tricky and repetitive day truncation logic into a reusable function. This would result in something like:
/* Creates a DateTime instance for the given year, month and day.
* It will truncate the possiblyTruncatedDay down to the largest number possible
* for the given month in case if it's out of bounds.
* Eg. trying to create an illegal Feb 31 instance will fallback to Feb 28 or 29) */
private static DateTime CreateTruncating(int year, int month, int possiblyTruncatedDay)
{
return new DateTime(
year,
month,
Math.Min(possiblyTruncatedDay, DateTime.DaysInMonth(year, month)));
}
public static DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
DateTime expirationDateThisMonth = CreateTruncating(currentYear, currentMonth, expirationDay);
if (expirationDateThisMonth >= DateTime.Now)
{
return expirationDateThisMonth;
}
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
return CreateTruncating(dateInOneMonth.Year, dateInOneMonth.Month, expirationDay);
}
$endgroup$
I would try to clean it up by extracting the tricky and repetitive day truncation logic into a reusable function. This would result in something like:
/* Creates a DateTime instance for the given year, month and day.
* It will truncate the possiblyTruncatedDay down to the largest number possible
* for the given month in case if it's out of bounds.
* Eg. trying to create an illegal Feb 31 instance will fallback to Feb 28 or 29) */
private static DateTime CreateTruncating(int year, int month, int possiblyTruncatedDay)
{
return new DateTime(
year,
month,
Math.Min(possiblyTruncatedDay, DateTime.DaysInMonth(year, month)));
}
public static DateTime GetNextExpirationDate(DateTime finalExpirationDate)
{
int expirationDay = finalExpirationDate.Day;
int currentMonth = DateTime.Now.Month;
int currentYear = DateTime.Now.Year;
DateTime expirationDateThisMonth = CreateTruncating(currentYear, currentMonth, expirationDay);
if (expirationDateThisMonth >= DateTime.Now)
{
return expirationDateThisMonth;
}
DateTime dateInOneMonth = DateTime.Now.AddMonths(1);
return CreateTruncating(dateInOneMonth.Year, dateInOneMonth.Month, expirationDay);
}
answered 7 hours ago
Konrad MorawskiKonrad Morawski
1,798710
1,798710
$begingroup$
Not a bad idea. Would it make sense to nest theCreateTruncating()
method insideGetNextExpirationDate
, since it is exclusively used by this method?
$endgroup$
– Noceo
1 hour ago
add a comment |
$begingroup$
Not a bad idea. Would it make sense to nest theCreateTruncating()
method insideGetNextExpirationDate
, since it is exclusively used by this method?
$endgroup$
– Noceo
1 hour ago
$begingroup$
Not a bad idea. Would it make sense to nest the
CreateTruncating()
method inside GetNextExpirationDate
, since it is exclusively used by this method?$endgroup$
– Noceo
1 hour ago
$begingroup$
Not a bad idea. Would it make sense to nest the
CreateTruncating()
method inside GetNextExpirationDate
, since it is exclusively used by this method?$endgroup$
– Noceo
1 hour ago
add a comment |
$begingroup$
Naming
Expiration
vs Final Expiration
seems kind of confusing, imo. Might I suggest PenaltyDate
for when amount they owe increases, and ExpirationDate
for that final date that they must pay.
Testability
As others have mentioned, passing in a referenceDate
will allow you test more easily test outcomes.
Modularity
Breaking the redundant part into a separate function like others have suggested.
Bringing it all together
public static DateTime GetNextPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
var penaltyDate = GetPenaltyDate(expirationDate, referenceDate);
if (penaltyDate < referenceDate)
{
var nextPenaltyDate = GetPenaltyDate(expirationDate, referenceDate.AddMonths(1));
return nextPenaltyDate;
}
else
{
return penaltyDate;
}
}
private static DateTime GetPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
int daysInMonth = DateTime.DaysInMonth(referenceDate.Year, referenceDate.Month);
int penaltyDay = Math.Min(expirationDate.Day, daysInMonth);
return new DateTime(referenceDate.Year, referenceDate.Month, penaltyDay);
}
Testing Mine via Console
class Program
{
static void Main(string[] args)
{
Print(
expirationDate: new DateTime(2019, 12, 15),
referenceDate: new DateTime(2019, 03, 12)
);
Print(
expirationDate: new DateTime(2019, 10, 11),
referenceDate: new DateTime(2019, 04, 16)
);
Console.ReadKey();
}
private static void Print(DateTime expirationDate, DateTime referenceDate)
{
Console.WriteLine("Final Expiration Date: {0}", expirationDate);
Console.WriteLine("Reference Date: {0}", referenceDate);
Console.WriteLine("Output: {0}", Expiration.GetNextPenaltyDate(expirationDate, referenceDate));
}
}
My Results
My output is formatted differently, but if I'm not mistaken the data matches.
Final Expiration Date: 12/15/2019 12:00:00 AM
Reference Date: 3/12/2019 12:00:00 AM
Output: 3/15/2019 12:00:00 AM
Final Expiration Date: 10/11/2019 12:00:00 AM
Reference Date: 4/16/2019 12:00:00 AM
Output: 5/11/2019 12:00:00 AM
$endgroup$
add a comment |
$begingroup$
Naming
Expiration
vs Final Expiration
seems kind of confusing, imo. Might I suggest PenaltyDate
for when amount they owe increases, and ExpirationDate
for that final date that they must pay.
Testability
As others have mentioned, passing in a referenceDate
will allow you test more easily test outcomes.
Modularity
Breaking the redundant part into a separate function like others have suggested.
Bringing it all together
public static DateTime GetNextPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
var penaltyDate = GetPenaltyDate(expirationDate, referenceDate);
if (penaltyDate < referenceDate)
{
var nextPenaltyDate = GetPenaltyDate(expirationDate, referenceDate.AddMonths(1));
return nextPenaltyDate;
}
else
{
return penaltyDate;
}
}
private static DateTime GetPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
int daysInMonth = DateTime.DaysInMonth(referenceDate.Year, referenceDate.Month);
int penaltyDay = Math.Min(expirationDate.Day, daysInMonth);
return new DateTime(referenceDate.Year, referenceDate.Month, penaltyDay);
}
Testing Mine via Console
class Program
{
static void Main(string[] args)
{
Print(
expirationDate: new DateTime(2019, 12, 15),
referenceDate: new DateTime(2019, 03, 12)
);
Print(
expirationDate: new DateTime(2019, 10, 11),
referenceDate: new DateTime(2019, 04, 16)
);
Console.ReadKey();
}
private static void Print(DateTime expirationDate, DateTime referenceDate)
{
Console.WriteLine("Final Expiration Date: {0}", expirationDate);
Console.WriteLine("Reference Date: {0}", referenceDate);
Console.WriteLine("Output: {0}", Expiration.GetNextPenaltyDate(expirationDate, referenceDate));
}
}
My Results
My output is formatted differently, but if I'm not mistaken the data matches.
Final Expiration Date: 12/15/2019 12:00:00 AM
Reference Date: 3/12/2019 12:00:00 AM
Output: 3/15/2019 12:00:00 AM
Final Expiration Date: 10/11/2019 12:00:00 AM
Reference Date: 4/16/2019 12:00:00 AM
Output: 5/11/2019 12:00:00 AM
$endgroup$
add a comment |
$begingroup$
Naming
Expiration
vs Final Expiration
seems kind of confusing, imo. Might I suggest PenaltyDate
for when amount they owe increases, and ExpirationDate
for that final date that they must pay.
Testability
As others have mentioned, passing in a referenceDate
will allow you test more easily test outcomes.
Modularity
Breaking the redundant part into a separate function like others have suggested.
Bringing it all together
public static DateTime GetNextPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
var penaltyDate = GetPenaltyDate(expirationDate, referenceDate);
if (penaltyDate < referenceDate)
{
var nextPenaltyDate = GetPenaltyDate(expirationDate, referenceDate.AddMonths(1));
return nextPenaltyDate;
}
else
{
return penaltyDate;
}
}
private static DateTime GetPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
int daysInMonth = DateTime.DaysInMonth(referenceDate.Year, referenceDate.Month);
int penaltyDay = Math.Min(expirationDate.Day, daysInMonth);
return new DateTime(referenceDate.Year, referenceDate.Month, penaltyDay);
}
Testing Mine via Console
class Program
{
static void Main(string[] args)
{
Print(
expirationDate: new DateTime(2019, 12, 15),
referenceDate: new DateTime(2019, 03, 12)
);
Print(
expirationDate: new DateTime(2019, 10, 11),
referenceDate: new DateTime(2019, 04, 16)
);
Console.ReadKey();
}
private static void Print(DateTime expirationDate, DateTime referenceDate)
{
Console.WriteLine("Final Expiration Date: {0}", expirationDate);
Console.WriteLine("Reference Date: {0}", referenceDate);
Console.WriteLine("Output: {0}", Expiration.GetNextPenaltyDate(expirationDate, referenceDate));
}
}
My Results
My output is formatted differently, but if I'm not mistaken the data matches.
Final Expiration Date: 12/15/2019 12:00:00 AM
Reference Date: 3/12/2019 12:00:00 AM
Output: 3/15/2019 12:00:00 AM
Final Expiration Date: 10/11/2019 12:00:00 AM
Reference Date: 4/16/2019 12:00:00 AM
Output: 5/11/2019 12:00:00 AM
$endgroup$
Naming
Expiration
vs Final Expiration
seems kind of confusing, imo. Might I suggest PenaltyDate
for when amount they owe increases, and ExpirationDate
for that final date that they must pay.
Testability
As others have mentioned, passing in a referenceDate
will allow you test more easily test outcomes.
Modularity
Breaking the redundant part into a separate function like others have suggested.
Bringing it all together
public static DateTime GetNextPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
var penaltyDate = GetPenaltyDate(expirationDate, referenceDate);
if (penaltyDate < referenceDate)
{
var nextPenaltyDate = GetPenaltyDate(expirationDate, referenceDate.AddMonths(1));
return nextPenaltyDate;
}
else
{
return penaltyDate;
}
}
private static DateTime GetPenaltyDate(DateTime expirationDate, DateTime referenceDate)
{
int daysInMonth = DateTime.DaysInMonth(referenceDate.Year, referenceDate.Month);
int penaltyDay = Math.Min(expirationDate.Day, daysInMonth);
return new DateTime(referenceDate.Year, referenceDate.Month, penaltyDay);
}
Testing Mine via Console
class Program
{
static void Main(string[] args)
{
Print(
expirationDate: new DateTime(2019, 12, 15),
referenceDate: new DateTime(2019, 03, 12)
);
Print(
expirationDate: new DateTime(2019, 10, 11),
referenceDate: new DateTime(2019, 04, 16)
);
Console.ReadKey();
}
private static void Print(DateTime expirationDate, DateTime referenceDate)
{
Console.WriteLine("Final Expiration Date: {0}", expirationDate);
Console.WriteLine("Reference Date: {0}", referenceDate);
Console.WriteLine("Output: {0}", Expiration.GetNextPenaltyDate(expirationDate, referenceDate));
}
}
My Results
My output is formatted differently, but if I'm not mistaken the data matches.
Final Expiration Date: 12/15/2019 12:00:00 AM
Reference Date: 3/12/2019 12:00:00 AM
Output: 3/15/2019 12:00:00 AM
Final Expiration Date: 10/11/2019 12:00:00 AM
Reference Date: 4/16/2019 12:00:00 AM
Output: 5/11/2019 12:00:00 AM
answered 53 mins ago
Shelby115Shelby115
1,586517
1,586517
add a comment |
add a comment |
$begingroup$
Potentially simpler version?
/// <summary>
/// Number of calendar months between two DateTimes, rounding down partial months.
/// Will return x such that startDate.AddMonths(x) < endDate and
/// startDate.AddMonths(x+1) >= endDate.
/// Ignores time components.
/// </summary>
/// <param name="startDate">Start Date</param>
/// <param name="endDate">End Date</param>
/// <returns>Number of Months between Start Date and End Date</returns>
public static int MonthsBetween(DateTime startDate, DateTime endDate)
{
return (endDate.Year - startDate.Year) * 12 +
(endDate.Month - startDate.Month) +
((endDate.Day >= startDate.Day) ? 0 : -1); //rounding down for partial months
}
/// <summary>
/// Gets the next monthly expiration date after the Reference Date
/// with day component aligned with Final Expiration Date
/// Ignores time components.
/// </summary>
/// <param name="finalExpirationDate">Final Expiration Date</param>
/// <param name="referenceDate">Reference Date</param>
/// <returns>Next monthly expiration date after Reference Date</returns>
public DateTime GetNextExpirationDate(DateTime finalExpirationDate, DateTime referenceDate)
{
return finalExpirationDate.AddMonths(-MonthsBetween(referenceDate, finalExpirationDate));
}
It's unclear from your description what you want to do if today is an expiration date. The code above will generate today's date in that scenario, which is what your code does. If you want to generate next month's expiration date in that scenario, change the >=
to a >
in MonthsBetween
(and adjust the comments as appropriate, of course).
New contributor
$endgroup$
add a comment |
$begingroup$
Potentially simpler version?
/// <summary>
/// Number of calendar months between two DateTimes, rounding down partial months.
/// Will return x such that startDate.AddMonths(x) < endDate and
/// startDate.AddMonths(x+1) >= endDate.
/// Ignores time components.
/// </summary>
/// <param name="startDate">Start Date</param>
/// <param name="endDate">End Date</param>
/// <returns>Number of Months between Start Date and End Date</returns>
public static int MonthsBetween(DateTime startDate, DateTime endDate)
{
return (endDate.Year - startDate.Year) * 12 +
(endDate.Month - startDate.Month) +
((endDate.Day >= startDate.Day) ? 0 : -1); //rounding down for partial months
}
/// <summary>
/// Gets the next monthly expiration date after the Reference Date
/// with day component aligned with Final Expiration Date
/// Ignores time components.
/// </summary>
/// <param name="finalExpirationDate">Final Expiration Date</param>
/// <param name="referenceDate">Reference Date</param>
/// <returns>Next monthly expiration date after Reference Date</returns>
public DateTime GetNextExpirationDate(DateTime finalExpirationDate, DateTime referenceDate)
{
return finalExpirationDate.AddMonths(-MonthsBetween(referenceDate, finalExpirationDate));
}
It's unclear from your description what you want to do if today is an expiration date. The code above will generate today's date in that scenario, which is what your code does. If you want to generate next month's expiration date in that scenario, change the >=
to a >
in MonthsBetween
(and adjust the comments as appropriate, of course).
New contributor
$endgroup$
add a comment |
$begingroup$
Potentially simpler version?
/// <summary>
/// Number of calendar months between two DateTimes, rounding down partial months.
/// Will return x such that startDate.AddMonths(x) < endDate and
/// startDate.AddMonths(x+1) >= endDate.
/// Ignores time components.
/// </summary>
/// <param name="startDate">Start Date</param>
/// <param name="endDate">End Date</param>
/// <returns>Number of Months between Start Date and End Date</returns>
public static int MonthsBetween(DateTime startDate, DateTime endDate)
{
return (endDate.Year - startDate.Year) * 12 +
(endDate.Month - startDate.Month) +
((endDate.Day >= startDate.Day) ? 0 : -1); //rounding down for partial months
}
/// <summary>
/// Gets the next monthly expiration date after the Reference Date
/// with day component aligned with Final Expiration Date
/// Ignores time components.
/// </summary>
/// <param name="finalExpirationDate">Final Expiration Date</param>
/// <param name="referenceDate">Reference Date</param>
/// <returns>Next monthly expiration date after Reference Date</returns>
public DateTime GetNextExpirationDate(DateTime finalExpirationDate, DateTime referenceDate)
{
return finalExpirationDate.AddMonths(-MonthsBetween(referenceDate, finalExpirationDate));
}
It's unclear from your description what you want to do if today is an expiration date. The code above will generate today's date in that scenario, which is what your code does. If you want to generate next month's expiration date in that scenario, change the >=
to a >
in MonthsBetween
(and adjust the comments as appropriate, of course).
New contributor
$endgroup$
Potentially simpler version?
/// <summary>
/// Number of calendar months between two DateTimes, rounding down partial months.
/// Will return x such that startDate.AddMonths(x) < endDate and
/// startDate.AddMonths(x+1) >= endDate.
/// Ignores time components.
/// </summary>
/// <param name="startDate">Start Date</param>
/// <param name="endDate">End Date</param>
/// <returns>Number of Months between Start Date and End Date</returns>
public static int MonthsBetween(DateTime startDate, DateTime endDate)
{
return (endDate.Year - startDate.Year) * 12 +
(endDate.Month - startDate.Month) +
((endDate.Day >= startDate.Day) ? 0 : -1); //rounding down for partial months
}
/// <summary>
/// Gets the next monthly expiration date after the Reference Date
/// with day component aligned with Final Expiration Date
/// Ignores time components.
/// </summary>
/// <param name="finalExpirationDate">Final Expiration Date</param>
/// <param name="referenceDate">Reference Date</param>
/// <returns>Next monthly expiration date after Reference Date</returns>
public DateTime GetNextExpirationDate(DateTime finalExpirationDate, DateTime referenceDate)
{
return finalExpirationDate.AddMonths(-MonthsBetween(referenceDate, finalExpirationDate));
}
It's unclear from your description what you want to do if today is an expiration date. The code above will generate today's date in that scenario, which is what your code does. If you want to generate next month's expiration date in that scenario, change the >=
to a >
in MonthsBetween
(and adjust the comments as appropriate, of course).
New contributor
edited 36 mins ago
New contributor
answered 1 hour ago
StoborStobor
1011
1011
New contributor
New contributor
add a comment |
add a comment |
Noceo is a new contributor. Be nice, and check out our Code of Conduct.
Noceo is a new contributor. Be nice, and check out our Code of Conduct.
Noceo is a new contributor. Be nice, and check out our Code of Conduct.
Noceo is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214700%2ffind-the-next-monthly-expiration-date%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown