How to get all IANA Timezones from an offset in NodaTime? - c#

In our ASP.NET MVC 5 application we have user profiles with a Timezone ID (IANA).
public class User
{
public int Id { get; set; }
public string TimeZoneId { get; set; }
}
The task is to send all users an email at 6 AM local time. Our plan is to send out emails every hour to all timezones where the local time is 6 AM at that point in time (calculated based on the UTC time of the server).
Since we only have the Timezone ID to go by, I was hoping to get the corresponding Timezone IDs for, let's say, offset -4:00:00 -- taking into account DST.
string[] timezoneIds;
I could then query my database like so:
db.Users.Where(x => timezoneIds.Contains(x.TimeZoneId));
My question is, obviously, is this a decent idea or are there best practices for this problem that I am not aware of?
Thanks.

Simpler code for the "building a dictionary" that Serge shows: use LINQ's lookup, DateTimeZone.GetUtcOffset, and Offset keys instead of TimeSpan values:
var now = SystemClock.Instance.GetCurrentInstant();
var provider = DateTimeZoneProviders.Tzdb;
var idsByCurrentOffset = provider.Ids.ToLookup(id => provider[id].GetUtcOffset(now));
Then you can use:
var offset = Offset.FromHours(5);
foreach (var id in idsByCurrentOffset[offset])
{
...
}
(You don't even need to check for existence first, as the lookup indexer returns an empty sequence when presented with a key that doesn't exist in the lookup.)

You could try the following:
First create a dictionary with all the offsets and the timezones belonging to that offset
Instant now = SystemClock.Instance.Now;
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
Dictionary<TimeSpan, List<string>> timezonesForOffset = new Dictionary<TimeSpan, List<string>>();
foreach(var id in timeZoneProvider.Ids){
ZonedDateTime zdt = now.InZone(timeZoneProvider[id]);
var timespan = zdt.Offset.ToTimeSpan();
if(timezonesForOffset.ContainsKey(timespan)){
timezonesForOffset[timespan].Add(id);
} else {
timezonesForOffset[timespan] = new List<string> {id, };
}
}
After you have done that you could use the following snippet to get all users within a certain timezone
var timespan2 = new TimeSpan(1,0,0);
var timezonesWithOffset = timezonesForOffset[timespan2];
var usersinTimezone = db.Users.Where(x=> timezonesWithOffset.Contains(x.TimezoneId)).ToList();
That would get all users in the timezone utc+1

Related

How to get a MongoDB BsonDocument from a collection based on last timestamp and some other filters using C#

For a building security system I want to get the last swiped card time for a particular student based on their id serial number. I am using this code
var _collection = database.GetCollection<BsonDocument>("entrycard_swipetimes");
var filter = Builders<BsonDocument>.Filter.Eq("studentid", "stu-1234") & Builders<BsonDocument>.Filter.Eq("Carddetails.LastSwipeTimestamp", "2021-11-25T10:50:00.5230694Z");
var doc = _collection.Find(filter).FirstOrDefault();
This is a snippet of the json document
{
"studentid"; "stu-1234",
"Carddetails":
{
"LastSwipeTimestamp": "2021-11-25T10:50:00.5230694Z"
}
}
The code above works and I get a record, but I need to know the exact time string in advance. How can get only the last time a particular student swiped their card? How do I get the last timestamp of all the students in the system and have it paged by say 20 students at a time for the last 30 days?
Modify Filter and add a sort thusly:
var filter = Builders<BsonDocument>.Filter.Eq("studentid", "stu-1234");
var doc = _collection.Find(filter).Sort("{\"Carddetails.LastSwipeTimestamp\":-1}").FirstOrDefault();
Highly recommend that you change Carddetails.LastSwipeTimestamp to be a real datetime type instead of an ISO8601 string.
To get the last time a particular student (studentId) swiped their card:
var filter = Builders<BsonDocument>.Filter.Eq("studentid", studentId);
var doc = collection
.Find(filter)
.SortByDescending(bson => bson["Carddetails.LastSwipeTimestamp"])
.FirstOrDefault();
To get the last timestamp of all the students in the system and have it paged by pageSize students at a time for the last lastDaysCount days:
var doc = collection
.Aggregate()
.Group(new BsonDocument
{
{ "_id", "$studentid" },
{
"LastSwipeTimestamp", new BsonDocument
{
{ "$max", "$Carddetails.LastSwipeTimestamp" }
}
},
})
.Match(bson => bson["LastSwipeTimestamp"] > DateTime.Now.AddDays(-lastDaysCount))
.Skip((page - 1) * pageSize)
.Limit(pageSize)
.ToList();
But for this to work, you should store your dates as Date type.

