diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java index 183f783..76adc9a 100644 --- a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java +++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java @@ -33,13 +33,14 @@ public interface CurrentMonitorDao { void updateSummaries(BreakerGroup _rootGroup, Set _daysToSummarize, TimeZone _tz); String authenticateAccount(String _username, String _password); - String getAuthCodeForEmail(String _email); + String getAuthCodeForEmail(String _email, TimeZone _tz); Account authCodeToAccount(String _authCode); AuthCode decryptAuthCode(String _authCode); Account putAccount(Account _account); Account getAccount(int _accountId); Account getAccountByUsername(String _username); + TimeZone getTimeZoneForAccount(int _accountId); MongoProxy getProxy(); } diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java index 6f62d6c..d846057 100644 --- a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java +++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java @@ -32,7 +32,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; @@ -55,7 +54,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao { proxy.ensureIndex(BreakerPower.class, DaoSort.sort("account_id").then("key")); proxy.ensureIndex(HubPowerMinute.class, DaoSort.sort("account_id").then("minute")); proxy.ensureIndex(BreakerGroupEnergy.class, DaoSort.sort("account_id").then("group_id").then("view_mode")); - proxy.ensureIndex(BreakerGroupSummary.class, DaoSort.sort("account_id").then("group_id").then("view_mode")); + proxy.ensureIndex(BreakerGroupSummary.class, DaoSort.sort("account_id").then("group_id").then("view_mode").then("start")); proxy.ensureIndex(DirtyMinute.class, DaoSort.sort("posted")); for (DirtyMinute minute : proxy.queryAll(DirtyMinute.class)) { updateSummaries(minute); @@ -95,7 +94,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao { private void updateSummaries(DirtyMinute _minute) { DebugTimer timer = new DebugTimer("Updating summaries", logger); List minutes = proxy.query(HubPowerMinute.class, new DaoQuery("account_id", _minute.getAccountId()).and("minute", _minute.getMinute())); - TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + TimeZone tz = getTimeZoneForAccount(_minute.getAccountId()); BreakerConfig config = getConfig(_minute.getAccountId()); BreakerGroup group = CollectionUtils.getFirst(config.getBreakerGroups()); Date day = DateUtils.getMidnightBefore(_minute.getMinuteAsDate(), tz); @@ -103,7 +102,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao { if (summary == null) summary = new BreakerGroupEnergy(group, minutes, EnergyBlockViewMode.DAY, day, tz); else - summary.addEnergy(group, minutes, tz); + summary.addEnergy(group, minutes); putBreakerGroupEnergy(summary); updateSummaries(group, CollectionUtils.asHashSet(day), tz); timer.stop(); @@ -216,12 +215,13 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao { } @Override - public String getAuthCodeForEmail(String _email) { + public String getAuthCodeForEmail(String _email, TimeZone _tz) { _email = _email.toLowerCase().trim(); Account account = getAccountByUsername(_email); if (account == null) { account = new Account(); account.setUsername(_email); + account.setTimezone(_tz.getID()); putAccount(account); } return aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(account.getId(), account.getAuxiliaryAccountIds()))); @@ -259,6 +259,20 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao { return clearPassword(proxy.queryOne(Account.class, new DaoQuery("username", NullUtils.makeNotNull(_username).toLowerCase().trim()))); } + @Override + public TimeZone getTimeZoneForAccount(int _accountId) { + String timezone = proxy.queryForOneField(Account.class, new DaoQuery("_id", String.valueOf(_accountId)), "timezone"); + TimeZone tz = null; + try { + if (NullUtils.isNotEmpty(timezone)) + tz = TimeZone.getTimeZone(timezone); + } + catch (Exception _e) { + logger.error("TimeZone not configured correctly for account {}", _accountId); + } + return tz == null ? TimeZone.getTimeZone("America/Chicago") : tz; + } + private Account clearPassword(Account _account) { if (_account == null) return null; diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java index a418213..3634e8a 100644 --- a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java @@ -10,6 +10,7 @@ public class Account { @PrimaryKey private int id; private String username; private String password; + private String timezone; private List auxiliaryAccountIds; public int getId() { @@ -36,6 +37,14 @@ public class Account { password = _password; } + public String getTimezone() { + return timezone; + } + + public void setTimezone(String _timezone) { + timezone = _timezone; + } + public List getAuxiliaryAccountIds() { return auxiliaryAccountIds; } diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java index 943bc64..fb76616 100644 --- a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java @@ -28,38 +28,41 @@ public class BreakerGroupEnergy { private List energyBlocks; private double toGrid; private double fromGrid; + private TimeZone timezone; public BreakerGroupEnergy() { } - public BreakerGroupEnergy(BreakerGroup _group, Map> _powerReadings, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) { + public BreakerGroupEnergy(BreakerGroup _group, Map> _powerReadings, EnergyBlockViewMode _viewMode, Date _start, TimeZone _timezone) { groupId = _group.getId(); groupName = _group.getName(); viewMode = _viewMode; start = _start; accountId = _group.getAccountId(); - subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, _powerReadings, _viewMode, _start, _tz)); + timezone = _timezone; + subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, _powerReadings, _viewMode, _start, timezone)); energyBlocks = new ArrayList<>(); List breakerKeys = CollectionUtils.transform(_group.getBreakers(), Breaker::getKey); if (!breakerKeys.isEmpty()) { for (BreakerPower power : CollectionUtils.aggregate(breakerKeys, _powerReadings::get)) { - addEnergy(groupId, power.getReadTime(), power.getPower(), _tz); + addEnergy(groupId, power.getReadTime(), power.getPower()); } } } - public BreakerGroupEnergy(BreakerGroup _group, List _power, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) { + public BreakerGroupEnergy(BreakerGroup _group, List _power, EnergyBlockViewMode _viewMode, Date _start, TimeZone _timezone) { groupId = _group.getId(); groupName = _group.getName(); viewMode = _viewMode; start = _start; accountId = _group.getAccountId(); - subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, (List)null, _viewMode, _start, _tz)); + timezone = _timezone; + subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, (List)null, _viewMode, _start, timezone)); energyBlocks = new ArrayList<>(); - addEnergy(_group, _power, _tz); + addEnergy(_group, _power); } - public void addEnergy(BreakerGroup _group, List _hubPower, TimeZone _tz) { + public void addEnergy(BreakerGroup _group, List _hubPower) { Map breakers = CollectionUtils.transformToMap(_group.getAllBreakers(), Breaker::getKey); Map breakerKeyToGroup = new HashMap<>(); for (BreakerGroup group : _group.getAllBreakerGroups()) { @@ -67,14 +70,14 @@ public class BreakerGroupEnergy { breakerKeyToGroup.put(b.getKey(), group); } } - addEnergy(breakers, breakerKeyToGroup, _hubPower, _tz); + addEnergy(breakers, breakerKeyToGroup, _hubPower); } - public void addEnergy(Map _breakers, Map _breakerKeyToGroup, List _hubPower, TimeZone _tz) { + public void addEnergy(Map _breakers, Map _breakerKeyToGroup, List _hubPower) { if (CollectionUtils.isEmpty(_hubPower) || CollectionUtils.anyQualify(_hubPower, _p->_p.getAccountId() != accountId)) return; Date minute = CollectionUtils.getFirst(_hubPower).getMinuteAsDate(); - resetEnergy(minute, _tz); + resetEnergy(minute); Map meters = new HashMap<>(); for (HubPowerMinute hubPower : _hubPower) { for (BreakerPowerMinute breaker : CollectionUtils.makeNotNull(hubPower.getBreakers())) { @@ -92,7 +95,7 @@ public class BreakerGroupEnergy { else meter.solar[idx] += -power; if (power != 0.0) - addEnergy(group.getId(), minute, power, _tz); + addEnergy(group.getId(), minute, power); idx++; } } @@ -108,21 +111,21 @@ public class BreakerGroupEnergy { } } - public void resetEnergy(Date _readTime, TimeZone _tz) { - EnergyBlock block = getBlock(_readTime, _tz, false); + public void resetEnergy(Date _readTime) { + EnergyBlock block = getBlock(_readTime, false); if (block != null) block.setJoules(0); for (BreakerGroupEnergy subGroup : CollectionUtils.makeNotNull(subGroups)) { - subGroup.resetEnergy(_readTime, _tz); + subGroup.resetEnergy(_readTime); } } - public void addEnergy(String _groupId, Date _readTime, double _joules, TimeZone _tz) { + public void addEnergy(String _groupId, Date _readTime, double _joules) { if (NullUtils.isEqual(groupId, _groupId)) - getBlock(_readTime, _tz).addJoules(_joules); + getBlock(_readTime).addJoules(_joules); else { for (BreakerGroupEnergy subGroup : CollectionUtils.makeNotNull(subGroups)) { - subGroup.addEnergy(_groupId, _readTime, _joules, _tz); + subGroup.addEnergy(_groupId, _readTime, _joules); } } } @@ -134,9 +137,10 @@ public class BreakerGroupEnergy { energy.setAccountId(_group.getAccountId()); energy.setViewMode(_viewMode); energy.setStart(_start); + energy.setTimeZone(_tz); energy.setSubGroups(CollectionUtils.transform(_group.getSubGroups(), _g -> BreakerGroupEnergy.summary(_g, _energies, _viewMode, _start, _tz))); for (BreakerGroupSummary curEnergy : CollectionUtils.makeNotNull(_energies.get(_group.getId()))) { - EnergyBlock block = energy.getBlock(curEnergy.getStart(), _tz); + EnergyBlock block = energy.getBlock(curEnergy.getStart()); block.addJoules(curEnergy.getJoules()); energy.setToGrid(energy.getToGrid()+curEnergy.getToGrid()); energy.setFromGrid(energy.getFromGrid()+curEnergy.getFromGrid()); @@ -144,20 +148,20 @@ public class BreakerGroupEnergy { return energy; } - private EnergyBlock getBlock(Date _readTime, TimeZone _tz) { - return getBlock(_readTime, _tz, true); + private EnergyBlock getBlock(Date _readTime) { + return getBlock(_readTime, true); } - private EnergyBlock getBlock(Date _readTime, TimeZone _tz, boolean _add) { + private EnergyBlock getBlock(Date _readTime, boolean _add) { int size = CollectionUtils.size(energyBlocks); - int idx = viewMode.blockIndex(_readTime, _tz); + int idx = viewMode.blockIndex(_readTime, timezone); if (_add && (idx >= size)) { if (energyBlocks == null) energyBlocks = new ArrayList<>(); LinkedList newBlocks = new LinkedList<>(); - Date end = viewMode.toBlockEnd(_readTime, _tz); + Date end = viewMode.toBlockEnd(_readTime, timezone); while (idx >= size) { - Date start = viewMode.decrementBlock(end, _tz); + Date start = viewMode.decrementBlock(end, timezone); newBlocks.add(new EnergyBlock(start, end, 0)); end = start; size++; @@ -254,6 +258,14 @@ public class BreakerGroupEnergy { fromGrid = _fromGrid; } + public TimeZone getTimeZone() { + return timezone; + } + + public void setTimeZone(TimeZone _timezone) { + timezone = _timezone; + } + public double wattHours() { return joules() / 3600; } diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java index c4c9eb9..8813e4d 100644 --- a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java @@ -32,6 +32,7 @@ public class AccountSerializer extends AbstractDaoSerializer d.put("_id", String.valueOf(_o.getId())); d.put("username", _o.getUsername()); d.put("password", _o.getPassword()); + d.put("timezone", _o.getTimezone()); if (CollectionUtils.isNotEmpty(_o.getAuxiliaryAccountIds())) d.put("aux_account_ids", CollectionUtils.toByteArray(_o.getAuxiliaryAccountIds())); return d; @@ -44,6 +45,7 @@ public class AccountSerializer extends AbstractDaoSerializer o.setId(DaoSerializer.getInteger(_d, "_id")); o.setUsername(DaoSerializer.getString(_d, "username")); o.setPassword(DaoSerializer.getString(_d, "password")); + o.setTimezone(DaoSerializer.getString(_d, "timezone")); o.setAuxiliaryAccountIds(CollectionUtils.fromByteArrayOfIntegers(DaoSerializer.getByteArray(_d, "aux_account_ids"))); return o; } diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java index 382e2c7..36d8a30 100644 --- a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java +++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java @@ -4,6 +4,7 @@ import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy; import com.lanternsoftware.datamodel.currentmonitor.EnergyBlock; import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode; import com.lanternsoftware.util.CollectionUtils; +import com.lanternsoftware.util.DateUtils; import com.lanternsoftware.util.dao.AbstractDaoSerializer; import com.lanternsoftware.util.dao.DaoEntity; import com.lanternsoftware.util.dao.DaoProxyType; @@ -41,10 +42,11 @@ public class BreakerGroupEnergySerializer extends AbstractDaoSerializer 0) { Date start = _o.getStart(); Date now = new Date(); - TimeZone tz = TimeZone.getTimeZone("America/Chicago"); ByteBuffer bb = ByteBuffer.allocate(_o.getViewMode().blockCount(start, tz) * 4); for (EnergyBlock b : _o.getEnergyBlocks()) { if (b.getStart().before(start)) @@ -71,7 +73,6 @@ public class BreakerGroupEnergySerializer extends AbstractDaoSerializer blocks = new ArrayList<>(); byte[] blockData = DaoSerializer.getByteArray(_d, "blocks"); if (CollectionUtils.length(blockData) > 0) { ByteBuffer bb = ByteBuffer.wrap(blockData); Date start = o.getStart(); while (bb.hasRemaining()) { - EnergyBlock block = new EnergyBlock(start, o.getViewMode().toBlockEnd(start, tz), bb.getFloat()); + EnergyBlock block = new EnergyBlock(start, o.getViewMode().toBlockEnd(start, o.getTimeZone()), bb.getFloat()); blocks.add(block); start = block.getEnd(); } diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java index 021fcff..7c97e40 100644 --- a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java @@ -5,6 +5,7 @@ import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.lanternsoftware.currentmonitor.context.Globals; +import com.lanternsoftware.util.DateUtils; import com.lanternsoftware.util.LanternFiles; import com.lanternsoftware.util.NullUtils; import com.lanternsoftware.util.ResourceLoader; @@ -38,7 +39,7 @@ public class AuthServlet extends CMServlet { if (idToken != null) { GoogleIdToken.Payload payload = idToken.getPayload(); String email = payload.getEmail(); - authCode = Globals.dao.getAuthCodeForEmail(email); + authCode = Globals.dao.getAuthCodeForEmail(email, DateUtils.fromTimeZoneId(_req.getHeader("timezone"))); } } catch (Exception _e) { diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java index 8bf2ed2..5217d73 100644 --- a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java +++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java @@ -3,9 +3,8 @@ package com.lanternsoftware.currentmonitor.servlet; import com.lanternsoftware.currentmonitor.context.Globals; import com.lanternsoftware.datamodel.currentmonitor.Account; import com.lanternsoftware.datamodel.currentmonitor.SignupResponse; +import com.lanternsoftware.util.DateUtils; import com.lanternsoftware.util.NullUtils; -import com.lanternsoftware.util.dao.DaoEntity; -import com.lanternsoftware.util.dao.DaoSerializer; import com.lanternsoftware.util.email.EmailValidator; import com.lanternsoftware.util.servlet.BasicAuth; @@ -38,6 +37,7 @@ public class SignupServlet extends CMServlet { acct = new Account(); acct.setUsername(auth.getUsername()); acct.setPassword(auth.getPassword()); + acct.setTimezone(DateUtils.fromTimeZoneId(_req.getHeader("timezone")).getID()); Globals.dao.putAccount(acct); String authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword()); jsonResponse(_rep, SignupResponse.success(authCode)); diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java index 723c6f9..d491f9c 100644 --- a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java +++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java @@ -26,7 +26,7 @@ public class RebuildSummaries { public static void main(String[] args) { int accountId = 1; CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg")); - TimeZone tz = TimeZone.getTimeZone("America/Chicago"); + TimeZone tz = dao.getTimeZoneForAccount(accountId); Date start = DateUtils.date(1, 7, 2021, tz); // Date start = DateUtils.getMidnightBeforeNow(tz); Date end = DateUtils.getMidnightAfterNow(tz); @@ -49,7 +49,7 @@ public class RebuildSummaries { if (energy == null) energy = new BreakerGroupEnergy(root, minute, EnergyBlockViewMode.DAY, day.getKey(), tz); else - energy.addEnergy(breakers, breakerKeyToGroup, minute, tz); + energy.addEnergy(breakers, breakerKeyToGroup, minute); } timer.stop(); if (energy != null) diff --git a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java index 4c2d0aa..b0697e2 100644 --- a/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java +++ b/util/lantern-util-common/src/main/java/com/lanternsoftware/util/DateUtils.java @@ -1,5 +1,6 @@ package com.lanternsoftware.util; +import java.sql.Time; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -655,4 +656,27 @@ public abstract class DateUtils { return cal; } + public static String getTimeZoneId(TimeZone _tz) { + return getTimeZoneId(_tz, "America/Chicago"); + } + + public static String getTimeZoneId(TimeZone _tz, String _default) { + return (_tz == null) ? _default : _tz.getID(); + } + + public static TimeZone defaultTimeZone(TimeZone _tz) { + return defaultTimeZone(_tz, "America/Chicago"); + } + + public static TimeZone defaultTimeZone(TimeZone _tz, String _default) { + return (_tz == null) ? TimeZone.getTimeZone(_default) : _tz; + } + + public static TimeZone fromTimeZoneId(String _id) { + return fromTimeZoneId(_id, "America/Chicago"); + } + + public static TimeZone fromTimeZoneId(String _id, String _defaultId) { + return TimeZone.getTimeZone(NullUtils.isEmpty(_id) ? _defaultId : _id); + } }