Specify a time to users in multiple time zones (especially USA/Canada) - datetime

I'm implementing a sign up facility which sends a link with a token in it - the token is valid for 1 hour. So in the email (let's say it is 14:20 now) I want to say:
You must click this link by 15:30
The audience for this site will be in Ireland / UK, USA / Canada and perhaps some in Europe - so I wanted to list the expiry time in several time zones that these (non technical) people will understand.
So this is what I came up with
Click by:
Ireland/UK > 25 Apr 2018 13:59
CET (Berlin) > 25 Apr 2018 14:59
Pacific (Los Angeles) > 25 Apr 2018 05:59
Mountain (Denver) > 25 Apr 2018 06:59
Central (Chicago) > 25 Apr 2018 07:59
Eastern (New York) > 25 Apr 2018 08:59
Now, I understand that Denver is currently MDT (and MST in the winter), but here in Ireland, we are now in IST (UTC + 1) or GMT in the winter/fall - but if you ask a random person what timezone are we in, at best you will get GMT as a response all year round. So, I list the time there as a generic 'Mountain' and give a sample city.
How is this approach for people in USA / Canada?
My code is below and here is a live link
<?php
$exipry = 60*60 + time();
$now_obj = new DateTime(date("Y-m-d H:i:s", $exipry));
$now_obj->setTimezone(new DateTimeZone('Europe/Dublin'));
$now_hour_from_IRELAND_datetime = $now_obj->format('d M Y H:i');
$now_obj->setTimezone(new DateTimeZone('Europe/Berlin'));
$now_hour_from_CET_datetime = $now_obj->format('d M Y H:i');
$now_obj->setTimezone(new DateTimeZone('America/Los_Angeles'));
$now_hour_from_pacific_datetime = $now_obj->format('d M Y H:i');
$now_obj->setTimezone(new DateTimeZone('America/Denver'));
$now_hour_from_mountain_datetime = $now_obj->format('d M Y H:i');
$now_obj->setTimezone(new DateTimeZone('America/Chicago'));
$now_hour_from_central_datetime = $now_obj->format('d M Y H:i');
$now_obj->setTimezone(new DateTimeZone('America/New_York'));
$now_hour_from_eastern_datetime = $now_obj->format('d M Y H:i');
print("<h1>1 hour from now is:</h1>");
print("Ireland/UK > $now_hour_from_IRELAND_datetime<p>");
print("CET (Berlin) > $now_hour_from_CET_datetime<p>");
print("Pacific (Los Angeles) > $now_hour_from_pacific_datetime<p>");
print("Mountain (Denver) > $now_hour_from_mountain_datetime<p>");
print("Central (Chicago) > $now_hour_from_central_datetime<p>");
print("Eastern (New York) > $now_hour_from_eastern_datetime<p>");
?>