DateTimeOffset: TZ Offset is reset to +00:00 when fetching data from LiteDb collection

When inserting, the offset is OK, however when retrieving the document, it's resetting to +00:00
Property:
public DateTimeOffset CreatedOn { get; set; }
Insert:
user.CreatedOn = DateTimeOffset.Now; // 01/20/2021 6:05:21 PM +05:30
col.Insert(user);
col.EnsureIndex(x => x.Username);
Find:
using (var db = _liteDbConnection.Create() as LiteDatabase)
{
var col = db.GetCollection<AuthUser>(USERS_COLLECTION);
return col.FindOne(x => x.UserId == userId);
}
user.CreatedOn becomes
01/20/2021 6:05:21 PM +00:00
Am I doing something wrong?
From the documentation
Following the BSON specification, DateTime values are stored only up to the miliseconds. All DateTime values are converted to UTC on storage and converted back to local time on retrieval.
It doesn't look like there's actual DateTimeOffset support. (Personally I think it's a terrible idea to convert to local time on retrieval, but that's a slightly different matter.) Additionally, it looks like it's not really converting to local time properly, given the incorrect offset of 0.
I would suggest avoiding using DateTimeOffset with LiteDb until it's really supported (maintaining the offset).
This happens because LiteDB stores a DateTimeOffset using value.UtcDateTime. This is unfortunate, but it can't be changed due to compatibility. However, you can override this behavior by creating a custom serializer:
BsonMapper.Global.RegisterType<DateTimeOffset>
(
serialize: obj =>
{
var doc = new BsonDocument();
doc["DateTime"] = obj.DateTime.Ticks;
doc["Offset"] = obj.Offset.Ticks;
return doc;
},
deserialize: doc => new DateTimeOffset(doc["DateTime"].AsInt64, new TimeSpan(doc["Offset"].AsInt64))
);

Is there a way to have a common time users cannot modify even if they change time in settings

I'm working an an app that's Puts and Gets data to Firebase, based on Date, and Time my problem now is that users can modify it in settings and either get future data or old data I've tried using the dateTime.now however that can be modified I've also tried datetime.utcnow but it can also be modified. So my question is is there away I can work this out so users cannot modify time or even if they do the app time isn't affected.?
Sample of code stored to Firebase.
await CrossCloudFirestore.Current
.Instance
.Collection(DataPaths.NewsFeed)
.Document(guid)
.SetAsync(new {
ID = ticks + id,
ProfileImage = prefrences.profileImage,
UserName = usernames,
Title = title,
Date = DateTime.Now.ToString("dd MMM yyyy"),
Time = DateTime.UtcNow.ToString("HH:mm:ss"),
Details = details,
OrderTime = orderTime
});
Another Sample of Something I was Attempting.
//All 3 Needs sorting/fixing utc vs local/GMT time
//DateTime date = DateTime.UtcNow;
//var act = TimeZoneInfo.ConvertTime(date, TimeZoneInfo.Local);
//Application.Current.MainPage.DisplayAlert("", act.ToString(), "OK");
The solution is, that you should get the current time from an API rather than from the device.
The way to do it, is to set Date & Time to a value gotten from an API.
So I personally like this http://worldclockapi.com/api/json/est/now; copy this into your code where you would like to set your date & time, and you should be all fine.
You can edit the API URL to match your time zone; lets say you want Central Standard time, then this would be your API URL http://worldclockapi.com/api/json/cst/now.
Feel free to search for other APIs using Google.
Now, how do you set your variable to an API result value? ~ Like this:
var url = "http://worldclockapi.com/api/json/cst/now";
var client = new HttpClient();
var currentDateTime = client.Get(url);
await CrossCloudFirestore.Current
.Instance
.Collection(DataPaths.NewsFeed)
.Document(guid)
.SetAsync(new {
ID = ticks + id,
ProfileImage = prefrences.profileImage,
UserName = usernames,
Title = title,
Date = currentDateTime.ToString("dd MMM yyyy"),
Time = currentDateTime.ToString("HH:mm:ss"),
Details = details,
OrderTime = orderTime
});

