ParserFunctions: Support timezone / meridian formats [#648]

staging
gnosygnu 4 years ago
parent 9cb60f2540
commit 763c7b52b7

@ -85,6 +85,9 @@ public class DateAdp implements CompareAble, Gfo_invk {
: Timezone_offset_test
;
}
public String Timezone_id() {
return "UTC"; // under.getTimeZone().getID(); // NOTE: timezone is always UTC, unless over-ridden by tests
}
public DateAdp XtoUtc() {
java.util.Date date = under.getTime();
java.util.TimeZone tz = under.getTimeZone();
@ -109,6 +112,7 @@ public class DateAdp implements CompareAble, Gfo_invk {
long dst_adj = dst ? 3600000 : 0;
return (under.getTimeInMillis() + offsetFromUTC + dst_adj) / 1000;
}
public int WeekOfYear() {return under.get(Calendar.WEEK_OF_YEAR);}
public int Frac() {return under.get(Calendar.MILLISECOND);}
public DateAdp Add_frac(int val) {return CloneAndAdd(Calendar.MILLISECOND, val);}

@ -60,6 +60,9 @@ public class DateAdp__tst {
@Test public void XtoUtc() {
fxt.Test__to_utc("2012-01-01 00:00", "2012-01-01 05:00"); //4=Wed
}
@Test public void Timezone_id() {
fxt.Test__timezone_id("2015-12-26T10:03:53Z", "UTC");
}
}
class DateAdp__fxt {
public void Test__parse_gplx(String raw, String expd) {
@ -83,4 +86,7 @@ class DateAdp__fxt {
public void Test__to_utc(String raw, String expd) {
Tfds.Eq(expd, DateAdp_.parse_gplx(raw).XtoUtc().XtoStr_fmt_yyyy_MM_dd_HH_mm());
}
public void Test__timezone_id(String raw, String expd) {
Gftest.Eq__str(expd, DateAdp_.parse_gplx(raw).XtoUtc().Timezone_id());
}
}

@ -52,6 +52,10 @@ public class Pft_fmt_itm_ {
, Tid_hijiri_month_idx = 31
, Tid_hijiri_day_idx = 32
, Tid_hijiri_month_name = 33
, Tid_timezone_id_full = 35
, Tid_timezone_id_abrv = 36
, Tid_timezone_offset_4 = 37
, Tid_timezone_offset_4_colon = 38
;
public static final Pft_fmt_itm
@ -107,6 +111,10 @@ public class Pft_fmt_itm_ {
, Hijiri_month_idx = new Pft_fmt_itm_hijiri_month_idx()
, Hijiri_day_idx = new Pft_fmt_itm_hijiri_day_idx()
, Hijiri_month_name = new Pft_fmt_itm_hijiri_month_name()
, Timezone_id_full = new Pft_fmt_itm_timezone_id(Bool_.N)
, Timezone_id_abrv = new Pft_fmt_itm_timezone_id(Bool_.Y)
, Timezone_offset_4 = new Pft_fmt_itm_timezone_offset_4(Bool_.N)
, Timezone_offset_4_colon = new Pft_fmt_itm_timezone_offset_4(Bool_.Y)
;
public static final Btrie_fast_mgr Regy = Btrie_fast_mgr.cs()
.Add(Byte_ascii.Ltr_Y , Pft_fmt_itm_.Year_len4) // 2012
@ -139,6 +147,10 @@ public class Pft_fmt_itm_ {
.Add(Byte_ascii.Ltr_Z , Pft_fmt_itm_.Timezone_offset) // timezone offset in seconds
.Add(Byte_ascii.Ltr_c , Pft_fmt_itm_.Iso_fmt) // 2012-01-02T03:04:05+00:00
.Add(Byte_ascii.Ltr_r , Pft_fmt_itm_.Rfc_5322) // Mon 02 Jan 2012 08:04:05 +0000
.Add(Byte_ascii.Ltr_e , Pft_fmt_itm_.Timezone_id_full) // UTC
.Add(Byte_ascii.Ltr_T , Pft_fmt_itm_.Timezone_id_abrv) // UTC
.Add(Byte_ascii.Ltr_O , Pft_fmt_itm_.Timezone_offset_4) // timezone offset in seconds +0000
.Add(Byte_ascii.Ltr_P , Pft_fmt_itm_.Timezone_offset_4_colon) // timezone offset in seconds with colon +00:00
.Add("xr" , Pft_fmt_itm_.Roman) // MCXI
.Add("xkY" , Pft_fmt_itm_.Thai) // Year += 543
.Add("xoY" , Pft_fmt_itm_.Minguo) // Year -= 1911

@ -81,12 +81,14 @@ class Pft_fmt_itm_dayOfYear implements Pft_fmt_itm {
class Pft_fmt_itm_am_pm implements Pft_fmt_itm {
public int TypeId() {return Pft_fmt_itm_.Tid_AmPm;}
public void Fmt(Bry_bfr bfr, Xowe_wiki wiki, Xol_lang_itm lang, DateAdp date, Pft_func_formatdate_bldr bldr) {
boolean am = date.Hour() < 13;
boolean am = date.Hour() < 12;
byte[] val = null;
if ( am && lower) val = Ary_am_lower;
else if ( am && !lower) val = Ary_am_upper;
else if (!am && lower) val = Ary_pm_lower;
else if (!am && !lower) val = Ary_pm_upper;
if (am) {
val = lower ? Ary_am_lower : Ary_am_upper;
}
else {
val = lower ? Ary_pm_lower : Ary_pm_upper;
}
bfr.Add(val);
} private static final byte[] Ary_am_upper = Bry_.new_a7("AM"), Ary_pm_upper = Bry_.new_a7("PM"), Ary_am_lower = Bry_.new_a7("am"), Ary_pm_lower = Bry_.new_a7("pm");
public Pft_fmt_itm_am_pm(boolean lower) {this.lower = lower;} private boolean lower;

@ -0,0 +1,51 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 gnosygnu@gmail.com
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
or alternatively under the terms of the Apache License Version 2.0.
You may use XOWA according to either of these licenses as is most appropriate
for your project on a case-by-case basis.
The terms of each license can be found in the source code repository:
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.xtns.pfuncs.times; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.*;
import gplx.xowa.langs.*;
class Pft_fmt_itm_timezone_offset_4 implements Pft_fmt_itm {
private final boolean colon;
public Pft_fmt_itm_timezone_offset_4(boolean colon) {this.colon = colon;}
public int TypeId() {return Pft_fmt_itm_.Tid_timezone_offset_4;}
public void Fmt(Bry_bfr bfr, Xowe_wiki wiki, Xol_lang_itm lang, DateAdp date, Pft_func_formatdate_bldr bldr) {
// get tz_secs
int tz_secs = date.Timezone_offset();
// add "+" or "-"
if (tz_secs < 0) {
bfr.Add_byte(Byte_ascii.Dash);
tz_secs *= -1;
}
else {
bfr.Add_byte(Byte_ascii.Plus);
}
// calc mins / hours
int tz_mins = tz_secs / 60;
// add bfr
bfr.Add_int_fixed((tz_mins / 60), 2);
if (colon)
bfr.Add_byte(Byte_ascii.Colon);
bfr.Add_int_fixed((tz_mins % 60), 2);
}
}
class Pft_fmt_itm_timezone_id implements Pft_fmt_itm {
public Pft_fmt_itm_timezone_id(boolean abrv) {}
public int TypeId() {return Pft_fmt_itm_.Tid_timezone_id_full;}
public void Fmt(Bry_bfr bfr, Xowe_wiki wiki, Xol_lang_itm lang, DateAdp date, Pft_func_formatdate_bldr bldr) {
bfr.Add_str_a7(date.Timezone_id());
}
}

@ -107,4 +107,65 @@ public class Pft_func_time__basic__tst {
@Test public void Iso8601_T() {fxt.Test_parse_tmpl_str("{{#time:Y-m-d h:i:s A|T1:23}}" , "2012-01-02 01:23:00 AM");} // handle "T" flag; PAGE:pl.w:StarCraft_II:_Wings_of_Liberty
@Test public void Iso8601_T_ws() {fxt.Test_parse_tmpl_str("{{#time:Y-m-d h:i:s A|T 1:23}}" , "2012-01-02 01:23:00 AM");} // handle "T" flag and ws
@Test public void Iso8601_T_fail() {fxt.Test_parse_tmpl_str("{{#time:Y-m-d h:i:s A|T2012-01-02}}" , "<strong class=\"error\">Invalid hour: T</strong>");} // handle "T" flag and ws
@Test public void Timezone_id() {// hard-coded to return "UTC"; DATE:2020-01-18
fxt.Test_parse_tmpl_str("{{#time:e|2012-01-02 03:04:05}}", "UTC");
}
@Test public void Timezone_abrv() { // hard-coded to return "UTC"; DATE:2020-01-18
fxt.Test_parse_tmpl_str("{{#time:T|2012-01-02 03:04:05}}", "UTC");
}
@Test public void Timezone_offset_4() { // hard-coded to return "+0000"; DATE:2020-01-18
fxt.Test_parse_tmpl_str("{{#time:O|2012-01-02 03:04:05}}", "+0000");
}
@Test public void Timezone_offset_4_colon() { // hard-coded to return "+00:00"; DATE:2020-01-18
fxt.Test_parse_tmpl_str("{{#time:O|2012-01-02 03:04:05}}", "+0000");
}
@Test public void Timezone_offset_4__vals() {
Pft_func_time__fxt func_fxt = new Pft_func_time__fxt(fxt);
func_fxt.Test__parse_w_offset_mins( 60, "{{#time:O|2012-01-02 03:04:05}}", "+0100");
func_fxt.Test__parse_w_offset_mins(-300, "{{#time:O|2012-01-02 03:04:05}}", "-0500");
func_fxt.Test__parse_w_offset_mins( 330, "{{#time:O|2012-01-02 03:04:05}}", "+0530");
}
@Test public void Meridian__AM__bugfix() {
fxt.Test_parse_tmpl_str("{{#time:A|2012-01-02 12:00:00}}", "PM");
}
@Test public void Meridian__AM() {
fxt.Test_parse_tmpl_str("{{#time:A|12:00 AM}}", "AM");
fxt.Test_parse_tmpl_str("{{#time:A|12:00 a.m}}", "AM");
fxt.Test_parse_tmpl_str("{{#time:A|12:00 am.}}", "AM");
fxt.Test_parse_tmpl_str("{{#time:A|12:00 a.m.}}", "AM");
}
@Test public void Meridian__PM() {
fxt.Test_parse_tmpl_str("{{#time:A|2:00 PM}}", "PM");
fxt.Test_parse_tmpl_str("{{#time:A|2:00 p.m}}", "PM");
fxt.Test_parse_tmpl_str("{{#time:A|2:00 pm.}}", "PM");
fxt.Test_parse_tmpl_str("{{#time:A|2:00 p.m.}}", "PM");
}
@Test public void Meridian__basic() {
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1PM}}" , "01:00:00 PM");
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1 PM}}" , "01:00:00 PM");
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1:02 PM}}" , "01:02:00 PM");
fxt.Test_parse_tmpl_str("{{#time:h:i:s A|1:02:03 PM}}" , "01:02:03 PM");
}
@Test public void Meridian__errors() {
fxt.Test_parse_tmpl_str("{{#time:h|PM}}" , "<strong class=\"error\">Invalid time. Nothing found before meridian</strong>");
fxt.Test_parse_tmpl_str("{{#time:h|monday PM}}" , "<strong class=\"error\">Invalid time. Text found before meridian</strong>");
fxt.Test_parse_tmpl_str("{{#time:h|0 AM}}" , "<strong class=\"error\">Invalid time. Invalid digit for meridian: 0</strong>");
fxt.Test_parse_tmpl_str("{{#time:h|13 AM}}" , "<strong class=\"error\">Invalid time. Invalid digit for meridian: 13</strong>");
fxt.Test_parse_tmpl_str("{{#time:h|1 :00 AM}}" , "<strong class=\"error\">Invalid time. Text found before 1st colon</strong>");
fxt.Test_parse_tmpl_str("{{#time:h|1 :00:00 AM}}" , "<strong class=\"error\">Invalid time. Text found before 2nd colon</strong>");
}
}
class Pft_func_time__fxt {
private final Xop_fxt fxt;
public Pft_func_time__fxt(Xop_fxt fxt) {this.fxt = fxt;}
public void Test__parse_w_offset_mins(int offset, String raw, String expd) {
try {
DateAdp.Timezone_offset_test = offset * 60;
fxt.Test_parse_tmpl_str(raw, expd);
}
finally {
DateAdp.Timezone_offset_test = Int_.Min_value;
}
}
}

@ -40,6 +40,7 @@ class Pxd_itm_ {
, Tid_unit_relative = 11 // next, previous
, Tid_unixtime = 12 // @123
, Tid_iso8601_t = 13 // T
, Tid_meridian = 14 // PM
, Tid_dash = Byte_ascii.Dash
, Tid_dot = Byte_ascii.Dot
, Tid_slash = Byte_ascii.Slash
@ -101,5 +102,6 @@ class Pft_func_time_log {
, Invalid_second = Gfo_msg_itm_.new_warn_(owner, "Invalid second: ~{0}")
, Invalid_date = Gfo_msg_itm_.new_warn_(owner, "Invalid date: ~{0}")
, Invalid_timezone = Gfo_msg_itm_.new_warn_(owner, "Invalid timezone: ~{0}")
, Invalid_time = Gfo_msg_itm_.new_warn_(owner, "Invalid time. ~{0}")
;
}

@ -40,7 +40,7 @@ class Pxd_itm_int extends Pxd_itm_base implements Pxd_itm_int_interface {
public int Val() {return val;} public Pxd_itm_int Val_(int v) {val = v; return this;} private int val;
public boolean Val_is_adj() {return val_is_adj;} public void Val_is_adj_(boolean v) {val_is_adj = v;} private boolean val_is_adj;
public int Xto_int_or(int or) {return val;}
public int Digits() {return digits;} private int digits;
public int Digits() {return digits;} private int digits; // NOTE: digits exists primarily for year evaluation; EX: 11 vs 2011
@Override public boolean Time_ini(Pxd_date_bldr bldr) {
int seg_idx = this.Seg_idx();
if (seg_idx == Pxd_itm_base.Seg_idx_skip) return true;

@ -0,0 +1,113 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 gnosygnu@gmail.com
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
or alternatively under the terms of the Apache License Version 2.0.
You may use XOWA according to either of these licenses as is most appropriate
for your project on a case-by-case basis.
The terms of each license can be found in the source code repository:
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.xtns.pfuncs.times; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.*;
import gplx.core.brys.*;
class Pxd_itm_meridian extends Pxd_itm_base implements Pxd_itm_prototype {
private final boolean pm;
public Pxd_itm_meridian(int ary_idx, boolean pm) {this.Ctor(ary_idx); this.pm = pm;}
@Override public byte Tkn_tid() {return Pxd_itm_.Tid_meridian;}
@Override public int Eval_idx() {return 19;}
@Override public boolean Time_ini(Pxd_date_bldr bldr) {return true;}
public Pxd_itm MakeNew(int ary_idx) {return new Pxd_itm_meridian(ary_idx, pm);}
@Override public boolean Eval(Pxd_parser state) {
Pxd_itm[] tkns = state.Tkns();
// init some vars
Pxd_itm itm = null;
boolean colon_found = false;
// get previous item before meridian, skipping ws
int itm_idx;
for (itm_idx = this.Ary_idx() - 1; itm_idx > -1; --itm_idx) {
itm = tkns[itm_idx];
if (itm.Tkn_tid() != Pxd_itm_.Tid_ws)
break;
}
// null-check to handle error cases like "AM"
if (itm == null) {
return Fail(state, "Nothing found before meridian");
}
else {
// itm is not int, then err; EX: "monday AM"
if (itm.Tkn_tid() != Pxd_itm_.Tid_int) {
return Fail(state, "Text found before meridian");
}
// itm is an int
else {
// int_itm may be minute / seconds if there are colons; EX "12AM" vs "12:00AM" or "12:00:00AM"
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_colon) {
colon_found = true;
itm_idx--;
// make sure int exists before ":"
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_int) {
itm = tkns[itm_idx - 1];
itm_idx--;
// check again for ":"
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_colon) {
itm_idx--;
// make sure int exists before ":"
if (itm_idx > 0 && tkns[itm_idx - 1].Tkn_tid() == Pxd_itm_.Tid_int) {
itm = tkns[itm_idx - 1];
}
else {
return Fail(state, "Text found before 2nd colon");
}
}
}
else {
return Fail(state, "Text found before 1st colon");
}
}
// hour found
Pxd_itm_int hour_itm = (Pxd_itm_int)itm;
int hour = hour_itm.Val();
// invalid digit; fail
if (hour == 0 || hour > 12) {
return Fail(state, "Invalid digit for meridian: " + Int_.To_str(hour));
}
else {
// update hour
if (pm) {
// convert "1 PM" -> hour=13
hour_itm.Val_(hour + 12);
}
else { // convert "12 AM" -> 00:00
if (hour == 12) {
hour_itm.Val_(0);
}
}
// if no colon, eval int now else int will get eval'd as year; EX: "12 AM"
if (!colon_found)
return Pxd_eval_seg.Eval_as_h(state, hour_itm);
else {
// colon exists; don't need to do anything else b/c parser will handle normally; i.e.: "12:00 PM" is just like "12:00"
}
}
}
}
return true;
}
private boolean Fail(Pxd_parser state, String err) {
// MW just throws "Invalid time."; add extra description message for debugging purposes
state.Err_set(Pft_func_time_log.Invalid_time, Bfr_arg_.New_bry(err));
return false;
}
}

@ -222,6 +222,8 @@ class Pxd_parser_ {
Init_relative();
trie.Add_obj(Pxd_itm_unixtime.Name_const, new Pxd_itm_unixtime(-1, -1));
trie.Add_obj(Pxd_itm_iso8601_t.Name_const, new Pxd_itm_iso8601_t(-1, -1));
Init_meridian(Bool_.N, "am", "a.m", "am.", "a.m.");
Init_meridian(Bool_.Y, "pm", "p.m", "pm.", "p.m.");
}
private static void Init_reg_months(String[] names) {
for (int i = 0; i < names.length; i++)
@ -255,6 +257,11 @@ class Pxd_parser_ {
trie.Add_obj("previous", Pxd_itm_unit_relative.Prev);
trie.Add_obj("this", Pxd_itm_unit_relative.This);
}
private static void Init_meridian(boolean is_pm, String... ary) {
Pxd_itm_meridian meridian = new Pxd_itm_meridian(-1, is_pm);
for (String itm : ary)
trie.Add_obj(itm, meridian);
}
}
/*
NOTE_1:parsing works by completing previous items and then setting current;

Loading…
Cancel
Save