Looks correct to me.
Be sure to test it for 'Asia/Kolkata' too. That's a good test because its time zone offset is on a half-hour.
Ditto for 'America/Phoenix' because they stay on standard time all year.
Usually apps like this ask each user to provide a timezone name during onboarding. (But many users don't bother)
In the US when we want to specify a timezone in a way where it doesn't have to change between summer and winter, we say "Eastern Time", "Central Time", "Mountain Time", "Pacific Time," and Hawaii and Alaska time. The Canadians also have "Atlantic Time" ('America/Halifax'). In Arizona ('America/Phoenix') they say 'Arizona Time'.

Related

How can I get the key to increment when it is a string

I need to take someone’s age, and then outputs a key event from every year they have lived through.
dYearlyEvents = {
"1993": "Bill Clinton is inaugurated as the 42nd president.",
"1994": "The People's Republic of China gets its first connection to the Internet.",
"1995": "eBay is founded by Pierre Omidyar.",
"1996": "Murder of Tupac Shakur.",
"1997": "The first episode of Pokémon airs on TV Tokyo.",
"1998": "Death of Frank Sinatra.",
"1999": "The Columbine High School massacre in Colorado, United States, causes 15 deaths.",
"2000": "The Sony PlayStation 2 releases in Japan. ",
}
sBirthYear = (input("What year were you born in: \n"))
while True:
if sBirthYear in dYearlyEvents:
print(dYearlyEvents[sBirthYear])
sBirthYear += 1
This is what I tried but obviously as the input is a string it wont add a year every time it loops to print all events from 1993 to 2000 instead just prints 1993.

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

Weird time offset dealing with date/time and timezone

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.

Merge and update columns

I am trying to rebuild some MS Access update query logic with R's merge function, as the update query logic is missing a few arguments.
Table link Google drive
In my database "Invoice Account allocation", there are 2 tables:
Account_Mapping Table:
Origin Origin Postal Destination Destination Invoice
country code country postal code Account
FRA 01 GBR * ZR001
FRA 02 BEL * ZR003
BEL 50 ARG * ZR002
GER 01 ITA * ZR002
POL 02 ESP * ZR001
ESP 50 NED * ZR003
* 95 FRA 38 ZR001
BEL * * * ZR002
* * * * ZR003
FRA * FRA 25 ZR004
Load_ID
ID Origin Postal Destination Destination Default
country code postal code Invoice Account
2019SN0201948 FRA 98 FRA 38 XXAC001
2019SN0201958 POL 56 GBR 15 XXAC001
2019SN0201974 BEL 50 ARG 27 XXAC001
2019SN0201986 FRA 02 GER 01 XXAC001
The default invoice account in tables (Load_ID and Status_ID) is updated by the invoice account from the Account_Mapping table.
The tables Account_Mapping and Load_ID can be joined by:
Origin country & Origin country,
Origin Postal code & Postal code,
Destination country & Destination, and
Destination postal code & Destination postal code.
In the account_mapping table, there are several "*", it means the string value can take any value. I am not able build this logic with merge function. Please help me with a better logic.
New_Assigned_Account_Final <- merge(Load_ID, Account_Mapping, by.x =
c("Origin country","Postal code","Destination", "Destination postal code"),
by.y =
c("Origin country","Origin Postal code","Destination country", "Destination
postal code"))
Desired result:
Updated Load_ID table as below.
Load_ID:
ID Origin Postal Destination Destination Default
country code postal code Invoice Account
2019SN0201948 FRA 98 FRA 38 ZR003
2019SN0201958 POL 56 GBR 15 ZR003
2019SN0201974 BEL 50 ARG 27 ZR002
2019SN0201986 FRA 02 GER 01 ZR003
For the first ID, the default ID becomes "ZR003" because, "FRA" as Origin country doesn't have a Postal code - "98", so it falls under the all "*" bucket and is allocated to ZR003.
For the third ID, the default ID becomes "ZR002" because, "BEL" as Origin country has a Postal code - "50" associated with it, and the destination postal code of "ARG" can be anything because of the "*" in the Destination postal code column, therefore it is allocated to ZR002.
Thank you for your inputs.

Find date for same day of the week last year?

OK so for example, today is Tuesday, Feb 02. Well the equivalent "Tuesday" from last year was on Feb 03.
How can I find this out programmatically?
Thanks!!
According to Google, there are 604,800,000 milliseconds in a week. That times 52 should give you the same day of the week a year later (right?).
For example:
var date:Date = new Date(2010, 1, 2);
trace(date);
date.setMilliseconds(date.milliseconds - 604800000 * 52);
trace(date);
Output:
Tue Feb 2 00:00:00 GMT-0800 2010
Tue Feb 3 00:00:00 GMT-0800 2009
Just my two cents. I don't like the idea, that the second answer assumes 52 weeks in a year, it will work for a single year, but is a solution to only this exact problem - eg. if you want to check the same thing moving back 10 years it won't work. I'd do it like this:
var today:Date = new Date();
// Here we store the day of the week
var currentDay:int = today.day;
trace (today);
const milisecondsInADay:uint = 1000*60*60*24;
// Here we move back a year, but we can just as well move back 10 years
// or 2 months
today.fullYear -= 1;
// Find the closest date that is the same day of the week as current day
today.time -= milisecondsInADay*(today.day-currentDay);
trace (today);
returns:
Tue Feb 2 21:13:18 GMT+0100 2010
Tue Feb 3 21:13:18 GMT+0100 2009

Resources