MongoDB C# driver and DateTime field

I'm inserting a document to MongoDB collection using C# driver, one of the fields types vs a DateTime when i debug the application i see the server time in the "FrameTimeStamp" fields i'm passing to Mongo, this is my code:
FrameDocument frameDoc = new FrameDocument();
frameDoc.Frame = imageBA;
frameDoc.EventCodeId = 1;
frameDoc.SesionId = 1;
frameDoc.FrameTimeStamp = DateTime.Now;
frameDoc.ServerUserId = (int)toMongoDt.Rows[0]["ServerUserId"];
frameDoc.TraderId = (int)toMongoDt.Rows[0]["TraderId"];
frameDoc.ActivePick = (int)toMongoDt.Rows[0]["ActivePick"];
frameDoc.TraderName = (string)toMongoDt.Rows[0]["TraderName"];
frameDoc.ServerUserName = (string)toMongoDt.Rows[0]["ServerUserName"];
var mongoCon = "mongodb://127.0.0.1";
MongoClient client = new MongoClient(mongoCon);
var db = client.GetDatabase("Video");
var frameCollection = db.GetCollection<FrameDocument>("Frame");
frameCollection.InsertOne(frameDoc);
in the shell when I'm reading the data i see it in the following format:
2016-08-14T06:10:33.295Z and the time is not the server time, its UTC, how can i force mongo to write the DateTime as i pass it,?
as per CSHARP-185
MongoDB stores all DateTimes in UTC. Any local times you supply are
converted to UTC when stored in the database. The recommended approach
is to always convert DateTime values to UTC yourself before storing
them in the database, that way you are in full control. You can also
tell the C# driver that you want to work in LocalTime, like this:
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime Date
{ get; set; }

MongoDB C# driver aggregation between dates returns null fields

