Weird time offset dealing with date/time and timezone - datetime

I have been trying to code some functions to create google calendars and google calendar events based on information from multiple cells on a google spreadsheet.
First issues posted here with the date part has already been addressed.
Now I´m haing issues with the Time part.
The following code:
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Passeios");
var timeStart = ss.getRange(6,4).getValue();
var timeEnd = ss.getRange(6,5).getValue();
var ssTZ = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
Logger.log("timeStart: " + timeStart );
Logger.log("timeEnd: " + timeEnd);
var dateStart = ss.getRange(6,8).getValue();
var dateStartObj = new Date(Utilities.formatDate(dateStart, 'ssTZ' , 'MMMM dd, yyyy HH:mm:ss Z'));
var timeStartObj= new Date(Utilities.formatDate(timeStart, 'ssTZ' , 'MMMM dd, yyyy HH:mm:ss Z'));
var justTimeStart = Utilities.formatDate(timeStart, ssTZ, 'HH:mm');
Logger.log(" Time Start Object: " + timeStartObj);
Logger.log("Time Start Object Hours: " + timeStartObj.getHours());
Logger.log("Time Start Object Minutes: " + timeStartObj.getMinutes());
Logger.log("Start Time HH:mm: " + justTimeStart)
var hourStart = Utilities.formatDate(timeStart, ssTZ, 'HH');
var minutesStart = Utilities.formatDate(timeStart, ssTZ, 'mm');
var hourEnd = Utilities.formatDate(timeEnd, ssTZ, 'HH');
var minutesEnd = Utilities.formatDate(timeEnd, ssTZ, 'mm');
Logger.log(" TimeZone :" + ssTZ);
Logger.log(hourStart);
Logger.log(minutesStart);
Logger.log(hourEnd);
Logger.log(minutesEnd);
Produces the following log
timeStart: Sat Dec 30 1899 07:06:28 GMT-0300 (BRT)
timeEnd: Sat Dec 30 1899 07:36:28 GMT-0300 (BRT)
Time Start Object: Sat Dec 30 1899 07:06:28 GMT-0300 (BRT)
Time Start Object Hours: 7
Time Start Object Minutes: 6
Start Time HH:mm: 07:00
TimeZone :America/Sao_Paulo
07
00
07
30
The Spreadsheet cell is formatted as HH:mm and it shows
07:00 for start time
07:30 for end time
As you can see there is some 6 minutes and 28 seconds offset that I am not sure where is coming from when logging the cell value or when constructing a Date() object with the cell value.
Formating the cell to just Hours or just Minutes or HH:mm does not carry that offset.
EDIT.
I noticed that the Date() constructor had the ssTZ variable between single quote marks so it is probably discarded as it should not be recognized as a valid Timezone.
Not sure what it uses instead but the difference between the actual spreadsheet time zone and the misquoted one, seems to be 28 seconds that I also do not understand where they come from.
The 6 minutes offset is still there as you can check on the following code and log print.
var timeStart = ss.getRange(6,4).getValue();
var ssTZ = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
var timeStartObj1= new Date(Utilities.formatDate(timeStart, 'ssTZ' , 'MMMM dd, yyyy HH:mm:ss Z'));
var timeStartObj2= new Date(Utilities.formatDate(timeStart, ssTZ , 'MMMM dd, yyyy HH:mm:ss Z'));
var justTimeStart = Utilities.formatDate(timeStart, ssTZ, 'HH:mm');
Logger.log("timeStart: " + timeStart );
Logger.log(" Time Start Object1: " + timeStartObj1);
Logger.log(" Time Start Object2: " + timeStartObj2);
Logger.log("justTimeStart: " + justTimeStart)
timeStart: Sat Dec 30 1899 07:06:28 GMT-0300 (BRT)
Time Start Object1: Sat Dec 30 1899 07:06:28 GMT-0300 (BRT)
Time Start Object2: Sat Dec 30 1899 07:06:00 GMT-0300 (BRT)
justTimeStart: 07:00
EDIT 2
It has something to do with timezone and probably some adjustment due to the Date beign interpreted as 120 years ago in 1899.
When using the following constructor without the Z at the string specifying the format:
var timeStartObj3= new Date(Utilities.formatDate(timeStart, ssTZ , 'MMMM dd, yyyy HH:mm:ss'));
Logger.log(" Time Start Object3: " + timeStartObj3);
The log result is:
Time Start Object3: Sat Dec 30 1899 07:00:00 GMT-0300 (BRT)
EDIT 3.
Getting weirder and weirder...
If I use text concatenation on the Logger.log call I get a different String that if I call the log with just the var name:
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Passeios");
var timeStart = ss.getRange(6,4).getValue();
Logger.log("timeStart: " + timeStart);
Logger.log(timeStart);
timeStart: Sat Dec 30 1899 07:06:28 GMT-0300 (BRT)
Sat Dec 30 07:00:00 GMT-03:06 1899
I do understand it is in fact the same Time represented differently (I asume the 28 seconds are there even when not shown).
My guess is there's different behaviour from the text parsing method whether concatenation is used or not (which is at least confusing).
I still do not know where those 06 minutes and 28 seconds come from or how to ensure consistency when using Time and date coming from cell values with just date or just time and having to mix them.
This is really confusing...

