How can I get Breeze.js to save my timezone in my SQL DB - date

We are trying to sort out how to have a user input a date-time with a timezone and save that in my SQL database. I have a column of type datetimeoffset in my SQL server.
$scope.saveItemChanges = function ()
{
var td = new Date();
$scope.chosenEditItem.ServiceStatusUpdateDateTimeOffset = td;
$log.info( td.toString() + "ISO " + td.toISOString() + "UTC " + td.toUTCString() + "JSON " + td.toJSON());
mainDataService.saveChanges()
.then (saveSucceededCloseEdit)
.fail (displayErrors)
}
In my database I'm not seeing the timezone. Only rows 1007-1009 got inserted via breeze the other rows are testing.
Basically I want to be able to capture the date-time and timezone on the client and store it in my DB. When someones goes back to view the record I need to be able to determine what timezone the date-time is for. I'm not sure what I'm missing in the path from Client -> Server that is stopping the timezone from being saved in the DB.
Breeze Date Info http://www.breezejs.com/documentation/date-time
Console logging:
Database record:

Javascript treats all dates as UTC, but you can still change it on the server.
First, you will need to know what the timezone on the client is and send it to the server. You can do this by having an unmapped property (i.e. won't be saved to the DB):
var UnusualDate = function () {
this.timezoneOffset = 0;
};
em.metadataStore.registerEntityTypeCtor("UnusualDate", UnusualDate);
var td = new Date();
var uD = em.createEntity("UnusualDate", { creationDate: td });
uD.setProperty("timezoneOffset", td.getTimezoneOffset()/-60);
Then, on the server BeforeSaveEntity or BeforeSaveEntities, you retrieve the timezone offset and reset the date accordingly:
var timezoneOffset = Convert.ToInt32(entityInfo.UnmappedValuesMap["timezoneOffset"]);
UnusualDate uDate = entityInfo.Entity as UnusualDate;
uDate.CreationDate = uDate.CreationDate.ToOffset(new TimeSpan(timezoneOffset, 0, 0));
If you are not familiar with BeforeSaveEntity and BeforeSaveEntities, you can take a look at the documentation at http://www.breezejs.com/documentation/efcontextprovider-methods.

Related

Creating a LocalTime BsonDatetime object

I have a large logging and data entry program that receives DataTable object with 1 DataRow. The DataTable have random amount of columns, with random column name and type so i cannot have class for each. Using these DataColumn i get the DataType and build a BsonDocument from scratch out of this.
Here's a short example
public void ParseData(DataTable table)
{
// create the document
var document = new BsonDocument();
// get the only row in the table
var row = table.Rows[0];
// for each column we add the property
foreach (DataColumn column in table.Columns)
{
// create an empty value
BsonValue value = null;
// current column value
var columnValue = row[column.ColumnName];
// set the value based on the datatype
if (column.DataType == typeof(string)) value = new BsonString(columnValue.ToString());
else if (column.DataType == typeof(int)) value = new BsonInt32(Convert.ToInt32(columnValue));
else if (column.DataType == typeof(float)) value = new BsonDouble(Convert.ToDouble(columnValue));
else if (column.DataType == typeof(double)) value = new BsonDouble(Convert.ToDouble(columnValue));
else if (column.DataType == typeof(bool)) value = new BsonBoolean(Convert.ToBoolean(columnValue));
else if (column.DataType == typeof(DateTime)) value = new BsonDateTime(Convert.ToDateTime(columnValue));
// add the element
document.Add(new BsonElement(column.ColumnName, value));
}
// insert the document in the generic collection
InsertDocument(document);
}
As you can see it's pretty simple. I have removed a lot of types in the list as we have many custom types that might pass so i just kept the basic ones. The problem is that i cannot figure out how to force the BsonDateTime to save as local time in the collection. When doing filters with legacy apps it's not working. I need them to be saved as local time. It's never been an issue in the past but because of those legacy apps from the early 90's that still need support i have to figure something out.
I also need to reload them as local time. If i could, i would save them as string but i can't because since all columns are random i do not know when loading if a specific BsonString is really a string or if it's a DateTime. For reloading i must not reload really as local time. I must reload the exact value in the database. I only control the creation of the document. But reading i only control a few one's that will be reading from it that are in C#, Java and C++. The rest are legacy apps that companies doesn't even exist anymore.
I did try to just modify every single date that came in the system to account for UTC and change the date to when saved as UTC it's stored property and filters from legacy apps still works but all of .NET, Java and C++ apps load up the wrong value and not the written value.
Is there a way to just disable UTC in a specific collection or database in MongoDB directly like you can in SQL server ?
MongoDB stores times in UTC and does not have time zone support. You can store any values you like but they will be interpreted as UTC timestamps by most MongoDB-related software.

What is the easiest way to import excel/google sheet spreadsheet to a Cloud Firestore database?

I need to import a large table of data into my database in one go. I currently have it as an Excel file but I am happy to copy it to Google sheets etc.
So far I've added a few entries manually directly via cloud firestore.
Is there a solution already out there to achieve this?
I think the easiest way to export table data into Firestore is to use a Google Apps Script Library (for Google Sheets).
Step 1
Make a Copy of THIS example Google Spreadsheet I created as an example
Step 2
From the menu of YOUR copy of the Example Google Spreadsheet from step 1, click Tools > Script Editor. This should open up the example Google App Script associated with the example spreadsheet.
Step 3
Follow the Steps for installing this library and then update the script with the following:
email
key
projectID
These variables are generated by going to the Google Service Accounts page. This will require that you already have a Firebase or Google Cloud account setup. I won't repeat all the steps that are already iterated in in the aforementioned Github writeup. Just follow them carefully, and realize that the private_key is THE ENTIRE KEY starting with -----BEGIN PRIVATE KEY-----\n, EVERYTHING in between, and ending with \n-----END PRIVATE KEY-----\n
Step 4
Insert a page on your spreadsheet that contains your data, and EDIT the script to use your new sheet name and your data. I have HEAVILY commented the script so it's pretty clear what almost every line of code is doing. For those of you that just want to peek at the Google App Script that's behind this spreadsheet, here's the code:
// Note this Script uses an external library as per this page:
// https://github.com/grahamearley/FirestoreGoogleAppsScript
// This solution requires a Google Spreadhseet and a Firebase Account
// FOLLOW THE INSTRUCTIONS ON THAT GITHUB REPO TO SETUP NEEDED API KEYS!!!
//Global Variables
const ss = SpreadsheetApp.getActiveSpreadsheet(); // Gets the active "workbook"
const sheet = ss.getSheetByName('Restaurants'); // CHANGE TO YOUR SHEET NAME
const headerRowNumber = 1; // If you have more than one row for your header, then change this value to number of header rows
// If you want to mark modified cells, then set up a trigger for the following function:
// Edit > Current Project Triggers > (+ Add Trigger) > On Edit Spreadsheet etc
function onEdit(e) {
var cell = ss.getActiveCell(); //This will also effectively get our row
var dataRange = sheet.getDataRange(); //This checks for all rows/columns with data
var modifiedCol = dataRange.getLastColumn()-1; //Our "modified" column should be the second to last
if (cell.getColumn() < modifiedCol && cell.getRow() > headerRowNumber) { //If we edit any cells to the left of our modified column and below our header...
var celltoMark = sheet.getRange(cell.getRowIndex(),modifiedCol) //Get the R/C cordinates of cell to place modified time
celltoMark.setValue(new Date()); //write timestamp to that cell
}
};
// This will parse any comma separated lists you create in any of your fields (useful for search words, or attributes, etc)
function listToArray(list) {
var ogArray = list.split(","); //Input is a comma separated list
let trimmedArr = ogArray.map(string => string.trim()); //Let's strip out the leading/trailing whitespaces if any
return trimmedArr; //return the cleaned array
}
function writeToFireStore() {
const email = 'sheets#yourprojectid.iam.gserviceaccount.com'; // CHANGE THIS!!!
const key = '-----BEGIN PRIVATE KEY-----\nYOURPRIVATEKEY\n-----END PRIVATE KEY-----\n'; // CHANGE THIS!!!
const projectID = 'yourprojectid'; // CHANGE THIS!!!
var firestore = FirestoreApp.getFirestore(email, key, projectID);
const collection = "MySpreadsheetData"; // Name of your Firestore Database "Collection"
var dataRange = sheet.getDataRange().offset(headerRowNumber, 0, sheet.getLastRow() - headerRowNumber); //this is your data range
var data = dataRange.getValues(); // this is an array of your datarange's values
var lastCol = dataRange.getLastColumn(); // this is the last column with a header
var newDoc = {}; // Instantiate your data object. Each one will become the data for your firestore documents
// r = row number in this case
for (let r = 0; r <= dataRange.getLastRow(); r++) {
//Logger.log("R = ",r);
var cellMod = dataRange.getCell(r+1, lastCol-1);
var cellFS = dataRange.getCell(r+1, lastCol);
var cellModVal = cellMod.getValue();
var cellFSVal = cellFS.getValue();
//
// IMPORTANT READ THIS IMPORTANT READ THIS IMPORTANT READ THIS IMPORTANT READ THIS IMPORTANT READ THIS!!!
// Well, read the line below...
if (r > 2) break; //Comment Out this line after you're done testing otherwise you'll write all your rows to firestore after every run
newDoc[r] = {
name : data[r][1],
category : data[r][2],
cuisine : data[r][3],
address: {
add1: data[r][4],
add2: data[r][5],
city: data[r][6],
state: data[r][7],
zip: data[r][8]
},
tel: data[r][9],
searchterms: listToArray(data[r][10]) //Let's turn a csv list into an array
}
// For the sake of efficiency and to save $, we WON'T create documents that have already been created...
// ...and we won't update documents that have a fireStore Timestamp that's newer than a Modified Timestamp
// If there's not firestore timestamp in our spreadsheet, then let's create firestore document and update firestore stamp:
if (!cellFSVal) {
var now = new Date(); //Generate timestamp right now
try {
firestore.createDocument(collection + "/" + data[r][0], newDoc[r]); // To Use Your Own Document ID
//Now let's insert a timestamp in our Firestore TS column of the sheet so we know it's been added to Firestore
cellFS.setValue(now);
Logger.log("Row ",r,"(",data[r][1],") is NEW and was added to FireStore Successfully");
} catch (e) {
Logger.log("Error: ",e," : Document with same name already existed in Firestore.");
}
}
//var if FS Timestamp exists but, the modified time stamp is greater, let's update the Firstore Document
else if ((cellFSVal) && (cellModVal > cellFSVal)) {
try {
firestore.updateDocument(collection + "/" + data[r][0], newDoc[r]);
//Now let's insert a timestamp in our Firestore TS column of the sheet so we know it's been updated to Firestore
cellFS.setValue(now);
Logger.log("Row ",r,"(",data[r][1],") updated/edited.");
} catch (e) {
Logger.log("Error: ",e," : Document existed, we tried updating it, but jack shit happened.");
}
}
else {
Logger.log("Row ",r,"(",data[r][1],") Already in Firestore & hasn't been modified. Skipped.");
}
}
}
Step 5
Once your script is modified to your needs, it's time to run the script. Simply save it (File > Save), then choose the function "writeToFireStore" from the "Select function" dropdown selector in the menu bar (in between the icon of the bug, and the lightbulb), then hit the PLAY icon (to the left of the bug icon). At this point, you will likely be prompted to accept permissions to run the script, (which you need to accept if you want to run the script). Once you've accepted the permissions, then run the "writeToFireStore" function again if it hasn't already run, and voila!
NOTES:
I created a function that automatically writes a Modified Timestamp to the second to last column in the target sheet, and when you run the function, writes a Firestore Timestamp (so you know which rows have been successfully exported to Firestore). This way, if you run the firestore function again, and you haven't changed the data on your sheet, it won't bother updating the database with the same data (and will save you money and/or server resources). For this functionality to work, you must setup project Triggers (which is explained inside the script in the comments).

Determine correct date format of Google Form response

What is the best way to determine the correct date format (dd/mm or mm/dd) of a Google Form response.
When I use the namedValues object:
function onFormSubmit(e){
var namedValues = e.namedValues;
var date = namedValues['Date']; // Date=[05/06/2018]
var date = new Date(date);
Logger.log(date); //Sun May 06 00:00:00 GMT+10:00 2018
}
When I use the value from the spreadsheet:
function onFormSubmit(e){
var range = e.range;
var row = range.getRow();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Form Responses 1');
var date = sheet.getRange(row,2).getValue();
Logger.log(date); //Mon Jun 05 00:00:00 GMT+10:00 2018
}
I don't know whether I should be, but I am hesitant using the values from the spreadsheet in an onFormSubmit trigger, since I have experienced instability in the past where I think the trigger was running before the data was being posted to the spreadsheet.
I cannot find anything in the Google Forms documentation stating whether a date response is always in a consistent format. If it was always dd/mm/yyyy I could construct the date using the string parts.
Is there a way to use determine the correct date format from the namedValues object?
P.S I would rather not use the moment.js library for this one requirement, so keen to understand if its possible without.
You Google form will constantly submit the date in the same format, but your sheet might change the appearance of formatting.
Also, there is now a date format utility built into Apps script. You can change the date string to the format that you need.
// This formats the date as Greenwich Mean Time in the format
// year-month-dateThour-minute-second.
var formattedDate = Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
Logger.log(formattedDate);

Utilities.formatDate() in Google Apps Script outputs previous date (e.g. input 25.05.2015 -> output 24.05.2015)

I have a problem with Google Docs' Utilities.formatDate() function.
I have a spreadsheet that contains all of the orders we place in the lab. When an order is delivered our lab manager enters the delivery date in the relevant cell in such a spreadsheet, in the following format: dd.MM.yyyy.
I created a script that, provided certain conditions, will email whoever placed that order alerting them that the order has been delivered on that particular date. Here is the code:
function DeliveryAlerts() {
try {
var email_dict = {"Y":"Y#Z.com"}
var spreadsheet = SpreadsheetApp.openById("ABC");
SpreadsheetApp.setActiveSpreadsheet(spreadsheet);
var sheet = spreadsheet.getSheetByName("Orders");
var values = sheet.getRange("A2:Q251").getValues();
var bgcolours = sheet.getRange("A2:Q251").getBackgrounds();
for(var i=0;i<=249;i++)
{
var j = i + 2;
if (values[i][16]=="Yes" && values[i][11]!="" && bgcolours[i][16]!="#b8b8b8")
{
var email_address = email_dict[values[i][13]];
var cur_date = Utilities.formatDate(values[i][11], "GMT+1", "EEE dd.MM.yyyy");
var message = "Hello there,\n\nYour order of " + values[i][4] + " has been delivered on "+ cur_date +".\n\nBest wishes";
var subject = "Delivery alert";
MailApp.sendEmail(email_address, subject, message,{replyTo:"abc#abc.com", name:"ABC"});
sheet.getRange("Q"+j).setBackground("#b8b8b8");
}
}
} catch (err) {
MailApp.sendEmail("abc#abc.com", "Delivery Alerts Script in Order Master List", err);
}
}
I use
Utilities.formatDate(values[i][11], "GMT+1", "EEE dd.MM.yyyy") to reformat the date from, say, 25.05.2015 (that is, the value in the cell) to Mon 25.05.2015. However, what I get instead is Sun 24.05.2015.
Does anybody know what is going on?
Thank you in advance.
Nicola
Check the time zone setting in the script editor. Under the FILE menu, choose PROJECT PROPERTIES in the script editor. It's possible to have a different time zone setting in Apps Script, than is in the spreadsheet. This is a common issue that arises. Apps Script allows a separate time zone setting from the spreadsheet. Also, even if the time is only off by one minute, if the time setting of the date is all zeros, it's common to get the problem that you are having. When a user enters a date, it's possible that no time setting is made. So the time is set to all zeros. The date is correct, but the time is all zeros. Even if the date was typed in at 3 in the afternoon, for example, and the date is correct, the time setting can be midnight of that day. So, even if you subtracted one second from that date, it would now be the day before.

breezejs: date is not set to the right time

I've noticed that if a date property comes back from the server with the value "2013-07-11T17:11:04.700", then breeze changes the value to Thu Jul 11 19:11:04 UTC+0200 2013.
Notice the time is now 2 hours ahead !
I had already come across this issue when saving entities, so I had to explicitly convert my date properties using momentjs :
date.hours(date.hours() - moment().zone() / 60);
But now it seems the problem occurs also when doing read operations.
What's the best way to make sure breeze does not alter values of my date properties ?
Breeze does not manipulate the datetimes going to and from the server in any way EXCEPT to add a UTZ timezone specifier to any dates returned from the server that do not already have one. This is only done because different browsers interpret dates without a timezone specifier differently and we want consistency between browsers.
The source of your issues is likely to be that when you save your data with dates to the database, that the dateTime datatype you are using does NOT contain a timezone offset. This means that when the data is retrieved you are likely "losing" the offset and the Breeze default mentioned above kicks in. This can be corrected by using a database date time datatype with an timezone offset ( datetime2 or datetimeoffset in SQLServer).
Note that your browser DOES format dates according to it's current timezone.
Another approach is that you can replace Breeze's DataType.parseDateFromServer to NOT infer any time zone info if it is not provided:
breeze.DataType.parseDateFromServer = function (source) {
return new Date(Date.parse(source));
};
However, this can run into the problem that different browsers interpret DateTime strings without a time zone offset differently... So you may still get strange results depending on the browser. If that happens you will need to add some browser detection code to the snippet above.
Another alternative is to do the following using the moment.js library.
breeze.DataType.parseDateFromServer = function (source) {
var date = moment(source);
return date.toDate();
};
Not sure how helpful this is, but hopefully Breeze's behavior is clearer.
By default, Breeze does not provide any way to do this, but you can keep the below code in your model JS file to overcome this issue:
breeze.DataType.parseDateFromServer = function (source) {
if (typeof source === 'string') {
//Check for local offset time or UTC time from server
if (source.slice(-1) !== "Z") {
var oldSource = source;
try {
source = source.substring(0, source.lastIndexOf("-") - 1)
source = new Date(source);
var tzDifference = source.getTimezoneOffset();
//convert the offset to milliseconds, add to targetTime, and make a new Date
var offsetTime = new Date(source.getTime() + tzDifference * 60 * 1000);
return offsetTime;
}
catch (err) {
source = new Date(source);
return source;
}
}
else {
source = new Date(source);
var tzDifference = source.getTimezoneOffset();
//convert the offset to milliseconds, add to targetTime, and make a new Date
var offsetTime = new Date(source.getTime() + tzDifference * 60 * 1000);
return offsetTime;
}
}
}