Parsing a string into a ZonedDateTime with a DateTimeFormatter - java-8

I'm trying to parse this String into a ZonedDateTime:
"Mon 14 Aug 2017 02:00 AM CEST"
Here my last try:
System.out.println("Test ZonedDateTime: " + ZonedDateTime.parse(
"Mon 14 Aug 2017 02:00 AM CEST",
DateTimeFormatter.ofPattern("EEEE dd M yyyy KK:mm a z")));
And the response:
Exception in thread "main" java.time.format.DateTimeParseException: Text 'Mon 14 Aug 2017 02:00 AM CEST' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.ZonedDateTime.parse(ZonedDateTime.java:597)
at be.hypertux.test.localtime.Main.main(Main.java:17)
Any ideas?

One problem is that short timezone names like CEST and CET are ambiguous and not standard. The ideal is to use IANA timezones names (always in the format Continent/City, like America/Sao_Paulo or Europe/Berlin).
I'm assuming that CEST is the Central Europe Summer Time, which is used by lots of different countries (that's why it's ambiguous: you can't know which country or region it is, because it's a too broad range).
Although most abbreviations are not recognized (due to its ambiguity), some "defaults" are assumed for retro-compatibility reasons. In the version I'm using (JDK 1.8.0_131), it defaults to Europe/Paris, but not sure if that's what you need. And it's not guaranteed to work for all abbreviations. In this case, you can define a preferred timezone to be used (and that will an arbitrary choice, but there's no other way since CEST is ambiguous).
Another problem is that the month and day of week are in English (Aug and Mon), and you didn't specify a java.util.Locale. In this case, the DateTimeFormatter takes the system's default locale (and it's probably not English - check the value of Locale.getDefault()). Anyway, the default locale can be changed without notice, even at runtime, so it's better to specify one when you're dealing with localized data (like month and day of week names).
So, you must specify a locale and define an arbitrary timezone as the preferred one to be used when an ambiguous name like CEST is found. For that, you can use a java.time.format.DateTimeFormatterBuilder, a set of preferred timezones and a java.time.format.TextStyle:
// create set of preferred timezones
Set<ZoneId> zones = new HashSet<>();
// my arbitrary choice for CEST
zones.add(ZoneId.of("Europe/Brussels"));
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
// date and time
.appendPattern("EEE dd MMM yyyy hh:mm a ")
// timezone short name with custom set of preferred zones
.appendZoneText(TextStyle.SHORT, zones)
// create formatter (use English locale for month and day of week)
.toFormatter(Locale.ENGLISH);
String input = "Mon 14 Aug 2017 02:00 AM CEST";
System.out.println(ZonedDateTime.parse(input, formatter));
The output will be:
2017-08-14T02:00+02:00[Europe/Brussels]
Note that I used Europe/Brussels as the preferred timezone. You can check all the available zone names (and choose accordingly) with ZoneId.getAvailableZoneIds().
I'm using hh for the hours, which is the hour-clock-of-am-pm field (values from 1 to 12). But in your code you used KK, which is the hour-of-am-pm field (values from 0 to 11). Check which one is best for your case.
A timezone is the set of all different offsets that a region had, has and will have during its history, and the dates when Daylight Saving Time starts and ends, etc. If 2 regions had some difference in this history, they'll have different timezones (even though they use the same rules today).
Just because Paris and Brussels use the same rules today (CET and CEST), it doesn't mean it'll be like this forever (because timezones rules are defined by governments and laws and there's no guarantee that they won't be changed at any time in the future).
That's why you must define some specific timezone instead of relying on ambiguous short names (even though their use is common and widespread).

In order for your format string to work, your date would need to be formatted like so: Monday 14 8 2017 02:00 AM CEST
Take out an E and add a couple of Ms and that should do it.

Related

Difference of lastModifiedDateTime and bodyLastModifiedDateTime in Microsoft Graph todo task apis

In Microsoft Graph Todo Task Item documents (this link) there are two fields with two different names but with the same description: lastModifiedDateTime and bodyLastModifiedDateTime.
Anyone knows the difference of these 2 fields?
You are right, the description on create todoTask is wrong.
If you check todotask resource's properties the bodyLastModifiedDateTime has the correct description.
The date and time when the task body was last modified. By default, it
is in UTC. You can provide a custom time zone in the request header.
The property value uses ISO 8601 format and is always in UTC time. For
example, midnight UTC on Jan 1, 2020 would look like this:
'2020-01-01T00:00:00Z'.

PST/PDT in DateTime.parse in Ruby

I want to be able to parse a date string which might be in PST or PDT
according to daylight savings. This means that half a year the date will
be PST and the other half the date will be PDT.
This is my code now:
DateTime.parse('2016-02-21 10:00:02 PST/PDT')
This will only parse the date as PST (GMT-8).
How can I parse a date+time in PST/PDT automatically?
Thanks!
Just a note:
DateTime.parse('2016-02-21 10:00:02 PST/PDT') will always parse to PST (Standard Time), just like DateTime.parse('2016-02-21 10:00:02 PDT') will always parse to PDT (Daylight time). This is because DateTime & Time libraries are expecting that the timezone is explicit, rather than 'PST/PDT' which is saying 'I could be x or y'. If it was smarter it could work out that both of these where in the same zone and that they were daylight savings equivalents, but sadly not at this moment.
A few options:
1) Use a timezone gem to translate the time into local zones
2) Manually put in PST or PDT depending on year
I tend to store all date/times in UTC and translate as I need them.
I use the TZInfo gem to display/calculate based on local time. It uses timezones from here
recorded_time = Time.now.getutc
tz = TZInfo::Timezone.get('US/Pacific')
local_created_at = tz.utc_to_local(recorded_time)
Now this will not solve your issue if you have data already stored in DateTime already or if your inbound data is already marked up this way. If it is, I would suggest you parse it based on date. You can create a look up table using this data: http://www.timeanddate.com/time/zone/usa/los-angeles
EDIT: Just realised the TZinfo is able to solve this for you:
tz = TZInfo::Timezone.get('US/Pacific')
=> #<TZInfo::DataTimezone: US/Pacific>
tz.local_to_utc(Time.parse('2016-07-21 10:00:02 PST/PDT'))
=> 2016-07-21 17:00:02 UTC
tz.local_to_utc(Time.parse('2016-02-21 10:00:02 PST/PDT'))
=> 2016-02-21 18:00:02 UTC

