Yesterday Uruguay changed their clocks, and now I keep seeing exceptions when converting specific times for their timezone:
ERROR Exception: - DateTime ConvertTimeToUtc(DateTime, String) (05/10/2014 02:31:00, America/Montevideo)
NodaTime.SkippedTimeException: Specified argument was out of the range of valid values.
Parameter name: Local time 05/10/2014 02:31:00 is invalid in time zone America/Montevideo
I understand how a local time can be invalid:
"For example, suppose the time zone goes forward at 2am, so the second after 01:59:59 becomes 03:00:00. In that case, local times such as 02:30:00 never occur."
However, what I don't understand (and I probably need more coffee), is why NodaTime is not accounting for this? Should it not be aware that 02:31 is now an invalid local time - or should I be doing additional handling to account for this?
Functions I'm calling:
var timeZone = DateTimeZoneProviders.Tzdb[timezoneName];
var localTime = LocalDateTime.FromDateTime(timeToConvert).InZoneStrictly(timeZone);;
return DateTime.SpecifyKind(localTime.ToDateTimeUtc(), DateTimeKind.Utc);
Yes, it is aware that it's an invalid local time - which is why when you specifically ask it to convert that local time into UTC, it throws an exception. It's roughly equivalent to calling Math.sqrt(-1).
The InZoneStrictly call you're making specifically throws an exception on either ambiguous or skipped times. If you use InZoneLeniently you won't get an exception, but it may not give you the result you want. Or, you could use LocalDateTime.InZone(DateTimeZone, ZoneLocalMappingResolver) which will allow you to map invalid local date/time values however you want.
As side-notes:
Your localTime variable is a ZonedDateTime, so the name is a bit misleading
You don't need to call SpecifyKind - ToDateTimeUtc will always return a DateTime with a kind of Utc, hence the name.
Related
I want to use the NodaTime library to convert the date from one timezone to another like this.
string fromSystemTimeZoneId = "GMT Standard Time";
string toSystemTimeZoneId = "Central Standard Time";
TimeZoneInfo fromTimeZone = TimeZoneInfo.FindSystemTimeZoneById(fromSystemTimeZoneId);
TimeZoneInfo toTimeZone = TimeZoneInfo.FindSystemTimeZoneById(toSystemTimeZoneId);
var convertedTime = TimeZoneInfo.ConvertTime(inputDateTime, fromTimeZone, toTimeZone);
The above code works perfectly fine for me, But now I want to use IANA standard time zone (like Europe/London and America/Chicago) in place of windows OS-provided time zone ids.
I am using .net 4.7.2 and cannot upgrade the framework due to some limitations.
I have gone through this answer, But I am looking for simple few lines of code nothing complicated.
But I am looking for simple few lines of code nothing complicated.
But you're trying to do something complicated. Noda Time makes everything explicit, which means it's clear exactly what's going on - at the cost of being a little more verbose.
DateTime has no concept of a time zone itself (other than for the special cases where the Kind is Utc or Local). So TimeZoneInfo.ConvertTime has to:
Consider what instant in time is represented by inputDateTime in fromTimeZone
Work out what that instant in time looks like in outputDateTime
In Noda Time, those are are two separate operations, if you want to start and end with a LocalDateTime:
LocalDateTime inputDateTime = ...;
DateTimeZone fromTimeZone = ...;
DateTimeZone toTimeZone = ...;
// There are options for how this conversion is performed, as noted in other questions
ZonedDateTime inputDateTimeInFromTimeZone = inputDateTime.InZoneLeniently(fromTimeZone);
// This conversion is always unambiguous, because ZonedDateTime unambiguously
// refers to a single instant in time
ZonedDateTime inputDateTimeInToTimeZone = inputDateTimeInFromTimeZone.WithZone(toTimeZone);
LocalDateTime localPart = inputDateTimeInToTimeZone.LocalDateTime;
So that's basically the equivalent conversion - but you need to be explicit about how you want to handle skipped/ambiguous inputs. If you want everything in your app to use the same conversion, you can wrap that in a method that looks just like TimeZoneInfo.ConvertTime. But why not just keep things in a ZonedDateTime instead of LocalDateTime to start with? Then you don't get into the ambiguous position - other than potentially when converting user input (which is entirely separate from converting from one time zone to another).
When the time index is integer(e.g. starting from 0 for each user), running dfs shows warnings:
UnusedPrimitiveWarning: Some specified primitives were not used during DFS:
agg_primitives: ['avg_time_between', 'time_since_first', 'time_since_last', 'trend']
groupby_trans_primitives: ['cum_count', 'time_since', 'time_since_previous']
This may be caused by a using a value of max_depth that is too small, not setting interesting values, or it may indicate no compatible variable types for the primitive were found in the data.
However, the timeindex can be an integer in many cases (e.g. https://www.kaggle.com/c/riiid-test-answer-prediction/data):
When user1 started to be logged, timestamp of its first log is 0 (say, 2020-01-01 13:10:10)
When 2nd log of user1 to be logged after next 100 seconds, the timestamp of its second log is 100.
Likewise, when user2 started to be logged, timestamp of its first log is always 0 (Notice: 2020-11-01 00:00:00, in this time()
As you can see here, user1 and user2 started to be logged at different date and time, but they have the same time index. It seems like they use the relative time as a feature.
In this case, even though I set the timestamp variable as ft.variable_types.TimeIndex(numeric_time_index) when creating entityset, it still showed the same warning and features generated by ['avg_time_between', 'time_since_first', 'time_since_last', 'trend'] didn't appear.
How can I handle it?
Thanks for the question. The time_since and time_since_first primitives are currently implemented to handle only Datetime and DatetimeTimeIndex variables. To handle cases where the time index is numeric, you can create custom primitives to handle NumericTimeIndex variables.
from featuretools.primitives import AggregationPrimitive, TransformPrimitive
from featuretools.variable_types import NumericTimeIndex
class TimeSinceNumeric(TransformPrimitive):
input_types = [NumericTimeIndex]
...
class TimeSinceFirstNumeric(AggregationPrimitive):
input_types = [NumericTimeIndex]
...
Then, you can pass in the custom primitives directly to DFS.
ft.dfs(
...
trans_primitives=[TimeSinceNumeric],
agg_primitives=[TimeSinceFirstNumeric],
)
I am using agenda.js in my Node project, backed with a MongoDB database, to handle batch processes we need to run. This is working well. I do have a question about timezones, however. When I use the every() operation, it seems like it accepts the job name, and the schedule. So I have been seeding jobs to the database like so:
for (let job of dbJobs) {
await agenda.every(schedule, job.name);
}
Note that for the above, schedule is in cron format -- 00 05 * * 1-5.
This works. However, from what I can tell, every() doesn't accept an argument for repeatTimezone. So what does it do to calculate the timezone in those cases?
To clarify, when I look at the document in the database after the job has been added using every(), the repeatTimezone property exists, but its value is set to null.
Other agenda operations, like repeatEvery(), do accept an argument for timezone, like so:
job.repeatEvery('0 6 * * *', {
timezone: 'America/New_York'
});
Since I'm using every(), I have been managing this by first seeding the database using every(), and then running a Mongo updateMany() to add the timzeone explicitly to all jobs:
async function addTimezoneToJobs() {
try {
const db = await client.db(dbName);
await db.collection('batch_processes').updateMany({}, {
$set: {
repeatTimezone: 'America/New_York'
}
});
} catch (error) {
console.log(error);
}
}
But strangely enough, agenda seems to calculate the same time even when I don't explicitly add the repeatTimezone property value to the jobs as when I do.
What's happening here that I'm not understanding? How is the runtime calculated with every(), and is there a way to pass in timezone?
FYI: I am not in the same timezone as that which needs to be set in the db.
Your Question seems to be 2 part, I'm not exactly sure I'll be able to explain it very well but let me try
So, your first question
However, from what I can tell, every() doesn't accept an argument for Timezone
Well Technically you can add Timezone option to every() as well because what this method does is it calls job.repeatEvery internally and as you already know you can add timezone to that. To Support my answer, I found 2 evidence
From Documentation as every accepts 4 parameters
every(interval, name, [data], [options])
options is an optional argument that will be passed to job.repeatEvery. In order to use this argument, data must also be specified.
So you can technically pass timezone if you pass data as well
From SourceCode, here you can see they use job.repeatEvery(interval, options) internally.
Now, To your Second Question
what does it do to calculate the timezone in those cases?
Well They have a very unique yet required module named ComputeNextRunAt().
So I went through their Source Code and figured Out that this is to Compute when will be the next run for your job based on startingTime and Interval.
Your Code works because you have once (Initially) mentioned in your job to follow America/New_York timezone, so every next job interval is calculated based on that, that's the reason you don't need to specify it again.
So, If initially you haven't had specified the timezone attribute, you would have gotten your local Timezone but now you did so, it calculates the next interval based on that.
My model will optionally take a time in many utterances.
So examples...
add wet diaper. <-------- assumes current time
add wet diaper at 4:00 PM
add bottle at noon
What would be the best way to model 4 PM/AM so that in my actions I can collect the time?
You'll want to define an action that accepts an optional input, and make sure the type is 'DateTimeExpression'. In your NL training, you can say things like 'do this thing at 4PM' or 'do this thing'. Both are valid because you made the date optional. In your javascript, check to see if the user said a date. If they did, use it. If not, you can default to now. Refer to this link for grabbing the time from javascript. The date api will parse dates to the device's local time by default (can be overridden).
In your action
input (searchDate) {
type (time.DateTimeExpression)
min(Optional) max (One)
}
In your javascript
var currentTime = dates.ZonedDateTime.now().getDateTime();
if (searchDate)
currentTime = //Grab the date from searchDate.
Use this for reference.
There is no primitive type DateTime, but there is a viv.time library you can use, and in fact there is viv.time.DateTimeExpression concept you can use.
viv.time.DateTimeExpression not only handles "4 AM" but also "tomorrow 4 AM", you can read more here. I would say it is one of the most commonly used library concepts.
The first answer of this question suggests that mongoose would adapt the date according to server timezone when retrieving data.
However, I don't have this comportement.
I set the (node) server timezone with :
process.env.TZ='Europe/Paris'
For exemple if I create a simple model like :
const mongoose = require("mongoose");
const testSchema = new mongoose.Schema({
myDate: { type: Date, required: true },
}, { timestamps: true });
exports.Comment = mongoose.default.model('TestSchema', testSchema);
But if I create a date with 2020-01-01 20:20:20, when doing TestSchema.find() the date will be: 2020-01-01T19:20:20.000Z so there are two things that I don't understand :
Europe/Paris is actually UTC +2, so I would expect the date to be either 2020-01-01T18:20:20.000Z in UTC or 2020-01-01T20:20:20.000Z with the server timezone
How to have mongoose automatically set the date to the correct timezone?
I know that myDate is a Date object, so I can convert it manually but I'd rather not have to do it myself for simple reasons like forgetting to convert one of the dates in the application or not having to do it every time a Date field is added
An easy solution that I can think of would be to register a global plugin for mongoose which would use schema.set('toJSON', ... and schema.set('toObject', ...) with the transform method so I can loop through schema fields and if the field is a Date, update it to my timezone.
But I see two problems with this approch :
It doesn't sound very good performance-wise if I am querying a lot of documents each with a lot of fields
As you can see here I am currently not able to register global plugins...
What would be the best method to get the date in the server timezone format? I would rather still store them in UTC but set the hour according to the server timezone.
EDIT :
I just saw that while console.log(myDate) outputs 2018-01-01T19:20:20.000Z console.log(myDate.toString() outputs Mon Jan 01 2018 20:20:20 GMT+0100 (Central European Standard Time) so it seems likes this could be used, even tho I'd rather still have a Date object and converting it to string just before sending it to the client (would need some formatting tho since this format is not very user friendly). But then again, how would I do this globally and not for every date
A few things:
Europe/Paris at 2020-01-01T20:20:20 is UTC+1. It doesn't switch to UTC+2 until Summer Time kicks in on March 29th. Reference here. Thus the conversion to 2020-01-01T19:20:20Z is correct.
The output of console.log when passed a Date object is implementation specific. Some implementations will emit the output of .toString() (which is in local time in RFC 2822 format), and some will emit the output of .toISOString() (which is in UTC in ISO 8601 extended format). That is why you see the difference.
In general, it is not good to send a local time without also sending a time zone offset. ISO 8601 format is ideal, but you should send either 2020-01-01T19:20:20Z, or 2020-01-01T20:20:20+01:00. Don't just send the date and time without an offset to the client. Otherwise, if your client could be in a different time zone then they would interpret the value incorrectly.
Keep in mind that Date objects are not time zone aware. They contain only a Unix timestamp internally, and they convert only to the system's local time zone for the functions that work in local time. They cannot work in any other time zone.
Relying on the system local time zone is bad for portability. One doesn't always have the ability to change it, and it doesn't do well when you have to work in multiple time zones. It would be better to not rely on setting a local time zone from Node's TZ variable. Instead, consider writing your code to be independent of any local time zone setting.
A time zone aware date library can help with most of your concerns. I can recommend Luxon, js-Joda, Moment + Moment-Timezone, or date-fns + date-fns-timezone.
"how would I do this globally" is something I'm not following in your question. Try the approach I described, and if you still have issues then open a new question. Try to be specific and ask a single question. You're likely to get better results that way. Please read How do I ask a good question? and How to create a Minimal, Complete, and Verifiable example. Thanks.
To solve the issue:
npm i mongoose-timezone
In your schema file:
import timeZone from "mongoose-timezone";
const testSchema = new mongoose.Schema({
myDate: { type: Date, required: true },
}, { timestamps: true });
// mongoose will save the dates based on user's timezone
testSchema.plugin(timeZone)
mongoose-timezone basically adds the current timezone offset to the
date before store and removes the offset when retrieving data. This
way dates are kept proportional in the database and in the app.