The 6 minutes and 28 seconds come from the Local Mean Time (LMT) offset between São Paulo and GMT. You can see it in the TZDB sources:
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone America/Sao_Paulo -3:06:28 - LMT 1914
-3:00 Brazil -03/-02 1963 Oct 23 0:00
-3:00 1:00 -02 1964
-3:00 Brazil -03/-02
The LMT entry is in the first row. The last column (1914) is the "Until" date - meaning that in the TZDB, LMT is in use until 1914. After that, the next rule in the zone entry applies (-3:00).
LMT is calculated based on the longitude and latitude of the reference location. It has no bearing on timekeeping that may have been in use in the region at that time. In many cases with old dates, there's no historical information available to know how exactly time was kept that long ago.
In other words, your example date from 1899 is from a period before known timekeeping practices in Brazil, and thus local mean time is applied instead.
Use a more modern date and you should get results that make more sense to you by today's standards.

Google sheets stores a Date or time cell value as a number corresponding to the number of full days (or fractions) starting 12/30/1899 0:00:00 as explained here.
When dealing with a cell that only contains a Time value, the getValues() function will produce a Date object with the date part set to 12/30/1899 but this will have a corresponding timezone offset which in time creates that odd offset as explained by #Matt Johnson on previous answer
The big problem is that this offset will not be consistent for diferent timezones as that date will produce different offsets for different locations/timezones and different years, so you will need to address a whole bunch of possible situations.
When trying to create Date() objects, this produces different results as it seems sometimes that offset is passed as a timezone offset and other times it is part of the actual time value depending on the constructor and format the text gets parsed to, creating a lot of confusion, as you can see from this code:
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Passeios");
var ssTZ = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
var timeStart = ss.getRange(6,4).getValue();
var date1 = new Date(timeStart);
var date2 = new Date(Utilities.formatDate(timeStart, ssTZ , 'MMMM dd, yyyy HH:mm:ss Z'));
var date3 = new Date(Utilities.formatDate(timeStart, ssTZ , 'MMMM dd, yyyy HH:mm:ss'));
var date4 = new Date(Utilities.formatDate(timeStart, ssTZ , "yyyy-MM-dd'T'HH:mm:ss'Z'"));
Logger.log("date1: " + date1);
Logger.log("date2: " + date2);
Logger.log("date3: " + date3);
Logger.log("date4: " + date4);
Producing this log:
date1: Sat Dec 30 1899 07:06:28 GMT-0300 (BRT)
date2: Sat Dec 30 1899 07:06:00 GMT-0300 (BRT)
date3: Sat Dec 30 1899 07:00:00 GMT-0300 (BRT)
date4: Sat Dec 30 1899 04:00:00 GMT-0300 (BRT)
Sometimes the timezone offset is ignored alltogether, other times, only the seconds part is ignored and also it might be interpreted as a completely different timezone by hours. (That string format was taken from the formatDate() class documentation example)
As #TheMaster suggested, this might be addressed at the spreadsheet creating auxiliary cells/column on the sheet to add a more consistent date part but this might not be practical in cases where this information is dynamically processed (in my case a combination of multiple QUERY results across multiple answers from multiple associated forms).
Another approach also suggested by #TheMaster might be to use getDisplayValues() and parse the text but this might create all sort of trouble if the display format is ever changed in the sheet.
There doesn't seem to be a definitive answer
I think the least messy approach to this problem will be to use numeric variables in the code and parse numeric values just for the Hours and minutes using Utilities.formatDate() with the format property set just to 'HH' and 'mm' respectively.
var hours = Utilities.formatDate(timeStart, ssTZ, 'HH');
var minutes = Utilities.formatDate(timeStart, ssTZ, 'mm');
Logger.log("Hours: "+ hours);
Logger.log("Minutes: "+ minutes);
Gives the following result:
Hours: 07
Minutes: 00
This seems to be consistent and it disregards the timezone associated offsets and will produce a simple number for the hour and a simple number for the minutes corresponding to de displayed values at the sheet, which can then be used consistently to create Date() objects.
Note that the cell on the spreadsheet still needs to be formatted as Date/time for this to work but different from parsing text from a getDisplayValues() result, any date/time format will still work the same.
I'm not interested in the seconds or milliseconds but I suspect those could be addressed the same way.
Hopefully this helps someone else along the way.

