val date = "01-10-1967"
val pattern = "dd-MM-yyyy"
val formatter = DateTimeFormat.forPattern(pattern)
formatter.parseMillis(date) // this line fails
The last line fails with:
Cannot parse "01-10-1967": Illegal instant due to time zone offset transition (America/Argentina/Buenos_Aires)
Any idea why?
(JodaTime version is 2.3)
The 1st of October 1967 was in Argentina a day where they changed from standard time to summer time, i.e. added 1 hour, on 00:00.
Since you are not providing a concrete time, I would assume that it defaults to exactly 00:00 which simply did not exist on that day.
Cf. the official faq:
What does 'Illegal instant due to time zone offset transition' mean?
Joda-Time only allows the key classes to store valid date-times. For
example, 31st February is not a valid date so it can't be stored
(except in Partial). The same principle of valid date-times applies to
daylight savings time (DST). In many places DST is used, where the
local clock moves forward by an hour in spring and back by an hour in
autumn/fall. This means that in spring, there is a "gap" where a local
time does not exist. The error "Illegal instant due to time zone
offset transition" refers to this gap. It means that your application
tried to create a date-time inside the gap - a time that did not
exist. Since Joda-Time objects must be valid, this is not allowed.
Possible solutions might be (taken from the faq):
Use LocalDateTime, as all local date-times are valid.
When converting a LocalDate to a DateTime, then use toDateTimeAsStartOfDay() as this handles and manages any gaps.
When parsing, use parseLocalDateTime() if the string being parsed has no time-zone.
Since you aren't interested in time information anyway, I think you might even want to replace formatter.parseMillis(date) with formatter.parseLocalDate(date). If for some reason you still need milliseconds, this Stack Overflow question might help.
Related
Is there a standard for encoding a date as a timestamp? My thoughts:
This should be 12:00pm UTC in local time, eg 9:00am at T-3, therefore anyone consuming the timestamp, regardless of their -12/+12 offset, will recognize the same date, regardless of whether they parse at the UTC timezone
It could be 12:00pm at UTC
It could be the start of the day (12:00am) at UTC
It could be start of the day (12:00am UTC) in local time eg 9:00pm at T-3
Is there an official spec or standard to adhere to?
It would be easy to point to this document and say 'this is the standard' as opposed to being unaware and having to change our logic down the line.
There isn't a standard for this, because a date and a timestamp are logically two very different concepts.
A date covers the entire range of time on that day, not a specific point in time.
It may be a different date for a person in another time zone at any given point in time, but dates themselves do not have any association with time zones. Visualize a date as just a square on a calendar, not a point on a timeline.
Many APIs will use midnight (00:00) as the default time when a date-only value is assigned to a date+time value. However:
Whether it is UTC based or local-time based is very dependent on that particular API. There is no standard for this, nor is one answer necessarily better than the other.
Assigning a local-time midnight can be problematic for time zones with transitions near midnight. For example, in Santiago, Chile on 2019-09-08, the day started at 01:00 due to the start of DST. There was no 00:00 on that day.
Also, you tagged your question with momentjs. Since a Moment object is basically a timestamp (not a date), then Moment.js will generally assign the start of the day if provided a date-only value. The time zone involved is key to deciding which moment that actually is, which illustrates my prior points.
For example:
// Parsing as UTC
moment.utc('2019-09-08').format() //=> "2019-09-08T00:00:00Z"
// Parsing as Local Time (my local time zone is US Pacific Time)
moment('2019-09-08').format() //=> "2019-09-08T00:00:00-07:00"
// Parsing in a specific time zone (on a day without midnight)
moment.tz('2019-09-08', 'America/Santiago').format() //=> "2019-09-08T01:00:00-03:00"
Also keep in mind that sometimes APIs can be misnamed. The JavaScript Date object is not a date-only value, but actually a timestamp, just like a moment.
I have an enum TimeFrame that has values like: Yesterday, LastWeek, NextMonth etc.
I'm trying to write a method that takes in a TimeFrame and returns the start and end dates of that period of time.
I looked into the new Java 8 Period class which can take a start time and end time but it doesn't seem there's any clean way to retrieve those values afterwards.
How could I return the start and end date at once cleanly without using a List (seems like the wrong data structure) or some ugly datetime arithmetic?
No, a Period doesn't record a start/end - it just represents the amount of time between them, in a calendrical sense (rather than the "precise amount of time" represented by a Duration).
It sounds like basically you should create your own class that has a start date and an end date. You can still use Period to implement that, but you'd have getStartDate() and getEndDate() returning LocalDate values. You shouldn't need to do any heavy date/time arithmetic yourself - just work out the start date and add the appropriate period, or the end date and subtract the appropriate period, depending on the time frame.
Time marches on
Be careful about passing around enum values for “yesterday”, “tomorrow”, and so on. That raises a couple of issues, time zone and midnight.
Dates and day-of-week only have meaning in the context of a time zone. For any given moment, the date varies around the globe. For example, a few minutes after midnight in Paris is still “yesterday” in Montréal. So when ever you intend “yesterday” and such, always specify the desired/expected time zone as well.
Each non-atomic command and line of code executes separately from the previous. Each execution takes a moment of time, however brief. At any of those moments midnight in the specified time zone may be rolling over into a new day. At that stroke of midnight, your relative-time flag such as “yesterday” suddenly takes on a whole new meaning. That meaning is likely different that was intended by the earlier code given that conditions (the date) were different when that code began.
So it makes more sense to me to be passing around Instant objects, or perhaps OffsetDateTime or ZonedDateTime objects. These date-time values are frozen, representing a specific moment on the timeline. Your earlier original code can verify the meaning of that value, check that the date is indeed a Friday or some such. After such verification, the value can be passed on to other code. Now you need not worry about strange occasional bugs occurring at midnight.
java.time
You don't really need to build a class or enum to express your intention of relative time such as “yesterday”. The java.time classes built into Java 8 and later have plain-reading methods for adding and subtracting days, weeks, months. These are basically one-liners, so just call these plus… and minus… methods directly.
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime yesterday = now.minusDays( 1 );
ZonedDateTime weekLater = now.plusWeeks( 1 );
That code is implicitly applying the JVM’s current default time zone. Better to specify.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now( zoneId );
You may want the date-only without time-of-day and without time zone. Use LocalDate.
LocalDate weekPrior = now.toLocalDate().minusWeeks( 1 );
You may want to get first moment of the day. Not always the time of 00:00:00.0.
ZonedDateTime yesterdayStart = now.minusDays( 1 ).toLocalDate().atStartOfDay( zoneId );
If you want to represent the span of time defined by a pair of date-time values, look at the Interval class found in the ThreeTen-Extra project that extends the java.time framework. This class tracks a pair of Instant objects which are moments on the timeline in UTC. You can extract an Instant from your ZonedDateTime by calling toInstant.
Interval intervalYesterday = Interval.of( yesterdayStart.toInstant() , yesterdayStart.plusDays( 1 ).toInstant() );
To get from an Instant back to a zoned date-time, apply a ZoneId.
ZonedDateTime zdt = ZonedDateTime( instant , zoneId );
For a date-only interval, you'll need to build your own class that stores a pair of LocalDate objects. I would call it something like DateRange.
Tip: Search "Half-Open" to learn about the practice of tracking spans of time where beginning in inclusive while the ending is exclusive.
How do I get date function to return date according to current system date?
Right now, with the code snippet below, it always returns UK time, not the current system date.
<calculate>
<script>$ = concat( num2date(date(), DateFmt()), " ", num2Time(Time(), TimeFmt()) )</script>
Any help is appreciated!
It's probably not UK time exactly, but rather GMT (or UTC, to use a more precise term). The UK happens to be aligned to GMT in the winter, but in the summer it advances one hour to BST for daylight saving time.
Now, I've never used LiveCycle myself, but nonetheless, I've read through the somewhat minimal docs for LiveCycle FormCalc Date and Time Functions, and the spec, and it appears to me that a few critical mistakes were made.
The date and time functions return UTC-based values, but only the time-related functions have been made aware of the local time zone. That is, there are separate Num2Time and Num2GMTime functions, but there is only one Num2Date function.
The Num2Date function works in terms of whole integer days, and thus they are simply days since 1900-01-01. Therefore, the number being passed in to the function must already be representative of the desired time zone. However, the Date function only gets the current GMT date. There does not appear to be a function to get the current local date.
It's different on the time side, because of the millisecond precision involved. However, there's yet another flaw here. Despite the docs saying that the Time function returning "the number of milliseconds since the epoch", its actually returning only the number of milliseconds since midnight GMT. There is no day-over-day accumulation of milliseconds from the date part. The docs here are even lying in their sample code which says:
Returns the current system time as the number of milliseconds since the epoch.
Time() => 71533235 at precisely 3:52:15 P.M. on September 15th, 2003 to a user in the Eastern Standard Time (EST) zone.
If that was indeed the case (and ensuring to use their 1900-01-01 epoch), the value would actually include an additional 3272572800000 milliseconds representing the days between 1900-01-01 and 2003-09-15, bringing the total timestamp to 3272644333235. Additionally, there's a typo there, because the timestamp they give is 3:52:13, not 3:52:15. Clearly nobody paid close attention to these docs!
The real problem is that one cannot be certain that the number of milliseconds since midnight of the current day in the local time zone is the same on every day. If instead of getting the current time, you were working with past stored time values, you might be an hour off (+ or -) if the current offset is different due to daylight saving time. Example: Eastern time might be UTC-5 or UTC-4, but only the offset currently in effect will be used by the Num2Time function, even if the date you're working with is using the other offset.
So, in summary, the Date function is insufficient, leading to your observations, and the date/time functionality in general is poorly designed. Given the constraints of this API, I can't even recommend a workaround. There would have to be a LocalDate function of some kind to be used instead of the Date function, but it doesn't exist.
The only advice I can offer is that it appears (from my research, not experience) that LiveCycle can use either FormCalc or JavaScript. So - use JavaScript instead.
In our app we're storing datetimes that belong to many different timezones.
We decided to use the Joda LocalDateTime type - so that the user always gets literally whatever they entered in the first place. This is exactly what we need.
Internally we know which timezone the user belongs to - so when they enter a datetime we do a check like this:
dateTimeZone.isLocalDateTimeGap(localDateTime)
If that datetime does not exist in their timezone (it's in the daylight-savings gap) we display an error message that the date is not correct, thus preventing incorrect datetimes from being stored in the DB.
For storing we're using a timestamp column. Problems start when the user-entered datetime exists in their timezone but does not exist in the database timezone (Europe/Berlin). E.g. when I store LocalDateTime 2015-03-29 02:30:00 from the Europe/London timezone (this is valid - in London the gap is between 01:00 and 02:00), PostgreSQL shifts the hour by 1 and saves it as 2015-03-29 03:30:00.
What to do? Is there a way to tell PostgreSQL not do anything regarding timezones and just store datetimes literally as Joda represents them? (other than storing them as strings ;))
In PostgreSQL 7.3 and higher, timestamp is equivalent to timestamp without time zone. That data type is not time zone aware. It stores only a date and time. If you are finding it shifted, then it might be related to the code or tools you are using to store or retrieve the data.
Note that before version 7.3, timestamp was equivalent to timestamp with timezone. This is mentioned in the first note-box in the documentation here.
Postgres offers two date-time types per the SQL standard. The standard barely touches on the topic unfortunately, so the behavior described here is specific to Postgres. Other databases may behave differently.
TIMESTAMP WITHOUT TIME ZONEStores just a date and a time-of-day. Any time zone or offset-from-UTC passed is ignored.
TIMESTAMP WITH TIME ZONEFirst adjusts the passed date+time using its passed zone/offset to get a value in UTC. The passed zone/offset is then discarded after the adjustment is made; if needed, you must store that original zone/offset information in a separate column yourself.
Be aware that TIMESTAMP WITHOUT TIME ZONE does not represent an actual moment, does not store a point on the timeline. Without the context of a zone or offset, it has no real meaning. It represents a range of possible moments over a span of about 26-27 hours. Good for problems such as storing a appointment far enough out in the future that the time zone rules may be changed before its arrival. Also good for problems such as “Christmas starts after midnight on December 25 this year”, where you mean a different moment in time in each zone with each zone westward arriving later and later in succession.
When recording actual moments, specific points on the timeline, use TIMESTAMP WITH TIME ZONE.
The modern approach in Java uses the java.time classes rather than either the Joda-Time library or the troublesome old legacy date-time classes bundled with the earliest versions of Java.
TIMESTAMP WITHOUT TIME ZONE
For TIMESTAMP WITHOUT TIME ZONE, the equivalent class in java.time is LocalDateTime for a date and time-of-day without any offset or zone.
As others pointed out, some tools may dynamically apply a time zone to the retrieved value in a misguided and confusing albeit well-intentioned anti-feature. The following Java code will retrieve your true date-time value sans zone/offset.
Requires a JDBC driver compliant with JDBC 4.2 or later to directly work with java.time types.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ; // Retrieving a `TIMESTAMP WITHOUT TIME ZONE` value.
To insert/update database:
myPreparedStatement.setObject( … , ldt ) ; // Inserting/updating a `TIMESTAMP WITHOUT TIME ZONE` column.
TIMESTAMP WITH TIME ZONE
Your discussion of time zones suggests you are concerned with actual moments on the timeline. So you should absolutely be using TIMESTAMP WITH TIME ZONE instead of TIMESTAMP WITHOUT TIME ZONE. You should not be messing about with Daylight Saving Time (DST) gaps and such. Let java.time and Postgres do that work for you, with much better code already written and tested.
To retrieve:
Instant instant = myResultSet.getObject( … , Instant.class ) ; // Retrieving a `TIMESTAMP WITH TIME ZONE` value in UTC.
ZonedDateTime zdt = instant.atZone( ZoneId.of( "Africa/Tunis" ) ) ; // Adjusting from a UTC value to a specific time zone.
To insert/update database:
myPreparedStatement.setObject( … , zdt ) ; // Inserting/updating a `TIMESTAMP WITH TIME ZONE` column.
To retrieve from database:
Instant instant = myResultSet.getObject( … , Instant.class ) ;
E.g. when I store LocalDateTime 2015-03-29 02:30:00 from the Europe/London timezone
No, no, no. Do not work this way. You are misusing the types of both Java and Postgres.
If the user entered 2015-03-29 02:30:00 intended to represent a moment in Europe/London time zone, then parse as a LocalDateTime and immediately apply a ZoneId to get a ZonedDateTime.
To parse, replace the SPACE in the middle with a T to comply with ISO 8601 standard formatting used by default in the java.time classes.
String input = "2015-03-29 02:30:00".replace( " " , "T" ) ;
LocalDateTime ldt = LocalDateTime.parse( input ) ;
ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
To see that same moment in UTC, extract a Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant instant = zdt.toInstant() ;
Pass the instant via JDBC for storage in the database in a TIMESTAMP WITH TIME ZONE.
myPreparedStatement.setObject( … , instant ) ;
Use objects, not strings
Note that all my code here is using java.time objects to exchange data with the database. Always use these objects rather than mere strings for exchanging date-time values.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
In a scala program, I receive from client side a specific date for instance:
2013-10-20T23:59:59.999Z
and I really want to keep this date when saving into DB and not convert to local, so this line:
debug("--sql timestamp: " + new Timestamp(reading.timestamp.getMillis()))
is printing out: 2013-10-21 02:59:59.999(I am in Romania).
Is there any way I can ignore timezone?
This is Timestamp.toString() behavior. java.sql.Timestamp extends java.util.Date and in its toString() method it uses, in particular, super.getHours(), which, according to javadoc, returns hours interpreted in local timezone - exactly as you observe.
However, internally Timestamp still holds correct timestamp value. There may be problems with storing it to the database, though. See this answer.
2013-10-20T23:59:59.999Z and 2013-10-21 02:59:59.999 are actually the same time: 2013-10-20T23:59:59.999Z is in the UTC time zone (Z), whereas the second one is relative, and expressed as your local time zone (UTC+3 then in Romania).
In PostgreSQL, you should store your timestamps as TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) in your database to handle this. You'll always be able to print it out later in the time zone you choose then (e.g. UTC). (You might be interested in this recent question to understand why the storage type matters.)
If you want to print out the timestamp in the UTC/Z time zone again, new DateTime(millis, DateTimeZone.UTC) should help (with Joda Time).