I'm working with some values from WikiPedia that are in some cases Million of years ago (Like the formation of the moon, which WikiData reports as being formed: "-4527000000-01-01T00:00:00Z". In other case I'm simply looking at years that might be 10000 B.C. etc., still expressed as "-10000-01-01T00:00:00Z".
As far as I could figure out testing NodaTime, and reading the documentation, these ancient years are not supported (OR I overlooked something). For example, this fails:
OffsetDateTime defaultValue = new OffsetDateTime(new LocalDateTime(77, 1, 1, 0, 0), Offset.Zero);
var pattern = OffsetDateTimePattern.Create("yyyy-MM-ddTHH:mm:ss'Z'", CultureInfo.InvariantCulture, defaultValue);
string P = "-1000-01-25T20:34:25Z";
var result = pattern.Parse(P);
NodaTime.Text.UnparsableValueException: The value string includes a negative value where only a non-negative one is allowed. Value being parsed: '^-1000-01-25T20:34:25Z'. (^ indicates error position.)
at NodaTime.Text.ParseResult`1.GetValueOrThrow() in C:\Users\jon\Test\Projects\nodatime\build\releasebuild\src\NodaTime\Text\ParseResult.cs:line 81
at TryNodaTime.Program.Main(String[] args
using sing this string works fine btw: string P = "1000-01-25T20:34:25Z";
I'm hoping I just overlooked something obvious as to handle very large old dates/years. Other examples includes old cities btw, which have exact date from long before year 0.
Guidance on how to handle these would be greatly appreciated.
EDIT: Found this example, which requires me to first detect the year manually, and then use LocalDate if it's negative, however, doesn't solve the larger issue here as it fails below -9999:
LocalDate BCDATE = new LocalDate(Era.BeforeCommon, -15000, 10, 1);
Console.WriteLine($"BC: {BCDATE}");
Noda Time doesn't support ancient or far future values.
The range of values supported is in the user guide.
Noda Time can only represent values within a limited range. This is for pragmatic reasons: while it would be possible to try to model every instant since some estimate of the big bang until some expected "end of time," this would come at a significant cost to the experience of the 99.99% of programmers who just need to deal with dates and times in a reasonably modern (but not far-flung future) era.
The Noda Time 1.x releases supported years between around -27000 and +32000 (in the Gregorian calendar), but had significant issues around the extremes. Noda Time 2.0 has a smaller range, with more clearly-defined behaviour.
...
Additionally, all calendars are restricted to four digit formats, even in year-of-era representations, which avoids ever having to parse 5-digit years. This leads to a Gregorian calendar from 9999 BCE to 9999 CE inclusive, or -9998 to 9999 in "absolute" years. The range of other calendars is determined from this and from natural restrictions (such as not being proleptic).
I'm afraid I think it may well be better not to use Noda Time at all for this use case, rather than trying to work around it - unless you're okay with effectively reporting the far-distant dates verbatim and doing no other work with them.
Sorry about this - it's one of those situations where trying to support all use cases does harm to the far more common use case :(
Related
I often find myself working with databases with nullable date fields so other return something along the lines of select coalesce( t.dt, cast( '0001-01-01' as date) ) from t (for sql server) and implicitly treating LocalDate as a unsigned value in my code. LocalDate defines a MinIsoValue and MaxIsoValue defining the smallest and biggest value that can be represented. MinIsoValue is defined as -9998-01-01 obviously stating LocalDate is a signed value. Usually negative dates don't really make sense in my problem domain hence that I refer to 0001-01-01 as "the beginning of time" in most cases.
My questions are:
Why is there no LocalDate.Zero? If the min and max are demarcated I think it would make sense to define "the middle" or "zero" as the point from where the sign starts to matter.
I can't be the only one who has dealt with this but I can't really find any super useful information on how to deal with this. I've tried looking in the NodaTime newsgroup, tickets and SO questions. How do other people solve this? In the case of a database query just use '-9998-01-01' instead of '0001-01-01'? Are there better solutions?
Most of the software I'm writing doesn't need negative dates, and in most of my cases it wouldn't make sense and would like an exception if it does happen. Does a unsigned version of LocalDate make sense?
(I'm using LocalDate as an example here, same applies to LocalDateTime. Not to sure about LocalTime as I never have to reason at that low of a resolution about time.)
LocalDate.Zero doesn't really have any particularly special meaning - it just happens to be where BCE becomes CE. You can easily create your own static property for it though. (I don't remember ever seeing a feature request for this.)
As Sweeper said, it's not that LocalDate is "signed" - the "absolute year zero" is pretty arbitrary, and the year 1 in one calendar will often be a different year in a different calendar. I strongly suspect that in the vast majority of problem domains where the year 1BCE isn't relevant, the year 1CE isn't relevant either... if you're only thinking about "modern dates" then you'll probably have a cut-off of 1800CE or later. If you're actually dealing with historical dates that go as far back as 1CE, then it's pretty likely you need to handle 1BCE too.
I suspect you're fine to just keep writing database queries that coalesce null to 0001-01-01, and if you want to make sure you don't have any dates earlier than that, you can just use value = LocalDate.Max(value, YourConstantClass.MinLocalDate) or similar to "clamp" it.
So, I noticed DateTime.MinValue defaults to 01-01-0001 (dd-MM-yyyy), so for example let's say we have a museum database/class object whatever, how do you store an object that is from 10,000 BC or how about dinosaur bones that are from millions of years prior to today?
Can it be a signed value? like, the year "-10000" represents BC?
or we would need to rely on strings and be unable to natively work with dates prior to year 1?
I checked this question out that asks for year zero, but it doesn't have any helpful insights, other than apparently not everyone knows there is no such thing as year zero. how make a datetime object in year 0 with python
No, DateTime doesn't handle anything before 1CE.
My Noda Time project does support BCE dates, but still limited to about 9998 BCE as the earliest it can handle.
Once you're talking about prehistoric times, you probably have a different set of use cases from normal date/time types anyway - so just extending the range of the existing types may well not help you much. (As an example, quite often ancient history deals with relative dates: "I know battle X took place 3 years into the reign of king Y, but I don't know exactly when either of them happened.") I'd suggest you think about what your actual use cases are, and what you need to do with the date/time information. Then you can look into whether existing libraries meet your needs, or whether you need to write your own abstractions.
After digging around, I've found that other than creating one's own types, there is no easy native solutions to this specific use case for dates, so I want to propose my solution in case its of use to anyone. Just add a column that will have a sign, a - and a + (or a 0 and 1 I guess with a boolean type). Dates with + are AC and dates with - are BC. On your code or otherwise you'll have to handle the sign. Its simple and requires no extra libraries or technologies.
Since dates have no zeros the sign should not create interference, and just minding negative dates move in reverse order in your logic will solve any issues with that. However this solution only works for dates up to year 9999, so in order to be able to handle even farther away dates, a more complex sistem would have to be programmed. like handling each part of a date in a separate column so it can cover as many years as int or double can do numbers.
However, I do think positive dates from the year 3000 up to the year 9999 will hardly ever be used, much less towards millions of years into the future. But maybe it will be of use for fan proyect databases for sci-fi universes like 40K or SW or ST.
Goals
I have a list of LocalDate items that represent sets of start dates and end dates.
I would like to be able to store date ranges, so that I can perform operations on them as a set of overlapping or distinct ranges, etc.
Questions
Does NodaTime provide some sort of DateRange construct that I've missed in the docs?
Am I thinking about this wrong? Is there a more natural / preferred way to accomplish this that NodaTime already allows for?
Am I setting myself up for trouble by attempting to think about a date range using a LocalDate for a start and an end date?
I'm completely new to NodaTime and assuming that this is a conceptual misunderstanding on my part.
Update: I noticed a similar question on the subject from 2009, but that seems to refer to another utilies class; I'm assuming that since then NodaTime may have evolved to accomodate this situation.
Noda Time provides an Interval type for a range of Instant values, but it doesn't provide range types for the other types. Part of the reason for this is the nuance of how ranges are used for different types.
If I give you a range of instants, it is always treated as a half open interval. The start value is included, but the end value is excluded. Humans do this naturally any time we provide a time value, such as when I say an event runs from 1:00 to 2:00, clearly I mean that the event is over at 2:00, so 2:00 is excluded.
But with whole calendar date ranges, typically the end dates are inclusive. To represent the entire month of January (as a range of LocalDate values), I would probably say Jan 1st through Jan 31st, and I am including the last day in its entirety.
We could probably add some additional range types to enforce these things, but we would need to think about how much value there is in having them in the API when you could probably just create them as needed. I'm not saying I'm for or against it either way, but that's probably something to be debated on the Noda Time user group.
To answer your specific questions:
No, there is no predefined range class for local dates.
The only other thing to consider is that calendar math is usually done via the Period class. For example, to determine how many days there are between two calendar dates:
LocalDate ld1 = new LocalDate(2012, 1, 1);
LocalDate ld2 = new LocalDate(2013, 12, 25);
Period period = Period.Between(ld1, ld2, PeriodUnits.Days);
long days = period.Days;
No, there's nothing wrong with creating a range class of local dates, there just might not be a whole lot of advantage. You may do just as well by having two properties, StartDate and EndDate, on your own classes. Just be careful about the inclusiveness of the end dates, vs the exclusiveness you'd see with an interval or time range.
And lastly, you said:
... so that I can perform operations on them as a set of overlapping or distinct ranges, etc.
You're probably looking for operations like intersection, union, calculating gaps, sorting, etc. These and more are defined by the Time Period Library, but Noda Time doesn't currently have anything like that. If one was to create it, it should probably be in a companion library ("NodaTime.Ranges", perhaps?). Likely it wouldn't be desired to pull it into the core, but you never know...
If you do end up using that Time Period Library, please make sure you recognize that it works with DateTime only, and is completely oblivious to DateTimeKind. So in order to be productive with it, you should probably make sure you are only working with UTC values, or "unspecified" calendar dates, and try not to ask it things like "how many hours are in a day" because it will get it wrong for days with daylight saving time transitions.
I have 2 DateTime values:
date1 <- {15-07-13 20:45:10} with Kind = Unspecified
date2 <- {15-07-13 20:45:10} with Kind = UTC
When comparing these 2 dates, the 2 dates are equal.
if (DateTime.Compare(date1, date2)!=0)
...
Can someone can explain why?
A little bit more strange to me: when converting the date1 (which is Unspecified kind) to UTC, I clearly see that the date is different:
date1.ToUniversalTime() --> {15-07-13 18:45:10} with Kind = UTC
Does someone can explain me why?
Yup. It's because DateTime is a fundamentally broken type, IMO. Basically the Kind isn't used in comparisons. Doing so would quite possibly have broken old code, and it's not always what you want. It was added on for .NET 1.1, and not always in a great way - it definitely wasn't fully integrated in every way you might have expected, as you've seen for comparisons.
As another example, even for a Kind of Local (which is meant to be the system local time zone) it's ignored for arithmetic, which means a call of AddHours(1) really only adds to the local time, rather than it representing elapsed time (which could end up being the same local time or two hours later in local time, around DST transitions).
My advice is just to avoid comparing DateTime values of different kinds in the first place. It's almost never what you want to do.
(Of course I'd also recommend using Noda Time, but that's a slightly different matter.)
From the documentation on DateTimeKind (emphasis is mine):
The members of the DateTimeKind enumeration are used in conversion
operations between local time and Coordinated Universal Time (UTC),
but not in comparison or arithmetic operations.
Note up front, my question turns out to be similar to SO question 1668172.
This is a design question that surely must have popped up for others before, yet I couldn't find an answer that fits my situation. I want to record date-of-birth in my application, with several 'levels' of information:
NULL value, i.e. DoB is unkown
1950-??-?? Only the DoB year value is known, date/month aren't
????-11-23 Just a month, day, or combination of the two, but without a year
1950-11-23 Full DoB is known
The technologies I'm using for my app are as follows:
Asp.NET 4 (C#), probably with MVC
Some ORM solution, probably Linq-to-sql or NHibernate's
MSSQL Server 2008, at first just Express edition
Possibilities for the SQL bit that crossed my mind so far:
1) Use one nullable varchar column e.g. 1950-11-23, and replace unkowns with 'X's, e.g. XXXX-11-23 or 1950-XX-XX
2) Use three nullable int columns e.g. 1950, 11, and 23
3) Use an INT column for year, plus a datetime column for full known DoBs
For the C# end of this problem I merely got to these two options:
A) Use a string property to represent DoB, convert only for view purposes.
B) Use a custom(?) struct or class for DoB with three nullable integers
C) Use a nullable DateTime alongside a nullable integer for year
The solutions seem to form matched pairs at 1A, 2B or 3C. Of course 1A isn't a nice solution, but it does set a baseline.
Any tips and links are highly appreciated. Well, if they're related, anyhow :)
Edit, about the answers: I marked one answer as accepted, because I think it will work for me. It's worth looking at the other answers too though, if you've stumbled here with the same question.
The SQL Side
My latest idea on this subject is to use a range for dates that are uncertain or can have different specificity. Given two columns:
DobFromDate (inclusive)
DobToDate (exclusive)
Here's how it would work with your scenarios:
Specificity DobFromDate DobToDate
----------- ----------- ----------
YMD 2006-05-05 2006-05-06
YM 2006-05-01 2006-06-01
Y 2006-01-01 2007-01-01
Unknown 0000-01-01 9999-12-31
-> MD, M, D not supported with this scheme
Note that there's no reason this couldn't be carried all the way to hour, minute, second, millisecond, and so on.
Then when querying for people born on a specific day:
DECLARE #BornOnDay date = '2006-05-16'
-- Include lower specificity:
SELECT *
FROM TheTable
WHERE
DobFromDate <= #BornOnDay
AND #BornOnDay < DobToDate;
-- Exclude lower specificity:
SELECT *
FROM TheTable
WHERE
DobFromDate = #BornOnDay
AND DobToDate = DateAdd(Day, 1, #BornOnDay);
This to me has the best mix of maintainability, ease of use, and expressive power. It won't handle loss of precision in the more significant values (e.g., you know the month and day but not the year) but if that can be worked around then I think it is a winner.
If you will ever be querying by date, then in general the better solutions (in my mind) are going to be those that preserve the items as dates on the server in some fashion.
Also, note that if you're looking for a date range rather than a single day, with my solution you still only need two conditions, not four:
DECLARE
#FromBornOnDay date = '2006-05-16',
#ToBornOnDay date = '2006-05-23';
-- Include lower specificity:
SELECT *
FROM TheTable
WHERE
DobFromDate < #ToBornOnDay
AND #FromBornOnDay < DobToDate;
The C# Side
I would use a custom class with all the methods needed to do appropriate date math and date comparisons on it. You know the business requirements for how you will use dates that are unknown, and can encode the logic within the class. If you need something before a certain date, will you use only known or unknown items? What will ToString() return? These are things, in my mind, best solved with a class.
I like the idea of 3 int nullable columns and a struct of 3 nullable int in C#.
it does take some effort in db handling but you can avoid parsing around strings and you can also query with SQL directly by year or year and month and so on...
Whatever you do is going to be messy DB wise. For consumers of these kind of dates, I would write a special class/struct which encapsulates what sort of date it is (I'd probably call it something like PartialDate), to make it easier to deal with for consumers- much like Martin Fowler advocates a Money Class.
If you expose a DateTime directly in C#, this could lead to confusion if you had a "date" of ????-11-23 and you wanted to determine if the customer was over 18 for example- how would you default the date, how would the consumer know that part of the date was invalid etc...
The added benefit of having a PartialDate is it will allow other people reading your code to quickly realise that they are not normal, complete dates and should not be treated as such!
Edit
Thinking about the Partial data concept some more, I decided to Google. I found that There is the concept of Partial on Joda time and an interesting PDF on the topic, which may or may not be useful to you.
Interesting problem...
I like solution 2B over solution 3C because with 3C, it wouldn't be normalized... when you update one of the ints, you'd have to update the DateTime as well or you would be out of sync.
However, when you read the data into your C# end, I'd have a property that would roll up all the ints into a string formatted like you have in solution 1 so that it could easily be displayed.
I'm curious what type of reporting you'll need to do on this data... or if you'll simply be storing and retrieving it from the database.
I would not worry to much about how to store the date, I would still store the date within a datetime field, BUT, if knowing if some part of the date was not populated, I would have flags for each section of the date that is not valid, so your schema would be:
DBODate as Date
DayIsSet as Bit
MonthIsSet as Bit
YearIsSet as Bit.
That way you can still implement all the valid date comparisons, and still know the precision of the date you are working on. (as for the date, I would always default to the missing portion as the min of that value: IE Month default is January, day is the first, year is 1900 or something).
Obviously, all of the solutions mentioned above do represent some kind of compromise.
Therefore, I would recommend to think carefully which of the 'levels' is the most likely one and optimize for that. Afterwards go for proper exception handling for the other rare cases.
I don't know whether reporting is an issue for you right now or may be later, but you might consider that as third dimension apart from the DB / C# issues.