I am using the c# driver for mongodb and want to use an aggregation query for a web API I am creating. For my aggregation query I am concerned with a profile class that has usernames, dates and steps. I want to create a query that selects usernames and gets their total steps for a given week, in descending order by total steps. I only want to display their Username and their total steps.
When I attempt the aggregation query I am having an issue with some of my fields coming up as null. So, I believe my query is not structured correctly.
I have a “Profile” class that I am using for my data currently.
[BsonIgnoreExtraElements]
[DataContract]
public class Profile
{
[DataMember]
public string Username { get; set; }
[DataMember]
public DateTime Date { get; set; }
[DataMember]
public uint? Steps { get; set; }
}
I have created some test data with for example profiles using the following
//Test data
for (uint index = 1; index < 20; index++)
{
Profile aprofile = new Profile
{
Username = string.Format("testuser{0}", index),
Date = RandomDay(),
Steps = (index + index + index)*2
};
AddProfile(aprofile);
}
If I run the code a few times and query a particular user, I get data that is like this:
[{"Username":"testuser1","Date":"2014-07-03T00:00:00Z","Steps":6},
{"Username":"testuser1","Date":"2014-07-07T05:00:00Z","Steps":6},
{"Username":"testuser1","Date":"2014-07-17T05:00:00Z","Steps":6},
{"Username":"testuser1","Date":"2014-07-18T05:00:00Z","Steps":6}]
Then, I have a couple static methods to find the earliest date and the latest date for my aggregation query.
//gets a datetime for the earlist date and time possible for the current week
public static DateTime GetStartOfCurrentWeek()
{
int DaysToSubtract = (int)DateTime.Now.DayOfWeek;
DateTime dt = DateTime.Now.Subtract(TimeSpan.FromDays(DaysToSubtract));
return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0, 0);
}
//gets a datetime for the latest date and time possible for the current week
public static DateTime GetEndOfCurrentWeek()
{
DateTime dt = GetStartOfCurrentWeek().AddDays(6);
return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59, 999);
}
My attempt at an aggregation query is below.
//Here is my aggregation query to get all profiles within a given week,
public IEnumerable<Profile> GetAllProfiles()
{
DateTime StartofWeek = GetStartOfCurrentWeek();
DateTime EndofWeek = GetEndOfCurrentWeek();
var match = new BsonDocument
{{ "$match", new BsonDocument
{{ "Date", new BsonDocument {
{"$gte", StartofWeek},
{"$lt", EndofWeek}
}}}
}};
var group = new BsonDocument
{{"$group",
new BsonDocument
{{ "_id", new BsonDocument
{{"id", "$Username"},
}},
{"Steps", new BsonDocument
{{"$sum", "$Steps"}}
}}
}};
var sort = new BsonDocument
{{"$sort", new BsonDocument
{{"Steps", -1}}
}};
var pipeline = new[] {match, group, sort};
var args = new AggregateArgs { Pipeline = pipeline, OutputMode = AggregateOutputMode.Inline };
// run the aggregation query and get a list of BsonDocuments
IEnumerable<BsonDocument> documents = _profiles.Aggregate(args);
}
However, the results I am getting are showing the usernames as Null and the dates as null.
[{"Username":null,"Date":"0001-01-01T00:00:00","Steps":96},
{"Username":null,"Date":"0001-01-01T00:00:00","Steps":66},
{"Username":null,"Date":"0001-01-01T00:00:00","Steps":24}]
What do I need to do to get my array of documents so that the usernames display along with the total steps I have (which do seem to be working). I don't want the Dates in my query results. Just the Username and their total steps for that week.
I don't really understand why do you need to query your mongo with BSon. You do have your model class. Aggregation you are trying to perform will look like:
var result = collection.Aggregate()
.Match(r => r.Date >= StartofWeek && r.Date < EndofWeek)
.Group(r=>r.Username, r=> new {UserName = r.Key, Dates = r.Select(d=>d.Date), Steps = r.Sum(x=>x.Steps)})
.ToList();
Note, that you could get not one date, but array of it, therefore i aggregate it as Dates.
Reason, why you don't see Date and username is that you don't query it, in your group statement i see just sum of steps. if you really want to continue to query this way, you should add it to your group stage, something like:
var group = new BsonDocument
{
{ "_id", new BsonDocument{{"id", "$Username"},
}},
{"Steps", new BsonDocument{{"$sum", "$Steps"}}},
{ "Dates", new BsonDocument{{"$addToSet", "$Date"}} },
{ "Username", new BsonDocument{{"$first", "$Username"}} },
};
This way i can see the Username and dates in resulting BsonDocument.
But as i said already, this way to query Mongo is not the best one to do in c#
Your group stage has one flaw, and that is you didn't included the field you want out of your query.
By just changing your group stage, you should be getting the desired field.
var group = new BsonDocument
{{"$group",
new BsonDocument
{
{ "_id", new BsonDocument
{{"id", "$Username"}}
},
{ "Steps", new BsonDocument
{{"$sum", "$Steps"}}
},
{ "Username", new BsonDocument
{{"$first", "$Username"}} // since the rest of the selected object is groupped, we should give a strategy to pick a $Username, and it is safe to use $first aggregate function here
}
}
}};
And if you are not comfortable with the date in your output result just add a project stage, and reshape your output document.

Categories