Is Date.today in UTC?

Calling Date.today in Ruby returns the current date. However, what timezone is it in? I assume UTC, but I want to make sure. The documentation doesn't state either way.
You can get away by using
Time.now.utc.to_date
in ruby
TL;DR: Date.today uses the system’s local time zone. If you require it be in UTC, instead get the date from a UTC time, e.g. Time.now.utc.to_date.
Dates do not have timezones, since they don't represent a time.
That said, as for how it calculates the current day, let's look at this extract from the code for Date.today:
time_t t;
struct tm tm;
// ...
if (time(&t) == -1)
rb_sys_fail("time");
if (!localtime_r(&t, &tm))
rb_sys_fail("localtime");
It then proceeds to use use tm to create the Date object. Since tm contains the system's local time using localtime(), Date.today therefore uses the system's local time, not UTC.
You can always use Time#utc on any Time convert it in-place to UTC, or Time#getutc to return a new equivalent Time object in UTC. You could then call Time#to_date on that to get a Date. So: some_time.getutc.to_date.
If you’re using ActiveSupport’s time zone support (included with Rails), note that it is completely separate from Ruby’s time constructors and does not affect them (i.e. it does not change how Time.now or Date.today work). See also ActiveSupport extensions to Time.
An instance of Date is represented as an Astronomical Julian Day Number; it has no fractional part of a day. While a Julian Day is relative to GMT - so technically an instance of Date should be considered to be in GMT - for most purposes you can ignore that and treat it as having no timezone. You would only care about a time zone if you converted it to a DateTime or Time.
ActiveSupport's tools for date conversion let you specify whether you want local time or UTC.
E.g.:
>> t = Date.today.to_time
=> Wed Apr 18 00:00:00 -0400 2012
>> t.zone
=> "EDT"
>> t = Date.today.to_time(:utc)
=> Wed Apr 18 00:00:00 UTC 2012
>> t.zone
=> "UTC"
You can use
Date.current
To get the current date on the configured timezone.
If your rails applications is configured to run in time zone UTC then just use Time.zone.today, otherwise force it by using Time.now.utc.to_date
Ruby
# Using local timezone
Date.today
Ruby on Rails
# Using UTC
Date.yesterday
Date.current
Date.tomorrow
This is specifically nasty because when using e.g. Date.today and Date.yesterday in conjunction, it might produce quite unexpected results depending on the timezone and the current time.
For example, at 00:30 in timezone CET (UTC+1) Date.yesterday will be two days before Date.today. In other cases, Date.today and Date.yesterday can have the same value.

