• (nodebb)

    <question type="stupid">How . . . how does someone know about DateTime() but not .AddMonths() ?</question>

  • (nodebb)

    {question type="stupid"}How . . . how does someone know about DateTime() but not .AddMonths() ?{/question}

  • (nodebb)

    Magic strings are just as bad as magic numbers. Or worse.

  • (nodebb)

    The one line we see is a terniary of the form value = (condition) ? replacementValue : value

    Which suggests there's a whole cascade of these statements one after the other, with the expectation that zero or one of the conditions evaluates as true, and if it's zero, then the original beginning value survives unscathed after n useless reassignments to itself.

    IOW, they've reinvented the switch / case statement, but badly. And depending on how the various replacement values are computed, several weird things could occur if two or more of the conditions proved true and the second true case used the results of the first true case as input rather than the default value as input.

    Just bad all around. As @dpm indirectly said, this is the work of somebody who knows the syntax of about half their language and a semi-random 1/10th of their language's + libraries complete OM / API.

  • (nodebb) in reply to dpm

    @dpm Your second post just calls out, screaming at the top of its lungs, for a "YMBNH" type of response...

  • (nodebb)

    What I don't get is, how did they not detect this earlier?

  • (nodebb) in reply to tom103

    @tom103 : Easily explained. The users never filed a coherent bug report before.

    Addendum 2025-10-16 09:55: Or this time, but they got lucky at last.

  • (nodebb)

    Remy, I disagree with your rant against drop-down text descriptions. For example, if you were using an app would you want to figure out the date for the start of the quarter or just be able to pick from a drop-down "Start of Quarter"? I work in the financial industry and we have these all over the place - a most useful one is PBD (Prior "US" Business Day) which skips over bank holidays.

  • Registered (unregistered)

    how does someone know about DateTime() but not .AddMonths() ?

    Because it is "AddMonths()" and they couldn't find the "SubtractMonths()" function

  • (author) in reply to Auction_God

    My complaint was about passing string literals to the code, instead of like, an enum.

  • dusoft (unregistered)

    That's why I use Carbon in PHP.

  • OldCoder (unregistered) in reply to Remy Porter

    ...which means they had no idea how to populate a dropdown box properly.

  • (nodebb) in reply to OldCoder

    We don't know that they didn't use an enum for the dropdown box. We just know that by the time we got here they had a string.

  • Champs (unregistered)

    Is there some reason for the DateTime constructor to not to use AddMonth logic in the month parameter, whether the value is out of bounds or not?

    This is a serious question that I am asking on -3/16/2026.

  • (nodebb) in reply to Steve_The_Cynic

    @dpm Your second post just calls out, screaming at the top of its lungs, for a "YMBNH" type of response...

    @Steve The Cynic: maybe dpm had this comment scheduled to be posted for "Same day same month same year". Remy would argue this should be labeled "now", but we here all know better ;)

  • Peter of the Norse (unregistered)

    As a long time PHP developer, I’ve seen this before back when it was the only way to do it. PHP 4 (and most of 5) is just a thin wrapper around C/C++ library function, so the only way to work with dates and time is a UNIX timestamp. The great tool was strtotime, which would take things like "Start of last month" as values. It would also consistently do the wrong thing. The documentation says that strtotime("February") will return a day in March if you run it on the 30th. It also did similar things when crossing year boundaries. While the DateTime class and friends fixed most of it, there were still weird ass problems well into PHP 7

  • (nodebb)

    Alternative option is to have last month implemented as "(Month - 1) % 12" (if it's 0-11) or "((Month - 1) % 12) + 1" (if it's 1-12)... modular arithmetic works backwards just fine and as expected - (-1 % 12) = 11.

  • (nodebb) in reply to Worf

    Unless you happen to be working with one of the many languages where -1 % 12 = -1.

  • Stuart (unregistered) in reply to Balor64

    My instinct would be to do that operation as "(Month + 11) % 12". Or "((Month + 10) % 12) + 1" if it's expected to be 1-12. That negates the entire question of what a negative number modulo something else would come out as.

    But my first instinct would be to look for DateTime operations within the language. If you're not using C or similar 'ancient' languages, there's no excuse for not having them. (If you ARE using C or similar 'ancient' languages, and it's not an already existing codebase, I have to ask - why?)

  • Officer Johnny Holzkopf (unregistered) in reply to Champs

    Question: Is -3/16/2026 the Antiwednesday of Sixteenember next year, or is it Antimarch the 16th next year? Please reply before 2025.addYear().

  • (nodebb) in reply to Champs

    Well, the correct way to handle those business cases in legacy syntax (pre .net 10) looks like this:

    using System;
    using System.Diagnostics;
    
    internal static class DateMutator
    {
        //--- Public Methods ---//
        
        public static DateTime MutateLocal(this DateTime self, Operation operation, bool isStrict = true)
        {
            if(self.Kind != DateTimeKind.Local) throw new NotSupportedException();
            
            var result = operation switch
            {
                    Operation.StartOfLastMonth => new DateTime(self.Year, 1, 1).AddYears(-1),
                    Operation.SameDayLastYear => self.AddYears(-1),
                    Operation.SameDayNextMonth => self.AddMonth(1),
                    _ => throw new UnreachableException()
            };
            
            if(isStrict)
            {
                switch(operation)
                {
                    case Operation.StartOfLastMonth: 
                        break;
                    
                    case Operation.SameDayLastYear:
                    case Operation.SameDayNextMonth:
                        if(self.Day != result.Day) throw new ArgumentOutOfRangeException(nameof(self), self, null);
                        break;
                }
            }
            
            return result;
        }
        
        //--- Public Types ---//
    
        public enum Operation
        {
            StartOfLastMonth,
            SameDayLastYear,
            SameDayNextMonth
        }
    }
    

    So you can see it only works for local dates (extremely important, not using UTC is the trillion dollar mistake) and there is an issue with the term "same day" that needs to be addressed.

    Addendum 2025-10-17 03:31: Welp, and there I mess up the first operation by using the wrong method :-)

    Addendum 2025-10-17 03:33: 03:31? What is that Remy? Alien time? It's 07:33 :-)

    Addendum 2025-10-17 03:38: And I forgot .Date for the SameDay operations. Ah well, should have written this properly with tests.

  • Kotarak (unregistered) in reply to Champs

    But of course this has problems when you do DATE(2025, (1 + 1), 29), which gives you the first of March. In Java the LocalDate.of(2025, 1, 29).plusMonths(1) gives you the 28th of February.

    However, Excel is consistent: because the 29 is out of bounds it adds a day. So there is that.

  • Black Mantha (unregistered) in reply to RogerC

    Worse. You never have a 9 in the wrong case, or a non-breaking 2 instead of a normal 2.

  • (nodebb) in reply to Champs

    Fixed it up, so yeah, there's a lot that goes into local time nonsense. Best to avoid it, because business logic based on time zones will mess with you as soon as you have to deal with more than one. And I'm pretty sure I haven't even covered all of it, just the basics.

    using System;
    using System.Diagnostics;
    
    internal static class DateMutator
    {
        //--- Public Methods ---//
        
        public static DateTime MutateLocal(this DateTime self, Operation operation, Mode mode = Mode.Strict)
        {
            if(self.Kind != DateTimeKind.Local) throw new NotSupportedException();
            
            var result = operation switch
            {
                Operation.StartOfLastMonth => new DateTime(self.Year, 1, 1).AddMonths(-1),
                Operation.SameDayLastYear => self.Date.AddYears(-1),
                Operation.SameDayNextMonth => self.Date.AddMonths(1),
                _ => throw new UnreachableException()
            };
            
            if(mode.HasFlag(Mode.Strict))
            {
                switch(operation)
                {
                    case Operation.StartOfLastMonth: break;
                    
                    case Operation.SameDayLastYear:
                    case Operation.SameDayNextMonth:
                        if(self.Day != result.Day) throw new ArgumentOutOfRangeException(nameof(self), self, null);
                        break;
                        
                    default: throw new UnreachableException();
                }
            }
            
            if(mode.HasFlag(Mode.IncludeTime))
                self.Add(new(0, self.Hour, self.Minute, self.Second, self.Millisecond, self.Microsecond));
            
            return DateTime.SpecifyKind(result, DateTimeKind.Local);
        }
        
        //--- Public Types ---//
    
        public enum Operation
        {
            StartOfLastMonth = 1,
            SameDayLastYear = 2,
            SameDayNextMonth = 3
        }
        
        [Flags]
        public enum Mode
        {
            IncludeTime = 1,
            Strict = 2
        }
    }
    
  • TS (unregistered)

    "The end users of Claus's application do a lot of work in January. It's the busiest time of the year for them."

    I thought their busiest time of year was December?

  • anonymous (unregistered) in reply to Champs

    Honestly I am not sure what that would accomplish in cases like this.

    I mean, yes, it would help avoid the very niche problem of someone abusing the DateTime class in this way. But it would add both a big performance penalty to the constructor (not to be neglected when dealing with systems that generate a lot of these objects like messaging or logging systems) and more importantly lead to a constructor that behaves very strangely to the end user. All for the dubious benefit of "fixing" a bug that was introduced through misuse of the class to begin with.

    I mean, imagine something like this: new DateTime(2024, -3, 12, 15, 73, 13). If the constructor was implemented as you say that would produce the date: 2023.09.12 16:13:13. Or maybe 2023.10.12 16:12:13, 2023.10.12 16:13:13 or 2023.09.12 16:12:13. It all depends on how you treat 0 as a value since right now DateTime considers it invalid.

    But even ignoring that gotcha let me ask you. Do any of those really make more sense than "-3 is not a valid value for month"?

Leave a comment on “A Date Next Month”

Log In or post as a guest

Replying to comment #685441:

« Return to Article