I have an odd issue with an NSDateFormatter, I am passing the following string as a date format "dd/MM/yy"
If I enter 50 for the year I get a conversion to 1950 however anything below that for instance 49 results in 2049. Any ideas how I can remedy this?
Many thanks.
It sounds like you'll need to force a four digit response (or programmatically prepend two digits of "19") to wherever you're drawing your string from. Lots of people are using dates in the near to mid-term future like "12/21/12" (end of the Mayan Calendar era) so it's natural that a 2 digit year assumes 2000+ for digits 1-50 and 1999- for digits (50-99).
I'm also seeing a number of Google hits on the keyword terms "NSDateFormatter" & "century", b.t.w.
Related
Here is my data:
I am trying to build a SUMIFS formula to sum the sessions, if the month = "last month" (i.e., parsed out of these strings), and the Channel Grouping = "Display".
Here's what I have so far:
=SUMIFS(H3:H,F3:F,________,G3:G,"Direct")
Since this is a string, not a date, I am not sure how to get it to match "last month".
Why not build up a string like this (or just hard-code it?)
=sumifs(H3:H,F3:F,year(today())&"|"&text(month(today())-1,"00"),G3:G,"Direct")
This builds up a string equal to "2017|03" by taking the year from today's date (2017) and one less than the month number from today's date which at time of writing is April so 4-1=3. The text function formats it with a leading zero. So the whole thing is"2017" & "|" & "03" which gives "2017|03" - this is compared against column F.
Note: January would be a special case (existing formula would give "2018|00" for previous month to January 2018 so would need a bit of extra code to cover this case and make it fully automatic).
By 'hard-code it' I mean just put 2017|03 in as a literal string like this
=sumifs(H3:H,F3:F,"2017|03",G3:G,"Direct")
then just change it manually for different months.
Here is a more general formula
=sumifs(H3:H,F3:F,year(eomonth(today(),-1))&"|"&text(month(eomonth(today(),-1)),"00"),G3:G,"Direct")
Just change the -1 to -2 etc. for different numbers of months.
EDIT
In light of #Max Makhrov's answer, this can be shortened significantly to
=sumifs(H3:H,F3:F,text(eomonth(today(),-1),"YYYY|MM"),G3:G,"Direct")
I would like to add two more options:
1
This formula is slightly shorter and more powerrful, because it gives the full control over date format:
=TEXT(TODAY(),"YYYY|MM")
formula syntax is here:
https://support.google.com/docs/answer/3094139?hl=en
2
In your case converting date to string is more efficient because it calculates one time in the formula, so there's fewer calculations. But sometimes you need to convert text into date. In this case I prefer using regular expresions:
=JOIN("/",{REGEXEXTRACT("2017|03","(\d{4})\|(\d{2})"),1})*1
How it works
REGEXEXTRACT("2017|03","(\d{4})\|(\d{2})") gives 2 separate cells output:
2017 03
{..., 1} adds 1 to ... and adds it to the right:
2017 03 1
JOIN("/", ...) joins the ... input:
2017/03/1
This looks like date, but to make it real date, multimpy it by 1:
"2017/03/1"*1 converts string that looks like date into a number 42795 which is serial number for date 2017 march 01
On rubular I tested and confirmed that this does a good job confirming the desired format of a date entry:
\A\d\d\/\d\d\/\d\d\d\d\z
Tests:
01/02/2000 #pass
11/21/2014 #pass
11-21-2014 #fail
A3-21-2014 #fail
I want to make it a little bit better, and it will be good enough for me. What I want is to confirm that the "month field" (the first two digits) is anywhere from 01 - 12, where each single digit is led by a zero. (Ex: 01,02,03 etc as opposed to: 1,2,3).
Next: I want to do the same thing for the next two digits to confirm that the next two digits (the day field) is between 01 - 31. Same thing: Each single digit needs to lead with a zero.
Tests:
01/02/2017 #pass
12/31/2017 #pass
1/02/2017 #fail
01/2/2017 #fail
50/01/2017 #fail
01/50/2017 #fail
I realize that this regex will be inaccurate for those months that have fewer than 31 days, but it is good enough for what I am doing.
Well this should get you most of the way there:
/((02\/[0-2]\d)|((01|[0][3-9]|[1][0-2])\/(31|30|[0-2]\d)))\/[12]\d{3}/
Granted it does not handle the following:
Leap Years e.g. 02/29 is acceptable regardless of the year
All Years from 1000-2999 are acceptable
Months with only 30 days e.g. 09/31 is acceptable
Small Breakdown in case links break:
Here is the runout on Rubular
Here is an explanation from Regex101
(02\/[0-2]\d) - Starts with 02/ then allow 0-2 followed by 0-9
OR ((01|[0][3-9]|[1][0-2]\/(31|30|[0-2]\d)) - Starts with (01 or 0 followed by 3-9 or 1 followed by 0-2) followed a / followed by 31 or 30 or 0-2 followed by 0-9
In both cases must be followed by 1 or 2 followed by 3 digits 0-9
Really wish ruby supported look behind conditionals like true pcre Example for edification
As a Note: as mentioned in the comments rescuing a parsing failure is probably easier than using a regex but I figured since you asked.
What I did was used the american_date gem. On your date inputs: the user should enter the date in the format of: "mm/dd/yyyy".
In order to force the user to enter the date in this format: I used jquery-inputmask-rails. I defined my mask like so:
$('.mask_american_date').inputmask({mask: "99/99/9999"});
Now there will be a nice mask on the date input that looks like this:
__/__/____
Now: all you need is a presence validator for the date field in your model:
validates_presence_of :date_of_birth, message: "Date is either invalid or is not present".
And this covers everything. How american date works is it takes the user input and attempts to convert it into a date. If it cannot convert the user input into a date for any reason: it will return nil which triggers the above validation.
This includes a bad month entry or a bad day entry. American Date is smart enough to know, for example, that September only has 30 days in it. So: if the user enters "31" for the day section, ex: 09/31/2017, american date will convert the date to nil.
I calculated a duration between two times, e.g. between 9:00 am and 11:00 am. So far so good. Now I need to decide if this duration is less 6 hours.
I do remember that this was pain in the s in excel but nevertheless I tried it the simple way:
=IF(E2 < 06:00:00; "y"; "n")
of course that didn't work. Next:
=IF(DURATION(E2) < DURATION(06:00:00); "y"; "n")
still, it didn't work.
So, okay, how can I compare two duration?
Divide hours by 24:
=IF(E2 < 6/24, "y", "n")
Value is E2 is a formatted time, actually 1 hour is 1/24, 1 day is 1.
Some info about date and time formats here:
http://www.excel-easy.com/examples/date-time-formats.html
You can also use the HOUR function if you want to
=if(HOUR(E2)<6,ʺyesʺ,ʺnoʺ)
or
=if(E2<time(6,0,0),ʺyesʺ,ʺnoʺ)
(if you write 06:00:00 in a formula it takes it as a string not a time)
but as I'm sure someone is about to point out, the first formula above gives the wrong answer for durations of more than a day (because it takes the hour part of a datetime).
What I find interesting is that you can assume for a worksheet formula that dates and times are represented as whole numbers (days) and fractions (parts of a day) just like in Excel. If you ever have to deal with them in Google App Scripts, you suddenly find that it's object-oriented and you have no choice but to use methods like hour() to manipulate them.
I needed to use the equivalent of:
=if(TIMEVALUE(E2)<6/24, "yes", "no")
I want to compare string representations of weeks, e.g. week "01/17" is before "02/17" and after "52/16".
The following code throws an exception, I guess because my string doesn't hint at the exact day of each week. However, I don't care - it could all be Mondays or Thursdays or whatever:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ww/YY", Locale.GERMANY);
LocalDate date1 = formatter.parse(str1, LocalDate::from);
Do I need to modify the parser? Or parse to some other format? Unfortunatley there is no object like YearMonth for weeks...
One solution would be to always default to the same day, say the Monday. You could build a custom formatter for that:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.appendPattern("ww/YY")
.parseDefaulting(ChronoField.DAY_OF_WEEK, 1)
.toFormatter(Locale.GERMANY);
You can now build LocalDates representing the Monday of the given week:
LocalDate d1 = LocalDate.parse("01/17", fmt);
LocalDate d2 = LocalDate.parse("52/16", fmt);
System.out.println(d1.isAfter(d2));
which prints true because 01/17 is after 52/16.
I wasn't able to find a way for this to work with the DateTimeFormatter class, but I would like to suggest a different approach.
The Threeten Extra library contains a number of classes that were deemed too specific to include in the java.time library. One of them is the YearWeek class you mention.
Your problem can be solved by parsing the week-number and year manually from the input-string and then invoking the YearWeek creator-method like this:
YearWeek yw = YearWeek.of(year, monthOfYear);
tl;dr
YearWeek.parse( "2017-W01" )
ISO 8601
Or parse to some other format?
Yes, use another format.
Use the standard ISO 8601 formats when serializing date-time values to text. The standard includes support for week dates.
For a year-week that would be four year digits, a hyphen, a W, and two digits for the week of the year.
2017-W01
Get clear on your definition of a “week”. The ISO 8601 definition is that:
The Week # 1 contains the first Thursday of the year, and
Runs Monday-Sunday.
So years run either 52 or 53 weeks long. And note that under this definition, the first few days of the year may be in the prior year when week-numbering. Likewise, the last few days of the year may be in the following year when week-numbering.
If you want to indicate a particular day within that week, append a hyphen and a single digit running 1-7 for Monday-Sunday.
Tip: To see ISO 8601 week numbers by default on your computer, you may need to adjust your OS setting. For example, on macOS set System Preferences > Language & Region > Calendar > ISO 8601 to make apps such as Calendar.app to display week numbers with this standard definition.
2017-W01-7
By the way, a couple of similar representations:
An ordinal date meaning the year and the day-of-year-number running from 1-366 is year, a hyphen, and a three-digit number: 2017-123
Month-Day without year is two hyphens, month number, hyphen, and day-of-month number: --01-07
Note that the use of Locale as seen in the Question is irrelevant here with the standard ISO 8601 formats.
YearWeek
Unfortunatley there is no object like YearMonth for weeks...
Ahhh, but there is such a class.
For a class to directly represent the idea of a week-year, see the correct Answer by Henrik. That Answer shows the ThreeTen-Extra library’s class YearWeek.
The YearWeek class can directly parse and generate strings in standard format.
YearWeek yw = YearWeek.parse( "2017-W01" );
You can compare the YearWeek objects with methods: compareTo, equals, isBefore, isAfter.
yw.isBefore( thatYw )
The ThreeTen-Extra project offers other classes such as YearQuarter that you may find useful.
I believe the issue I am having now should be much easier in MS Excel. However, since my company uses Google Spreadsheet so I have to figure out a way.
Basically, I have a cell that contains a date value like "12/19/11", and I have another cell contains a value like "DT 30". The task assigned to me is to add the value 30(days) to the date, so the result should be "1/19/2012".
I did some trying in Google Spreadsheet, I have two questions. The first is to how to extract the numeric value "30" out of the string "DT 30", the second question is that, there seems to be no date add function built in Google Docs.
Could any experts offer some suggestions?
I like to keep it simple. If A1 holds the date and B1 holds the number of months to add, then
=date(year(A1),month(A1)+B1,day(A1))
would calculate the required result. The same way could be used for days or years
To extract a numeric value out of your string you can use these 2 functions (Assuming you have your value in cell 'A1'):
=VALUE(REGEXEXTRACT(A1, "\d+"))
This will get you a numeric value.
I've found no date add function in docs, but you can convert your date into internal date number and then add days number (If your value is in cell 'A2'):
=DATEVALUE(A2) + 30
I hope this will help.
You can just add the number to the cell with the date.
so if A1: 12/3/2012 and A2: =A1+7 then A2 would display 12/10/2012
You can use the DATE(Year;Month;Day) to make operations on date:
Examples:
=DATE(2013;3;8 + 30) give the result... 7 april 2013 !
=DATE(2013;3 + 15; 8) give the result... 8 june 2014 !
It's very surprising but it works...
The direct use of EDATE(Start_date, months) do the job of ADDDate.
Example:
Consider A1 = 20/08/2012 and A2 = 3
=edate(A1; A2)
Would calculate 20/11/2012
PS: dd/mm/yyyy format in my example
As with #kidbrax's answer, you can use the + to add days. To get this to work I had to explicitly declare my cell data as being a date:
A1: =DATE(2014, 03, 28)
A2: =A1+1
Value of A2 is now 29th March 2014
Using pretty much the same approach as used by Burnash, for the final result you can use ...
=regexextract(A1,"[0-9]+")+A2
where A1 houses the string with text and number
and A2 houses the date of interest
what's wrong with simple add and convert back?
if A1 is a date field, and A2 hold the number of days to add:
=TO_DATE((DATEVALUE(A1)+A2)
=TO_DATE(TO_PURE_NUMBER(Insert Date cell, i.e. AM4)+[how many days to add in numbers, e.g. 3 days])
Looks like in practice:
=TO_DATE(TO_PURE_NUMBER(AM4)+3)
Essentially you are converting the date into a pure number and back into a date again.
In a fresh spreadsheet (US locale) with 12/19/11 in A1 and DT 30 in B1 then:
=A1+right(B1,2)
in say C1 returns 1/18/12.
As a string function RIGHT returns Text but that can be coerced into a number when adding. In adding a number to dates unity is treated as one day. Within (very wide) limits, months and even years are adjusted automatically.