Is there a full implementation for ISO-8601 date parsing for Ruby?

The Time.iso8601 method is a restricted subset of ISO-8601.
What are its limitations?
Does anyone know of a full implementation for Ruby? I'm using MRI 1.8.7.
Update
It looks like there isn't a single class that handles all of the various 8601 date and date/time combinations. However, I have managed to work around the problems by using both the Date.parse and Time.iso8601 methods. The downside is that you need to decide in code whether the input looks like a date or a date/time.
Warning : Timezone differences
Time.iso8601 and Time.parse behave differently.
>> Time.parse("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 18:27:00 +0100 2010
>> Time.iso8601("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 17:27:00 UTC 2010
Differences between Time.iso8601 and ISO-8601
This document touches on the differences between what is in ISO-8601 and what is supported by Ruby. The short answer is that the number of possible formats is restricted.
Yes, but unfortunately it's in Ruby 1.9.
require "date"
Date.iso8601("2010-W32-5").strftime
#=> "2010-08-13"
I don't believe there are any implementations for Ruby 1.8.7 (or at least I couldn't find any). You could either try to upgrade to Ruby 1.9, which is pretty stable as of 1.9.2. Alternatively, you could try to parse the dates yourself.
To convert an ISO8601 date into the local time zone, do this:
require "time"
dt1 = Time.parse("2010-09-06T12:27:00.10-05:00")
To convert an ISO8601 date into UTC, do this:
dt2 = Time.iso8601("2010-09-06T12:27:00.10-05:00")
If you compare the dates returned by the above queries, they will be identical (i.e. dt1 === dt2). However, accessing date components (like year, month, day, hour, etc.) will return values appropriate for the time zone (either UTC or local). The same applies to strftime.

Paypal date formatting: what is "HH:MM:SS DD Mmm YY, YYYY PST"?

I'm looking at the PayPal IPN docs, and it says the datetime stamps of their strings are formatted as:
HH:MM:SS DD Mmm YY, YYYY PST
So year is specified twice?
Once in double digits, and another with 4 digits?
This looks bizarre.
This seems to be a bug in the documentation. The actual format should be "HH:MM:SS Mmm DD, YYYY PST" (e.g. "08:30:06 Apr 19, 2017 PDT")
Actually in PHP you need to use date("Y-m-d\TH:i:s\Z") . That will result in something that looks like 2012-04-30T00:05:47Z -- I didn't notice a difference between urlencoded and non.
Where are you guys finding this info? This information is elusive in their documentation and cost me an hour or two of hunting and trying stuff. The only place I see this format is in the TIMESTAMP field. Having a hard time with the PayPal NVP API's PROFILESTARTDATE for CreateRecurringPaymentsProfile and a "Subscription start date should be valid" error.
For php, the syntax is date("G:i:s M m, Y T");
this is the correct format according to their documentation - 2010-03-27T12:34:49Z
so it is - YYYY-MM-DDTHH:MM:SSZ (I don't know what the T in the middle and Z is but it's constant for all the dates)
I've created PayPal NVP library in Java, so if you want to check how it works, or use it,
you are more than welcome. it's on sourceforge - payapal-nvp.sourceforge.net
Complete date plus hours, minutes, seconds and a decimal fraction of a
second
YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
Where TZD = time zone designator (Z or +hh:mm or -hh:mm)
Example
1994-11-05T08:15:30-05:00 corresponds to November 5, 1994, 8:15:30 am, US Eastern Standard Time.
1994-11-05T13:15:30Z corresponds to the same instant.
https://www.w3.org/TR/NOTE-datetime
PayPal Format to Any format 100% working and easy copy Paste
$payPalFormat = "18:30:30 Feb 28, 2008 PST";
$subrotoFormat = date('Y-m-d', strtotime($payPalFormat));
Result: 2008-02-29
All Format: https://www.w3schools.com/php/func_date_date.asp
https://gist.github.com/subrotoice/d820863ce65eb0d8434a47a76d005df4 (Subroto Biswas Gist)
Actually, I think the right format is: yyyy-MM-ddTHH:MM:ssZ
The case is important.

Resources