Related

How to get a timezone string (a la "America/Los_Angeles" or "Pacific Standard Time") from a type Time with TZ info in Golang? [duplicate]

I have an UTC time and a time offset in seconds, and need to return the corresponding Go time value.
It is trivial to instantiate the UTC time value by using the time.Unix() function. But to set the Zone, I need to determine the time.Location.
How can I find the time.Location when knowing the UTC time and time offset ?
Without an actual entry to lookup in the time zone database, you can't know the true location for the time. If you want to work with just an offset, you can create a fixed location using time.FixedZone
edt := time.FixedZone("EDT", -60*60*4)
t, _ := time.ParseInLocation("02 Jan 06 15:04", "15 Sep 17 14:55", edt)
fmt.Println(t)
// 2017-09-15 14:55:00 -0400 EDT
You can opt to specify a non-existent zone name, or none at all, as long as the output format you use doesn't require one.
minus4 := time.FixedZone("", -60*60*4)
t, _ = time.ParseInLocation("02 Jan 06 15:04", "15 Sep 17 14:55", minus4)
fmt.Println(t.Format(time.RFC3339))
// 2017-09-15T14:55:00-04:00

How to parse CCYY-MM-DDThh:mm:ss[.sss...] date format

As we all know, date parsing in Go has it's quirks*.
However, I have now come up against needing to parse a datetime string in CCYY-MM-DDThh:mm:ss[.sss...] to a valid date in Go.
This CCYY format is a format that seems to be ubiquitous in astronomy, essentially the CC is the current century, so although we're in 2022, the century is the 21st century, meaning the date in CCYY format would be 2122.
How do I parse a date string in this format, when we can't specify a coded layout?
Should I just parse in that format, and subtract one "century" e.g., 2106 becomes 2006 in the parsed datetime...?
Has anyone come up against this niche problem before?
*(I for one would never have been able to remember January 2nd, 3:04:05 PM of 2006, UTC-0700 if it wasn't the exact time of my birth! I got lucky)
The time package does not support parsing centuries. You have to handle it yourself.
Also note that a simple subtraction is not enough, as e.g. the 21st century takes place between January 1, 2001 and December 31, 2100 (the year may start with 20 or 21). If the year ends with 00, you do not have to subtract 100 years.
I would write a helper function to parse such dates:
func parse(s string) (t time.Time, err error) {
t, err = time.Parse("2006-01-02T15:04:05[.000]", s)
if err == nil && t.Year()%100 != 0 {
t = t.AddDate(-100, 0, 0)
}
return
}
Testing it:
fmt.Println(parse("2101-12-31T12:13:14[.123]"))
fmt.Println(parse("2122-10-29T12:13:14[.123]"))
fmt.Println(parse("2100-12-31T12:13:14[.123]"))
fmt.Println(parse("2201-12-31T12:13:14[.123]"))
Which outputs (try it on the Go Playground):
2001-12-31 12:13:14.123 +0000 UTC <nil>
2022-10-29 12:13:14.123 +0000 UTC <nil>
2100-12-31 12:13:14.123 +0000 UTC <nil>
2101-12-31 12:13:14.123 +0000 UTC <nil>
As for remembering the layout's time:
January 2, 15:04:05, 2006 (zone: -0700) is a common order in the US, and in this representation parts are in increasing numerical order: January is month 1, 15 hour is 3PM, year 2006 is 6. So the ordinals are 1, 2, 3, 4, 5, 6, 7.
I for one would never have been able to remember January 2nd, 3:04:05 PM of 2006, UTC-0700 if it wasn't the exact time of my birth! I got lucky.
The reason for the Go time package layout is that it is derived from the Unix (and Unix-like) date command format. For example, on Linux,
$ date
Fri Apr 15 08:20:43 AM EDT 2022
$
Now, count from left to right,
Month = 1
Day = 2
Hour = 3 (or 15 = 12 + 3)
Minute = 4
Second = 5
Year = 6
Note: Rob Pike is an author of The Unix Programming Environment

Groovy: Time in ISO 8601 format

How to get the current time and 15 min ago time in iso 8601 format (YYYY-MM-DDTHH:mm:ss) in groovy?
You can use java time's Instant and the toString() format
import java.time.*
def now = Instant.now()
def fifteenAgo = now.minus(Duration.ofMinutes(15))
println "Now is ${now} and 15 mins ago was ${fifteenAgo}"
Prints:
Now is 2020-06-30T19:53:17.445039Z and 15 mins ago was 2020-06-30T19:38:17.445039Z
You can formast the date in any way you want in Groovy, by doing e.g.
println new Date().format("yyyy-MM-dd HH.mm.ss.SSSSS Z")
Then, you can do calculations on the date, like this:
new Date(System.currentTimeMillis()-91*60*1000)
which will minus 91 minutes (91min * 60sec * 1000ms).
Then you can put the statements together, which is why Groovy is great:
def a = new Date(System.currentTimeMillis()-91*60*1000).format("YYYY-MM-DD")
And so you can get the half before the T. And the half after the T:
def b = new Date(System.currentTimeMillis()-91*60*1000).format("HH:mm:ss")
And then concatenate them with a T:
println "91 minutes ago in iso 8601 format is: ${a}T${b}"
There are other ways of doing it, like with TimeCategory.minus, but this is a good illustration. I used 91 minutes, but you can adapt it to your own requirtement.

Momentjs - Add days with specific time

I'm using momentjs to calculate certain dates. For example:
moment().add(7, 'days');
This returns date in 7 days in same time like now:
Thu May 21 2020 12:06:35 GMT+0200.
What is the function for adding days but in specific time like:
Thu May 21 2020 17:00:00 GMT+0200.
You could chain the add() to get add hours,minutes,seconds.
console.log('Current time: ', moment().format("DD-MM-YYYY hh:mm:ss"));
var start_time = moment()
.add(1, "days")
.add(4, "hours")
.add(21, "seconds");
console.log('modified time: ', start_time.format("DD-MM-YYYY hh:mm:ss"));
<script src="https://momentjs.com/downloads/moment.js"></script>
I found a solution:
moment({ hour: 17 }).add(7, 'd')
This ads exactly 7 days to today in 17:00:00.

moment-timezone.js get utc offset for a specific date with timezone

I have users entering a date and a time zone (e.g. "America/Los Angeles") for that date and I'd like to convert that to UTC but to do that I need the utc offset for the time on that date.
I can easily convert a date to the offset for the time zone if I already know the UTC date but I need the other way around...
The utc offset can change depending on the date due to daylight saving so I need a way to enter a date and a timezone and get back the offset from UTC using that.
Knowing the most recent switch from PST to PDT On march 11 at 2AM I tried using
var tzOffset = moment.tz("3/11/2018 3:00 AM", "America/Los_Angeles").utcOffset();
document.write('utc offset is : ' + tzOffset + '<br/>') ;
but that gives 480 when the correct answer is 420
I can get the correct answer 420 if I use parseZone like so:
var tzOffset2 = moment.parseZone("3/11/2018 3:00 AM -07:00").utcOffset();
document.write('utc offset2 is : ' + tzOffset2 + '<br/>') ;
but that means I need to already know the -7 offset that I'm trying to find...
So how do I find the utcOffset for a specific date/time like "3/11/2018 3:00 AM" and timezone like "America/Los_Angeles"? Thanks
Your input is not in a ISO 8601 or RFC 2822 format recognized by moment(String), so you have to specify the format as second parameter using moment(String, String) (please note that, as docs states: The moment.tz constructor takes all the same arguments as the moment constructor, but uses the last argument as a time zone identifier.)
Your code could be like the following:
var tzOffset = moment.tz("3/11/2018 3:00 AM", "D/M/YYYY h:mm A", "America/Los_Angeles").utcOffset();
document.write('utc offset is : ' + tzOffset + '<br/>') ;
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data-2012-2022.min.js"></script>

Resources